From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/naga/src/front/glsl/builtins.rs | 2314 ++++++++++++++++++++++ 1 file changed, 2314 insertions(+) create mode 100644 third_party/rust/naga/src/front/glsl/builtins.rs (limited to 'third_party/rust/naga/src/front/glsl/builtins.rs') diff --git a/third_party/rust/naga/src/front/glsl/builtins.rs b/third_party/rust/naga/src/front/glsl/builtins.rs new file mode 100644 index 0000000000..9e3a578c6b --- /dev/null +++ b/third_party/rust/naga/src/front/glsl/builtins.rs @@ -0,0 +1,2314 @@ +use super::{ + ast::{ + BuiltinVariations, FunctionDeclaration, FunctionKind, Overload, ParameterInfo, + ParameterQualifier, + }, + context::Context, + Error, ErrorKind, Frontend, Result, +}; +use crate::{ + BinaryOperator, DerivativeAxis as Axis, DerivativeControl as Ctrl, Expression, Handle, + ImageClass, ImageDimension as Dim, ImageQuery, MathFunction, Module, RelationalFunction, + SampleLevel, Scalar, ScalarKind as Sk, Span, Type, TypeInner, UnaryOperator, VectorSize, +}; + +impl crate::ScalarKind { + const fn dummy_storage_format(&self) -> crate::StorageFormat { + match *self { + Sk::Sint => crate::StorageFormat::R16Sint, + Sk::Uint => crate::StorageFormat::R16Uint, + _ => crate::StorageFormat::R16Float, + } + } +} + +impl Module { + /// Helper function, to create a function prototype for a builtin + fn add_builtin(&mut self, args: Vec, builtin: MacroCall) -> Overload { + let mut parameters = Vec::with_capacity(args.len()); + let mut parameters_info = Vec::with_capacity(args.len()); + + for arg in args { + parameters.push(self.types.insert( + Type { + name: None, + inner: arg, + }, + Span::default(), + )); + parameters_info.push(ParameterInfo { + qualifier: ParameterQualifier::In, + depth: false, + }); + } + + Overload { + parameters, + parameters_info, + kind: FunctionKind::Macro(builtin), + defined: false, + internal: true, + void: false, + } + } +} + +const fn make_coords_arg(number_of_components: usize, kind: Sk) -> TypeInner { + let scalar = Scalar { kind, width: 4 }; + + match number_of_components { + 1 => TypeInner::Scalar(scalar), + _ => TypeInner::Vector { + size: match number_of_components { + 2 => VectorSize::Bi, + 3 => VectorSize::Tri, + _ => VectorSize::Quad, + }, + scalar, + }, + } +} + +/// Inject builtins into the declaration +/// +/// This is done to not add a large startup cost and not increase memory +/// usage if it isn't needed. +pub fn inject_builtin( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, + mut variations: BuiltinVariations, +) { + log::trace!( + "{} variations: {:?} {:?}", + name, + variations, + declaration.variations + ); + // Don't regeneate variations + variations.remove(declaration.variations); + declaration.variations |= variations; + + if variations.contains(BuiltinVariations::STANDARD) { + inject_standard_builtins(declaration, module, name) + } + + if variations.contains(BuiltinVariations::DOUBLE) { + inject_double_builtin(declaration, module, name) + } + + match name { + "texture" + | "textureGrad" + | "textureGradOffset" + | "textureLod" + | "textureLodOffset" + | "textureOffset" + | "textureProj" + | "textureProjGrad" + | "textureProjGradOffset" + | "textureProjLod" + | "textureProjLodOffset" + | "textureProjOffset" => { + let f = |kind, dim, arrayed, multi, shadow| { + for bits in 0..=0b11 { + let variant = bits & 0b1 != 0; + let bias = bits & 0b10 != 0; + + let (proj, offset, level_type) = match name { + // texture(gsampler, gvec P, [float bias]); + "texture" => (false, false, TextureLevelType::None), + // textureGrad(gsampler, gvec P, gvec dPdx, gvec dPdy); + "textureGrad" => (false, false, TextureLevelType::Grad), + // textureGradOffset(gsampler, gvec P, gvec dPdx, gvec dPdy, ivec offset); + "textureGradOffset" => (false, true, TextureLevelType::Grad), + // textureLod(gsampler, gvec P, float lod); + "textureLod" => (false, false, TextureLevelType::Lod), + // textureLodOffset(gsampler, gvec P, float lod, ivec offset); + "textureLodOffset" => (false, true, TextureLevelType::Lod), + // textureOffset(gsampler, gvec+1 P, ivec offset, [float bias]); + "textureOffset" => (false, true, TextureLevelType::None), + // textureProj(gsampler, gvec+1 P, [float bias]); + "textureProj" => (true, false, TextureLevelType::None), + // textureProjGrad(gsampler, gvec+1 P, gvec dPdx, gvec dPdy); + "textureProjGrad" => (true, false, TextureLevelType::Grad), + // textureProjGradOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); + "textureProjGradOffset" => (true, true, TextureLevelType::Grad), + // textureProjLod(gsampler, gvec+1 P, float lod); + "textureProjLod" => (true, false, TextureLevelType::Lod), + // textureProjLodOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); + "textureProjLodOffset" => (true, true, TextureLevelType::Lod), + // textureProjOffset(gsampler, gvec+1 P, ivec offset, [float bias]); + "textureProjOffset" => (true, true, TextureLevelType::None), + _ => unreachable!(), + }; + + let builtin = MacroCall::Texture { + proj, + offset, + shadow, + level_type, + }; + + // Parse out the variant settings. + let grad = level_type == TextureLevelType::Grad; + let lod = level_type == TextureLevelType::Lod; + + let supports_variant = proj && !shadow; + if variant && !supports_variant { + continue; + } + + if bias && !matches!(level_type, TextureLevelType::None) { + continue; + } + + // Proj doesn't work with arrayed or Cube + if proj && (arrayed || dim == Dim::Cube) { + continue; + } + + // texture operations with offset are not supported for cube maps + if dim == Dim::Cube && offset { + continue; + } + + // sampler2DArrayShadow can't be used in textureLod or in texture with bias + if (lod || bias) && arrayed && shadow && dim == Dim::D2 { + continue; + } + + // TODO: glsl supports using bias with depth samplers but naga doesn't + if bias && shadow { + continue; + } + + let class = match shadow { + true => ImageClass::Depth { multi }, + false => ImageClass::Sampled { kind, multi }, + }; + + let image = TypeInner::Image { + dim, + arrayed, + class, + }; + + let num_coords_from_dim = image_dims_to_coords_size(dim).min(3); + let mut num_coords = num_coords_from_dim; + + if shadow && proj { + num_coords = 4; + } else if dim == Dim::D1 && shadow { + num_coords = 3; + } else if shadow { + num_coords += 1; + } else if proj { + if variant && num_coords == 4 { + // Normal form already has 4 components, no need to have a variant form. + continue; + } else if variant { + num_coords = 4; + } else { + num_coords += 1; + } + } + + if !(dim == Dim::D1 && shadow) { + num_coords += arrayed as usize; + } + + // Special case: texture(gsamplerCubeArrayShadow) kicks the shadow compare ref to a separate argument, + // since it would otherwise take five arguments. It also can't take a bias, nor can it be proj/grad/lod/offset + // (presumably because nobody asked for it, and implementation complexity?) + if num_coords >= 5 { + if lod || grad || offset || proj || bias { + continue; + } + debug_assert!(dim == Dim::Cube && shadow && arrayed); + } + debug_assert!(num_coords <= 5); + + let vector = make_coords_arg(num_coords, Sk::Float); + let mut args = vec![image, vector]; + + if num_coords == 5 { + args.push(TypeInner::Scalar(Scalar::F32)); + } + + match level_type { + TextureLevelType::Lod => { + args.push(TypeInner::Scalar(Scalar::F32)); + } + TextureLevelType::Grad => { + args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); + args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); + } + TextureLevelType::None => {} + }; + + if offset { + args.push(make_coords_arg(num_coords_from_dim, Sk::Sint)); + } + + if bias { + args.push(TypeInner::Scalar(Scalar::F32)); + } + + declaration + .overloads + .push(module.add_builtin(args, builtin)); + } + }; + + texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f) + } + "textureSize" => { + let f = |kind, dim, arrayed, multi, shadow| { + let class = match shadow { + true => ImageClass::Depth { multi }, + false => ImageClass::Sampled { kind, multi }, + }; + + let image = TypeInner::Image { + dim, + arrayed, + class, + }; + + let mut args = vec![image]; + + if !multi { + args.push(TypeInner::Scalar(Scalar::I32)) + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::TextureSize { arrayed })) + }; + + texture_args_generator( + TextureArgsOptions::SHADOW | TextureArgsOptions::MULTI | variations.into(), + f, + ) + } + "texelFetch" | "texelFetchOffset" => { + let offset = "texelFetchOffset" == name; + let f = |kind, dim, arrayed, multi, _shadow| { + // Cube images aren't supported + if let Dim::Cube = dim { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Sampled { kind, multi }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let coordinates = make_coords_arg(dim_value + arrayed as usize, Sk::Sint); + + let mut args = vec![image, coordinates, TypeInner::Scalar(Scalar::I32)]; + + if offset { + args.push(make_coords_arg(dim_value, Sk::Sint)); + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::ImageLoad { multi })) + }; + + // Don't generate shadow images since they aren't supported + texture_args_generator(TextureArgsOptions::MULTI | variations.into(), f) + } + "imageSize" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::empty(), + }, + }; + + declaration + .overloads + .push(module.add_builtin(vec![image], MacroCall::TextureSize { arrayed })) + }; + + texture_args_generator(variations.into(), f) + } + "imageLoad" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::LOAD, + }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let mut coord_size = dim_value + arrayed as usize; + // > Every OpenGL API call that operates on cubemap array + // > textures takes layer-faces, not array layers + // + // So this means that imageCubeArray only takes a three component + // vector coordinate and the third component is a layer index. + if Dim::Cube == dim && arrayed { + coord_size = 3 + } + let coordinates = make_coords_arg(coord_size, Sk::Sint); + + let args = vec![image, coordinates]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::ImageLoad { multi: false })) + }; + + // Don't generate shadow nor multisampled images since they aren't supported + texture_args_generator(variations.into(), f) + } + "imageStore" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::STORE, + }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let mut coord_size = dim_value + arrayed as usize; + // > Every OpenGL API call that operates on cubemap array + // > textures takes layer-faces, not array layers + // + // So this means that imageCubeArray only takes a three component + // vector coordinate and the third component is a layer index. + if Dim::Cube == dim && arrayed { + coord_size = 3 + } + let coordinates = make_coords_arg(coord_size, Sk::Sint); + + let args = vec![ + image, + coordinates, + TypeInner::Vector { + size: VectorSize::Quad, + scalar: Scalar { kind, width: 4 }, + }, + ]; + + let mut overload = module.add_builtin(args, MacroCall::ImageStore); + overload.void = true; + declaration.overloads.push(overload) + }; + + // Don't generate shadow nor multisampled images since they aren't supported + texture_args_generator(variations.into(), f) + } + _ => {} + } +} + +/// Injects the builtins into declaration that don't need any special variations +fn inject_standard_builtins( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, +) { + match name { + "sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS" + | "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => { + declaration.overloads.push(module.add_builtin( + vec![ + TypeInner::Image { + dim: match name { + "sampler1D" | "sampler1DArray" => Dim::D1, + "sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" => { + Dim::D2 + } + "sampler3D" => Dim::D3, + _ => Dim::Cube, + }, + arrayed: matches!( + name, + "sampler1DArray" + | "sampler2DArray" + | "sampler2DMSArray" + | "samplerCubeArray" + ), + class: ImageClass::Sampled { + kind: Sk::Float, + multi: matches!(name, "sampler2DMS" | "sampler2DMSArray"), + }, + }, + TypeInner::Sampler { comparison: false }, + ], + MacroCall::Sampler, + )) + } + "sampler1DShadow" + | "sampler1DArrayShadow" + | "sampler2DShadow" + | "sampler2DArrayShadow" + | "samplerCubeShadow" + | "samplerCubeArrayShadow" => { + let dim = match name { + "sampler1DShadow" | "sampler1DArrayShadow" => Dim::D1, + "sampler2DShadow" | "sampler2DArrayShadow" => Dim::D2, + _ => Dim::Cube, + }; + let arrayed = matches!( + name, + "sampler1DArrayShadow" | "sampler2DArrayShadow" | "samplerCubeArrayShadow" + ); + + for i in 0..2 { + let ty = TypeInner::Image { + dim, + arrayed, + class: match i { + 0 => ImageClass::Sampled { + kind: Sk::Float, + multi: false, + }, + _ => ImageClass::Depth { multi: false }, + }, + }; + + declaration.overloads.push(module.add_builtin( + vec![ty, TypeInner::Sampler { comparison: true }], + MacroCall::SamplerShadow, + )) + } + } + "sin" | "exp" | "exp2" | "sinh" | "cos" | "cosh" | "tan" | "tanh" | "acos" | "asin" + | "log" | "log2" | "radians" | "degrees" | "asinh" | "acosh" | "atanh" + | "floatBitsToInt" | "floatBitsToUint" | "dFdx" | "dFdxFine" | "dFdxCoarse" | "dFdy" + | "dFdyFine" | "dFdyCoarse" | "fwidth" | "fwidthFine" | "fwidthCoarse" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }], + match name { + "sin" => MacroCall::MathFunction(MathFunction::Sin), + "exp" => MacroCall::MathFunction(MathFunction::Exp), + "exp2" => MacroCall::MathFunction(MathFunction::Exp2), + "sinh" => MacroCall::MathFunction(MathFunction::Sinh), + "cos" => MacroCall::MathFunction(MathFunction::Cos), + "cosh" => MacroCall::MathFunction(MathFunction::Cosh), + "tan" => MacroCall::MathFunction(MathFunction::Tan), + "tanh" => MacroCall::MathFunction(MathFunction::Tanh), + "acos" => MacroCall::MathFunction(MathFunction::Acos), + "asin" => MacroCall::MathFunction(MathFunction::Asin), + "log" => MacroCall::MathFunction(MathFunction::Log), + "log2" => MacroCall::MathFunction(MathFunction::Log2), + "asinh" => MacroCall::MathFunction(MathFunction::Asinh), + "acosh" => MacroCall::MathFunction(MathFunction::Acosh), + "atanh" => MacroCall::MathFunction(MathFunction::Atanh), + "radians" => MacroCall::MathFunction(MathFunction::Radians), + "degrees" => MacroCall::MathFunction(MathFunction::Degrees), + "floatBitsToInt" => MacroCall::BitCast(Sk::Sint), + "floatBitsToUint" => MacroCall::BitCast(Sk::Uint), + "dFdxCoarse" => MacroCall::Derivate(Axis::X, Ctrl::Coarse), + "dFdyCoarse" => MacroCall::Derivate(Axis::Y, Ctrl::Coarse), + "fwidthCoarse" => MacroCall::Derivate(Axis::Width, Ctrl::Coarse), + "dFdxFine" => MacroCall::Derivate(Axis::X, Ctrl::Fine), + "dFdyFine" => MacroCall::Derivate(Axis::Y, Ctrl::Fine), + "fwidthFine" => MacroCall::Derivate(Axis::Width, Ctrl::Fine), + "dFdx" => MacroCall::Derivate(Axis::X, Ctrl::None), + "dFdy" => MacroCall::Derivate(Axis::Y, Ctrl::None), + "fwidth" => MacroCall::Derivate(Axis::Width, Ctrl::None), + _ => unreachable!(), + }, + )) + } + } + "intBitsToFloat" | "uintBitsToFloat" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = match name { + "intBitsToFloat" => Scalar::I32, + _ => Scalar::U32, + }; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }], + MacroCall::BitCast(Sk::Float), + )) + } + } + "pow" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + declaration.overloads.push( + module + .add_builtin(vec![ty(), ty()], MacroCall::MathFunction(MathFunction::Pow)), + ) + } + } + "abs" | "sign" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 - float/sint + for bits in 0..0b1000 { + let size = match bits & 0b11 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = match bits >> 2 { + 0b0 => Scalar::F32, + _ => Scalar::I32, + }; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "abs" => MathFunction::Abs, + "sign" => MathFunction::Sign, + _ => unreachable!(), + }), + )) + } + } + "bitCount" | "bitfieldReverse" | "bitfieldExtract" | "bitfieldInsert" | "findLSB" + | "findMSB" => { + let fun = match name { + "bitCount" => MathFunction::CountOneBits, + "bitfieldReverse" => MathFunction::ReverseBits, + "bitfieldExtract" => MathFunction::ExtractBits, + "bitfieldInsert" => MathFunction::InsertBits, + "findLSB" => MathFunction::FindLsb, + "findMSB" => MathFunction::FindMsb, + _ => unreachable!(), + }; + + let mc = match fun { + MathFunction::ExtractBits => MacroCall::BitfieldExtract, + MathFunction::InsertBits => MacroCall::BitfieldInsert, + _ => MacroCall::MathFunction(fun), + }; + + // bits layout + // bit 0 - int/uint + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let scalar = match bits & 0b1 { + 0b0 => Scalar::I32, + _ => Scalar::U32, + }; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let mut args = vec![ty()]; + + match fun { + MathFunction::ExtractBits => { + args.push(TypeInner::Scalar(Scalar::I32)); + args.push(TypeInner::Scalar(Scalar::I32)); + } + MathFunction::InsertBits => { + args.push(ty()); + args.push(TypeInner::Scalar(Scalar::I32)); + args.push(TypeInner::Scalar(Scalar::I32)); + } + _ => {} + } + + // we need to cast the return type of findLsb / findMsb + let mc = if scalar.kind == Sk::Uint { + match mc { + MacroCall::MathFunction(MathFunction::FindLsb) => MacroCall::FindLsbUint, + MacroCall::MathFunction(MathFunction::FindMsb) => MacroCall::FindMsbUint, + mc => mc, + } + } else { + mc + }; + + declaration.overloads.push(module.add_builtin(args, mc)) + } + } + "packSnorm4x8" | "packUnorm4x8" | "packSnorm2x16" | "packUnorm2x16" | "packHalf2x16" => { + let fun = match name { + "packSnorm4x8" => MathFunction::Pack4x8snorm, + "packUnorm4x8" => MathFunction::Pack4x8unorm, + "packSnorm2x16" => MathFunction::Pack2x16unorm, + "packUnorm2x16" => MathFunction::Pack2x16snorm, + "packHalf2x16" => MathFunction::Pack2x16float, + _ => unreachable!(), + }; + + let ty = match fun { + MathFunction::Pack4x8snorm | MathFunction::Pack4x8unorm => TypeInner::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::F32, + }, + MathFunction::Pack2x16unorm + | MathFunction::Pack2x16snorm + | MathFunction::Pack2x16float => TypeInner::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar::F32, + }, + _ => unreachable!(), + }; + + let args = vec![ty]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))); + } + "unpackSnorm4x8" | "unpackUnorm4x8" | "unpackSnorm2x16" | "unpackUnorm2x16" + | "unpackHalf2x16" => { + let fun = match name { + "unpackSnorm4x8" => MathFunction::Unpack4x8snorm, + "unpackUnorm4x8" => MathFunction::Unpack4x8unorm, + "unpackSnorm2x16" => MathFunction::Unpack2x16snorm, + "unpackUnorm2x16" => MathFunction::Unpack2x16unorm, + "unpackHalf2x16" => MathFunction::Unpack2x16float, + _ => unreachable!(), + }; + + let args = vec![TypeInner::Scalar(Scalar::U32)]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))); + } + "atan" => { + // bits layout + // bit 0 - atan/atan2 + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let fun = match bits & 0b1 { + 0b0 => MathFunction::Atan, + _ => MathFunction::Atan2, + }; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let mut args = vec![ty()]; + + if fun == MathFunction::Atan2 { + args.push(ty()) + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))) + } + } + "all" | "any" | "not" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b11 { + let size = match bits { + 0b00 => VectorSize::Bi, + 0b01 => VectorSize::Tri, + _ => VectorSize::Quad, + }; + + let args = vec![TypeInner::Vector { + size, + scalar: Scalar::BOOL, + }]; + + let fun = match name { + "all" => MacroCall::Relational(RelationalFunction::All), + "any" => MacroCall::Relational(RelationalFunction::Any), + "not" => MacroCall::Unary(UnaryOperator::LogicalNot), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" => { + for bits in 0..0b1001 { + let (size, scalar) = match bits { + 0b0000 => (VectorSize::Bi, Scalar::F32), + 0b0001 => (VectorSize::Tri, Scalar::F32), + 0b0010 => (VectorSize::Quad, Scalar::F32), + 0b0011 => (VectorSize::Bi, Scalar::I32), + 0b0100 => (VectorSize::Tri, Scalar::I32), + 0b0101 => (VectorSize::Quad, Scalar::I32), + 0b0110 => (VectorSize::Bi, Scalar::U32), + 0b0111 => (VectorSize::Tri, Scalar::U32), + _ => (VectorSize::Quad, Scalar::U32), + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "lessThan" => BinaryOperator::Less, + "greaterThan" => BinaryOperator::Greater, + "lessThanEqual" => BinaryOperator::LessEqual, + "greaterThanEqual" => BinaryOperator::GreaterEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "equal" | "notEqual" => { + for bits in 0..0b1100 { + let (size, scalar) = match bits { + 0b0000 => (VectorSize::Bi, Scalar::F32), + 0b0001 => (VectorSize::Tri, Scalar::F32), + 0b0010 => (VectorSize::Quad, Scalar::F32), + 0b0011 => (VectorSize::Bi, Scalar::I32), + 0b0100 => (VectorSize::Tri, Scalar::I32), + 0b0101 => (VectorSize::Quad, Scalar::I32), + 0b0110 => (VectorSize::Bi, Scalar::U32), + 0b0111 => (VectorSize::Tri, Scalar::U32), + 0b1000 => (VectorSize::Quad, Scalar::U32), + 0b1001 => (VectorSize::Bi, Scalar::BOOL), + 0b1010 => (VectorSize::Tri, Scalar::BOOL), + _ => (VectorSize::Quad, Scalar::BOOL), + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "equal" => BinaryOperator::Equal, + "notEqual" => BinaryOperator::NotEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "min" | "max" => { + // bits layout + // bit 0 through 1 - scalar kind + // bit 2 through 4 - dims + for bits in 0..0b11100 { + let scalar = match bits & 0b11 { + 0b00 => Scalar::F32, + 0b01 => Scalar::I32, + 0b10 => Scalar::U32, + _ => continue, + }; + let (size, second_size) = match bits >> 2 { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + + let args = vec![ + match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + match second_size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + ]; + + let fun = match name { + "max" => MacroCall::Splatted(MathFunction::Max, size, 1), + "min" => MacroCall::Splatted(MathFunction::Min, size, 1), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "mix" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 through 4 - types + // + // 0b10011 is the last element since splatted single elements + // were already added + for bits in 0..0b10011 { + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let (scalar, splatted, boolean) = match bits >> 2 { + 0b000 => (Scalar::I32, false, true), + 0b001 => (Scalar::U32, false, true), + 0b010 => (Scalar::F32, false, true), + 0b011 => (Scalar::F32, false, false), + _ => (Scalar::F32, true, false), + }; + + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let args = vec![ + ty(scalar), + ty(scalar), + match (boolean, splatted) { + (true, _) => ty(Scalar::BOOL), + (_, false) => TypeInner::Scalar(scalar), + _ => ty(scalar), + }, + ]; + + declaration.overloads.push(module.add_builtin( + args, + match boolean { + true => MacroCall::MixBoolean, + false => MacroCall::Splatted(MathFunction::Mix, size, 2), + }, + )) + } + } + "clamp" => { + // bits layout + // bit 0 through 1 - float/int/uint + // bit 2 through 3 - dims + // bit 4 - splatted + // + // 0b11010 is the last element since splatted single elements + // were already added + for bits in 0..0b11011 { + let scalar = match bits & 0b11 { + 0b00 => Scalar::F32, + 0b01 => Scalar::I32, + 0b10 => Scalar::U32, + _ => continue, + }; + let size = match (bits >> 2) & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let splatted = bits & 0b10000 == 0b10000; + + let base_ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar(scalar), + false => base_ty(), + }; + + let args = vec![base_ty(), limit_ty(), limit_ty()]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::Clamp(size))) + } + } + "barrier" => declaration + .overloads + .push(module.add_builtin(Vec::new(), MacroCall::Barrier)), + // Add common builtins with floats + _ => inject_common_builtin(declaration, module, name, 4), + } +} + +/// Injects the builtins into declaration that need doubles +fn inject_double_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) { + match name { + "abs" | "sign" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F64; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "abs" => MathFunction::Abs, + "sign" => MathFunction::Sign, + _ => unreachable!(), + }), + )) + } + } + "min" | "max" => { + // bits layout + // bit 0 through 2 - dims + for bits in 0..0b111 { + let (size, second_size) = match bits { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + let scalar = Scalar::F64; + + let args = vec![ + match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + match second_size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + ]; + + let fun = match name { + "max" => MacroCall::Splatted(MathFunction::Max, size, 1), + "min" => MacroCall::Splatted(MathFunction::Min, size, 1), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "mix" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 through 3 - splatted/boolean + // + // 0b1010 is the last element since splatted with single elements + // is equal to normal single elements + for bits in 0..0b1011 { + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Quad), + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => None, + }; + let scalar = Scalar::F64; + let (splatted, boolean) = match bits >> 2 { + 0b00 => (false, false), + 0b01 => (false, true), + _ => (true, false), + }; + + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let args = vec![ + ty(scalar), + ty(scalar), + match (boolean, splatted) { + (true, _) => ty(Scalar::BOOL), + (_, false) => TypeInner::Scalar(scalar), + _ => ty(scalar), + }, + ]; + + declaration.overloads.push(module.add_builtin( + args, + match boolean { + true => MacroCall::MixBoolean, + false => MacroCall::Splatted(MathFunction::Mix, size, 2), + }, + )) + } + } + "clamp" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 - splatted + // + // 0b110 is the last element since splatted with single elements + // is equal to normal single elements + for bits in 0..0b111 { + let scalar = Scalar::F64; + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let splatted = bits & 0b100 == 0b100; + + let base_ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar(scalar), + false => base_ty(), + }; + + let args = vec![base_ty(), limit_ty(), limit_ty()]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::Clamp(size))) + } + } + "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" | "equal" + | "notEqual" => { + let scalar = Scalar::F64; + for bits in 0..0b11 { + let size = match bits { + 0b00 => VectorSize::Bi, + 0b01 => VectorSize::Tri, + _ => VectorSize::Quad, + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "lessThan" => BinaryOperator::Less, + "greaterThan" => BinaryOperator::Greater, + "lessThanEqual" => BinaryOperator::LessEqual, + "greaterThanEqual" => BinaryOperator::GreaterEqual, + "equal" => BinaryOperator::Equal, + "notEqual" => BinaryOperator::NotEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + // Add common builtins with doubles + _ => inject_common_builtin(declaration, module, name, 8), + } +} + +/// Injects the builtins into declaration that can used either float or doubles +fn inject_common_builtin( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, + float_width: crate::Bytes, +) { + let float_scalar = Scalar { + kind: Sk::Float, + width: float_width, + }; + match name { + "ceil" | "round" | "roundEven" | "floor" | "fract" | "trunc" | "sqrt" | "inversesqrt" + | "normalize" | "length" | "isinf" | "isnan" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let args = vec![match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }]; + + let fun = match name { + "ceil" => MacroCall::MathFunction(MathFunction::Ceil), + "round" | "roundEven" => MacroCall::MathFunction(MathFunction::Round), + "floor" => MacroCall::MathFunction(MathFunction::Floor), + "fract" => MacroCall::MathFunction(MathFunction::Fract), + "trunc" => MacroCall::MathFunction(MathFunction::Trunc), + "sqrt" => MacroCall::MathFunction(MathFunction::Sqrt), + "inversesqrt" => MacroCall::MathFunction(MathFunction::InverseSqrt), + "normalize" => MacroCall::MathFunction(MathFunction::Normalize), + "length" => MacroCall::MathFunction(MathFunction::Length), + "isinf" => MacroCall::Relational(RelationalFunction::IsInf), + "isnan" => MacroCall::Relational(RelationalFunction::IsNan), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "dot" | "reflect" | "distance" | "ldexp" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let fun = match name { + "dot" => MacroCall::MathFunction(MathFunction::Dot), + "reflect" => MacroCall::MathFunction(MathFunction::Reflect), + "distance" => MacroCall::MathFunction(MathFunction::Distance), + "ldexp" => MacroCall::MathFunction(MathFunction::Ldexp), + _ => unreachable!(), + }; + + let second_scalar = match fun { + MacroCall::MathFunction(MathFunction::Ldexp) => Scalar::I32, + _ => float_scalar, + }; + + declaration + .overloads + .push(module.add_builtin(vec![ty(float_scalar), ty(second_scalar)], fun)) + } + } + "transpose" => { + // bits layout + // bit 0 through 3 - dims + for bits in 0..0b1001 { + let (rows, columns) = match bits { + 0b0000 => (VectorSize::Bi, VectorSize::Bi), + 0b0001 => (VectorSize::Bi, VectorSize::Tri), + 0b0010 => (VectorSize::Bi, VectorSize::Quad), + 0b0011 => (VectorSize::Tri, VectorSize::Bi), + 0b0100 => (VectorSize::Tri, VectorSize::Tri), + 0b0101 => (VectorSize::Tri, VectorSize::Quad), + 0b0110 => (VectorSize::Quad, VectorSize::Bi), + 0b0111 => (VectorSize::Quad, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + declaration.overloads.push(module.add_builtin( + vec![TypeInner::Matrix { + columns, + rows, + scalar: float_scalar, + }], + MacroCall::MathFunction(MathFunction::Transpose), + )) + } + } + "inverse" | "determinant" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b11 { + let (rows, columns) = match bits { + 0b00 => (VectorSize::Bi, VectorSize::Bi), + 0b01 => (VectorSize::Tri, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + let args = vec![TypeInner::Matrix { + columns, + rows, + scalar: float_scalar, + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "inverse" => MathFunction::Inverse, + "determinant" => MathFunction::Determinant, + _ => unreachable!(), + }), + )) + } + } + "mod" | "step" => { + // bits layout + // bit 0 through 2 - dims + for bits in 0..0b111 { + let (size, second_size) = match bits { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + + let mut args = Vec::with_capacity(2); + let step = name == "step"; + + for i in 0..2 { + let maybe_size = match i == step as u32 { + true => size, + false => second_size, + }; + + args.push(match maybe_size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }) + } + + let fun = match name { + "mod" => MacroCall::Mod(size), + "step" => MacroCall::Splatted(MathFunction::Step, size, 0), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + // TODO: https://github.com/gfx-rs/naga/issues/2526 + // "modf" | "frexp" => { ... } + "cross" => { + let args = vec![ + TypeInner::Vector { + size: VectorSize::Tri, + scalar: float_scalar, + }, + TypeInner::Vector { + size: VectorSize::Tri, + scalar: float_scalar, + }, + ]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Cross))) + } + "outerProduct" => { + // bits layout + // bit 0 through 3 - dims + for bits in 0..0b1001 { + let (size1, size2) = match bits { + 0b0000 => (VectorSize::Bi, VectorSize::Bi), + 0b0001 => (VectorSize::Bi, VectorSize::Tri), + 0b0010 => (VectorSize::Bi, VectorSize::Quad), + 0b0011 => (VectorSize::Tri, VectorSize::Bi), + 0b0100 => (VectorSize::Tri, VectorSize::Tri), + 0b0101 => (VectorSize::Tri, VectorSize::Quad), + 0b0110 => (VectorSize::Quad, VectorSize::Bi), + 0b0111 => (VectorSize::Quad, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + let args = vec![ + TypeInner::Vector { + size: size1, + scalar: float_scalar, + }, + TypeInner::Vector { + size: size2, + scalar: float_scalar, + }, + ]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Outer))) + } + } + "faceforward" | "fma" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let args = vec![ty(), ty(), ty()]; + + let fun = match name { + "faceforward" => MacroCall::MathFunction(MathFunction::FaceForward), + "fma" => MacroCall::MathFunction(MathFunction::Fma), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "refract" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let args = vec![ty(), ty(), TypeInner::Scalar(Scalar::F32)]; + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Refract))) + } + } + "smoothstep" => { + // bit 0 - splatted + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let splatted = bits & 0b1 == 0b1; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + if splatted && size.is_none() { + continue; + } + + let base_ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let ty = || match splatted { + true => TypeInner::Scalar(float_scalar), + false => base_ty(), + }; + declaration.overloads.push(module.add_builtin( + vec![ty(), ty(), base_ty()], + MacroCall::SmoothStep { splatted: size }, + )) + } + } + // The function isn't a builtin or we don't yet support it + _ => {} + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum TextureLevelType { + None, + Lod, + Grad, +} + +/// A compiler defined builtin function +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum MacroCall { + Sampler, + SamplerShadow, + Texture { + proj: bool, + offset: bool, + shadow: bool, + level_type: TextureLevelType, + }, + TextureSize { + arrayed: bool, + }, + ImageLoad { + multi: bool, + }, + ImageStore, + MathFunction(MathFunction), + FindLsbUint, + FindMsbUint, + BitfieldExtract, + BitfieldInsert, + Relational(RelationalFunction), + Unary(UnaryOperator), + Binary(BinaryOperator), + Mod(Option), + Splatted(MathFunction, Option, usize), + MixBoolean, + Clamp(Option), + BitCast(Sk), + Derivate(Axis, Ctrl), + Barrier, + /// SmoothStep needs a separate variant because it might need it's inputs + /// to be splatted depending on the overload + SmoothStep { + /// The size of the splat operation if some + splatted: Option, + }, +} + +impl MacroCall { + /// Adds the necessary expressions and statements to the passed body and + /// finally returns the final expression with the correct result + pub fn call( + &self, + frontend: &mut Frontend, + ctx: &mut Context, + args: &mut [Handle], + meta: Span, + ) -> Result>> { + Ok(Some(match *self { + MacroCall::Sampler => { + ctx.samplers.insert(args[0], args[1]); + args[0] + } + MacroCall::SamplerShadow => { + sampled_to_depth(ctx, args[0], meta, &mut frontend.errors); + ctx.invalidate_expression(args[0], meta)?; + ctx.samplers.insert(args[0], args[1]); + args[0] + } + MacroCall::Texture { + proj, + offset, + shadow, + level_type, + } => { + let mut coords = args[1]; + + if proj { + let size = match *ctx.resolve_type(coords, meta)? { + TypeInner::Vector { size, .. } => size, + _ => unreachable!(), + }; + let mut right = ctx.add_expression( + Expression::AccessIndex { + base: coords, + index: size as u32 - 1, + }, + Span::default(), + )?; + let left = if let VectorSize::Bi = size { + ctx.add_expression( + Expression::AccessIndex { + base: coords, + index: 0, + }, + Span::default(), + )? + } else { + let size = match size { + VectorSize::Tri => VectorSize::Bi, + _ => VectorSize::Tri, + }; + right = ctx.add_expression( + Expression::Splat { size, value: right }, + Span::default(), + )?; + ctx.vector_resize(size, coords, Span::default())? + }; + coords = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Divide, + left, + right, + }, + Span::default(), + )?; + } + + let extra = args.get(2).copied(); + let comps = frontend.coordinate_components(ctx, args[0], coords, extra, meta)?; + + let mut num_args = 2; + + if comps.used_extra { + num_args += 1; + }; + + // Parse out explicit texture level. + let mut level = match level_type { + TextureLevelType::None => SampleLevel::Auto, + + TextureLevelType::Lod => { + num_args += 1; + + if shadow { + log::warn!("Assuming LOD {:?} is zero", args[2],); + + SampleLevel::Zero + } else { + SampleLevel::Exact(args[2]) + } + } + + TextureLevelType::Grad => { + num_args += 2; + + if shadow { + log::warn!( + "Assuming gradients {:?} and {:?} are not greater than 1", + args[2], + args[3], + ); + SampleLevel::Zero + } else { + SampleLevel::Gradient { + x: args[2], + y: args[3], + } + } + } + }; + + let texture_offset = match offset { + true => { + let offset_arg = args[num_args]; + num_args += 1; + match ctx.lift_up_const_expression(offset_arg) { + Ok(v) => Some(v), + Err(e) => { + frontend.errors.push(e); + None + } + } + } + false => None, + }; + + // Now go back and look for optional bias arg (if available) + if let TextureLevelType::None = level_type { + level = args + .get(num_args) + .copied() + .map_or(SampleLevel::Auto, SampleLevel::Bias); + } + + texture_call(ctx, args[0], level, comps, texture_offset, meta)? + } + + MacroCall::TextureSize { arrayed } => { + let mut expr = ctx.add_expression( + Expression::ImageQuery { + image: args[0], + query: ImageQuery::Size { + level: args.get(1).copied(), + }, + }, + Span::default(), + )?; + + if arrayed { + let mut components = Vec::with_capacity(4); + + let size = match *ctx.resolve_type(expr, meta)? { + TypeInner::Vector { size: ori_size, .. } => { + for index in 0..(ori_size as u32) { + components.push(ctx.add_expression( + Expression::AccessIndex { base: expr, index }, + Span::default(), + )?) + } + + match ori_size { + VectorSize::Bi => VectorSize::Tri, + _ => VectorSize::Quad, + } + } + _ => { + components.push(expr); + VectorSize::Bi + } + }; + + components.push(ctx.add_expression( + Expression::ImageQuery { + image: args[0], + query: ImageQuery::NumLayers, + }, + Span::default(), + )?); + + let ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size, + scalar: Scalar::U32, + }, + }, + Span::default(), + ); + + expr = ctx.add_expression(Expression::Compose { components, ty }, meta)? + } + + ctx.add_expression( + Expression::As { + expr, + kind: Sk::Sint, + convert: Some(4), + }, + Span::default(), + )? + } + MacroCall::ImageLoad { multi } => { + let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; + let (sample, level) = match (multi, args.get(2)) { + (_, None) => (None, None), + (true, Some(&arg)) => (Some(arg), None), + (false, Some(&arg)) => (None, Some(arg)), + }; + ctx.add_expression( + Expression::ImageLoad { + image: args[0], + coordinate: comps.coordinate, + array_index: comps.array_index, + sample, + level, + }, + Span::default(), + )? + } + MacroCall::ImageStore => { + let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; + ctx.emit_restart(); + ctx.body.push( + crate::Statement::ImageStore { + image: args[0], + coordinate: comps.coordinate, + array_index: comps.array_index, + value: args[2], + }, + meta, + ); + return Ok(None); + } + MacroCall::MathFunction(fun) => ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )?, + mc @ (MacroCall::FindLsbUint | MacroCall::FindMsbUint) => { + let fun = match mc { + MacroCall::FindLsbUint => MathFunction::FindLsb, + MacroCall::FindMsbUint => MathFunction::FindMsb, + _ => unreachable!(), + }; + let res = ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: None, + arg2: None, + arg3: None, + }, + Span::default(), + )?; + ctx.add_expression( + Expression::As { + expr: res, + kind: Sk::Sint, + convert: Some(4), + }, + Span::default(), + )? + } + MacroCall::BitfieldInsert => { + let conv_arg_2 = ctx.add_expression( + Expression::As { + expr: args[2], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + let conv_arg_3 = ctx.add_expression( + Expression::As { + expr: args[3], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Math { + fun: MathFunction::InsertBits, + arg: args[0], + arg1: Some(args[1]), + arg2: Some(conv_arg_2), + arg3: Some(conv_arg_3), + }, + Span::default(), + )? + } + MacroCall::BitfieldExtract => { + let conv_arg_1 = ctx.add_expression( + Expression::As { + expr: args[1], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + let conv_arg_2 = ctx.add_expression( + Expression::As { + expr: args[2], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Math { + fun: MathFunction::ExtractBits, + arg: args[0], + arg1: Some(conv_arg_1), + arg2: Some(conv_arg_2), + arg3: None, + }, + Span::default(), + )? + } + MacroCall::Relational(fun) => ctx.add_expression( + Expression::Relational { + fun, + argument: args[0], + }, + Span::default(), + )?, + MacroCall::Unary(op) => { + ctx.add_expression(Expression::Unary { op, expr: args[0] }, Span::default())? + } + MacroCall::Binary(op) => ctx.add_expression( + Expression::Binary { + op, + left: args[0], + right: args[1], + }, + Span::default(), + )?, + MacroCall::Mod(size) => { + ctx.implicit_splat(&mut args[1], meta, size)?; + + // x - y * floor(x / y) + + let div = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Divide, + left: args[0], + right: args[1], + }, + Span::default(), + )?; + let floor = ctx.add_expression( + Expression::Math { + fun: MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + Span::default(), + )?; + let mult = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Multiply, + left: floor, + right: args[1], + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Subtract, + left: args[0], + right: mult, + }, + Span::default(), + )? + } + MacroCall::Splatted(fun, size, i) => { + ctx.implicit_splat(&mut args[i], meta, size)?; + + ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )? + } + MacroCall::MixBoolean => ctx.add_expression( + Expression::Select { + condition: args[2], + accept: args[1], + reject: args[0], + }, + Span::default(), + )?, + MacroCall::Clamp(size) => { + ctx.implicit_splat(&mut args[1], meta, size)?; + ctx.implicit_splat(&mut args[2], meta, size)?; + + ctx.add_expression( + Expression::Math { + fun: MathFunction::Clamp, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )? + } + MacroCall::BitCast(kind) => ctx.add_expression( + Expression::As { + expr: args[0], + kind, + convert: None, + }, + Span::default(), + )?, + MacroCall::Derivate(axis, ctrl) => ctx.add_expression( + Expression::Derivative { + axis, + ctrl, + expr: args[0], + }, + Span::default(), + )?, + MacroCall::Barrier => { + ctx.emit_restart(); + ctx.body + .push(crate::Statement::Barrier(crate::Barrier::all()), meta); + return Ok(None); + } + MacroCall::SmoothStep { splatted } => { + ctx.implicit_splat(&mut args[0], meta, splatted)?; + ctx.implicit_splat(&mut args[1], meta, splatted)?; + + ctx.add_expression( + Expression::Math { + fun: MathFunction::SmoothStep, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: None, + }, + Span::default(), + )? + } + })) + } +} + +fn texture_call( + ctx: &mut Context, + image: Handle, + level: SampleLevel, + comps: CoordComponents, + offset: Option>, + meta: Span, +) -> Result> { + if let Some(sampler) = ctx.samplers.get(&image).copied() { + let mut array_index = comps.array_index; + + if let Some(ref mut array_index_expr) = array_index { + ctx.conversion(array_index_expr, meta, Scalar::I32)?; + } + + Ok(ctx.add_expression( + Expression::ImageSample { + image, + sampler, + gather: None, //TODO + coordinate: comps.coordinate, + array_index, + offset, + level, + depth_ref: comps.depth_ref, + }, + meta, + )?) + } else { + Err(Error { + kind: ErrorKind::SemanticError("Bad call".into()), + meta, + }) + } +} + +/// Helper struct for texture calls with the separate components from the vector argument +/// +/// Obtained by calling [`coordinate_components`](Frontend::coordinate_components) +#[derive(Debug)] +struct CoordComponents { + coordinate: Handle, + depth_ref: Option>, + array_index: Option>, + used_extra: bool, +} + +impl Frontend { + /// Helper function for texture calls, splits the vector argument into it's components + fn coordinate_components( + &mut self, + ctx: &mut Context, + image: Handle, + coord: Handle, + extra: Option>, + meta: Span, + ) -> Result { + if let TypeInner::Image { + dim, + arrayed, + class, + } = *ctx.resolve_type(image, meta)? + { + let image_size = match dim { + Dim::D1 => None, + Dim::D2 => Some(VectorSize::Bi), + Dim::D3 => Some(VectorSize::Tri), + Dim::Cube => Some(VectorSize::Tri), + }; + let coord_size = match *ctx.resolve_type(coord, meta)? { + TypeInner::Vector { size, .. } => Some(size), + _ => None, + }; + let (shadow, storage) = match class { + ImageClass::Depth { .. } => (true, false), + ImageClass::Storage { .. } => (false, true), + ImageClass::Sampled { .. } => (false, false), + }; + + let coordinate = match (image_size, coord_size) { + (Some(size), Some(coord_s)) if size != coord_s => { + ctx.vector_resize(size, coord, Span::default())? + } + (None, Some(_)) => ctx.add_expression( + Expression::AccessIndex { + base: coord, + index: 0, + }, + Span::default(), + )?, + _ => coord, + }; + + let mut coord_index = image_size.map_or(1, |s| s as u32); + + let array_index = if arrayed && !(storage && dim == Dim::Cube) { + let index = coord_index; + coord_index += 1; + + Some(ctx.add_expression( + Expression::AccessIndex { base: coord, index }, + Span::default(), + )?) + } else { + None + }; + let mut used_extra = false; + let depth_ref = match shadow { + true => { + let index = coord_index; + + if index == 4 { + used_extra = true; + extra + } else { + Some(ctx.add_expression( + Expression::AccessIndex { base: coord, index }, + Span::default(), + )?) + } + } + false => None, + }; + + Ok(CoordComponents { + coordinate, + depth_ref, + array_index, + used_extra, + }) + } else { + self.errors.push(Error { + kind: ErrorKind::SemanticError("Type is not an image".into()), + meta, + }); + + Ok(CoordComponents { + coordinate: coord, + depth_ref: None, + array_index: None, + used_extra: false, + }) + } + } +} + +/// Helper function to cast a expression holding a sampled image to a +/// depth image. +pub fn sampled_to_depth( + ctx: &mut Context, + image: Handle, + meta: Span, + errors: &mut Vec, +) { + // Get the a mutable type handle of the underlying image storage + let ty = match ctx[image] { + Expression::GlobalVariable(handle) => &mut ctx.module.global_variables.get_mut(handle).ty, + Expression::FunctionArgument(i) => { + // Mark the function argument as carrying a depth texture + ctx.parameters_info[i as usize].depth = true; + // NOTE: We need to later also change the parameter type + &mut ctx.arguments[i as usize].ty + } + _ => { + // Only globals and function arguments are allowed to carry an image + return errors.push(Error { + kind: ErrorKind::SemanticError("Not a valid texture expression".into()), + meta, + }); + } + }; + + match ctx.module.types[*ty].inner { + // Update the image class to depth in case it already isn't + TypeInner::Image { + class, + dim, + arrayed, + } => match class { + ImageClass::Sampled { multi, .. } => { + *ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Image { + dim, + arrayed, + class: ImageClass::Depth { multi }, + }, + }, + Span::default(), + ) + } + ImageClass::Depth { .. } => {} + // Other image classes aren't allowed to be transformed to depth + ImageClass::Storage { .. } => errors.push(Error { + kind: ErrorKind::SemanticError("Not a texture".into()), + meta, + }), + }, + _ => errors.push(Error { + kind: ErrorKind::SemanticError("Not a texture".into()), + meta, + }), + }; + + // Copy the handle to allow borrowing the `ctx` again + let ty = *ty; + + // If the image was passed through a function argument we also need to change + // the corresponding parameter + if let Expression::FunctionArgument(i) = ctx[image] { + ctx.parameters[i as usize] = ty; + } +} + +bitflags::bitflags! { + /// Influences the operation `texture_args_generator` + struct TextureArgsOptions: u32 { + /// Generates multisampled variants of images + const MULTI = 1 << 0; + /// Generates shadow variants of images + const SHADOW = 1 << 1; + /// Generates standard images + const STANDARD = 1 << 2; + /// Generates cube arrayed images + const CUBE_ARRAY = 1 << 3; + /// Generates cube arrayed images + const D2_MULTI_ARRAY = 1 << 4; + } +} + +impl From for TextureArgsOptions { + fn from(variations: BuiltinVariations) -> Self { + let mut options = TextureArgsOptions::empty(); + if variations.contains(BuiltinVariations::STANDARD) { + options |= TextureArgsOptions::STANDARD + } + if variations.contains(BuiltinVariations::CUBE_TEXTURES_ARRAY) { + options |= TextureArgsOptions::CUBE_ARRAY + } + if variations.contains(BuiltinVariations::D2_MULTI_TEXTURES_ARRAY) { + options |= TextureArgsOptions::D2_MULTI_ARRAY + } + options + } +} + +/// Helper function to generate the image components for texture/image builtins +/// +/// Calls the passed function `f` with: +/// ```text +/// f(ScalarKind, ImageDimension, arrayed, multi, shadow) +/// ``` +/// +/// `options` controls extra image variants generation like multisampling and depth, +/// see the struct documentation +fn texture_args_generator( + options: TextureArgsOptions, + mut f: impl FnMut(crate::ScalarKind, Dim, bool, bool, bool), +) { + for kind in [Sk::Float, Sk::Uint, Sk::Sint].iter().copied() { + for dim in [Dim::D1, Dim::D2, Dim::D3, Dim::Cube].iter().copied() { + for arrayed in [false, true].iter().copied() { + if dim == Dim::Cube && arrayed { + if !options.contains(TextureArgsOptions::CUBE_ARRAY) { + continue; + } + } else if Dim::D2 == dim + && options.contains(TextureArgsOptions::MULTI) + && arrayed + && options.contains(TextureArgsOptions::D2_MULTI_ARRAY) + { + // multisampling for sampler2DMSArray + f(kind, dim, arrayed, true, false); + } else if !options.contains(TextureArgsOptions::STANDARD) { + continue; + } + + f(kind, dim, arrayed, false, false); + + // 3D images can't be neither arrayed nor shadow + // so we break out early, this way arrayed will always + // be false and we won't hit the shadow branch + if let Dim::D3 = dim { + break; + } + + if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && !arrayed { + // multisampling + f(kind, dim, arrayed, true, false); + } + + if Sk::Float == kind && options.contains(TextureArgsOptions::SHADOW) { + // shadow + f(kind, dim, arrayed, false, true); + } + } + } + } +} + +/// Helper functions used to convert from a image dimension into a integer representing the +/// number of components needed for the coordinates vector (1 means scalar instead of vector) +const fn image_dims_to_coords_size(dim: Dim) -> usize { + match dim { + Dim::D1 => 1, + Dim::D2 => 2, + _ => 3, + } +} -- cgit v1.2.3