/// Returns `(major, minor)` of the SPIR-V language version. pubconstfn lang_version(&self) -> (u8, u8) { self.physical_layout.lang_version()
}
/// Reset `Writer` to its initial state, retaining any allocations. /// /// Why not just implement `Reclaimable` for `Writer`? By design, /// `Reclaimable::reclaim` requires ownership of the value, not just /// `&mut`; see the trait documentation. But we need to use this method /// from functions like `Writer::write`, which only have `&mut Writer`. /// Workarounds include unsafe code (`core::ptr::read`, then `write`, ugh) /// or something like a `Default` impl that returns an oddly-initialized /// `Writer`, which is worse. fn reset(&mutself) { usesuper::reclaimable::Reclaimable; use core::mem::take;
letmut id_gen = IdGenerator::default(); let gl450_ext_inst_id = id_gen.next(); let void_type = id_gen.next();
// Every field of the old writer that is not determined by the `Options` // passed to `Writer::new` should be reset somehow. let fresh = Writer { // Copied from the old Writer:
flags: self.flags,
bounds_check_policies: self.bounds_check_policies,
zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
force_loop_bounding: self.force_loop_bounding,
ray_query_initialization_tracking: self.ray_query_initialization_tracking,
trace_ray_argument_validation: self.trace_ray_argument_validation,
use_storage_input_output_16: self.use_storage_input_output_16,
capabilities_available: take(&mutself.capabilities_available),
fake_missing_bindings: self.fake_missing_bindings,
binding_map: take(&mutself.binding_map),
task_dispatch_limits: self.task_dispatch_limits,
mesh_shader_primitive_indices_clamp: self.mesh_shader_primitive_indices_clamp,
/// Indicate that the code requires any one of the listed capabilities. /// /// If nothing in `capabilities` appears in the available capabilities /// specified in the [`Options`] from which this `Writer` was created, /// return an error. The `what` string is used in the error message to /// explain what provoked the requirement. (If no available capabilities were /// given, assume everything is available.) /// /// The first acceptable capability will be added to this `Writer`'s /// [`capabilities_used`] table, and an `OpCapability` emitted for it in the /// result. For this reason, more specific capabilities should be listed /// before more general. /// /// [`capabilities_used`]: Writer::capabilities_used pub(super) fn require_any(
&mutself,
what: &'static str,
capabilities: &[spirv::Capability],
) -> Result<(), Error> { match *capabilities {
[] => Ok(()),
[first, ..] => { // Find the first acceptable capability, or return an error if // there is none. let selected = matchself.capabilities_available {
None => first,
Some(ref available) => { match capabilities
.iter() // need explicit type for hashbrown::HashSet::contains fn call to keep rustc happy
.find(|cap| available.contains::<spirv::Capability>(cap))
{
Some(&cap) => cap,
None => { return Err(Error::MissingCapabilities(what, capabilities.to_vec()))
}
}
}
}; self.capabilities_used.insert(selected);
Ok(())
}
}
}
/// Indicate that the code requires all of the listed capabilities. /// /// If all entries of `capabilities` appear in the available capabilities /// specified in the [`Options`] from which this `Writer` was created /// (including the case where [`Options::capabilities`] is `None`), add /// them all to this `Writer`'s [`capabilities_used`] table, and return /// `Ok(())`. If at least one of the listed capabilities is not available, /// do not add anything to the `capabilities_used` table, and return the /// first unavailable requested capability, wrapped in `Err()`. /// /// This method is does not return an [`enum@Error`] in case of failure /// because it may be used in cases where the caller can recover (e.g., /// with a polyfill) if the requested capabilities are not available. In /// this case, it would be unnecessary work to find *all* the unavailable /// requested capabilities, and to allocate a `Vec` for them, just so we /// could return an [`Error::MissingCapabilities`]). /// /// [`capabilities_used`]: Writer::capabilities_used pub(super) fn require_all(
&mutself,
capabilities: &[spirv::Capability],
) -> Result<(), spirv::Capability> { iflet Some(ref available) = self.capabilities_available { for requested in capabilities { if !available.contains(requested) { return Err(*requested);
}
}
}
for requested in capabilities { self.capabilities_used.insert(*requested);
}
Ok(())
}
/// Indicate that the code uses the given extension. pub(super) fn use_extension(&mutself, extension: &'static str) { self.extensions_used.insert(extension);
}
pub(super) fn get_type_id(&mutself, lookup_ty: LookupType) -> Word { matchself.lookup_type.entry(lookup_ty) {
Entry::Occupied(e) => *e.get(),
Entry::Vacant(e) => { let local = match lookup_ty {
LookupType::Handle(_handle) => unreachable!("Handles are populated at start"),
LookupType::Local(local) => local,
};
let id = self.id_gen.next();
e.insert(id); self.write_type_declaration_local(id, local);
id
}
}
}
pub(super) fn get_handle_type_id(&mutself, handle: Handle<crate::Type>) -> Word { self.get_type_id(LookupType::Handle(handle))
}
pub(super) fn get_expression_type_id(&mutself, tr: &TypeResolution) -> Word { let lookup_ty = self.get_expression_lookup_type(tr); self.get_type_id(lookup_ty)
}
pub(super) fn get_localtype_id(&mutself, local: LocalType) -> Word { self.get_type_id(LookupType::Local(local))
}
pub(super) fn get_pointer_type_id(&mutself, base: Word, class: spirv::StorageClass) -> Word { self.get_type_id(LookupType::Local(LocalType::Pointer { base, class }))
}
pub(super) fn get_handle_pointer_type_id(
&mutself,
base: Handle<crate::Type>,
class: spirv::StorageClass,
) -> Word { let base_id = self.get_handle_type_id(base); self.get_pointer_type_id(base_id, class)
}
pub(super) fn get_ray_query_pointer_id(&mutself) -> Word { let rq_id = self.get_type_id(LookupType::Local(LocalType::RayQuery)); self.get_pointer_type_id(rq_id, spirv::StorageClass::Function)
}
/// Return a SPIR-V type for a pointer to `resolution`. /// /// The given `resolution` must be one that we can represent /// either as a `LocalType::Pointer` or `LocalType::LocalPointer`. pub(super) fn get_resolution_pointer_id(
&mutself,
resolution: &TypeResolution,
class: spirv::StorageClass,
) -> Word { let resolution_type_id = self.get_expression_type_id(resolution); self.get_pointer_type_id(resolution_type_id, class)
}
pub(super) fn get_numeric_type_id(&mutself, numeric: NumericType) -> Word { self.get_type_id(LocalType::Numeric(numeric).into())
}
pub(super) fn get_u32_type_id(&mutself) -> Word { self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32))
}
pub(super) fn get_f32_type_id(&mutself) -> Word { self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32))
}
/// Used for "mulhi" to get the upper bits of multiplication. /// /// More specifically, `OpUMulExtended` multiplies 2 numbers and returns the lower and upper bits of the result /// as a user-defined struct type with 2 u32s. This defines that struct. pub(super) fn get_tuple_of_u32s_ty_id(&mutself) -> Word { iflet Some(val) = self.tuple_of_u32s_ty_id {
val
} else { let id = self.id_gen.next(); let u32_id = self.get_u32_type_id(); let ins = Instruction::type_struct(id, &[u32_id, u32_id]);
ins.to_words(&mutself.logical_layout.declarations); self.tuple_of_u32s_ty_id = Some(id);
id
}
}
/// Return `inner` as a `LocalType`, if that's possible. /// /// If `inner` can be represented as a `LocalType`, return /// `Some(local_type)`. /// /// Otherwise, return `None`. In this case, the type must always be looked /// up using a `LookupType::Handle`. fn localtype_from_inner(&mutself, inner: &crate::TypeInner) -> Option<LocalType> {
Some(match *inner { crate::TypeInner::Scalar(_)
| crate::TypeInner::Atomic(_)
| crate::TypeInner::Vector { .. }
| crate::TypeInner::Matrix { .. } => { // We expect `NumericType::from_inner` to handle all // these cases, so unwrap.
LocalType::Numeric(NumericType::from_inner(inner).unwrap())
} crate::TypeInner::CooperativeMatrix { .. } => {
LocalType::Cooperative(CooperativeType::from_inner(inner).unwrap())
} crate::TypeInner::Pointer { base, space } => { let base_type_id = self.get_handle_type_id(base);
LocalType::Pointer {
base: base_type_id,
class: map_storage_class(space),
}
} crate::TypeInner::ValuePointer {
size,
scalar,
space,
} => { let base_numeric_type = match size {
Some(size) => NumericType::Vector { size, scalar },
None => NumericType::Scalar(scalar),
};
LocalType::Pointer {
base: self.get_numeric_type_id(base_numeric_type),
class: map_storage_class(space),
}
} crate::TypeInner::Image {
dim,
arrayed,
class,
} => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)), crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler, crate::TypeInner::AccelerationStructure { .. } => LocalType::AccelerationStructure, crate::TypeInner::RayQuery { .. } => LocalType::RayQuery, crate::TypeInner::Array { .. }
| crate::TypeInner::Struct { .. }
| crate::TypeInner::BindingArray { .. } => return None,
})
}
/// Resolve the [`BindingInfo`] for a [`crate::ResourceBinding`] from the /// provided [`Writer::binding_map`]. /// /// If the specified resource is not present in the binding map this will /// return an error, unless [`Writer::fake_missing_bindings`] is set. fn resolve_resource_binding(
&self,
res_binding: &crate::ResourceBinding,
) -> Result<BindingInfo, Error> { matchself.binding_map.get(res_binding) {
Some(target) => Ok(*target),
None ifself.fake_missing_bindings => Ok(BindingInfo {
descriptor_set: res_binding.group,
binding: res_binding.binding,
binding_array_size: None,
}),
None => Err(Error::MissingBinding(*res_binding)),
}
}
/// Emits code for any wrapper functions required by the expressions in ir_function. /// The IDs of any emitted functions will be stored in [`Self::wrapped_functions`]. fn write_wrapped_functions(
&mutself,
ir_function: &crate::Function,
info: &FunctionInfo,
ir_module: &crate::Module,
) -> Result<(), Error> {
log::trace!("Generating wrapped functions for {:?}", ir_function.name);
for (expr_handle, expr) in ir_function.expressions.iter() { match *expr { crate::Expression::Binary { op, left, right } => { let expr_ty_inner = info[expr_handle].ty.inner_with(&ir_module.types); iflet Some(expr_ty) = NumericType::from_inner(expr_ty_inner) { match (op, expr_ty.scalar().kind) { // Division and modulo are undefined behaviour when the // dividend is the minimum representable value and the divisor // is negative one, or when the divisor is zero. These wrapped // functions override the divisor to one in these cases, // matching the WGSL spec.
( crate::BinaryOperator::Divide | crate::BinaryOperator::Modulo, crate::ScalarKind::Sint | crate::ScalarKind::Uint,
) => { self.write_wrapped_binary_op(
op,
expr_ty,
&info[left].ty,
&info[right].ty,
)?;
}
_ => {}
}
}
} crate::Expression::Load { pointer } => { ifletcrate::TypeInner::Pointer {
base: pointer_type,
space: crate::AddressSpace::Uniform,
} = *info[pointer].ty.inner_with(&ir_module.types)
{ ifself.std140_compat_uniform_types.contains_key(&pointer_type) { // Loading a std140 compat type requires the wrapper function // to convert to the regular type. self.write_wrapped_convert_from_std140_compat_type(
ir_module,
pointer_type,
)?;
}
}
} crate::Expression::Access { base, .. } => { ifletcrate::TypeInner::Pointer {
base: base_type,
space: crate::AddressSpace::Uniform,
} = *info[base].ty.inner_with(&ir_module.types)
{ // Dynamic accesses of a two-row matrix's columns require a // wrapper function. ifletcrate::TypeInner::Matrix {
rows: crate::VectorSize::Bi,
..
} = ir_module.types[base_type].inner
{ self.write_wrapped_matcx2_get_column(ir_module, base_type)?; // If the matrix is *not* directly a member of a struct, then // we additionally require a wrapper function to convert from // the std140 compat type to the regular type. if !is_uniform_matcx2_struct_member_access(
ir_function,
info,
ir_module,
base,
) { self.write_wrapped_convert_from_std140_compat_type(
ir_module, base_type,
)?;
}
}
}
}
_ => {}
}
}
Ok(())
}
/// Write a SPIR-V function that performs the operator `op` with Naga IR semantics. /// /// Define a function that performs an integer division or modulo operation, /// except that using a divisor of zero or causing signed overflow with a /// divisor of -1 returns the numerator unchanged, rather than exhibiting /// undefined behavior. /// /// Store the generated function's id in the [`wrapped_functions`] table. /// /// The operator `op` must be either [`Divide`] or [`Modulo`]. /// /// # Panics /// /// The `return_type`, `left_type` or `right_type` arguments must all be /// integer scalars or vectors. If not, this function panics. /// /// [`wrapped_functions`]: Writer::wrapped_functions /// [`Divide`]: crate::BinaryOperator::Divide /// [`Modulo`]: crate::BinaryOperator::Modulo fn write_wrapped_binary_op(
&mutself,
op: crate::BinaryOperator,
return_type: NumericType,
left_type: &TypeResolution,
right_type: &TypeResolution,
) -> Result<(), Error> { let return_type_id = self.get_localtype_id(LocalType::Numeric(return_type)); let left_type_id = self.get_expression_type_id(left_type); let right_type_id = self.get_expression_type_id(right_type);
// Check if we've already emitted this function. let wrapped = WrappedFunction::BinaryOp {
op,
left_type_id,
right_type_id,
}; let function_id = matchself.wrapped_functions.entry(wrapped) {
Entry::Occupied(_) => return Ok(()),
Entry::Vacant(e) => *e.insert(self.id_gen.next()),
};
let scalar = return_type.scalar();
ifself.flags.contains(WriterFlags::DEBUG) { let function_name = match op { crate::BinaryOperator::Divide => "naga_div", crate::BinaryOperator::Modulo => "naga_mod",
_ => unreachable!(),
}; self.debugs
.push(Instruction::name(function_id, function_name));
} letmut function = Function::default();
/// Writes a wrapper function to convert from a std140 compat type to its /// corresponding regular type. /// /// See [`Self::write_std140_compat_type_declaration`] for more details. fn write_wrapped_convert_from_std140_compat_type(
&mutself,
ir_module: &crate::Module,
r#type: Handle<crate::Type>,
) -> Result<(), Error> { if !self.std140_compat_uniform_types.contains_key(&r#type) { return Ok(());
} // Check if we've already emitted this function. let wrapped = WrappedFunction::ConvertFromStd140CompatType { r#type }; let function_id = matchself.wrapped_functions.entry(wrapped) {
Entry::Occupied(_) => return Ok(()),
Entry::Vacant(e) => *e.insert(self.id_gen.next()),
}; ifself.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::name(
function_id,
&format!("{:?}_from_std140", r#type.for_debug(&ir_module.types)),
));
} let param_type_id = self.std140_compat_uniform_types[&r#type].type_id; let return_type_id = self.get_handle_type_id(r#type);
let label_id = self.id_gen.next(); letmut block = Block::new(label_id);
let result_id = match ir_module.types[r#type].inner { // Param is struct containing a vector member for each of the // matrix's columns. Extract each column from the struct then // composite into a matrix. crate::TypeInner::Matrix {
columns,
rows: rows @ crate::VectorSize::Bi,
scalar,
} => { let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar });
letmut column_ids: ArrayVec<Word, 4> = ArrayVec::new(); for column in0..columns as u32 { let column_id = self.id_gen.next();
block.body.push(Instruction::composite_extract(
column_type_id,
column_id,
param_id,
&[column],
));
column_ids.push(column_id);
} let result_id = self.id_gen.next();
block.body.push(Instruction::composite_construct(
return_type_id,
result_id,
&column_ids,
));
result_id
} // Param is an array where the base type is the std140 compatible // type corresponding to `base`. Iterate through each element and // call its conversion function, then composite into a new array. crate::TypeInner::Array { base, size, .. } => { // Ensure the conversion function for the array's base type is // declared. self.write_wrapped_convert_from_std140_compat_type(ir_module, base)?;
let element_type_id = self.get_handle_type_id(base); let std140_info = self.std140_compat_uniform_types.get(&base); letmut element_ids = Vec::new(); let size = match size.resolve(ir_module.to_ctx())? { crate::proc::IndexableLength::Known(size) => size, crate::proc::IndexableLength::Dynamic => { return Err(Error::Validation( "Uniform buffers cannot contain dynamic arrays",
))
}
}; for i in0..size { let std140_element_id = self.id_gen.next(); let std140_element_type_id =
std140_info.map_or(element_type_id, |info| info.type_id);
block.body.push(Instruction::composite_extract(
std140_element_type_id,
std140_element_id,
param_id,
&[i],
));
// Only call the conversion function if a compatibility mapping actually exists. let final_element_id = if std140_info.is_some() { let conversion_fn_id = self.wrapped_functions
[&WrappedFunction::ConvertFromStd140CompatType { r#type: base }]; let id = self.id_gen.next();
block.body.push(Instruction::function_call(
element_type_id,
id,
conversion_fn_id,
&[std140_element_id],
));
id
} else {
std140_element_type_id
};
element_ids.push(final_element_id);
} let result_id = self.id_gen.next();
block.body.push(Instruction::composite_construct(
return_type_id,
result_id,
&element_ids,
));
result_id
} // Param is a struct where each two-row matrix member has been // decomposed in to separate vector members for each column. // Other members use their std140 compatible type if one exists, or // else their regular type. Iterate through each member, converting // or composing any matrices if required, then finally compose into // the struct. crate::TypeInner::Struct { ref members, .. } => { letmut member_ids = Vec::new(); letmut next_index = 0; for member in members { let member_id = self.id_gen.next(); let member_type_id = self.get_handle_type_id(member.ty); match ir_module.types[member.ty].inner { crate::TypeInner::Matrix {
columns,
rows: rows @ crate::VectorSize::Bi,
scalar,
} => { letmut column_ids: ArrayVec<Word, 4> = ArrayVec::new(); let column_type_id = self
.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); for _ in0..columns as u32 { let column_id = self.id_gen.next();
block.body.push(Instruction::composite_extract(
column_type_id,
column_id,
param_id,
&[next_index],
));
column_ids.push(column_id);
next_index += 1;
}
block.body.push(Instruction::composite_construct(
member_type_id,
member_id,
&column_ids,
));
}
_ => { // Ensure the conversion function for the member's // type is declared. self.write_wrapped_convert_from_std140_compat_type(
ir_module, member.ty,
)?; matchself.std140_compat_uniform_types.get(&member.ty) {
Some(std140_type_info) => { let std140_member_id = self.id_gen.next();
block.body.push(Instruction::composite_extract(
std140_type_info.type_id,
std140_member_id,
param_id,
&[next_index],
)); let function_id = self.wrapped_functions
[&WrappedFunction::ConvertFromStd140CompatType {
r#type: member.ty,
}];
block.body.push(Instruction::function_call(
member_type_id,
member_id,
function_id,
&[std140_member_id],
));
next_index += 1;
}
None => {
block.body.push(Instruction::composite_extract(
member_type_id,
member_id,
param_id,
&[next_index],
));
next_index += 1;
}
}
}
}
member_ids.push(member_id);
} let result_id = self.id_gen.next();
block.body.push(Instruction::composite_construct(
return_type_id,
result_id,
&member_ids,
));
result_id
}
_ => unreachable!(),
};
/// Writes a wrapper function to get an `OpTypeVector` column from an /// `OpTypeMatrix` with a dynamic index. /// /// This is used when accessing a column of a [`TypeInner::Matrix`] through /// a [`Uniform`] address space pointer. In such cases, the matrix will have /// been declared in SPIR-V using an alternative type where each column is a /// member of a containing struct. SPIR-V is unable to dynamically access /// struct members, so instead we load the matrix then call this function to /// access a column from the loaded value. /// /// [`TypeInner::Matrix`]: crate::TypeInner::Matrix /// [`Uniform`]: crate::AddressSpace::Uniform fn write_wrapped_matcx2_get_column(
&mutself,
ir_module: &crate::Module,
r#type: Handle<crate::Type>,
) -> Result<(), Error> { let wrapped = WrappedFunction::MatCx2GetColumn { r#type }; let function_id = matchself.wrapped_functions.entry(wrapped) {
Entry::Occupied(_) => return Ok(()),
Entry::Vacant(e) => *e.insert(self.id_gen.next()),
}; ifself.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::name(
function_id,
&format!("{:?}_get_column", r#type.for_debug(&ir_module.types)),
));
}
letmut function = Function::default(); let matrix_type_id = self.get_handle_type_id(r#type); let column_index_type_id = self.get_u32_type_id(); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); let matrix_param_id = self.id_gen.next(); let column_index_param_id = self.id_gen.next();
function.parameters.push(FunctionArgument {
instruction: Instruction::function_parameter(matrix_type_id, matrix_param_id),
handle_id: 0,
});
function.parameters.push(FunctionArgument {
instruction: Instruction::function_parameter(
column_index_type_id,
column_index_param_id,
),
handle_id: 0,
}); let function_type_id = self.get_function_type(LookupFunctionType {
parameter_type_ids: vec![matrix_type_id, column_index_type_id],
return_type_id: column_type_id,
});
function.signature = Some(Instruction::function(
column_type_id,
function_id,
spirv::FunctionControl::empty(),
function_type_id,
));
let label_id = self.id_gen.next(); letmut block = Block::new(label_id);
// Create a switch case for each column in the matrix, where each case // extracts its column from the matrix. Finally we use OpPhi to return // the correct column. let merge_id = self.id_gen.next();
block.body.push(Instruction::selection_merge(
merge_id,
spirv::SelectionControl::NONE,
)); let cases = (0..columns as u32)
.map(|i| super::instructions::Case {
value: i,
label_id: self.id_gen.next(),
})
.collect::<ArrayVec<_, 4>>();
// Which label we branch to in the default (column index out-of-bounds) // case depends on our bounds check policy. let default_id = matchself.bounds_check_policies.index { // For `Restrict`, treat the same as the final column. crate::proc::BoundsCheckPolicy::Restrict => cases.last().unwrap().label_id, // For `ReadZeroSkipWrite`, branch directly to the merge block. This // will be handled in the `OpPhi` below to produce a zero value. crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => merge_id, // For `Unchecked` we create a new block containing an // `OpUnreachable`. crate::proc::BoundsCheckPolicy::Unchecked => self.id_gen.next(),
};
function.consume(
block,
Instruction::switch(column_index_param_id, default_id, &cases),
);
// Emit a block for each case, and produce a list of variable and parent // block IDs that will be used in an `OpPhi` below to select the right // value. letmut var_parent_pairs = cases
.into_iter()
.map(|case| { letmut block = Block::new(case.label_id); let column_id = self.id_gen.next();
block.body.push(Instruction::composite_extract(
column_type_id,
column_id,
matrix_param_id,
&[case.value],
));
function.consume(block, Instruction::branch(merge_id));
(column_id, case.label_id)
}) // Need capacity for up to 4 columns plus possibly a default case.
.collect::<ArrayVec<_, 5>>();
// Emit a block or append the variable and parent `OpPhi` pair for the // column index out-of-bounds case, if required. matchself.bounds_check_policies.index { // Don't need to do anything for `Restrict` as we have branched from // the final column case's block. crate::proc::BoundsCheckPolicy::Restrict => {} // For `ReadZeroSkipWrite` we have branched directly from the block // containing the `OpSwitch`. The `OpPhi` should produce a zero // value. crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => {
var_parent_pairs.push((self.get_constant_null(column_type_id), label_id));
} // For `Unchecked` create a new block containing `OpUnreachable`. // This does not need to be handled by the `OpPhi`. crate::proc::BoundsCheckPolicy::Unchecked => {
function.consume(
Block::new(default_id),
Instruction::new(spirv::Op::Unreachable),
);
}
}
for argument in ir_function.arguments.iter() { let class = spirv::StorageClass::Input; let handle_ty = ir_module.types[argument.ty].inner.is_handle(); let argument_type_id = if handle_ty { self.get_handle_pointer_type_id(argument.ty, spirv::StorageClass::UniformConstant)
} else { self.get_handle_type_id(argument.ty)
};
iflet Some(refmut iface) = interface { let id = iflet Some(ref binding) = argument.binding { let name = argument.name.as_deref();
let varying_id = self.write_varying(
ir_module,
iface.stage,
class,
name,
argument.ty,
binding,
)?;
iface.varying_ids.push(varying_id); let id = self.load_io_with_f16_polyfill(
&mut prelude.body,
varying_id,
argument_type_id,
); if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) {
local_invocation_index_id = Some(id);
local_invocation_index_var_id = Some(varying_id);
}
id
} elseifletcrate::TypeInner::Struct { ref members, .. } =
ir_module.types[argument.ty].inner
{ let struct_id = self.id_gen.next(); letmut constituent_ids = Vec::with_capacity(members.len()); for member in members { let type_id = self.get_handle_type_id(member.ty); let name = member.name.as_deref(); let binding = member.binding.as_ref().unwrap(); let varying_id = self.write_varying(
ir_module,
iface.stage,
class,
name,
member.ty,
binding,
)?;
iface.varying_ids.push(varying_id); let id = self.load_io_with_f16_polyfill(&mut prelude.body, varying_id, type_id);
constituent_ids.push(id); if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex)
{
local_invocation_index_id = Some(id);
local_invocation_index_var_id = Some(varying_id);
}
}
prelude.body.push(Instruction::composite_construct(
argument_type_id,
struct_id,
&constituent_ids,
));
struct_id
} else {
unreachable!("Missing argument binding on an entry point");
};
ep_context.argument_ids.push(id);
} else { let argument_id = self.id_gen.next(); let instruction = Instruction::function_parameter(argument_type_id, argument_id); ifself.flags.contains(WriterFlags::DEBUG) { iflet Some(ref name) = argument.name { self.debugs.push(Instruction::name(argument_id, name));
}
}
function.parameters.push(FunctionArgument {
instruction,
handle_id: if handle_ty { let id = self.id_gen.next();
prelude.body.push(Instruction::load( self.get_handle_type_id(argument.ty),
id,
argument_id,
None,
));
id
} else { 0
},
});
parameter_type_ids.push(argument_type_id);
};
}
let return_type_id = match ir_function.result {
Some(ref result) => { iflet Some(refmut iface) = interface { letmut has_point_size = false; let class = spirv::StorageClass::Output; iflet Some(ref binding) = result.binding {
has_point_size |=
*binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); let type_id = self.get_handle_type_id(result.ty); let varying_id = if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { 0
} else { let varying_id = self.write_varying(
ir_module,
iface.stage,
class,
None,
result.ty,
binding,
)?;
iface.varying_ids.push(varying_id);
varying_id
};
ep_context.results.push(ResultMember {
id: varying_id,
type_id,
built_in: binding.to_built_in(),
});
} elseifletcrate::TypeInner::Struct { ref members, .. } =
ir_module.types[result.ty].inner
{ for member in members { let type_id = self.get_handle_type_id(member.ty); let name = member.name.as_deref(); let binding = member.binding.as_ref().unwrap();
has_point_size |=
*binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); // This isn't an actual builtin in SPIR-V. It can only appear as the // output of a task shader and the output is used when writing the // entry point return, in which case the id is ignored anyway. let varying_id = if *binding
== crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize)
{ 0
} else { let varying_id = self.write_varying(
ir_module,
iface.stage,
class,
name,
member.ty,
binding,
)?;
iface.varying_ids.push(varying_id);
varying_id
};
ep_context.results.push(ResultMember {
id: varying_id,
type_id,
built_in: binding.to_built_in(),
});
}
} else {
unreachable!("Missing result binding on an entry point");
}
ifself.flags.contains(WriterFlags::FORCE_POINT_SIZE)
&& iface.stage == crate::ShaderStage::Vertex
&& !has_point_size
{ // add point size artificially let varying_id = self.id_gen.next(); let pointer_type_id = self.get_f32_pointer_type_id(class);
Instruction::variable(pointer_type_id, varying_id, class, None)
.to_words(&mutself.logical_layout.declarations); self.decorate(
varying_id,
spirv::Decoration::BuiltIn,
&[spirv::BuiltIn::PointSize as u32],
);
iface.varying_ids.push(varying_id);
if interface.is_some() {
function.entry_point_context = Some(ep_context);
}
// fill up the `GlobalVariable::access_id` for gv inself.global_variables.iter_mut() {
gv.reset_for_function();
} for (handle, var) in ir_module.global_variables.iter() { if info[handle].is_empty() { continue;
}
letmut gv = self.global_variables[handle].clone(); iflet Some(refmut iface) = interface { // Have to include global variables in the interface ifself.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) {
iface.varying_ids.push(gv.var_id);
}
}
match ir_module.types[var.ty].inner { // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing. crate::TypeInner::BindingArray { .. } => {
gv.access_id = gv.var_id;
}
_ => { // Handle globals are pre-emitted and should be loaded automatically. if var.space == crate::AddressSpace::Handle { let var_type_id = self.get_handle_type_id(var.ty); let id = self.id_gen.next();
prelude
.body
.push(Instruction::load(var_type_id, id, gv.var_id, None));
gv.access_id = gv.var_id;
gv.handle_id = id;
} elseif global_needs_wrapper(ir_module, var) { let class = map_storage_class(var.space); let pointer_type_id = matchself.std140_compat_uniform_types.get(&var.ty) {
Some(std140_type_info) if var.space == crate::AddressSpace::Uniform => { self.get_pointer_type_id(std140_type_info.type_id, class)
}
_ => self.get_handle_pointer_type_id(var.ty, class),
}; let index_id = self.get_index_constant(0); let id = self.id_gen.next();
prelude.body.push(Instruction::access_chain(
pointer_type_id,
id,
gv.var_id,
&[index_id],
));
gv.access_id = id;
} else { // by default, the variable ID is accessed as is
gv.access_id = gv.var_id;
};
}
}
// work around borrow checking in the presence of `self.xxx()` calls self.global_variables[handle] = gv;
}
// Create a `BlockContext` for generating SPIR-V for the function's // body. letmut context = BlockContext {
ir_module,
ir_function,
fun_info: info,
function: &mut function, // Re-use the cached expression table from prior functions.
cached: core::mem::take(&mutself.saved_cached),
// Steal the Writer's temp list for a bit.
temp_list: core::mem::take(&mutself.temp_list),
force_loop_bounding: self.force_loop_bounding,
writer: self,
expression_constness: super::ExpressionConstnessTracker::from_arena(
&ir_function.expressions,
),
ray_query_tracker_expr: crate::FastHashMap::default(),
};
// fill up the pre-emitted and const expressions
context.cached.reset(ir_function.expressions.len()); for (handle, expr) in ir_function.expressions.iter() { if (expr.needs_pre_emit() && !matches!(*expr, crate::Expression::LocalVariable(_)))
|| context.expression_constness.is_const(handle)
{
context.cache_expression_value(handle, &mut prelude)?;
}
}
for (handle, variable) in ir_function.local_variables.iter() { let id = context.gen_id();
ifletcrate::TypeInner::RayQuery { .. } = ir_module.types[variable.ty].inner { // Don't refactor this into a struct: Although spirv itself allows opaque types in structs, // the vulkan environment for spirv does not. Putting ray queries into structs can cause // confusing bugs. let u32_type_id = context.writer.get_u32_type_id(); let ptr_u32_type_id = context
.writer
.get_pointer_type_id(u32_type_id, spirv::StorageClass::Function); let tracker_id = context.gen_id(); let tracker_init_id = context.writer.get_constant_scalar(crate::Literal::U32( crate::back::RayQueryPoint::empty().bits(),
)); let tracker_instruction = Instruction::variable(
ptr_u32_type_id,
tracker_id,
spirv::StorageClass::Function,
Some(tracker_init_id),
);
context
.function
.ray_query_initialization_tracker_variables
.insert(
handle,
LocalVariable {
id: tracker_id,
instruction: tracker_instruction,
},
); let f32_type_id = context.writer.get_f32_type_id(); let ptr_f32_type_id = context
.writer
.get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); let t_max_tracker_id = context.gen_id(); let t_max_tracker_init_id =
context.writer.get_constant_scalar(crate::Literal::F32(0.0)); let t_max_tracker_instruction = Instruction::variable(
ptr_f32_type_id,
t_max_tracker_id,
spirv::StorageClass::Function,
Some(t_max_tracker_init_id),
);
for (handle, expr) in ir_function.expressions.iter() { match *expr { crate::Expression::LocalVariable(_) => { // Cache the `OpVariable` instruction we generated above as // the value of this expression.
context.cache_expression_value(handle, &mut prelude)?;
} crate::Expression::Access { base, .. }
| crate::Expression::AccessIndex { base, .. } => { // Count references to `base` by `Access` and `AccessIndex` // instructions. See `access_uses` for details.
*context.function.access_uses.entry(base).or_insert(0) += 1;
}
_ => {}
}
}
// Consume the `BlockContext`, ending its borrows and letting the // `Writer` steal back its cached expression table and temp_list. let BlockContext {
cached, temp_list, ..
} = context; self.saved_cached = cached; self.temp_list = temp_list;
fn write_cooperative_type_declaration_local(&mutself, id: Word, coop: CooperativeType) { let instruction = match coop {
CooperativeType::Matrix {
columns,
rows,
scalar,
role,
} => { let scalar_id = self.get_localtype_id(LocalType::Numeric(NumericType::Scalar(scalar))); let scope_id = self.get_index_constant(spirv::Scope::Subgroup as u32); let columns_id = self.get_index_constant(columns as u32); let rows_id = self.get_index_constant(rows as u32); let role_id = self.get_index_constant(spirv::CooperativeMatrixUse::from(role) as u32);
Instruction::type_coop_matrix(id, scalar_id, scope_id, rows_id, columns_id, role_id)
}
};
fn write_type_declaration_arena(
&mutself,
module: &crate::Module,
handle: Handle<crate::Type>,
) -> Result<Word, Error> { let ty = &module.types[handle]; // If it's a type that needs SPIR-V capabilities, request them now. // This needs to happen regardless of the LocalType lookup succeeding, // because some types which map to the same LocalType have different // capability requirements. See https://github.com/gfx-rs/wgpu/issues/5569 self.request_type_capabilities(&ty.inner)?; let id = iflet Some(local) = self.localtype_from_inner(&ty.inner) { // This type can be represented as a `LocalType`, so check if we've // already written an instruction for it. If not, do so now, with // `write_type_declaration_local`. matchself.lookup_type.entry(LookupType::Local(local)) { // We already have an id for this `LocalType`.
Entry::Occupied(e) => *e.get(),
// It's a type we haven't seen before.
Entry::Vacant(e) => { let id = self.id_gen.next();
e.insert(id);
self.write_type_declaration_local(id, local);
id
}
}
} else { use spirv::Decoration;
let id = self.id_gen.next(); let instruction = match ty.inner { crate::TypeInner::Array { base, size, stride } => { self.decorate(id, Decoration::ArrayStride, &[stride]);
/// Writes a std140 layout compatible type declaration for a type. Returns /// the ID of the declared type, or None if no declaration is required. /// /// This should be called for any type for which there exists a /// [`GlobalVariable`] in the [`Uniform`] address space. If the type already /// adheres to std140 layout rules it will return without declaring any /// types. If the type contains another type which requires a std140 /// compatible type declaration, it will recursively call itself. /// /// When `handle` refers to a [`TypeInner::Matrix`] with 2 rows, the /// declared type will be an `OpTypeStruct` containing an `OpVector` for /// each of the matrix's columns. /// /// When `handle` refers to a [`TypeInner::Array`] whose base type is a /// matrix with 2 rows, this will declare an `OpTypeArray` whose element /// type is the matrix's corresponding std140 compatible type. /// /// When `handle` refers to a [`TypeInner::Struct`] and any of its members /// require a std140 compatible type declaration, this will declare a new /// struct with the following rules: /// * Struct or array members will be declared with their std140 compatible /// type declaration, if one is required. /// * Two-row matrix members will have each of their columns hoisted /// directly into the struct as 2-component vector members. /// * All other members will be declared with their normal type. /// /// Note that this means the Naga IR index of a struct member may not match /// the index in the generated SPIR-V. The mapping can be obtained via /// `Std140TypeInfo::member_indices`. /// /// [`GlobalVariable`]: crate::GlobalVariable /// [`Uniform`]: crate::AddressSpace::Uniform /// [`TypeInner::Matrix`]: crate::TypeInner::Matrix /// [`TypeInner::Array`]: crate::TypeInner::Array /// [`TypeInner::Struct`]: crate::TypeInner::Struct fn write_std140_compat_type_declaration(
&mutself,
module: &crate::Module,
handle: Handle<crate::Type>,
) -> Result<Option<Word>, Error> { iflet Some(std140_type_info) = self.std140_compat_uniform_types.get(&handle) { return Ok(Some(std140_type_info.type_id));
}
let type_inner = &module.types[handle].inner; let std140_type_id = match *type_inner { crate::TypeInner::Matrix {
columns,
rows: rows @ crate::VectorSize::Bi,
scalar,
} => { let std140_type_id = self.id_gen.next(); letmut member_type_ids: ArrayVec<Word, 4> = ArrayVec::new(); let column_type_id = self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); for column in0..columns as u32 {
member_type_ids.push(column_type_id); self.annotations.push(Instruction::member_decorate(
std140_type_id,
column,
spirv::Decoration::Offset,
&[column * rows as u32 * scalar.width as u32],
)); ifself.flags.contains(WriterFlags::DEBUG) { self.debugs.push(Instruction::member_name(
std140_type_id,
column,
&format!("col{column}"),
));
}
}
Instruction::type_struct(std140_type_id, &member_type_ids)
.to_words(&mutself.logical_layout.declarations); self.std140_compat_uniform_types.insert(
handle,
Std140CompatTypeInfo {
type_id: std140_type_id,
member_indices: Vec::new(),
},
);
Some(std140_type_id)
} crate::TypeInner::Array { base, size, stride } => { matchself.write_std140_compat_type_declaration(module, base)? {
Some(std140_base_type_id) => { let std140_type_id = self.id_gen.next(); self.decorate(std140_type_id, spirv::Decoration::ArrayStride, &[stride]); let instruction = match size.resolve(module.to_ctx())? { crate::proc::IndexableLength::Known(length) => { let length_id = self.get_index_constant(length);
Instruction::type_array(
std140_type_id,
std140_base_type_id,
length_id,
)
} crate::proc::IndexableLength::Dynamic => {
unreachable!()
}
};
instruction.to_words(&mutself.logical_layout.declarations); self.std140_compat_uniform_types.insert(
handle,
Std140CompatTypeInfo {
type_id: std140_type_id,
member_indices: Vec::new(),
},
);
Some(std140_type_id)
}
None => None,
}
} crate::TypeInner::Struct { ref members, .. } => { letmut needs_std140_type = false; for member in members { match module.types[member.ty].inner { // We don't need to write a std140 type for the matrix itself as // it will be decomposed into the parent struct. As a result, the // struct does need a std140 type, however. crate::TypeInner::Matrix {
rows: crate::VectorSize::Bi,
..
} => needs_std140_type = true, // If an array member needs a std140 type, because it is an array // (of an array, etc) of `matCx2`s, then the struct also needs // a std140 type which uses the std140 type for this member. crate::TypeInner::Array { .. } ifself
.write_std140_compat_type_declaration(module, member.ty)?
.is_some() =>
{
needs_std140_type = true;
}
_ => {}
}
}
if needs_std140_type { let std140_type_id = self.id_gen.next(); letmut member_ids = Vec::new(); letmut member_indices = Vec::new(); letmut next_index = 0;
pub(super) fn get_constant_scalar(&mutself, value: crate::Literal) -> Word { let scalar = CachedConstant::Literal(value.into()); iflet Some(&id) = self.cached_constants.get(&scalar) { return id;
} let id = self.id_gen.next(); self.write_constant_scalar(id, &value, None); self.cached_constants.insert(scalar, id);
id
}
fn write_constant_scalar(
&mutself,
id: Word,
value: &crate::Literal,
debug_name: Option<&String>,
) { ifself.flags.contains(WriterFlags::DEBUG) { iflet Some(name) = debug_name { self.debugs.push(Instruction::name(id, name));
}
} let type_id = self.get_numeric_type_id(NumericType::Scalar(value.scalar())); let instruction = match *value { crate::Literal::F64(value) => { let bits = value.to_bits();
Instruction::constant_64bit(type_id, id, bits as u32, (bits >> 32) as u32)
} crate::Literal::F32(value) => Instruction::constant_32bit(type_id, id, value.to_bits()), crate::Literal::F16(value) => { let low = value.to_bits();
Instruction::constant_16bit(type_id, id, low as u32)
} crate::Literal::U16(value) => Instruction::constant_16bit(type_id, id, value as u32), crate::Literal::I16(value) => { // Sign-extend into the 32-bit word so that `spirv-as` can // round-trip the disassembly (it expects signed values for // signed types).
Instruction::constant_16bit(type_id, id, value as i32 as u32)
} crate::Literal::U32(value) => Instruction::constant_32bit(type_id, id, value), crate::Literal::I32(value) => Instruction::constant_32bit(type_id, id, value as u32), crate::Literal::U64(value) => {
Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32)
} crate::Literal::I64(value) => {
Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32)
} crate::Literal::Bool(true) => Instruction::constant_true(type_id, id), crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
unreachable!("Abstract types should not appear in IR presented to backends");
}
};
fn generate_workgroup_vars_init_block(
&mutself,
entry_id: Word,
ir_module: &crate::Module,
info: &FunctionInfo,
local_invocation_index: Option<Word>,
interface: &mut FunctionInterface,
function: &mut Function,
) -> Option<Word> { let body = ir_module
.global_variables
.iter()
.filter(|&(handle, var)| { let task_exception = (var.space == crate::AddressSpace::TaskPayload)
&& interface.stage == crate::ShaderStage::Task;
!info[handle].is_empty()
&& (var.space == crate::AddressSpace::WorkGroup || task_exception)
})
.map(|(handle, var)| { // It's safe to use `var_id` here, not `access_id`, because only // variables in the `Uniform` and `StorageBuffer` address spaces // get wrapped, and we're initializing `WorkGroup` variables. let var_id = self.global_variables[handle].var_id; let var_type_id = self.get_handle_type_id(var.ty); let init_word = self.get_constant_null(var_type_id);
Instruction::store(var_id, init_word, None)
})
.collect::<Vec<_>>();
if body.is_empty() { return None;
}
letmut pre_if_block = Block::new(entry_id);
let local_invocation_index = iflet Some(local_invocation_index) = local_invocation_index {
local_invocation_index
} else { let varying_id = self.id_gen.next(); let class = spirv::StorageClass::Input; let u32_ty_id = self.get_u32_type_id(); let pointer_type_id = self.get_pointer_type_id(u32_ty_id, class);
let next_id = self.id_gen.next();
function.consume(post_if_block, Instruction::branch(next_id));
Some(next_id)
}
/// Generate an `OpVariable` for one value in an [`EntryPoint`]'s IO interface. /// /// The [`Binding`]s of the arguments and result of an [`EntryPoint`]'s /// [`Function`] describe a SPIR-V shader interface. In SPIR-V, the /// interface is represented by global variables in the `Input` and `Output` /// storage classes, with decorations indicating which builtin or location /// each variable corresponds to. /// /// This function emits a single global `OpVariable` for a single value from /// the interface, and adds appropriate decorations to indicate which /// builtin or location it represents, how it should be interpolated, and so /// on. The `class` argument gives the variable's SPIR-V storage class, /// which should be either [`Input`] or [`Output`]. /// /// [`Binding`]: crate::Binding /// [`Function`]: crate::Function /// [`EntryPoint`]: crate::EntryPoint /// [`Input`]: spirv::StorageClass::Input /// [`Output`]: spirv::StorageClass::Output fn write_varying(
&mutself,
ir_module: &crate::Module,
stage: crate::ShaderStage,
class: spirv::StorageClass,
debug_name: Option<&str>,
ty: Handle<crate::Type>,
binding: &crate::Binding,
) -> Result<Word, Error> { let id = self.id_gen.next(); let ty_inner = &ir_module.types[ty].inner; let needs_polyfill = self.needs_f16_polyfill(ty_inner);
let pointer_type_id = if needs_polyfill { let f32_value_local = super::f16_polyfill::F16IoPolyfill::create_polyfill_type(ty_inner)
.expect("needs_polyfill returned true but create_polyfill_type returned None");
let f32_type_id = self.get_localtype_id(f32_value_local); let ptr_id = self.get_pointer_type_id(f32_type_id, class); self.io_f16_polyfills.register_io_var(id, f32_type_id);
let no_decorations = // VUID-StandaloneSpirv-Flat-06202 // > The Flat, NoPerspective, Sample, and Centroid decorations // > must not be used on variables with the Input storage class in a vertex shader
(class == spirv::StorageClass::Input && stage == crate::ShaderStage::Vertex) || // VUID-StandaloneSpirv-Flat-06201 // > The Flat, NoPerspective, Sample, and Centroid decorations // > must not be used on variables with the Output storage class in a fragment shader
(class == spirv::StorageClass::Output && stage == crate::ShaderStage::Fragment);
if !no_decorations { match interpolation { // Perspective-correct interpolation is the default in SPIR-V.
None | Some(crate::Interpolation::Perspective) => (),
Some(crate::Interpolation::Flat) => {
others.push(Decoration::Flat);
}
Some(crate::Interpolation::Linear) => {
others.push(Decoration::NoPerspective);
}
Some(crate::Interpolation::PerVertex) => {
others.push(Decoration::PerVertexKHR); self.require_any( "`per_vertex` interpolation",
&[spirv::Capability::FragmentBarycentricKHR],
)?; self.use_extension("SPV_KHR_fragment_shader_barycentric");
}
} match sampling { // Center sampling is the default in SPIR-V.
None
| Some( crate::Sampling::Center
| crate::Sampling::First
| crate::Sampling::Either,
) => (),
Some(crate::Sampling::Centroid) => {
others.push(Decoration::Centroid);
}
Some(crate::Sampling::Sample) => { self.require_any( "per-sample interpolation",
&[spirv::Capability::SampleRateShading],
)?;
others.push(Decoration::Sample);
}
}
} if per_primitive && stage == crate::ShaderStage::Fragment {
others.push(Decoration::PerPrimitiveEXT);
}
Ok(BindingDecorations::Location {
location,
others,
blend_src,
})
} crate::Binding::BuiltIn(built_in) => { usecrate::BuiltIn as Bi; letmut others = ArrayVec::new();
let built_in = match built_in {
Bi::Position { invariant } => { if invariant {
others.push(Decoration::Invariant);
}
// Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: // // > Any variable with integer or double-precision floating- // > point type and with Input storage class in a fragment // > shader, must be decorated Flat if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { let is_flat = match ir_module.types[ty].inner { crate::TypeInner::Scalar(scalar)
| crate::TypeInner::Vector { scalar, .. } => match scalar.kind {
Sk::Uint | Sk::Sint | Sk::Bool => true,
Sk::Float => false,
Sk::AbstractInt | Sk::AbstractFloat => { return Err(Error::Validation( "Abstract types should not appear in IR presented to backends",
))
}
},
_ => false,
};
/// Load an IO variable, converting from `f32` to `f16` if polyfill is active. /// Returns the id of the loaded value matching `target_type_id`. pub(super) fn load_io_with_f16_polyfill(
&mutself,
body: &mut Vec<Instruction>,
varying_id: Word,
target_type_id: Word,
) -> Word { let tmp = self.id_gen.next(); iflet Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) {
body.push(Instruction::load(f32_ty, tmp, varying_id, None)); let converted = self.id_gen.next(); super::f16_polyfill::F16IoPolyfill::emit_f32_to_f16_conversion(
tmp,
target_type_id,
converted,
body,
);
converted
} else {
body.push(Instruction::load(target_type_id, tmp, varying_id, None));
tmp
}
}
/// Store an IO variable, converting from `f16` to `f32` if polyfill is active. pub(super) fn store_io_with_f16_polyfill(
&mutself,
body: &mut Vec<Instruction>,
varying_id: Word,
value_id: Word,
) { iflet Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { let converted = self.id_gen.next(); super::f16_polyfill::F16IoPolyfill::emit_f16_to_f32_conversion(
value_id, f32_ty, converted, body,
);
body.push(Instruction::store(varying_id, converted, None));
} else {
body.push(Instruction::store(varying_id, value_id, None));
}
}
// Note: we should be able to substitute `binding_array<Foo, 0>`, // but there is still code that tries to register the pre-substituted type, // and it is failing on 0. letmut substitute_inner_type_lookup = None; iflet Some(ref res_binding) = global_variable.binding { let bind_target = self.resolve_resource_binding(res_binding)?; self.decorate(id, Decoration::DescriptorSet, &[bind_target.descriptor_set]); self.decorate(id, Decoration::Binding, &[bind_target.binding]);
let init_word = global_variable
.init
.map(|constant| self.constant_ids[constant]); let inner_type_id = self.get_type_id(
substitute_inner_type_lookup.unwrap_or(LookupType::Handle(global_variable.ty)),
);
// generate the wrapping structure if needed let pointer_type_id = if global_needs_wrapper(ir_module, global_variable) { let wrapper_type_id = self.id_gen.next();
let pointer_type_id = self.id_gen.next();
Instruction::type_pointer(pointer_type_id, class, wrapper_type_id)
.to_words(&mutself.logical_layout.declarations);
pointer_type_id
} else { // This is a global variable in the Storage address space. The only // way it could have `global_needs_wrapper() == false` is if it has // a runtime-sized or binding array. // Runtime-sized arrays were decorated when iterating through struct content. // Now binding arrays require Block decorating. ifletcrate::AddressSpace::Storage { .. } = global_variable.space { match ir_module.types[global_variable.ty].inner { crate::TypeInner::BindingArray { base, .. } => { let ty = &ir_module.types[base]; letmut should_decorate = true; // Check if the type has a runtime array. // A normal runtime array gets validated out, // so only structs can be with runtime arrays ifletcrate::TypeInner::Struct { ref members, .. } = ty.inner { // only the last member in a struct can be dynamically sized iflet Some(last_member) = members.last() { iflet &crate::TypeInner::Array {
size: crate::ArraySize::Dynamic,
..
} = &ir_module.types[last_member.ty].inner
{
should_decorate = false;
}
}
} if should_decorate { let decorated_id = self.get_handle_type_id(base); self.decorate(decorated_id, Decoration::Block, &[]);
}
}
_ => (),
};
} if substitute_inner_type_lookup.is_some() {
inner_type_id
} else { self.get_handle_pointer_type_id(global_variable.ty, class)
}
};
/// Write the necessary decorations for a struct member. /// /// Emit decorations for the `index`'th member of the struct type /// designated by `struct_id`, described by `member`. fn decorate_struct_member(
&mutself,
struct_id: Word,
index: usize,
member: &crate::StructMember,
arena: &UniqueArena<crate::Type>,
) -> Result<(), Error> { use spirv::Decoration;
self.annotations.push(Instruction::member_decorate(
struct_id,
index as u32,
Decoration::Offset,
&[member.offset],
));
ifself.flags.contains(WriterFlags::DEBUG) { iflet Some(ref name) = member.name { self.debugs
.push(Instruction::member_name(struct_id, index as u32, name));
}
}
// Matrices and (potentially nested) arrays of matrices both require decorations, // so "see through" any arrays to determine if they're needed. letmut member_array_subty_inner = &arena[member.ty].inner; whileletcrate::TypeInner::Array { base, .. } = *member_array_subty_inner {
member_array_subty_inner = &arena[base].inner;
}
ifletcrate::TypeInner::Matrix {
columns: _,
rows,
scalar,
} = *member_array_subty_inner
{ let byte_stride = Alignment::from(rows) * scalar.width as u32; self.annotations.push(Instruction::member_decorate(
struct_id,
index as u32,
Decoration::ColMajor,
&[],
)); self.annotations.push(Instruction::member_decorate(
struct_id,
index as u32,
Decoration::MatrixStride,
&[byte_stride],
));
}
Ok(())
}
pub(super) fn get_function_type(&mutself, lookup_function_type: LookupFunctionType) -> Word { matchself
.lookup_function_type
.entry(lookup_function_type.clone())
{
Entry::Occupied(e) => *e.get(),
Entry::Vacant(_) => { let id = self.id_gen.next(); let instruction = Instruction::type_function(
id,
lookup_function_type.return_type_id,
&lookup_function_type.parameter_type_ids,
);
instruction.to_words(&mutself.logical_layout.declarations); self.lookup_function_type.insert(lookup_function_type, id);
id
}
}
}
// write all types for (handle, _) in ir_module.types.iter() { self.write_type_declaration_arena(ir_module, handle)?;
}
// write std140 layout compatible types required by uniforms for (_, var) in ir_module.global_variables.iter() { if var.space == crate::AddressSpace::Uniform { self.write_std140_compat_type_declaration(ir_module, var.ty)?;
}
}
// write all const-expressions as constants self.constant_ids
.resize(ir_module.global_expressions.len(), 0); for (handle, _) in ir_module.global_expressions.iter() { self.write_constant_expr(handle, ir_module, mod_info)?;
}
debug_assert!(self.constant_ids.iter().all(|&id| id != 0));
// write the name of constants on their respective const-expression initializer ifself.flags.contains(WriterFlags::DEBUG) { for (_, constant) in ir_module.constants.iter() { iflet Some(ref name) = constant.name { let id = self.constant_ids[constant.init]; self.debugs.push(Instruction::name(id, name));
}
}
}
// write all global variables for (handle, var) in ir_module.global_variables.iter() { // If a single entry point was specified, only write `OpVariable` instructions // for the globals it actually uses. Emit dummies for the others, // to preserve the indices in `global_variables`. let gvar = match ep_index {
Some(index) if mod_info.get_entry_point(index)[handle].is_empty() => {
GlobalVariable::dummy()
}
_ => { let id = self.write_global_variable(ir_module, var)?;
GlobalVariable::new(id)
}
}; self.global_variables.insert(handle, gvar);
}
// write all functions for (handle, ir_function) in ir_module.functions.iter() { let info = &mod_info[handle]; iflet Some(index) = ep_index { let ep_info = mod_info.get_entry_point(index); // If this function uses globals that we omitted from the SPIR-V // because the entry point and its callees didn't use them, // then we must skip it. if !ep_info.dominates_global_use(info) {
log::debug!("Skip function {:?}", ir_function.name); continue;
}
// Skip functions that that are not compatible with this entry point's stage. // // When validation is enabled, it rejects modules whose entry points try to call // incompatible functions, so if we got this far, then any functions incompatible // with our selected entry point must not be used. // // When validation is disabled, `fun_info.available_stages` is always just // `ShaderStages::all()`, so this will write all functions in the module, and // the downstream GLSL compiler will catch any problems. if !info.available_stages.contains(ep_info.available_stages) { continue;
}
} let id = self.write_function(ir_function, info, ir_module, None, &debug_info_inner)?; self.lookup_function.insert(handle, id);
}
// write all or one entry points for (index, ir_ep) in ir_module.entry_points.iter().enumerate() { if ep_index.is_some() && ep_index != Some(index) { continue;
} let info = mod_info.get_entry_point(index); let ep_instruction = self.write_entry_point(ir_ep, info, ir_module, &debug_info_inner)?;
ep_instruction.to_words(&mutself.logical_layout.entry_points);
}
for capability inself.capabilities_used.iter() {
Instruction::capability(*capability).to_words(&mutself.logical_layout.capabilities);
} for extension inself.extensions_used.iter() {
Instruction::extension(extension).to_words(&mutself.logical_layout.extensions);
} if ir_module.entry_points.is_empty() { // SPIR-V doesn't like modules without entry points
Instruction::capability(spirv::Capability::Linkage)
.to_words(&mutself.logical_layout.capabilities);
}
let addressing_model = spirv::AddressingModel::Logical; let memory_model = ifself
.capabilities_used
.contains(&spirv::Capability::VulkanMemoryModel)
{
spirv::MemoryModel::Vulkan
} else {
spirv::MemoryModel::GLSL450
}; //self.check(addressing_model.required_capabilities())?; //self.check(memory_model.required_capabilities())?;
// Try to find the entry point and corresponding index let ep_index = match pipeline_options {
Some(po) => { let index = ir_module
.entry_points
.iter()
.position(|ep| po.shader_stage == ep.stage && po.entry_point == ep.name)
.ok_or(Error::EntryPointNotFound)?;
Some(index)
}
None => None,
};
/// Return the set of capabilities the last module written used. pubconstfn get_capabilities_used(&self) -> &crate::FastIndexSet<spirv::Capability> {
&self.capabilities_used
}
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.