usecrate::{device::bgl, resource::InvalidResourceError, FastHashMap, FastHashSet}; use arrayvec::ArrayVec; use std::{collections::hash_map::Entry, fmt}; use thiserror::Error; use wgt::{BindGroupLayoutEntry, BindingType};
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum BindingError { #[error("Binding is missing from the pipeline layout")]
Missing, #[error("Visibility flags don't include the shader stage")]
Invisible, #[error( "Type on the shader side ({shader:?}) does not match the pipeline binding ({binding:?})"
)]
WrongType {
binding: BindingTypeName,
shader: BindingTypeName,
}, #[error("Storage class {binding:?} doesn't match the shader {shader:?}")]
WrongAddressSpace {
binding: naga::AddressSpace,
shader: naga::AddressSpace,
}, #[error("Address space {space:?} is not a valid Buffer address space")]
WrongBufferAddressSpace { space: naga::AddressSpace }, #[error("Buffer structure size {buffer_size}, added to one element of an unbound array, if it's the last field, ended up greater than the given `min_binding_size`, which is {min_binding_size}")]
WrongBufferSize {
buffer_size: wgt::BufferSize,
min_binding_size: wgt::BufferSize,
}, #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")]
WrongTextureViewDimension {
dim: naga::ImageDimension,
is_array: bool,
binding: BindingType,
}, #[error("Texture class {binding:?} doesn't match the shader {shader:?}")]
WrongTextureClass {
binding: naga::ImageClass,
shader: naga::ImageClass,
}, #[error("Comparison flag doesn't match the shader")]
WrongSamplerComparison, #[error("Derived bind group layout type is not consistent between stages")]
InconsistentlyDerivedType, #[error("Texture format {0:?} is not supported for storage use")]
BadStorageFormat(wgt::TextureFormat),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum FilteringError { #[error("Integer textures can't be sampled with a filtering sampler")]
Integer, #[error("Non-filterable float textures can't be sampled with a filtering sampler")]
Float,
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum InputError { #[error("Input is not provided by the earlier stage in the pipeline")]
Missing, #[error("Input type is not compatible with the provided {0}")]
WrongType(NumericType), #[error("Input interpolation doesn't match provided {0:?}")]
InterpolationMismatch(Option<naga::Interpolation>), #[error("Input sampling doesn't match provided {0:?}")]
SamplingMismatch(Option<naga::Sampling>),
}
/// Errors produced when validating a programmable stage of a pipeline. #[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum StageError { #[error( "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension limit {limit:?} and the total invocation limit {total}"
)]
InvalidWorkgroupSize {
current: [u32; 3],
current_total: u32,
limit: [u32; 3],
total: u32,
}, #[error("Shader uses {used} inter-stage components above the limit of {limit}")]
TooManyVaryings { used: u32, limit: u32 }, #[error("Unable to find entry point '{0}'")]
MissingEntryPoint(String), #[error("Shader global {0:?} is not available in the pipeline layout")]
Binding(naga::ResourceBinding, #[source] BindingError), #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")]
Filtering {
texture: naga::ResourceBinding,
sampler: naga::ResourceBinding, #[source]
error: FilteringError,
}, #[error("Location[{location}] {var} is not provided by the previous stage outputs")]
Input {
location: wgt::ShaderLocation,
var: InterfaceVar, #[source]
error: InputError,
}, #[error( "Unable to select an entry point: no entry point was found in the provided shader module"
)]
NoEntryPointFound, #[error( "Unable to select an entry point: \
multiple entry points were found in the provided shader module, \
but no entry point was specified"
)]
MultipleEntryPointsFound, #[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
}
fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> { use naga::StorageFormat as Sf; use wgt::TextureFormat as Tf;
NumericType {
dim, //Note: Shader always sees data as int, uint, or float. // It doesn't know if the original is normalized in a tighter form.
scalar,
}
}
fn from_texture_format(format: wgt::TextureFormat) -> Self { use naga::{Scalar, VectorSize as Vs}; use wgt::TextureFormat as Tf;
NumericType {
dim, //Note: Shader always sees data as int, uint, or float. // It doesn't know if the original is normalized in a tighter form.
scalar,
}
}
/// Return true if the fragment `format` is covered by the provided `output`. pubfn check_texture_format(
format: wgt::TextureFormat,
output: &NumericType,
) -> Result<(), NumericType> { let nt = NumericType::from_texture_format(format); if nt.is_subtype_of(output) {
Ok(())
} else {
Err(nt)
}
}
pubenum BindingLayoutSource<'a> { /// The binding layout is derived from the pipeline layout. /// /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces.
Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>), /// The binding layout is provided by the user in BGLs. /// /// This will be validated against the shader's interfaces.
Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>),
}
impl Interface { fn populate(
list: &mut Vec<Varying>,
binding: Option<&naga::Binding>,
ty: naga::Handle<naga::Type>,
arena: &naga::UniqueArena<naga::Type>,
) { let numeric_ty = match arena[ty].inner {
naga::TypeInner::Scalar(scalar) => NumericType {
dim: NumericDimension::Scalar,
scalar,
},
naga::TypeInner::Vector { size, scalar } => NumericType {
dim: NumericDimension::Vector(size),
scalar,
},
naga::TypeInner::Matrix {
columns,
rows,
scalar,
} => NumericType {
dim: NumericDimension::Matrix(columns, rows),
scalar,
},
naga::TypeInner::Struct { ref members, .. } => { for member in members { Self::populate(list, member.binding.as_ref(), member.ty, arena);
} return;
} ref other => { //Note: technically this should be at least `log::error`, but // the reality is - every shader coming from `glslc` outputs an array // of clip distances and hits this path :( // So we lower it to `log::warn` to be less annoying.
log::warn!("Unexpected varying type: {:?}", other); return;
}
};
pubfn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self { letmut resources = naga::Arena::new(); letmut resource_mapping = FastHashMap::default(); for (var_handle, var) in module.global_variables.iter() { let bind = match var.binding {
Some(ref br) => br.clone(),
_ => continue,
}; let naga_ty = &module.types[var.ty].inner;
let inner_ty = match *naga_ty {
naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner, ref ty => ty,
};
let ty = match *inner_ty {
naga::TypeInner::Image {
dim,
arrayed,
class,
} => ResourceType::Texture {
dim,
arrayed,
class,
},
naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
naga::TypeInner::AccelerationStructure => ResourceType::AccelerationStructure, ref other => ResourceType::Buffer {
size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(),
},
}; let handle = resources.append(
Resource {
name: var.name.clone(),
bind,
ty,
class: var.space,
},
Default::default(),
);
resource_mapping.insert(var_handle, handle);
}
letmut entry_points = FastHashMap::default();
entry_points.reserve(module.entry_points.len()); for (index, entry_point) in module.entry_points.iter().enumerate() { let info = info.get_entry_point(index); letmut ep = EntryPoint::default(); for arg in entry_point.function.arguments.iter() { Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
} iflet Some(ref result) = entry_point.function.result { Self::populate(
&mut ep.outputs,
result.binding.as_ref(),
result.ty,
&module.types,
);
}
for (var_handle, var) in module.global_variables.iter() { let usage = info[var_handle]; if !usage.is_empty() && var.binding.is_some() {
ep.resources.push(resource_mapping[&var_handle]);
}
}
for key in info.sampling_set.iter() {
ep.sampling_pairs
.insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
}
ep.dual_source_blending = info.dual_source_blending;
ep.workgroup_size = entry_point.workgroup_size;
pubfn check_stage(
&self,
layouts: &mut BindingLayoutSource<'_>,
shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
entry_point_name: &str,
stage_bit: wgt::ShaderStages,
inputs: StageIo,
compare_function: Option<wgt::CompareFunction>,
) -> Result<StageIo, StageError> { // Since a shader module can have multiple entry points with the same name, // we need to look for one with the right execution model. let shader_stage = Self::shader_stage_from_stage_bit(stage_bit); let pair = (shader_stage, entry_point_name.to_string()); let entry_point = matchself.entry_points.get(&pair) {
Some(some) => some,
None => return Err(StageError::MissingEntryPoint(pair.1)),
}; let (_stage, entry_point_name) = pair;
// check resources visibility for &handle in entry_point.resources.iter() { let res = &self.resources[handle]; let result = 'err: { match layouts {
BindingLayoutSource::Provided(layouts) => { // update the required binding size for this buffer iflet ResourceType::Buffer { size } = res.ty { match shader_binding_sizes.entry(res.bind.clone()) {
Entry::Occupied(e) => {
*e.into_mut() = size.max(*e.get());
}
Entry::Vacant(e) => {
e.insert(size);
}
}
}
let Some(map) = layouts.get(res.bind.group as usize) else { break'err Err(BindingError::Missing);
};
let Some(entry) = map.get(res.bind.binding) else { break'err Err(BindingError::Missing);
};
if !entry.visibility.contains(stage_bit) { break'err Err(BindingError::Invisible);
}
res.check_binding_use(entry)
}
BindingLayoutSource::Derived(layouts) => { let Some(map) = layouts.get_mut(res.bind.group as usize) else { break'err Err(BindingError::Missing);
};
let ty = match res.derive_binding_type(
entry_point
.sampling_pairs
.iter()
.any(|&(im, _samp)| im == handle),
) {
Ok(ty) => ty,
Err(error) => break'err Err(error),
};
// Check the compatibility between textures and samplers // // We only need to do this if the binding layout is provided by the user, as derived // layouts will inherently be correctly tagged. iflet BindingLayoutSource::Provided(layouts) = layouts { for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() { let texture_bind = &self.resources[texture_handle].bind; let sampler_bind = &self.resources[sampler_handle].bind; let texture_layout = layouts[texture_bind.group as usize]
.get(texture_bind.binding)
.unwrap(); let sampler_layout = layouts[sampler_bind.group as usize]
.get(sampler_bind.binding)
.unwrap();
assert!(texture_layout.visibility.contains(stage_bit));
assert!(sampler_layout.visibility.contains(stage_bit));
let sampler_filtering = matches!(
sampler_layout.ty,
BindingType::Sampler(wgt::SamplerBindingType::Filtering)
); let texture_sample_type = match texture_layout.ty {
BindingType::Texture { sample_type, .. } => sample_type,
_ => unreachable!(),
};
// check inputs compatibility for input in entry_point.inputs.iter() { match *input {
Varying::Local { location, ref iv } => { let result =
inputs
.get(&location)
.ok_or(InputError::Missing)
.and_then(|provided| { let (compatible, num_components) = match shader_stage { // For vertex attributes, there are defaults filled out // by the driver if data is not provided.
naga::ShaderStage::Vertex => { // vertex inputs don't count towards inter-stage
(iv.ty.is_compatible_with(&provided.ty), 0)
}
naga::ShaderStage::Fragment => { if iv.interpolation != provided.interpolation { return Err(InputError::InterpolationMismatch(
provided.interpolation,
));
} if iv.sampling != provided.sampling { return Err(InputError::SamplingMismatch(
provided.sampling,
));
}
(
iv.ty.is_subtype_of(&provided.ty),
iv.ty.dim.num_components(),
)
}
naga::ShaderStage::Compute => (false, 0),
}; if compatible {
Ok(num_components)
} else {
Err(InputError::WrongType(provided.ty))
}
}); match result {
Ok(num_components) => {
inter_stage_components += num_components;
}
Err(error) => { return Err(StageError::Input {
location,
var: iv.clone(),
error,
})
}
}
}
Varying::BuiltIn(_) => {}
}
}
if shader_stage == naga::ShaderStage::Vertex { for output in entry_point.outputs.iter() { //TODO: count builtins towards the limit?
inter_stage_components += match *output {
Varying::Local { ref iv, .. } => iv.ty.dim.num_components(),
Varying::BuiltIn(_) => 0,
};
iflet Some(
cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
) = compare_function
{ iflet Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) = *output
{
log::warn!( "Vertex shader with entry point {entry_point_name} outputs a @builtin(position) without the @invariant \
attribute and is used in a pipeline with {cmp:?}. On some machines, this can cause bad artifacting as {cmp:?} assumes \
the values output from the vertex shader exactly match the value in the depth buffer. The @invariant attribute on the \
@builtin(position) vertex output ensures that the exact same pixel depths are used every render."
);
}
}
}
}
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.