/// 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. pubfn 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 in0..=0b11 { let variant = bits & 0b1 != 0; let bias = bits & 0b10 != 0;
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); letmut num_coords = num_coords_from_dim;
if shadow && proj {
num_coords = 4;
} elseif dim == Dim::D1 && shadow {
num_coords = 3;
} elseif shadow {
num_coords += 1;
} elseif proj { if variant && num_coords == 4 { // Normal form already has 4 components, no need to have a variant form. continue;
} elseif 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); letmut args = vec![image, vector];
if num_coords == 5 {
args.push(TypeInner::Scalar(Scalar::F32));
}
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;
}
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 dim_value = image_dims_to_coords_size(dim); letmut 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);
// 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 dim_value = image_dims_to_coords_size(dim); letmut 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 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 in0..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),
};
letmut 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::FirstTrailingBit) => {
MacroCall::FindLsbUint
}
MacroCall::MathFunction(MathFunction::FirstLeadingBit) => {
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: VectorSize::Quad,
scalar: Scalar::F32,
},
MathFunction::Pack2x16unorm
| MathFunction::Pack2x16snorm
| MathFunction::Pack2x16float => TypeInner::Vector {
size: VectorSize::Bi,
scalar: Scalar::F32,
},
_ => unreachable!(),
};
declaration
.overloads
.push(module.add_builtin(args, MacroCall::MathFunction(fun)));
} "atan" => { // bits layout // bit 0 - atan/atan2 // bit 1 through 2 - dims for bits in0..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),
};
letmut 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 in0..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!(),
};
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 in0..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 in0..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()];
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 in0..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 in0..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::MathFunction(MathFunction::Outer)))
}
} "faceforward" | "fma" => { // bits layout // bit 0 through 1 - dims for bits in0..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 in0..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 in0..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
_ => {}
}
}
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) iflet TextureLevelType::None = level_type {
level = args
.get(num_args)
.copied()
.map_or(SampleLevel::Auto, SampleLevel::Bias);
}
/// 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<Expression>,
depth_ref: Option<Handle<Expression>>,
array_index: Option<Handle<Expression>>,
used_extra: bool,
}
impl Frontend { /// Helper function for texture calls, splits the vector argument into it's components fn coordinate_components(
&mutself,
ctx: &mut Context,
image: Handle<Expression>,
coord: Handle<Expression>,
extra: Option<Handle<Expression>>,
meta: Span,
) -> Result<CoordComponents> { iflet 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),
};
/// Helper function to cast a expression holding a sampled image to a /// depth image. pubfn sampled_to_depth(
ctx: &mut Context,
image: Handle<Expression>,
meta: Span,
errors: &mut Vec<Error>,
) { // 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 iflet Expression::FunctionArgument(i) = ctx[image] {
ctx.parameters[i as usize] = ty;
}
}
impl From<BuiltinVariations> for TextureArgsOptions { fn from(variations: BuiltinVariations) -> Self { letmut 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;
}
} elseif Dim::D2 == dim
&& options.contains(TextureArgsOptions::MULTI)
&& arrayed
&& options.contains(TextureArgsOptions::D2_MULTI_ARRAY)
{ // multisampling for sampler2DMSArray
f(kind, dim, arrayed, true, false);
} elseif !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 iflet Dim::D3 = dim { break;
}
if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && !arrayed { // multisampling
f(kind, dim, arrayed, true, false);
}
/// 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) constfn image_dims_to_coords_size(dim: Dim) -> usize { match dim {
Dim::D1 => 1,
Dim::D2 => 2,
_ => 3,
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.52 Sekunden
(vorverarbeitet am 2026-06-23)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.