/// Struct detailing a store operation that must happen after a function call struct ProxyWrite { /// The store target
target: Handle<Expression>, /// A pointer to read the value of the store
value: Handle<Expression>, /// An optional conversion to be applied
convert: Option<Scalar>,
}
let vector_size = match *expr_type {
TypeInner::Vector { size, .. } => Some(size),
_ => None,
};
let expr_is_bool = expr_type.scalar_kind() == Some(ScalarKind::Bool);
// Special case: if casting from a bool, we need to use Select and not As. match ctx.module.types[ty].inner.scalar() {
Some(result_scalar) if expr_is_bool && result_scalar.kind != ScalarKind::Bool => { let result_scalar = Scalar {
width: 4,
..result_scalar
}; let l0 = Literal::zero(result_scalar).unwrap(); let l1 = Literal::one(result_scalar).unwrap(); letmut reject = ctx.add_expression(Expression::Literal(l0), expr_meta)?; letmut accept = ctx.add_expression(Expression::Literal(l1), expr_meta)?;
#[allow(clippy::too_many_arguments)] fn matrix_one_arg(
&mutself,
ctx: &mut Context,
ty: Handle<Type>,
columns: crate::VectorSize,
rows: crate::VectorSize,
element_scalar: Scalar,
(mut value, expr_meta): (Handle<Expression>, Span),
meta: Span,
) -> Result<Handle<Expression>> { letmut components = Vec::with_capacity(columns as usize); // TODO: casts // `Expression::As` doesn't support matrix width // casts so we need to do some extra work for casts
ctx.forced_conversion(&mut value, expr_meta, element_scalar)?; match *ctx.resolve_type(value, expr_meta)? {
TypeInner::Scalar(_) => { // If a matrix is constructed with a single scalar value, then that // value is used to initialize all the values along the diagonal of // the matrix; the rest are given zeros. let vector_ty = ctx.module.types.insert( Type {
name: None,
inner: TypeInner::Vector {
size: rows,
scalar: element_scalar,
},
},
meta,
);
let zero_literal = Literal::zero(element_scalar).unwrap(); let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?;
for i in0..columns as u32 {
components.push(
ctx.add_expression(
Expression::Compose {
ty: vector_ty,
components: (0..rows as u32)
.map(|r| match r == i { true => value, false => zero,
})
.collect(),
},
meta,
)?,
)
}
}
TypeInner::Matrix {
rows: ori_rows,
columns: ori_cols,
..
} => { // If a matrix is constructed from a matrix, then each component // (column i, row j) in the result that has a corresponding component // (column i, row j) in the argument will be initialized from there. All // other components will be initialized to the identity matrix.
let zero_literal = Literal::zero(element_scalar).unwrap(); let one_literal = Literal::one(element_scalar).unwrap();
let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?; let one = ctx.add_expression(Expression::Literal(one_literal), meta)?;
let vector_ty = ctx.module.types.insert( Type {
name: None,
inner: TypeInner::Vector {
size: rows,
scalar: element_scalar,
},
},
meta,
);
for i in0..columns as u32 { if i < ori_cols as u32 { use std::cmp::Ordering;
let vector = ctx.add_expression(
Expression::AccessIndex {
base: value,
index: i,
},
meta,
)?;
components.push(match ori_rows.cmp(&rows) {
Ordering::Less => { let components = (0..rows as u32)
.map(|r| { if r < ori_rows as u32 {
ctx.add_expression(
Expression::AccessIndex {
base: vector,
index: r,
},
meta,
)
} elseif r == i {
Ok(one)
} else {
Ok(zero)
}
})
.collect::<Result<_>>()?;
ctx.add_expression(
Expression::Compose {
ty: vector_ty,
components,
},
meta,
)?
}
Ordering::Equal => vector,
Ordering::Greater => ctx.vector_resize(rows, vector, meta)?,
})
} else { let compose_expr = Expression::Compose {
ty: vector_ty,
components: (0..rows as u32)
.map(|r| match r == i { true => one, false => zero,
})
.collect(),
};
let vec = ctx.add_expression(compose_expr, meta)?;
#[allow(clippy::too_many_arguments)] fn function_call(
&mutself,
ctx: &mut Context,
stmt: &StmtContext,
name: String,
args: Vec<(Handle<Expression>, Span)>,
raw_args: &[Handle<HirExpr>],
meta: Span,
) -> Result<Option<Handle<Expression>>> { // Grow the typifier to be able to index it later without needing // to hold the context mutably for &(expr, span) in args.iter() {
ctx.typifier_grow(expr, span)?;
}
// Check if the passed arguments require any special variations letmut variations =
builtin_required_variations(args.iter().map(|&(expr, _)| ctx.get_type(expr)));
// Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, ctx.module, &name, variations);
// Borrow again but without mutability, at this point a declaration is guaranteed let declaration = self.lookup_function.get(&name).unwrap();
// Possibly contains the overload to be used in the call letmut maybe_overload = None; // The conversions needed for the best analyzed overload, this is initialized all to // `NONE` to make sure that conversions always pass the first time without ambiguity letmut old_conversions = vec![Conversion::None; args.len()]; // Tracks whether the comparison between overloads lead to an ambiguity letmut ambiguous = false;
// Iterate over all the available overloads to select either an exact match or a // overload which has suitable implicit conversions 'outer: for (overload_idx, overload) in declaration.overloads.iter().enumerate() { // If the overload and the function call don't have the same number of arguments // continue to the next overload if args.len() != overload.parameters.len() { continue;
}
log::trace!("Testing overload {}", overload_idx);
// Stores whether the current overload matches exactly the function call letmut exact = true; // State of the selection // If None we still don't know what is the best overload // If Some(true) the new overload is better // If Some(false) the old overload is better letmut superior = None; // Store the conversions for the current overload so that later they can replace the // conversions used for querying the best overload letmut new_conversions = vec![Conversion::None; args.len()];
// Loop through the overload parameters and check if the current overload is better // compared to the previous best overload. for (i, overload_parameter) in overload.parameters.iter().enumerate() { let call_argument = &args[i]; let parameter_info = &overload.parameters_info[i];
// If the image is used in the overload as a depth texture convert it // before comparing, otherwise exact matches wouldn't be reported if parameter_info.depth {
sampled_to_depth(ctx, call_argument.0, call_argument.1, &mutself.errors);
ctx.invalidate_expression(call_argument.0, call_argument.1)?
}
// Storage images cannot be directly compared since while the access is part of the // type in naga's IR, in glsl they are a qualifier and don't enter in the match as // long as the access needed is satisfied. iflet (
&TypeInner::Image {
class: crate::ImageClass::Storage {
format: overload_format,
access: overload_access,
},
dim: overload_dim,
arrayed: overload_arrayed,
},
&TypeInner::Image {
class: crate::ImageClass::Storage {
format: call_format,
access: call_access,
},
dim: call_dim,
arrayed: call_arrayed,
},
) = (overload_param_ty, call_arg_ty)
{ // Images size must match otherwise the overload isn't what we want let good_size = call_dim == overload_dim && call_arrayed == overload_arrayed; // Glsl requires the formats to strictly match unless you are builtin // function overload and have not been replaced, in which case we only // check that the format scalar kind matches let good_format = overload_format == call_format
|| (overload.internal
&& Scalar::from(overload_format) == Scalar::from(call_format)); if !(good_size && good_format) { continue'outer;
}
// While storage access mismatch is an error it isn't one that causes // the overload matching to fail so we defer the error and consider // that the images match exactly if !call_access.contains(overload_access) { self.errors.push(Error {
kind: ErrorKind::SemanticError(
format!( "'{}': image needs {:?} access but only {:?} was provided",
name, overload_access, call_access
)
.into(),
),
meta,
});
}
// The images satisfy the conditions to be considered as an exact match
new_conversions[i] = Conversion::Exact; continue;
} elseif overload_param_ty == call_arg_ty { // If the types match there's no need to check for conversions so continue
new_conversions[i] = Conversion::Exact; continue;
}
// Glsl defines that inout follows both the conversions for input parameters and // output parameters, this means that the type must have a conversion from both the // call argument to the function parameter and the function parameter to the call // argument, the only way this is possible is for the conversion to be an identity // (i.e. call argument = function parameter) iflet ParameterQualifier::InOut = parameter_info.qualifier { continue'outer;
}
// The function call argument and the function definition // parameter are not equal at this point, so we need to try // implicit conversions. // // Now there are two cases, the argument is defined as a normal // parameter (`in` or `const`), in this case an implicit // conversion is made from the calling argument to the // definition argument. If the parameter is `out` the // opposite needs to be done, so the implicit conversion is made // from the definition argument to the calling argument. let maybe_conversion = if parameter_info.qualifier.is_lhs() {
conversion(call_arg_ty, overload_param_ty)
} else {
conversion(overload_param_ty, call_arg_ty)
};
let conversion = match maybe_conversion {
Some(info) => info,
None => continue'outer,
};
// At this point a conversion will be needed so the overload no longer // exactly matches the call arguments
exact = false;
// Compare the conversions needed for this overload parameter to that of the // last overload analyzed respective parameter, the value is: // - `true` when the new overload argument has a better conversion // - `false` when the old overload argument has a better conversion let best_arg = match (conversion, old_conversions[i]) { // An exact match is always better, we don't need to check this for the // current overload since it was checked earlier
(_, Conversion::Exact) => false, // No overload was yet analyzed so this one is the best yet
(_, Conversion::None) => true, // A conversion from a float to a double is the best possible conversion
(Conversion::FloatToDouble, _) => true,
(_, Conversion::FloatToDouble) => false, // A conversion from a float to an integer is preferred than one // from double to an integer
(Conversion::IntToFloat, Conversion::IntToDouble) => true,
(Conversion::IntToDouble, Conversion::IntToFloat) => false, // This case handles things like no conversion and exact which were already // treated and other cases which no conversion is better than the other
_ => continue,
};
// Check if the best parameter corresponds to the current selected overload // to pass to the next comparison, if this isn't true mark it as ambiguous match best_arg { true => match superior {
Some(false) => ambiguous = true,
_ => {
superior = Some(true);
new_conversions[i] = conversion
}
}, false => match superior {
Some(true) => ambiguous = true,
_ => superior = Some(false),
},
}
}
// The overload matches exactly the function call so there's no ambiguity (since // repeated overload aren't allowed) and the current overload is selected, no // further querying is needed. if exact {
maybe_overload = Some(overload);
ambiguous = false; break;
}
match superior { // New overload is better keep it
Some(true) => {
maybe_overload = Some(overload); // Replace the conversions
old_conversions = new_conversions;
} // Old overload is better do nothing
Some(false) => {} // No overload was better than the other this can be caused // when all conversions are ambiguous in which the overloads themselves are // ambiguous.
None => {
ambiguous = true; // Assign the new overload, this helps ensures that in this case of // ambiguity the parsing won't end immediately and allow for further // collection of errors.
maybe_overload = Some(overload);
}
}
}
if ambiguous { self.errors.push(Error {
kind: ErrorKind::SemanticError(
format!("Ambiguous best function for '{name}'").into(),
),
meta,
})
}
let overload = maybe_overload.ok_or_else(|| Error {
kind: ErrorKind::SemanticError(format!("Unknown function '{name}'").into()),
meta,
})?;
let parameters_info = overload.parameters_info.clone(); let parameters = overload.parameters.clone(); let is_void = overload.void; let kind = overload.kind;
// Iterate through the function call arguments applying transformations as needed for (((parameter_info, call_argument), expr), parameter) in parameters_info
.iter()
.zip(&args)
.zip(raw_args)
.zip(¶meters)
{ if parameter_info.qualifier.is_lhs() { // Reprocess argument in LHS position let (handle, meta) = ctx.lower_expect_inner(stmt, self, *expr, ExprPos::Lhs)?;
// Write back all the variables that were scheduled to their original place for proxy_write in proxy_writes { letmut value = ctx.add_expression(
Expression::Load {
pointer: proxy_write.value,
},
meta,
)?;
/// Processes a function call argument that appears in place of an output /// parameter. #[allow(clippy::too_many_arguments)] fn process_lhs_argument(
&mutself,
ctx: &mut Context,
meta: Span,
parameter_ty: Handle<Type>,
parameter_info: &ParameterInfo,
original: Handle<Expression>,
call_argument: &(Handle<Expression>, Span),
proxy_writes: &mut Vec<ProxyWrite>,
arguments: &mut Vec<Handle<Expression>>,
) -> Result<()> { let original_ty = ctx.resolve_type(original, meta)?; let original_pointer_space = original_ty.pointer_space();
// The type of a possible spill variable needed for a proxy write letmut maybe_ty = match *original_ty { // If the argument is to be passed as a pointer but the type of the // expression returns a vector it must mean that it was for example // swizzled and it must be spilled into a local before calling
TypeInner::Vector { size, scalar } => Some(ctx.module.types.insert( Type {
name: None,
inner: TypeInner::Vector { size, scalar },
},
Span::default(),
)), // If the argument is a pointer whose address space isn't `Function`, an // indirection through a local variable is needed to align the address // spaces of the call argument and the overload parameter.
TypeInner::Pointer { base, space } if space != AddressSpace::Function => Some(base),
TypeInner::ValuePointer {
size,
scalar,
space,
} if space != AddressSpace::Function => { let inner = match size {
Some(size) => TypeInner::Vector { size, scalar },
None => TypeInner::Scalar(scalar),
};
// Since the original expression might be a pointer and we want a value // for the proxy writes, we might need to load the pointer. let value = if original_pointer_space.is_some() {
ctx.add_expression(Expression::Load { pointer: original }, Span::default())?
} else {
original
};
let overload_param_ty = &ctx.module.types[parameter_ty].inner; let call_arg_ty = ctx.get_type(call_argument.0); let needs_conversion = call_arg_ty != overload_param_ty;
let arg_scalar_comps = scalar_components(call_arg_ty);
// Since output parameters also allow implicit conversions from the // parameter to the argument, we need to spill the conversion to a // variable and create a proxy write for the original variable. if needs_conversion {
maybe_ty = Some(parameter_ty);
}
iflet Some(ty) = maybe_ty { // Create the spill variable let spill_var = ctx.locals.append(
LocalVariable {
name: None,
ty,
init: None,
},
Span::default(),
); let spill_expr =
ctx.add_expression(Expression::LocalVariable(spill_var), Span::default())?;
// If the argument is also copied in we must store the value of the // original variable to the spill variable. iflet ParameterQualifier::InOut = parameter_info.qualifier {
ctx.body.push(
Statement::Store {
pointer: spill_expr,
value,
},
Span::default(),
);
}
// Add the spill variable as an argument to the function call
arguments.push(spill_expr);
let convert = if needs_conversion {
arg_scalar_comps
} else {
None
};
// Register the temporary local to be written back to it's original // place after the function call iflet Expression::Swizzle {
size, mut vector,
pattern,
} = ctx.expressions[original]
{ iflet Expression::Load { pointer } = ctx.expressions[vector] {
vector = pointer;
}
for (i, component) in pattern.iter().take(size as usize).enumerate() { let original = ctx.add_expression(
Expression::AccessIndex {
base: vector,
index: *component as u32,
},
Span::default(),
)?;
let spill_component = ctx.add_expression(
Expression::AccessIndex {
base: spill_expr,
index: i as u32,
},
Span::default(),
)?;
// Check if the passed arguments require any special variations letmut variations = builtin_required_variations(
ctx.parameters
.iter()
.map(|&arg| &ctx.module.types[arg].inner),
);
// Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, ctx.module, &name, variations);
let function = Function {
name: Some(name),
arguments,
result,
local_variables: locals,
expressions,
named_expressions: crate::NamedExpressions::default(),
body,
diagnostic_filter_leaf: None,
};
'outer: for decl in declaration.overloads.iter_mut() { if parameters.len() != decl.parameters.len() { continue;
}
for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { let new_inner = &module.types[*new_parameter].inner; let old_inner = &module.types[*old_parameter].inner;
// Check if the passed arguments require any special variations letmut variations = builtin_required_variations(
ctx.parameters
.iter()
.map(|&arg| &ctx.module.types[arg].inner),
);
// Initiate the declaration if it wasn't previously initialized and inject builtins let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| {
variations |= BuiltinVariations::STANDARD;
Default::default()
});
inject_builtin(declaration, ctx.module, &name, variations);
let function = Function {
name: Some(name),
arguments,
result,
..Default::default()
};
'outer: for decl in declaration.overloads.iter() { if parameters.len() != decl.parameters.len() { continue;
}
for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { let new_inner = &module.types[*new_parameter].inner; let old_inner = &module.types[*old_parameter].inner;
/// Create a Naga [`EntryPoint`] that calls the GLSL `main` function. /// /// We compile the GLSL `main` function as an ordinary Naga [`Function`]. /// This function synthesizes a Naga [`EntryPoint`] to call that. /// /// Each GLSL input and output variable (including builtins) becomes a Naga /// [`GlobalVariable`]s in the [`Private`] address space, which `main` can /// access in the usual way. /// /// The `EntryPoint` we synthesize here has an argument for each GLSL input /// variable, and returns a struct with a member for each GLSL output /// variable. The entry point contains code to: /// /// - copy its arguments into the Naga globals representing the GLSL input /// variables, /// /// - call the Naga `Function` representing the GLSL `main` function, and then /// /// - build its return value from whatever values the GLSL `main` left in /// the Naga globals representing GLSL `output` variables. /// /// Upon entry, [`ctx.body`] should contain code, accumulated by prior calls /// to [`ParsingContext::parse_external_declaration`][pxd], to initialize /// private global variables as needed. This code gets spliced into the /// entry point before the call to `main`. /// /// [`GlobalVariable`]: crate::GlobalVariable /// [`Private`]: crate::AddressSpace::Private /// [`ctx.body`]: Context::body /// [pxd]: super::ParsingContext::parse_external_declaration pub(crate) fn add_entry_point(
&mutself,
function: Handle<Function>, mut ctx: Context,
) -> Result<()> { letmut arguments = Vec::new();
let body = Block::with_capacity( // global init body
ctx.body.len() + // prologue and epilogue self.entry_args.len() * 2 // Call, Emit for composing struct and return
+ 3,
);
let global_init_body = std::mem::replace(&mut ctx.body, body);
for arg inself.entry_args.iter() { if arg.storage != StorageQualifier::Input { continue;
}
let pointer = ctx
.expressions
.append(Expression::GlobalVariable(arg.handle), Default::default());
ctx.local_expression_kind_tracker
.insert(pointer, crate::proc::ExpressionKind::Runtime);
let ty = ctx.module.global_variables[arg.handle].ty;
ctx.arg_type_walker(
arg.name.clone(),
arg.binding.clone(),
pointer,
ty,
&mut |ctx, name, pointer, ty, binding| { let idx = arguments.len() as u32;
let len = ctx.expressions.len(); let load = ctx
.expressions
.append(Expression::Load { pointer }, Default::default());
ctx.local_expression_kind_tracker
.insert(load, crate::proc::ExpressionKind::Runtime);
ctx.body.push(
Statement::Emit(ctx.expressions.range_from(len)),
Default::default(),
);
components.push(load)
},
)?
}
let (ty, value) = if !components.is_empty() { let ty = ctx.module.types.insert( Type {
name: None,
inner: TypeInner::Struct { members, span },
},
Default::default(),
);
let len = ctx.expressions.len(); let res = ctx
.expressions
.append(Expression::Compose { ty, components }, Default::default());
ctx.local_expression_kind_tracker
.insert(res, crate::proc::ExpressionKind::Runtime);
ctx.body.push(
Statement::Emit(ctx.expressions.range_from(len)),
Default::default(),
);
(Some(ty), Some(res))
} else {
(None, None)
};
ctx.body
.push(Statement::Return { value }, Default::default());
impl Context<'_> { /// Helper function for building the input/output interface of the entry point /// /// Calls `f` with the data of the entry point argument, flattening composite types /// recursively /// /// The passed arguments to the callback are: /// - The ctx /// - The name /// - The pointer expression to the global storage /// - The handle to the type of the entry point argument /// - The binding of the entry point argument fn arg_type_walker(
&mutself,
name: Option<String>,
binding: crate::Binding,
pointer: Handle<Expression>,
ty: Handle<Type>,
f: &mutimpl FnMut(
&mut Context,
Option<String>,
Handle<Expression>,
Handle<Type>, crate::Binding,
),
) -> Result<()> { matchself.module.types[ty].inner { // TODO: Better error reporting // right now we just don't walk the array if the size isn't known at // compile time and let validation catch it
TypeInner::Array {
base,
size: crate::ArraySize::Constant(size),
..
} => { letmut location = match binding { crate::Binding::Location { location, .. } => location, crate::Binding::BuiltIn(_) => return Ok(()),
};
let interpolation = self.module.types[base]
.inner
.scalar_kind()
.map(|kind| match kind {
ScalarKind::Float => crate::Interpolation::Perspective,
_ => crate::Interpolation::Flat,
});
for index in0..size.get() { let member_pointer = self.add_expression(
Expression::AccessIndex {
base: pointer,
index,
},
Span::default(),
)?;
for (i, member) in members.clone().into_iter().enumerate() { let member_pointer = self.add_expression(
Expression::AccessIndex {
base: pointer,
index: i as u32,
},
Span::default(),
)?;
let binding = match member.binding {
Some(binding) => binding,
None => { let interpolation = self.module.types[member.ty]
.inner
.scalar_kind()
.map(|kind| match kind {
ScalarKind::Float => crate::Interpolation::Perspective,
_ => crate::Interpolation::Flat,
}); let binding = crate::Binding::Location {
location,
interpolation,
sampling: None,
second_blend_source: false,
};
location += 1;
binding
}
};
/// Helper enum containing the type of conversion need for a call #[derive(PartialEq, Eq, Clone, Copy, Debug)] enum Conversion { /// No conversion needed
Exact, /// Float to double conversion needed
FloatToDouble, /// Int or uint to float conversion needed
IntToFloat, /// Int or uint to double conversion needed
IntToDouble, /// Other type of conversion needed
Other, /// No conversion was yet registered
None,
}
/// Helper function, returns the type of conversion from `source` to `target`, if a /// conversion is not possible returns None. fn conversion(target: &TypeInner, source: &TypeInner) -> Option<Conversion> { use ScalarKind::*;
// Gather the `ScalarKind` and scalar width from both the target and the source let (target_scalar, source_scalar) = match (target, source) { // Conversions between scalars are allowed
(&TypeInner::Scalar(tgt_scalar), &TypeInner::Scalar(src_scalar)) => {
(tgt_scalar, src_scalar)
} // Conversions between vectors of the same size are allowed
(
&TypeInner::Vector {
size: tgt_size,
scalar: tgt_scalar,
},
&TypeInner::Vector {
size: src_size,
scalar: src_scalar,
},
) if tgt_size == src_size => (tgt_scalar, src_scalar), // Conversions between matrices of the same size are allowed
(
&TypeInner::Matrix {
rows: tgt_rows,
columns: tgt_cols,
scalar: tgt_scalar,
},
&TypeInner::Matrix {
rows: src_rows,
columns: src_cols,
scalar: src_scalar,
},
) if tgt_cols == src_cols && tgt_rows == src_rows => (tgt_scalar, src_scalar),
_ => return None,
};
// Check if source can be converted into target, if this is the case then the type // power of target must be higher than that of source let target_power = type_power(target_scalar); let source_power = type_power(source_scalar); if target_power < source_power { return None;
}
Some(match (target_scalar, source_scalar) { // A conversion from a float to a double is special
(Scalar::F64, Scalar::F32) => Conversion::FloatToDouble, // A conversion from an integer to a float is special
(
Scalar::F32,
Scalar {
kind: Sint | Uint,
width: _,
},
) => Conversion::IntToFloat, // A conversion from an integer to a double is special
(
Scalar::F64,
Scalar {
kind: Sint | Uint,
width: _,
},
) => Conversion::IntToDouble,
_ => Conversion::Other,
})
}
/// Helper method returning all the non standard builtin variations needed /// to process the function call with the passed arguments fn builtin_required_variations<'a>(args: impl Iterator<Item = &'a TypeInner>) -> BuiltinVariations { letmut variations = BuiltinVariations::empty();
for ty in args { match *ty {
TypeInner::ValuePointer { scalar, .. }
| TypeInner::Scalar(scalar)
| TypeInner::Vector { scalar, .. }
| TypeInner::Matrix { scalar, .. } => { if scalar == Scalar::F64 {
variations |= BuiltinVariations::DOUBLE
}
}
TypeInner::Image {
dim,
arrayed,
class,
} => { if dim == crate::ImageDimension::Cube && arrayed {
variations |= BuiltinVariations::CUBE_TEXTURES_ARRAY
}
if dim == crate::ImageDimension::D2 && arrayed && class.is_multisampled() {
variations |= BuiltinVariations::D2_MULTI_TEXTURES_ARRAY
}
}
_ => {}
}
}
variations
}
Messung V0.5 in Prozent
¤ 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.0.22Bemerkung:
(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.