summaryrefslogtreecommitdiffstats
path: root/third_party/rust/wgpu-core/src/binding_model.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/wgpu-core/src/binding_model.rs')
-rw-r--r--third_party/rust/wgpu-core/src/binding_model.rs953
1 files changed, 953 insertions, 0 deletions
diff --git a/third_party/rust/wgpu-core/src/binding_model.rs b/third_party/rust/wgpu-core/src/binding_model.rs
new file mode 100644
index 0000000000..d7b54ad5a5
--- /dev/null
+++ b/third_party/rust/wgpu-core/src/binding_model.rs
@@ -0,0 +1,953 @@
+#[cfg(feature = "trace")]
+use crate::device::trace;
+use crate::{
+ device::{
+ bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT,
+ },
+ error::{ErrorFormatter, PrettyError},
+ hal_api::HalApi,
+ id::{BindGroupLayoutId, BufferId, SamplerId, TextureId, TextureViewId},
+ init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction},
+ resource::{Resource, ResourceInfo, ResourceType},
+ resource_log,
+ snatch::{SnatchGuard, Snatchable},
+ track::{BindGroupStates, UsageConflict},
+ validation::{MissingBufferUsageError, MissingTextureUsageError},
+ Label,
+};
+
+use arrayvec::ArrayVec;
+
+#[cfg(feature = "serde")]
+use serde::Deserialize;
+#[cfg(feature = "serde")]
+use serde::Serialize;
+
+use std::{borrow::Cow, ops::Range, sync::Arc};
+
+use thiserror::Error;
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum BindGroupLayoutEntryError {
+ #[error("Cube dimension is not expected for texture storage")]
+ StorageTextureCube,
+ #[error("Read-write and read-only storage textures are not allowed by webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
+ StorageTextureReadWrite,
+ #[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(transparent)]
+ MissingFeatures(#[from] MissingFeatures),
+ #[error(transparent)]
+ MissingDownlevelFlags(#[from] MissingDownlevelFlags),
+}
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum 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 index {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]
+pub enum CreateBindGroupError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("Bind group layout is invalid")]
+ InvalidLayout,
+ #[error("Buffer {0:?} is invalid or destroyed")]
+ InvalidBuffer(BufferId),
+ #[error("Texture view {0:?} is invalid")]
+ InvalidTextureView(TextureViewId),
+ #[error("Texture {0:?} is invalid")]
+ InvalidTexture(TextureId),
+ #[error("Sampler {0:?} is invalid")]
+ InvalidSampler(SamplerId),
+ #[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("Bound buffer range {range:?} does not fit in buffer of size {size}")]
+ BindingRangeTooLarge {
+ buffer: BufferId,
+ range: Range<wgt::BufferAddress>,
+ size: u64,
+ },
+ #[error("Buffer binding size {actual} is less than minimum {min}")]
+ BindingSizeTooSmall {
+ buffer: BufferId,
+ actual: u64,
+ min: u64,
+ },
+ #[error("Buffer binding size is zero")]
+ BindingZeroSize(BufferId),
+ #[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 {binding} expects sample type = {layout_sample_type:?}, but given a view with format = {view_format:?}")]
+ InvalidTextureSampleType {
+ binding: u32,
+ layout_sample_type: wgt::TextureSampleType,
+ view_format: wgt::TextureFormat,
+ },
+ #[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 storages texture of format {0:?}")]
+ StorageReadNotSupported(wgt::TextureFormat),
+ #[error(transparent)]
+ ResourceUsageConflict(#[from] UsageConflict),
+}
+
+impl PrettyError for CreateBindGroupError {
+ fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
+ fmt.error(self);
+ match *self {
+ Self::BindingZeroSize(id) => {
+ fmt.buffer_label(&id);
+ }
+ Self::BindingRangeTooLarge { buffer, .. } => {
+ fmt.buffer_label(&buffer);
+ }
+ Self::BindingSizeTooSmall { buffer, .. } => {
+ fmt.buffer_label(&buffer);
+ }
+ Self::InvalidBuffer(id) => {
+ fmt.buffer_label(&id);
+ }
+ Self::InvalidTextureView(id) => {
+ fmt.texture_view_label(&id);
+ }
+ Self::InvalidSampler(id) => {
+ fmt.sampler_label(&id);
+ }
+ _ => {}
+ };
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum BindingZone {
+ #[error("Stage {0:?}")]
+ Stage(wgt::ShaderStages),
+ #[error("Whole pipeline")]
+ Pipeline,
+}
+
+#[derive(Clone, Debug, Error)]
+#[error("Too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}")]
+pub struct BindingTypeMaxCountError {
+ pub kind: BindingTypeMaxCountErrorKind,
+ pub zone: BindingZone,
+ pub limit: u32,
+ pub count: u32,
+}
+
+#[derive(Clone, Debug)]
+pub enum BindingTypeMaxCountErrorKind {
+ DynamicUniformBuffers,
+ DynamicStorageBuffers,
+ SampledTextures,
+ Samplers,
+ StorageBuffers,
+ StorageTextures,
+ UniformBuffers,
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct PerStageBindingTypeCounter {
+ vertex: u32,
+ fragment: u32,
+ compute: u32,
+}
+
+impl PerStageBindingTypeCounter {
+ pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
+ if stage.contains(wgt::ShaderStages::VERTEX) {
+ self.vertex += count;
+ }
+ if stage.contains(wgt::ShaderStages::FRAGMENT) {
+ self.fragment += count;
+ }
+ if stage.contains(wgt::ShaderStages::COMPUTE) {
+ self.compute += count;
+ }
+ }
+
+ pub(crate) fn max(&self) -> (BindingZone, u32) {
+ let max_value = self.vertex.max(self.fragment.max(self.compute));
+ let mut stage = wgt::ShaderStages::NONE;
+ if max_value == self.vertex {
+ stage |= wgt::ShaderStages::VERTEX
+ }
+ if max_value == self.fragment {
+ stage |= wgt::ShaderStages::FRAGMENT
+ }
+ if max_value == self.compute {
+ stage |= wgt::ShaderStages::COMPUTE
+ }
+ (BindingZone::Stage(stage), max_value)
+ }
+
+ pub(crate) fn merge(&mut self, other: &Self) {
+ self.vertex = self.vertex.max(other.vertex);
+ self.fragment = self.fragment.max(other.fragment);
+ self.compute = self.compute.max(other.compute);
+ }
+
+ pub(crate) fn validate(
+ &self,
+ limit: u32,
+ kind: BindingTypeMaxCountErrorKind,
+ ) -> Result<(), BindingTypeMaxCountError> {
+ let (zone, count) = self.max();
+ if limit < count {
+ Err(BindingTypeMaxCountError {
+ kind,
+ zone,
+ limit,
+ count,
+ })
+ } else {
+ Ok(())
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct BindingTypeMaxCountValidator {
+ dynamic_uniform_buffers: u32,
+ dynamic_storage_buffers: u32,
+ sampled_textures: PerStageBindingTypeCounter,
+ samplers: PerStageBindingTypeCounter,
+ storage_buffers: PerStageBindingTypeCounter,
+ storage_textures: PerStageBindingTypeCounter,
+ uniform_buffers: PerStageBindingTypeCounter,
+}
+
+impl BindingTypeMaxCountValidator {
+ pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
+ let count = binding.count.map_or(1, |count| count.get());
+ match binding.ty {
+ wgt::BindingType::Buffer {
+ ty: wgt::BufferBindingType::Uniform,
+ has_dynamic_offset,
+ ..
+ } => {
+ self.uniform_buffers.add(binding.visibility, count);
+ if has_dynamic_offset {
+ self.dynamic_uniform_buffers += count;
+ }
+ }
+ wgt::BindingType::Buffer {
+ ty: wgt::BufferBindingType::Storage { .. },
+ has_dynamic_offset,
+ ..
+ } => {
+ self.storage_buffers.add(binding.visibility, count);
+ if has_dynamic_offset {
+ self.dynamic_storage_buffers += count;
+ }
+ }
+ wgt::BindingType::Sampler { .. } => {
+ self.samplers.add(binding.visibility, count);
+ }
+ wgt::BindingType::Texture { .. } => {
+ self.sampled_textures.add(binding.visibility, count);
+ }
+ wgt::BindingType::StorageTexture { .. } => {
+ self.storage_textures.add(binding.visibility, count);
+ }
+ wgt::BindingType::AccelerationStructure => todo!(),
+ }
+ }
+
+ pub(crate) fn merge(&mut self, other: &Self) {
+ self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
+ self.dynamic_storage_buffers += other.dynamic_storage_buffers;
+ self.sampled_textures.merge(&other.sampled_textures);
+ self.samplers.merge(&other.samplers);
+ self.storage_buffers.merge(&other.storage_buffers);
+ self.storage_textures.merge(&other.storage_textures);
+ self.uniform_buffers.merge(&other.uniform_buffers);
+ }
+
+ pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
+ if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
+ return Err(BindingTypeMaxCountError {
+ kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
+ zone: BindingZone::Pipeline,
+ limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
+ count: self.dynamic_uniform_buffers,
+ });
+ }
+ if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
+ return Err(BindingTypeMaxCountError {
+ kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
+ zone: BindingZone::Pipeline,
+ limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
+ count: self.dynamic_storage_buffers,
+ });
+ }
+ self.sampled_textures.validate(
+ limits.max_sampled_textures_per_shader_stage,
+ BindingTypeMaxCountErrorKind::SampledTextures,
+ )?;
+ self.storage_buffers.validate(
+ limits.max_storage_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageBuffers,
+ )?;
+ self.samplers.validate(
+ limits.max_samplers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::Samplers,
+ )?;
+ self.storage_buffers.validate(
+ limits.max_storage_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageBuffers,
+ )?;
+ self.storage_textures.validate(
+ limits.max_storage_textures_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageTextures,
+ )?;
+ self.uniform_buffers.validate(
+ limits.max_uniform_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::UniformBuffers,
+ )?;
+ Ok(())
+ }
+}
+
+/// Bindable resource and the slot to bind it to.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct 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>,
+}
+
+/// Describes a group of bindings and the resources to be bound.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct 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 [`BindGroupLayout`].
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct 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]>,
+}
+
+pub type BindGroupLayouts<A> = crate::storage::Storage<BindGroupLayout<A>>;
+
+/// Bind group layout.
+#[derive(Debug)]
+pub struct BindGroupLayout<A: HalApi> {
+ pub(crate) raw: Option<A::BindGroupLayout>,
+ pub(crate) device: Arc<Device<A>>,
+ 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,
+ #[allow(unused)]
+ pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
+ pub(crate) info: ResourceInfo<BindGroupLayout<A>>,
+ pub(crate) label: String,
+}
+
+impl<A: HalApi> Drop for BindGroupLayout<A> {
+ fn drop(&mut self) {
+ if matches!(self.origin, bgl::Origin::Pool) {
+ self.device.bgl_pool.remove(&self.entries);
+ }
+ if let Some(raw) = self.raw.take() {
+ #[cfg(feature = "trace")]
+ if let Some(t) = self.device.trace.lock().as_mut() {
+ t.add(trace::Action::DestroyBindGroupLayout(self.info.id()));
+ }
+
+ resource_log!("Destroy raw BindGroupLayout {:?}", self.info.label());
+ unsafe {
+ use hal::Device;
+ self.device.raw().destroy_bind_group_layout(raw);
+ }
+ }
+ }
+}
+
+impl<A: HalApi> Resource for BindGroupLayout<A> {
+ const TYPE: ResourceType = "BindGroupLayout";
+
+ type Marker = crate::id::markers::BindGroupLayout;
+
+ fn as_info(&self) -> &ResourceInfo<Self> {
+ &self.info
+ }
+
+ fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
+ &mut self.info
+ }
+
+ fn label(&self) -> String {
+ self.label.clone()
+ }
+}
+impl<A: HalApi> BindGroupLayout<A> {
+ pub(crate) fn raw(&self) -> &A::BindGroupLayout {
+ self.raw.as_ref().unwrap()
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum CreatePipelineLayoutError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("Bind group layout {0:?} is invalid")]
+ InvalidBindGroupLayout(BindGroupLayoutId),
+ #[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 },
+}
+
+impl PrettyError for CreatePipelineLayoutError {
+ fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
+ fmt.error(self);
+ if let Self::InvalidBindGroupLayout(id) = *self {
+ fmt.bind_group_layout_label(&id);
+ };
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum 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))]
+pub struct 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]>,
+}
+
+#[derive(Debug)]
+pub struct PipelineLayout<A: HalApi> {
+ pub(crate) raw: Option<A::PipelineLayout>,
+ pub(crate) device: Arc<Device<A>>,
+ pub(crate) info: ResourceInfo<PipelineLayout<A>>,
+ pub(crate) bind_group_layouts: ArrayVec<Arc<BindGroupLayout<A>>, { hal::MAX_BIND_GROUPS }>,
+ pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
+}
+
+impl<A: HalApi> Drop for PipelineLayout<A> {
+ fn drop(&mut self) {
+ if let Some(raw) = self.raw.take() {
+ resource_log!("Destroy raw PipelineLayout {:?}", self.info.label());
+
+ #[cfg(feature = "trace")]
+ if let Some(t) = self.device.trace.lock().as_mut() {
+ t.add(trace::Action::DestroyPipelineLayout(self.info.id()));
+ }
+
+ unsafe {
+ use hal::Device;
+ self.device.raw().destroy_pipeline_layout(raw);
+ }
+ }
+ }
+}
+
+impl<A: HalApi> PipelineLayout<A> {
+ pub(crate) fn raw(&self) -> &A::PipelineLayout {
+ self.raw.as_ref().unwrap()
+ }
+
+ pub(crate) fn get_binding_maps(&self) -> ArrayVec<&bgl::EntryMap, { hal::MAX_BIND_GROUPS }> {
+ self.bind_group_layouts
+ .iter()
+ .map(|bgl| &bgl.entries)
+ .collect()
+ }
+
+ /// 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.
+ let mut used_stages = wgt::ShaderStages::NONE;
+ for (idx, range) in self.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;
+ } else if 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(())
+ }
+}
+
+impl<A: HalApi> Resource for PipelineLayout<A> {
+ const TYPE: ResourceType = "PipelineLayout";
+
+ type Marker = crate::id::markers::PipelineLayout;
+
+ fn as_info(&self) -> &ResourceInfo<Self> {
+ &self.info
+ }
+
+ fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
+ &mut self.info
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct BufferBinding {
+ pub buffer_id: BufferId,
+ pub offset: wgt::BufferAddress,
+ pub size: Option<wgt::BufferSize>,
+}
+
+// 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))]
+pub enum BindingResource<'a> {
+ Buffer(BufferBinding),
+ BufferArray(Cow<'a, [BufferBinding]>),
+ Sampler(SamplerId),
+ SamplerArray(Cow<'a, [SamplerId]>),
+ TextureView(TextureViewId),
+ TextureViewArray(Cow<'a, [TextureViewId]>),
+}
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum 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 {
+ 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 {
+ 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 {
+ idx: usize,
+ group: u32,
+ binding: u32,
+ offset: u32,
+ buffer_size: wgt::BufferAddress,
+ binding_range: Range<wgt::BufferAddress>,
+ maximum_dynamic_offset: wgt::BufferAddress,
+ },
+}
+
+#[derive(Debug)]
+pub struct 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,
+}
+
+pub(crate) fn buffer_binding_type_alignment(
+ limits: &wgt::Limits,
+ binding_type: wgt::BufferBindingType,
+) -> (u32, &'static str) {
+ match binding_type {
+ wgt::BufferBindingType::Uniform => (
+ limits.min_uniform_buffer_offset_alignment,
+ "min_uniform_buffer_offset_alignment",
+ ),
+ wgt::BufferBindingType::Storage { .. } => (
+ limits.min_storage_buffer_offset_alignment,
+ "min_storage_buffer_offset_alignment",
+ ),
+ }
+}
+
+#[derive(Debug)]
+pub struct BindGroup<A: HalApi> {
+ pub(crate) raw: Snatchable<A::BindGroup>,
+ pub(crate) device: Arc<Device<A>>,
+ pub(crate) layout: Arc<BindGroupLayout<A>>,
+ pub(crate) info: ResourceInfo<BindGroup<A>>,
+ pub(crate) used: BindGroupStates<A>,
+ pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction<A>>,
+ pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction<A>>,
+ 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<A: HalApi> Drop for BindGroup<A> {
+ fn drop(&mut self) {
+ if let Some(raw) = self.raw.take() {
+ resource_log!("Destroy raw BindGroup {:?}", self.info.label());
+
+ #[cfg(feature = "trace")]
+ if let Some(t) = self.device.trace.lock().as_mut() {
+ t.add(trace::Action::DestroyBindGroup(self.info.id()));
+ }
+
+ unsafe {
+ use hal::Device;
+ self.device.raw().destroy_bind_group(raw);
+ }
+ }
+ }
+}
+
+impl<A: HalApi> BindGroup<A> {
+ pub(crate) fn raw(&self, guard: &SnatchGuard) -> Option<&A::BindGroup> {
+ // 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 {
+ let _ = buffer.buffer.raw(guard)?;
+ }
+ for texture in &self.used_texture_ranges {
+ let _ = texture.texture.raw(guard)?;
+ }
+ self.raw.get(guard)
+ }
+ pub(crate) fn validate_dynamic_bindings(
+ &self,
+ bind_group_index: u32,
+ offsets: &[wgt::DynamicOffset],
+ limits: &wgt::Limits,
+ ) -> Result<(), BindError> {
+ if self.dynamic_binding_info.len() != offsets.len() {
+ return Err(BindError::MismatchedDynamicOffsetCount {
+ group: bind_group_index,
+ expected: self.dynamic_binding_info.len(),
+ actual: offsets.len(),
+ });
+ }
+
+ for (idx, (info, &offset)) in self
+ .dynamic_binding_info
+ .iter()
+ .zip(offsets.iter())
+ .enumerate()
+ {
+ let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type);
+ if offset as wgt::BufferAddress % alignment as u64 != 0 {
+ return Err(BindError::UnalignedDynamicBinding {
+ group: bind_group_index,
+ binding: info.binding_idx,
+ idx,
+ offset,
+ alignment,
+ limit_name,
+ });
+ }
+
+ if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
+ return Err(BindError::DynamicBindingOutOfBounds {
+ group: bind_group_index,
+ binding: info.binding_idx,
+ idx,
+ offset,
+ buffer_size: info.buffer_size,
+ binding_range: info.binding_range.clone(),
+ maximum_dynamic_offset: info.maximum_dynamic_offset,
+ });
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl<A: HalApi> Resource for BindGroup<A> {
+ const TYPE: ResourceType = "BindGroup";
+
+ type Marker = crate::id::markers::BindGroup;
+
+ fn as_info(&self) -> &ResourceInfo<Self> {
+ &self.info
+ }
+
+ fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
+ &mut self.info
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+#[non_exhaustive]
+pub enum GetBindGroupLayoutError {
+ #[error("Pipeline is invalid")]
+ InvalidPipeline,
+ #[error("Invalid group index {0}")]
+ InvalidGroupIndex(u32),
+}
+
+#[derive(Clone, Debug, Error, Eq, PartialEq)]
+#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
+pub struct LateMinBufferBindingSizeMismatch {
+ pub group_index: u32,
+ pub compact_index: usize,
+ pub shader_size: wgt::BufferAddress,
+ pub bound_size: wgt::BufferAddress,
+}