#[cfg(feature = "serde")] use serde::Deserialize; #[cfg(feature = "serde")] use serde::Serialize;
use std::{
borrow::Cow,
mem::ManuallyDrop,
ops::Range,
sync::{Arc, OnceLock, Weak},
};
usecrate::resource::Tlas; use thiserror::Error;
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum BindGroupLayoutEntryError { #[error("Cube dimension is not expected for texture storage")]
StorageTextureCube, #[error("Read-write and read-only storage textures are not allowed by baseline webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
StorageTextureReadWrite, #[error("Atomic storage textures are not allowed by baseline webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
StorageTextureAtomic, #[error("Arrays of bindings unsupported for this type of binding")]
ArrayUnsupported, #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")]
SampleTypeFloatFilterableBindingMultisampled, #[error("Multisampled texture binding view dimension must be 2d, got {0:?}")]
Non2DMultisampled(wgt::TextureViewDimension), #[error(transparent)]
MissingFeatures(#[from] MissingFeatures), #[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateBindGroupLayoutError { #[error(transparent)]
Device(#[from] DeviceError), #[error("Conflicting binding at index {0}")]
ConflictBinding(u32), #[error("Binding {binding} entry is invalid")]
Entry {
binding: u32, #[source]
error: BindGroupLayoutEntryError,
}, #[error(transparent)]
TooManyBindings(BindingTypeMaxCountError), #[error("Binding index {binding} is greater than the maximum number {maximum}")]
InvalidBindingIndex { binding: u32, maximum: u32 }, #[error("Invalid visibility {0:?}")]
InvalidVisibility(wgt::ShaderStages),
}
//TODO: refactor this to move out `enum BindingError`.
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateBindGroupError { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error( "Binding count declared with at most {expected} items, but {actual} items were provided"
)]
BindingArrayPartialLengthMismatch { actual: usize, expected: usize }, #[error( "Binding count declared with exactly {expected} items, but {actual} items were provided"
)]
BindingArrayLengthMismatch { actual: usize, expected: usize }, #[error("Array binding provided zero elements")]
BindingArrayZeroLength, #[error("The bound range {range:?} of {buffer} overflows its size ({size})")]
BindingRangeTooLarge {
buffer: ResourceErrorIdent,
range: Range<wgt::BufferAddress>,
size: u64,
}, #[error("Binding size {actual} of {buffer} is less than minimum {min}")]
BindingSizeTooSmall {
buffer: ResourceErrorIdent,
actual: u64,
min: u64,
}, #[error("{0} binding size is zero")]
BindingZeroSize(ResourceErrorIdent), #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
BindingsNumMismatch { actual: usize, expected: usize }, #[error("Binding {0} is used at least twice in the descriptor")]
DuplicateBinding(u32), #[error("Unable to find a corresponding declaration for the given binding {0}")]
MissingBindingDeclaration(u32), #[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError), #[error(transparent)]
MissingTextureUsage(#[from] MissingTextureUsageError), #[error("Binding declared as a single item, but bind group is using it as an array")]
SingleBindingExpected, #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")]
UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32), #[error( "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
)]
BufferRangeTooLarge {
binding: u32,
given: u32,
limit: u32,
}, #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
WrongBindingType { // Index of the binding
binding: u32, // The type given to the function
actual: wgt::BindingType, // Human-readable description of expected types
expected: &'static str,
}, #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
InvalidTextureMultisample {
binding: u32,
layout_multisampled: bool,
view_samples: u32,
}, #[error( "Texture binding {} expects sample type {:?}, but was given a view with format {:?} (sample type {:?})",
binding,
layout_sample_type,
view_format,
view_sample_type
)]
InvalidTextureSampleType {
binding: u32,
layout_sample_type: wgt::TextureSampleType,
view_format: wgt::TextureFormat,
view_sample_type: wgt::TextureSampleType,
}, #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
InvalidTextureDimension {
binding: u32,
layout_dimension: wgt::TextureViewDimension,
view_dimension: wgt::TextureViewDimension,
}, #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
InvalidStorageTextureFormat {
binding: u32,
layout_format: wgt::TextureFormat,
view_format: wgt::TextureFormat,
}, #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 }, #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
WrongSamplerComparison {
binding: u32,
layout_cmp: bool,
sampler_cmp: bool,
}, #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
WrongSamplerFiltering {
binding: u32,
layout_flt: bool,
sampler_flt: bool,
}, #[error("Bound texture views can not have both depth and stencil aspects enabled")]
DepthStencilAspect, #[error("The adapter does not support read access for storage textures of format {0:?}")]
StorageReadNotSupported(wgt::TextureFormat), #[error("The adapter does not support atomics for storage textures of format {0:?}")]
StorageAtomicNotSupported(wgt::TextureFormat), #[error("The adapter does not support write access for storage textures of format {0:?}")]
StorageWriteNotSupported(wgt::TextureFormat), #[error("The adapter does not support read-write access for storage textures of format {0:?}")]
StorageReadWriteNotSupported(wgt::TextureFormat), #[error(transparent)]
ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError), #[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
}
/// Bindable resource and the slot to bind it to. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pubstruct BindGroupEntry<'a> { /// Slot for which binding provides resource. Corresponds to an entry of the same /// binding index in the [`BindGroupLayoutDescriptor`]. pub binding: u32, /// Resource to attach to the binding pub resource: BindingResource<'a>,
}
/// Bindable resource and the slot to bind it to. #[derive(Clone, Debug)] pubstruct ResolvedBindGroupEntry<'a> { /// Slot for which binding provides resource. Corresponds to an entry of the same /// binding index in the [`BindGroupLayoutDescriptor`]. pub binding: u32, /// Resource to attach to the binding pub resource: ResolvedBindingResource<'a>,
}
/// Describes a group of bindings and the resources to be bound. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pubstruct BindGroupDescriptor<'a> { /// Debug label of the bind group. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The [`BindGroupLayout`] that corresponds to this bind group. pub layout: BindGroupLayoutId, /// The resources to bind to this bind group. pub entries: Cow<'a, [BindGroupEntry<'a>]>,
}
/// Describes a group of bindings and the resources to be bound. #[derive(Clone, Debug)] pubstruct ResolvedBindGroupDescriptor<'a> { /// Debug label of the bind group. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// The [`BindGroupLayout`] that corresponds to this bind group. pub layout: Arc<BindGroupLayout>, /// The resources to bind to this bind group. pub entries: Cow<'a, [ResolvedBindGroupEntry<'a>]>,
}
/// Describes a [`BindGroupLayout`]. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pubstruct BindGroupLayoutDescriptor<'a> { /// Debug label of the bind group layout. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Array of entries in this BindGroupLayout pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
}
/// Used by [`BindGroupLayout`]. It indicates whether the BGL must be /// used with a specific pipeline. This constraint only happens when /// the BGLs have been derived from a pipeline without a layout. #[derive(Debug)] pub(crate) enum ExclusivePipeline {
None,
Render(Weak<RenderPipeline>),
Compute(Weak<ComputePipeline>),
}
/// Bind group layout. #[derive(Debug)] pubstruct BindGroupLayout { pub(crate) raw: ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>, pub(crate) device: Arc<Device>, pub(crate) entries: bgl::EntryMap, /// It is very important that we know if the bind group comes from the BGL pool. /// /// If it does, then we need to remove it from the pool when we drop it. /// /// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool /// (derived BGLs) must not be removed. pub(crate) origin: bgl::Origin, pub(crate) exclusive_pipeline: OnceLock<ExclusivePipeline>, #[allow(unused)] pub(crate) binding_count_validator: BindingTypeMaxCountValidator, /// The `label` from the descriptor used to create the resource. pub(crate) label: String,
}
impl Drop for BindGroupLayout { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident()); if matches!(self.origin, bgl::Origin::Pool) { self.device.bgl_pool.remove(&self.entries);
} // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_bind_group_layout(raw);
}
}
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreatePipelineLayoutError { #[error(transparent)]
Device(#[from] DeviceError), #[error( "Push constant at index {index} has range bound {bound} not aligned to {}",
wgt::PUSH_CONSTANT_ALIGNMENT
)]
MisalignedPushConstantRange { index: usize, bound: u32 }, #[error(transparent)]
MissingFeatures(#[from] MissingFeatures), #[error("Push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")]
MoreThanOnePushConstantRangePerStage {
index: usize,
provided: wgt::ShaderStages,
intersected: wgt::ShaderStages,
}, #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
PushConstantRangeTooLarge {
index: usize,
range: Range<u32>,
max: u32,
}, #[error(transparent)]
TooManyBindings(BindingTypeMaxCountError), #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
TooManyGroups { actual: usize, max: usize }, #[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum PushConstantUploadError { #[error("Provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)]
TooLarge {
offset: u32,
end_offset: u32,
idx: usize,
range: wgt::PushConstantRange,
}, #[error("Provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")]
PartialRangeMatch {
actual: wgt::ShaderStages,
idx: usize,
matched: wgt::ShaderStages,
}, #[error("Provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")]
MissingStages {
actual: wgt::ShaderStages,
idx: usize,
missing: wgt::ShaderStages,
}, #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
UnmatchedStages {
actual: wgt::ShaderStages,
unmatched: wgt::ShaderStages,
}, #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
Unaligned(u32),
}
/// Describes a pipeline layout. /// /// A `PipelineLayoutDescriptor` can be used to create a pipeline layout. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pubstruct PipelineLayoutDescriptor<'a> { /// Debug label of the pipeline layout. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Bind groups that this pipeline uses. The first entry will provide all the bindings for /// "set = 0", second entry will provide all the bindings for "set = 1" etc. pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>, /// Set of push constant ranges this pipeline uses. Each shader stage that /// uses push constants must define the range in push constant memory that /// corresponds to its single `layout(push_constant)` uniform block. /// /// If this array is non-empty, the /// [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) feature must /// be enabled. pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
}
/// Describes a pipeline layout. /// /// A `PipelineLayoutDescriptor` can be used to create a pipeline layout. #[derive(Debug)] pubstruct ResolvedPipelineLayoutDescriptor<'a> { /// Debug label of the pipeline layout. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Bind groups that this pipeline uses. The first entry will provide all the bindings for /// "set = 0", second entry will provide all the bindings for "set = 1" etc. pub bind_group_layouts: Cow<'a, [Arc<BindGroupLayout>]>, /// Set of push constant ranges this pipeline uses. Each shader stage that /// uses push constants must define the range in push constant memory that /// corresponds to its single `layout(push_constant)` uniform block. /// /// If this array is non-empty, the /// [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) feature must /// be enabled. pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
}
#[derive(Debug)] pubstruct PipelineLayout { pub(crate) raw: ManuallyDrop<Box<dyn hal::DynPipelineLayout>>, pub(crate) device: Arc<Device>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) bind_group_layouts: ArrayVec<Arc<BindGroupLayout>, { hal::MAX_BIND_GROUPS }>, pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
}
impl Drop for PipelineLayout { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_pipeline_layout(raw);
}
}
}
/// Validate push constants match up with expected ranges. pub(crate) fn validate_push_constant_ranges(
&self,
stages: wgt::ShaderStages,
offset: u32,
end_offset: u32,
) -> Result<(), PushConstantUploadError> { // Don't need to validate size against the push constant size limit here, // as push constant ranges are already validated to be within bounds, // and we validate that they are within the ranges.
if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { return Err(PushConstantUploadError::Unaligned(offset));
}
// Push constant validation looks very complicated on the surface, but // the problem can be range-reduced pretty well. // // Push constants require (summarized from the vulkan spec): // 1. For each byte in the range and for each shader stage in stageFlags, // there must be a push constant range in the layout that includes that // byte and that stage. // 2. For each byte in the range and for each push constant range that overlaps that byte, // `stage` must include all stages in that push constant range’s `stage`. // // However there are some additional constraints that help us: // 3. All push constant ranges are the only range that can access that stage. // i.e. if one range has VERTEX, no other range has VERTEX // // Therefore we can simplify the checks in the following ways: // - Because 3 guarantees that the push constant range has a unique stage, // when we check for 1, we can simply check that our entire updated range // is within a push constant range. i.e. our range for a specific stage cannot // intersect more than one push constant range. letmut used_stages = wgt::ShaderStages::NONE; for (idx, range) inself.push_constant_ranges.iter().enumerate() { // contains not intersects due to 2 if stages.contains(range.stages) { if !(range.range.start <= offset && end_offset <= range.range.end) { return Err(PushConstantUploadError::TooLarge {
offset,
end_offset,
idx,
range: range.clone(),
});
}
used_stages |= range.stages;
} elseif stages.intersects(range.stages) { // Will be caught by used stages check below, but we can do this because of 1 // and is more helpful to the user. return Err(PushConstantUploadError::PartialRangeMatch {
actual: stages,
idx,
matched: range.stages,
});
}
// The push constant range intersects range we are uploading if offset < range.range.end && range.range.start < end_offset { // But requires stages we don't provide if !stages.contains(range.stages) { return Err(PushConstantUploadError::MissingStages {
actual: stages,
idx,
missing: stages,
});
}
}
} if used_stages != stages { return Err(PushConstantUploadError::UnmatchedStages {
actual: stages,
unmatched: stages - used_stages,
});
}
Ok(())
}
}
// Note: Duplicated in `wgpu-rs` as `BindingResource` // They're different enough that it doesn't make sense to share a common type #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pubenum BindingResource<'a> {
Buffer(BufferBinding),
BufferArray(Cow<'a, [BufferBinding]>),
Sampler(SamplerId),
SamplerArray(Cow<'a, [SamplerId]>),
TextureView(TextureViewId),
TextureViewArray(Cow<'a, [TextureViewId]>),
AccelerationStructure(TlasId),
}
// Note: Duplicated in `wgpu-rs` as `BindingResource` // They're different enough that it doesn't make sense to share a common type #[derive(Debug, Clone)] pubenum ResolvedBindingResource<'a> {
Buffer(ResolvedBufferBinding),
BufferArray(Cow<'a, [ResolvedBufferBinding]>),
Sampler(Arc<Sampler>),
SamplerArray(Cow<'a, [Arc<Sampler>]>),
TextureView(Arc<TextureView>),
TextureViewArray(Cow<'a, [Arc<TextureView>]>),
AccelerationStructure(Arc<Tlas>),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum BindError { #[error( "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
s0 = if *.expected >= 2 { "s" } else { "" },
s1 = if *.actual >= 2 { "s" } else { "" },
)]
MismatchedDynamicOffsetCount {
bind_group: ResourceErrorIdent,
group: u32,
actual: usize,
expected: usize,
}, #[error( "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
)]
UnalignedDynamicBinding {
bind_group: ResourceErrorIdent,
idx: usize,
group: u32,
binding: u32,
offset: u32,
alignment: u32,
limit_name: &'static str,
}, #[error( "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \
Buffer size is {buffer_size} bytes, the binding binds bytes {binding_range:?}, meaning the maximum the binding can be offset is {maximum_dynamic_offset} bytes",
)]
DynamicBindingOutOfBounds {
bind_group: ResourceErrorIdent,
idx: usize,
group: u32,
binding: u32,
offset: u32,
buffer_size: wgt::BufferAddress,
binding_range: Range<wgt::BufferAddress>,
maximum_dynamic_offset: wgt::BufferAddress,
},
}
#[derive(Debug)] pubstruct BindGroupDynamicBindingData { /// The index of the binding. /// /// Used for more descriptive errors. pub(crate) binding_idx: u32, /// The size of the buffer. /// /// Used for more descriptive errors. pub(crate) buffer_size: wgt::BufferAddress, /// The range that the binding covers. /// /// Used for more descriptive errors. pub(crate) binding_range: Range<wgt::BufferAddress>, /// The maximum value the dynamic offset can have before running off the end of the buffer. pub(crate) maximum_dynamic_offset: wgt::BufferAddress, /// The binding type. pub(crate) binding_type: wgt::BufferBindingType,
}
#[derive(Debug)] pubstruct BindGroup { pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>, pub(crate) device: Arc<Device>, pub(crate) layout: Arc<BindGroupLayout>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) used: BindGroupStates, pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>, pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>, pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>, /// Actual binding sizes for buffers that don't have `min_binding_size` /// specified in BGL. Listed in the order of iteration of `BGL.entries`. pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
}
impl Drop for BindGroup { fn drop(&mutself) { iflet Some(raw) = self.raw.take() {
resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_bind_group(raw);
}
}
}
}
impl BindGroup { pub(crate) fn try_raw<'a>(
&'a self,
guard: &'a SnatchGuard,
) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> { // Clippy insist on writing it this way. The idea is to return None // if any of the raw buffer is not valid anymore. for buffer in &self.used_buffer_ranges {
buffer.buffer.try_raw(guard)?;
} for texture in &self.used_texture_ranges {
texture.texture.try_raw(guard)?;
}
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.