wgpu_core/
binding_model.rs

1use alloc::{
2    borrow::{Cow, ToOwned},
3    boxed::Box,
4    string::String,
5    sync::{Arc, Weak},
6    vec::Vec,
7};
8use core::{fmt, mem::ManuallyDrop, num::Saturating, ops::Range};
9
10use arrayvec::ArrayVec;
11use thiserror::Error;
12
13#[cfg(feature = "serde")]
14use serde::Deserialize;
15#[cfg(feature = "serde")]
16use serde::Serialize;
17
18use wgt::error::{ErrorType, WebGpuError};
19
20use crate::{
21    device::{bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures},
22    id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId},
23    init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction},
24    pipeline::{ComputePipeline, RenderPipeline},
25    resource::{
26        Buffer, DestroyedResourceError, ExternalTexture, InvalidResourceError, Labeled,
27        MissingBufferUsageError, MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent,
28        Sampler, TextureView, Tlas, TrackingData,
29    },
30    resource_log,
31    snatch::{SnatchGuard, Snatchable},
32    track::{BindGroupStates, ResourceUsageCompatibilityError},
33    Label,
34};
35
36#[derive(Clone, Debug, Error)]
37#[non_exhaustive]
38pub enum BindGroupLayoutEntryError {
39    #[error("Cube dimension is not expected for texture storage")]
40    StorageTextureCube,
41    #[error("Atomic storage textures are not allowed by baseline webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
42    StorageTextureAtomic,
43    #[error("Arrays of bindings unsupported for this type of binding")]
44    ArrayUnsupported,
45    #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")]
46    SampleTypeFloatFilterableBindingMultisampled,
47    #[error("Multisampled texture binding view dimension must be 2d, got {0:?}")]
48    Non2DMultisampled(wgt::TextureViewDimension),
49    #[error(transparent)]
50    MissingFeatures(#[from] MissingFeatures),
51    #[error(transparent)]
52    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
53}
54
55#[derive(Clone, Debug, Error)]
56#[non_exhaustive]
57pub enum CreateBindGroupLayoutError {
58    #[error(transparent)]
59    Device(#[from] DeviceError),
60    #[error("Conflicting binding at index {0}")]
61    ConflictBinding(u32),
62    #[error("Binding {binding} entry is invalid")]
63    Entry {
64        binding: u32,
65        #[source]
66        error: BindGroupLayoutEntryError,
67    },
68    #[error(transparent)]
69    TooManyBindings(BindingTypeMaxCountError),
70    #[error("Bind groups may not contain both a binding array and a dynamically offset buffer")]
71    ContainsBothBindingArrayAndDynamicOffsetArray,
72    #[error("Bind groups may not contain both a binding array and a uniform buffer")]
73    ContainsBothBindingArrayAndUniformBuffer,
74    #[error("Binding index {binding} is greater than the maximum number {maximum}")]
75    InvalidBindingIndex { binding: u32, maximum: u32 },
76    #[error("Invalid visibility {0:?}")]
77    InvalidVisibility(wgt::ShaderStages),
78    #[error("Binding index {binding}: {access:?} access to storage textures with format {format:?} is not supported")]
79    UnsupportedStorageTextureAccess {
80        binding: u32,
81        access: wgt::StorageTextureAccess,
82        format: wgt::TextureFormat,
83    },
84}
85
86impl WebGpuError for CreateBindGroupLayoutError {
87    fn webgpu_error_type(&self) -> ErrorType {
88        match self {
89            Self::Device(e) => e.webgpu_error_type(),
90
91            Self::ConflictBinding(_)
92            | Self::Entry { .. }
93            | Self::TooManyBindings(_)
94            | Self::InvalidBindingIndex { .. }
95            | Self::InvalidVisibility(_)
96            | Self::ContainsBothBindingArrayAndDynamicOffsetArray
97            | Self::ContainsBothBindingArrayAndUniformBuffer
98            | Self::UnsupportedStorageTextureAccess { .. } => ErrorType::Validation,
99        }
100    }
101}
102
103#[derive(Clone, Debug, Error)]
104#[non_exhaustive]
105pub enum BindingError {
106    #[error(transparent)]
107    DestroyedResource(#[from] DestroyedResourceError),
108    #[error("Buffer {buffer}: Binding with size {binding_size} at offset {offset} would overflow buffer size of {buffer_size}")]
109    BindingRangeTooLarge {
110        buffer: ResourceErrorIdent,
111        offset: wgt::BufferAddress,
112        binding_size: u64,
113        buffer_size: u64,
114    },
115    #[error("Buffer {buffer}: Binding offset {offset} is greater than buffer size {buffer_size}")]
116    BindingOffsetTooLarge {
117        buffer: ResourceErrorIdent,
118        offset: wgt::BufferAddress,
119        buffer_size: u64,
120    },
121    #[error("Unbinding vertex buffer at slot {slot} expects offset to be 0. However an offset of {offset} was provided.")]
122    UnbindingVertexBufferOffsetNotZero { slot: u32, offset: u64 },
123    #[error("Unbinding vertex buffer at slot {slot} expects size to be 0. However a size of {size} was provided.")]
124    UnbindingVertexBufferSizeNotZero { slot: u32, size: u64 },
125}
126
127impl WebGpuError for BindingError {
128    fn webgpu_error_type(&self) -> ErrorType {
129        match self {
130            Self::DestroyedResource(e) => e.webgpu_error_type(),
131            Self::BindingRangeTooLarge { .. }
132            | Self::BindingOffsetTooLarge { .. }
133            | BindingError::UnbindingVertexBufferOffsetNotZero { .. }
134            | BindingError::UnbindingVertexBufferSizeNotZero { .. } => ErrorType::Validation,
135        }
136    }
137}
138
139// TODO: there may be additional variants here that can be extracted into
140// `BindingError`.
141#[derive(Clone, Debug, Error)]
142#[non_exhaustive]
143pub enum CreateBindGroupError {
144    #[error(transparent)]
145    Device(#[from] DeviceError),
146    #[error(transparent)]
147    DestroyedResource(#[from] DestroyedResourceError),
148    #[error(transparent)]
149    BindingError(#[from] BindingError),
150    #[error(
151        "Binding count declared with at most {expected} items, but {actual} items were provided"
152    )]
153    BindingArrayPartialLengthMismatch { actual: usize, expected: usize },
154    #[error(
155        "Binding count declared with exactly {expected} items, but {actual} items were provided"
156    )]
157    BindingArrayLengthMismatch { actual: usize, expected: usize },
158    #[error("Array binding provided zero elements")]
159    BindingArrayZeroLength,
160    #[error("Binding size {actual} of {buffer} is less than minimum {min}")]
161    BindingSizeTooSmall {
162        buffer: ResourceErrorIdent,
163        actual: u64,
164        min: u64,
165    },
166    #[error("{0} binding size is zero")]
167    BindingZeroSize(ResourceErrorIdent),
168    #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
169    BindingsNumMismatch { actual: usize, expected: usize },
170    #[error("Binding {0} is used at least twice in the descriptor")]
171    DuplicateBinding(u32),
172    #[error("Unable to find a corresponding declaration for the given binding {0}")]
173    MissingBindingDeclaration(u32),
174    #[error(transparent)]
175    MissingBufferUsage(#[from] MissingBufferUsageError),
176    #[error(transparent)]
177    MissingTextureUsage(#[from] MissingTextureUsageError),
178    #[error("Binding declared as a single item, but bind group is using it as an array")]
179    SingleBindingExpected,
180    #[error("Effective buffer binding size {size} for storage buffers is expected to align to {alignment}, but size is {size}")]
181    UnalignedEffectiveBufferBindingSizeForStorage { alignment: u32, size: u64 },
182    #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")]
183    UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32),
184    #[error(
185        "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
186    )]
187    BufferRangeTooLarge {
188        binding: u32,
189        given: u64,
190        limit: u64,
191    },
192    #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
193    WrongBindingType {
194        // Index of the binding
195        binding: u32,
196        // The type given to the function
197        actual: wgt::BindingType,
198        // Human-readable description of expected types
199        expected: &'static str,
200    },
201    #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
202    InvalidTextureMultisample {
203        binding: u32,
204        layout_multisampled: bool,
205        view_samples: u32,
206    },
207    #[error(
208        "Texture binding {} expects sample type {:?}, but was given a view with format {:?} (sample type {:?})",
209        binding,
210        layout_sample_type,
211        view_format,
212        view_sample_type
213    )]
214    InvalidTextureSampleType {
215        binding: u32,
216        layout_sample_type: wgt::TextureSampleType,
217        view_format: wgt::TextureFormat,
218        view_sample_type: wgt::TextureSampleType,
219    },
220    #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
221    InvalidTextureDimension {
222        binding: u32,
223        layout_dimension: wgt::TextureViewDimension,
224        view_dimension: wgt::TextureViewDimension,
225    },
226    #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
227    InvalidStorageTextureFormat {
228        binding: u32,
229        layout_format: wgt::TextureFormat,
230        view_format: wgt::TextureFormat,
231    },
232    #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
233    InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 },
234    #[error("External texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
235    InvalidExternalTextureMipLevelCount { binding: u32, mip_level_count: u32 },
236    #[error("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = {format:?} at binding {binding}")]
237    InvalidExternalTextureFormat {
238        binding: u32,
239        format: wgt::TextureFormat,
240    },
241    #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
242    WrongSamplerComparison {
243        binding: u32,
244        layout_cmp: bool,
245        sampler_cmp: bool,
246    },
247    #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
248    WrongSamplerFiltering {
249        binding: u32,
250        layout_flt: bool,
251        sampler_flt: bool,
252    },
253    #[error("TLAS binding {binding} is required to support vertex returns but is missing flag AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN")]
254    MissingTLASVertexReturn { binding: u32 },
255    #[error("Bound texture views can not have both depth and stencil aspects enabled")]
256    DepthStencilAspect,
257    #[error(transparent)]
258    ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError),
259    #[error(transparent)]
260    InvalidResource(#[from] InvalidResourceError),
261}
262
263impl WebGpuError for CreateBindGroupError {
264    fn webgpu_error_type(&self) -> ErrorType {
265        match self {
266            Self::Device(e) => e.webgpu_error_type(),
267            Self::DestroyedResource(e) => e.webgpu_error_type(),
268            Self::BindingError(e) => e.webgpu_error_type(),
269            Self::MissingBufferUsage(e) => e.webgpu_error_type(),
270            Self::MissingTextureUsage(e) => e.webgpu_error_type(),
271            Self::ResourceUsageCompatibility(e) => e.webgpu_error_type(),
272            Self::InvalidResource(e) => e.webgpu_error_type(),
273            Self::BindingArrayPartialLengthMismatch { .. }
274            | Self::BindingArrayLengthMismatch { .. }
275            | Self::BindingArrayZeroLength
276            | Self::BindingSizeTooSmall { .. }
277            | Self::BindingsNumMismatch { .. }
278            | Self::BindingZeroSize(_)
279            | Self::DuplicateBinding(_)
280            | Self::MissingBindingDeclaration(_)
281            | Self::SingleBindingExpected
282            | Self::UnalignedEffectiveBufferBindingSizeForStorage { .. }
283            | Self::UnalignedBufferOffset(_, _, _)
284            | Self::BufferRangeTooLarge { .. }
285            | Self::WrongBindingType { .. }
286            | Self::InvalidTextureMultisample { .. }
287            | Self::InvalidTextureSampleType { .. }
288            | Self::InvalidTextureDimension { .. }
289            | Self::InvalidStorageTextureFormat { .. }
290            | Self::InvalidStorageTextureMipLevelCount { .. }
291            | Self::WrongSamplerComparison { .. }
292            | Self::WrongSamplerFiltering { .. }
293            | Self::DepthStencilAspect
294            | Self::MissingTLASVertexReturn { .. }
295            | Self::InvalidExternalTextureMipLevelCount { .. }
296            | Self::InvalidExternalTextureFormat { .. } => ErrorType::Validation,
297        }
298    }
299}
300
301#[derive(Clone, Debug, Error)]
302pub enum BindingZone {
303    #[error("Stage {0:?}")]
304    Stage(wgt::ShaderStages),
305    #[error("Whole pipeline")]
306    Pipeline,
307}
308
309#[derive(Clone, Debug, Error)]
310#[error("Too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}. Check the limit `{}` passed to `Adapter::request_device`", .kind.to_config_str())]
311pub struct BindingTypeMaxCountError {
312    pub kind: BindingTypeMaxCountErrorKind,
313    pub zone: BindingZone,
314    pub limit: u32,
315    pub count: u32,
316}
317
318impl WebGpuError for BindingTypeMaxCountError {
319    fn webgpu_error_type(&self) -> ErrorType {
320        ErrorType::Validation
321    }
322}
323
324#[derive(Clone, Debug)]
325pub enum BindingTypeMaxCountErrorKind {
326    DynamicUniformBuffers,
327    DynamicStorageBuffers,
328    SampledTextures,
329    Samplers,
330    StorageBuffers,
331    StorageTextures,
332    UniformBuffers,
333    BindingArrayElements,
334    BindingArraySamplerElements,
335    BindingArrayAccelerationStructureElements,
336    AccelerationStructures,
337}
338
339impl BindingTypeMaxCountErrorKind {
340    fn to_config_str(&self) -> &'static str {
341        match self {
342            BindingTypeMaxCountErrorKind::DynamicUniformBuffers => {
343                "max_dynamic_uniform_buffers_per_pipeline_layout"
344            }
345            BindingTypeMaxCountErrorKind::DynamicStorageBuffers => {
346                "max_dynamic_storage_buffers_per_pipeline_layout"
347            }
348            BindingTypeMaxCountErrorKind::SampledTextures => {
349                "max_sampled_textures_per_shader_stage"
350            }
351            BindingTypeMaxCountErrorKind::Samplers => "max_samplers_per_shader_stage",
352            BindingTypeMaxCountErrorKind::StorageBuffers => "max_storage_buffers_per_shader_stage",
353            BindingTypeMaxCountErrorKind::StorageTextures => {
354                "max_storage_textures_per_shader_stage"
355            }
356            BindingTypeMaxCountErrorKind::UniformBuffers => "max_uniform_buffers_per_shader_stage",
357            BindingTypeMaxCountErrorKind::BindingArrayElements => {
358                "max_binding_array_elements_per_shader_stage"
359            }
360            BindingTypeMaxCountErrorKind::BindingArraySamplerElements => {
361                "max_binding_array_sampler_elements_per_shader_stage"
362            }
363            BindingTypeMaxCountErrorKind::BindingArrayAccelerationStructureElements => {
364                "max_binding_array_acceleration_structure_elements_per_shader_stage"
365            }
366            BindingTypeMaxCountErrorKind::AccelerationStructures => {
367                "max_acceleration_structures_per_shader_stage"
368            }
369        }
370    }
371}
372
373#[derive(Debug, Default)]
374pub(crate) struct PerStageBindingTypeCounter {
375    vertex: Saturating<u32>,
376    fragment: Saturating<u32>,
377    compute: Saturating<u32>,
378}
379
380impl PerStageBindingTypeCounter {
381    pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
382        if stage.contains(wgt::ShaderStages::VERTEX) {
383            self.vertex += count;
384        }
385        if stage.contains(wgt::ShaderStages::FRAGMENT) {
386            self.fragment += count;
387        }
388        if stage.contains(wgt::ShaderStages::COMPUTE) {
389            self.compute += count;
390        }
391    }
392
393    pub(crate) fn max(&self) -> (BindingZone, u32) {
394        let max_value = self.vertex.max(self.fragment.max(self.compute));
395        let mut stage = wgt::ShaderStages::NONE;
396        if max_value == self.vertex {
397            stage |= wgt::ShaderStages::VERTEX
398        }
399        if max_value == self.fragment {
400            stage |= wgt::ShaderStages::FRAGMENT
401        }
402        if max_value == self.compute {
403            stage |= wgt::ShaderStages::COMPUTE
404        }
405        (BindingZone::Stage(stage), max_value.0)
406    }
407
408    pub(crate) fn merge(&mut self, other: &Self) {
409        self.vertex += other.vertex;
410        self.fragment += other.fragment;
411        self.compute += other.compute;
412    }
413
414    pub(crate) fn validate(
415        &self,
416        limit: u32,
417        kind: BindingTypeMaxCountErrorKind,
418    ) -> Result<(), BindingTypeMaxCountError> {
419        let (zone, count) = self.max();
420        if limit < count {
421            Err(BindingTypeMaxCountError {
422                kind,
423                zone,
424                limit,
425                count,
426            })
427        } else {
428            Ok(())
429        }
430    }
431}
432
433#[derive(Debug, Default)]
434pub(crate) struct BindingTypeMaxCountValidator {
435    dynamic_uniform_buffers: u32,
436    dynamic_storage_buffers: u32,
437    sampled_textures: PerStageBindingTypeCounter,
438    samplers: PerStageBindingTypeCounter,
439    storage_buffers: PerStageBindingTypeCounter,
440    storage_textures: PerStageBindingTypeCounter,
441    uniform_buffers: PerStageBindingTypeCounter,
442    acceleration_structures: PerStageBindingTypeCounter,
443    binding_array_elements: PerStageBindingTypeCounter,
444    binding_array_sampler_elements: PerStageBindingTypeCounter,
445    binding_array_acceleration_structure_elements: PerStageBindingTypeCounter,
446    has_bindless_array: bool,
447}
448
449impl BindingTypeMaxCountValidator {
450    pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
451        let count = binding.count.map_or(1, |count| count.get());
452
453        if binding.count.is_some() {
454            self.binding_array_elements.add(binding.visibility, count);
455            self.has_bindless_array = true;
456
457            match binding.ty {
458                wgt::BindingType::Sampler(_) => {
459                    self.binding_array_sampler_elements
460                        .add(binding.visibility, count);
461                }
462                wgt::BindingType::AccelerationStructure { .. } => {
463                    self.binding_array_acceleration_structure_elements
464                        .add(binding.visibility, count);
465                }
466                _ => {}
467            }
468        } else {
469            match binding.ty {
470                wgt::BindingType::Buffer {
471                    ty: wgt::BufferBindingType::Uniform,
472                    has_dynamic_offset,
473                    ..
474                } => {
475                    self.uniform_buffers.add(binding.visibility, count);
476                    if has_dynamic_offset {
477                        self.dynamic_uniform_buffers += count;
478                    }
479                }
480                wgt::BindingType::Buffer {
481                    ty: wgt::BufferBindingType::Storage { .. },
482                    has_dynamic_offset,
483                    ..
484                } => {
485                    self.storage_buffers.add(binding.visibility, count);
486                    if has_dynamic_offset {
487                        self.dynamic_storage_buffers += count;
488                    }
489                }
490                wgt::BindingType::Sampler { .. } => {
491                    self.samplers.add(binding.visibility, count);
492                }
493                wgt::BindingType::Texture { .. } => {
494                    self.sampled_textures.add(binding.visibility, count);
495                }
496                wgt::BindingType::StorageTexture { .. } => {
497                    self.storage_textures.add(binding.visibility, count);
498                }
499                wgt::BindingType::AccelerationStructure { .. } => {
500                    self.acceleration_structures.add(binding.visibility, count);
501                }
502                wgt::BindingType::ExternalTexture => {
503                    // https://www.w3.org/TR/webgpu/#gpuexternaltexture
504                    // In order to account for many possible representations,
505                    // the binding conservatively uses the following, for each
506                    // external texture:
507                    // * Three sampled textures for up to 3 planes
508                    // * One additional sampled texture for a 3D LUT
509                    // * One sampler to sample the LUT
510                    // * One uniform buffer for metadata
511                    self.sampled_textures.add(binding.visibility, count * 4);
512                    self.samplers.add(binding.visibility, count);
513                    self.uniform_buffers.add(binding.visibility, count);
514                }
515            }
516        }
517    }
518
519    pub(crate) fn merge(&mut self, other: &Self) {
520        self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
521        self.dynamic_storage_buffers += other.dynamic_storage_buffers;
522        self.sampled_textures.merge(&other.sampled_textures);
523        self.samplers.merge(&other.samplers);
524        self.storage_buffers.merge(&other.storage_buffers);
525        self.storage_textures.merge(&other.storage_textures);
526        self.uniform_buffers.merge(&other.uniform_buffers);
527        self.acceleration_structures
528            .merge(&other.acceleration_structures);
529        self.binding_array_elements
530            .merge(&other.binding_array_elements);
531        self.binding_array_sampler_elements
532            .merge(&other.binding_array_sampler_elements);
533        self.binding_array_acceleration_structure_elements
534            .merge(&other.binding_array_acceleration_structure_elements);
535    }
536
537    pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
538        if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
539            return Err(BindingTypeMaxCountError {
540                kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
541                zone: BindingZone::Pipeline,
542                limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
543                count: self.dynamic_uniform_buffers,
544            });
545        }
546        if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
547            return Err(BindingTypeMaxCountError {
548                kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
549                zone: BindingZone::Pipeline,
550                limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
551                count: self.dynamic_storage_buffers,
552            });
553        }
554        self.sampled_textures.validate(
555            limits.max_sampled_textures_per_shader_stage,
556            BindingTypeMaxCountErrorKind::SampledTextures,
557        )?;
558        self.samplers.validate(
559            limits.max_samplers_per_shader_stage,
560            BindingTypeMaxCountErrorKind::Samplers,
561        )?;
562        self.storage_buffers.validate(
563            limits.max_storage_buffers_per_shader_stage,
564            BindingTypeMaxCountErrorKind::StorageBuffers,
565        )?;
566        self.storage_textures.validate(
567            limits.max_storage_textures_per_shader_stage,
568            BindingTypeMaxCountErrorKind::StorageTextures,
569        )?;
570        self.uniform_buffers.validate(
571            limits.max_uniform_buffers_per_shader_stage,
572            BindingTypeMaxCountErrorKind::UniformBuffers,
573        )?;
574        self.binding_array_elements.validate(
575            limits.max_binding_array_elements_per_shader_stage,
576            BindingTypeMaxCountErrorKind::BindingArrayElements,
577        )?;
578        self.binding_array_sampler_elements.validate(
579            limits.max_binding_array_sampler_elements_per_shader_stage,
580            BindingTypeMaxCountErrorKind::BindingArraySamplerElements,
581        )?;
582        self.binding_array_acceleration_structure_elements
583            .validate(
584                limits.max_binding_array_acceleration_structure_elements_per_shader_stage,
585                BindingTypeMaxCountErrorKind::BindingArrayAccelerationStructureElements,
586            )?;
587        self.acceleration_structures.validate(
588            limits.max_acceleration_structures_per_shader_stage,
589            BindingTypeMaxCountErrorKind::AccelerationStructures,
590        )?;
591        Ok(())
592    }
593
594    /// Validate that the bind group layout does not contain both a binding array and a dynamic offset array.
595    ///
596    /// This allows us to use `UPDATE_AFTER_BIND` on vulkan for bindless arrays. Vulkan does not allow
597    /// `UPDATE_AFTER_BIND` on dynamic offset arrays. See <https://github.com/gfx-rs/wgpu/issues/6737>
598    pub(crate) fn validate_binding_arrays(&self) -> Result<(), CreateBindGroupLayoutError> {
599        let has_dynamic_offset_array =
600            self.dynamic_uniform_buffers > 0 || self.dynamic_storage_buffers > 0;
601        let has_uniform_buffer = self.uniform_buffers.max().1 > 0;
602        if self.has_bindless_array && has_dynamic_offset_array {
603            return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndDynamicOffsetArray);
604        }
605        if self.has_bindless_array && has_uniform_buffer {
606            return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndUniformBuffer);
607        }
608        Ok(())
609    }
610}
611
612/// Bindable resource and the slot to bind it to.
613/// cbindgen:ignore
614#[derive(Clone, Debug)]
615#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
616pub struct BindGroupEntry<
617    'a,
618    B = BufferId,
619    S = SamplerId,
620    TV = TextureViewId,
621    TLAS = TlasId,
622    ET = ExternalTextureId,
623> where
624    [BufferBinding<B>]: ToOwned,
625    [S]: ToOwned,
626    [TV]: ToOwned,
627    [TLAS]: ToOwned,
628    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
629    <[S] as ToOwned>::Owned: fmt::Debug,
630    <[TV] as ToOwned>::Owned: fmt::Debug,
631    <[TLAS] as ToOwned>::Owned: fmt::Debug,
632{
633    /// Slot for which binding provides resource. Corresponds to an entry of the same
634    /// binding index in the [`BindGroupLayoutDescriptor`].
635    pub binding: u32,
636    #[cfg_attr(
637        feature = "serde",
638        serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>"))
639    )]
640    /// Resource to attach to the binding
641    pub resource: BindingResource<'a, B, S, TV, TLAS, ET>,
642}
643
644/// cbindgen:ignore
645pub type ResolvedBindGroupEntry<'a> = BindGroupEntry<
646    'a,
647    Arc<Buffer>,
648    Arc<Sampler>,
649    Arc<TextureView>,
650    Arc<Tlas>,
651    Arc<ExternalTexture>,
652>;
653
654/// Describes a group of bindings and the resources to be bound.
655#[derive(Clone, Debug)]
656#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
657pub struct BindGroupDescriptor<
658    'a,
659    BGL = BindGroupLayoutId,
660    B = BufferId,
661    S = SamplerId,
662    TV = TextureViewId,
663    TLAS = TlasId,
664    ET = ExternalTextureId,
665> where
666    [BufferBinding<B>]: ToOwned,
667    [S]: ToOwned,
668    [TV]: ToOwned,
669    [TLAS]: ToOwned,
670    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
671    <[S] as ToOwned>::Owned: fmt::Debug,
672    <[TV] as ToOwned>::Owned: fmt::Debug,
673    <[TLAS] as ToOwned>::Owned: fmt::Debug,
674    [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned,
675    <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug,
676{
677    /// Debug label of the bind group.
678    ///
679    /// This will show up in graphics debuggers for easy identification.
680    pub label: Label<'a>,
681    /// The [`BindGroupLayout`] that corresponds to this bind group.
682    pub layout: BGL,
683    #[cfg_attr(
684        feature = "serde",
685        serde(bound(
686            deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>"
687        ))
688    )]
689    /// The resources to bind to this bind group.
690    #[allow(clippy::type_complexity)]
691    pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>,
692}
693
694/// cbindgen:ignore
695pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor<
696    'a,
697    Arc<BindGroupLayout>,
698    Arc<Buffer>,
699    Arc<Sampler>,
700    Arc<TextureView>,
701    Arc<Tlas>,
702    Arc<ExternalTexture>,
703>;
704
705/// Describes a [`BindGroupLayout`].
706#[derive(Clone, Debug)]
707#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
708pub struct BindGroupLayoutDescriptor<'a> {
709    /// Debug label of the bind group layout.
710    ///
711    /// This will show up in graphics debuggers for easy identification.
712    pub label: Label<'a>,
713    /// Array of entries in this BindGroupLayout
714    pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
715}
716
717/// Used by [`BindGroupLayout`]. It indicates whether the BGL must be
718/// used with a specific pipeline. This constraint only happens when
719/// the BGLs have been derived from a pipeline without a layout.
720#[derive(Clone, Debug)]
721pub(crate) enum ExclusivePipeline {
722    None,
723    Render(Weak<RenderPipeline>),
724    Compute(Weak<ComputePipeline>),
725}
726
727impl From<&Arc<RenderPipeline>> for ExclusivePipeline {
728    fn from(pipeline: &Arc<RenderPipeline>) -> Self {
729        Self::Render(Arc::downgrade(pipeline))
730    }
731}
732
733impl From<&Arc<ComputePipeline>> for ExclusivePipeline {
734    fn from(pipeline: &Arc<ComputePipeline>) -> Self {
735        Self::Compute(Arc::downgrade(pipeline))
736    }
737}
738
739impl fmt::Display for ExclusivePipeline {
740    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741        match self {
742            ExclusivePipeline::None => f.write_str("None"),
743            ExclusivePipeline::Render(p) => {
744                if let Some(p) = p.upgrade() {
745                    p.error_ident().fmt(f)
746                } else {
747                    f.write_str("RenderPipeline")
748                }
749            }
750            ExclusivePipeline::Compute(p) => {
751                if let Some(p) = p.upgrade() {
752                    p.error_ident().fmt(f)
753                } else {
754                    f.write_str("ComputePipeline")
755                }
756            }
757        }
758    }
759}
760
761#[derive(Debug)]
762pub enum RawBindGroupLayout {
763    Owning(ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>),
764    /// The empty BGL was created by the device and will be destroyed by the device.
765    RefDeviceEmptyBGL,
766}
767
768/// Bind group layout.
769#[derive(Debug)]
770pub struct BindGroupLayout {
771    pub(crate) raw: RawBindGroupLayout,
772    pub(crate) device: Arc<Device>,
773    pub(crate) entries: bgl::EntryMap,
774    /// It is very important that we know if the bind group comes from the BGL pool.
775    ///
776    /// If it does, then we need to remove it from the pool when we drop it.
777    ///
778    /// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool
779    /// (derived BGLs) must not be removed.
780    pub(crate) origin: bgl::Origin,
781    pub(crate) exclusive_pipeline: crate::OnceCellOrLock<ExclusivePipeline>,
782    pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
783    /// The `label` from the descriptor used to create the resource.
784    pub(crate) label: String,
785}
786
787impl Drop for BindGroupLayout {
788    fn drop(&mut self) {
789        resource_log!("Destroy raw {}", self.error_ident());
790        if matches!(self.origin, bgl::Origin::Pool) {
791            self.device.bgl_pool.remove(&self.entries);
792        }
793        match self.raw {
794            RawBindGroupLayout::Owning(ref mut raw) => {
795                // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
796                let raw = unsafe { ManuallyDrop::take(raw) };
797                unsafe {
798                    self.device.raw().destroy_bind_group_layout(raw);
799                }
800            }
801            RawBindGroupLayout::RefDeviceEmptyBGL => {}
802        }
803    }
804}
805
806crate::impl_resource_type!(BindGroupLayout);
807crate::impl_labeled!(BindGroupLayout);
808crate::impl_parent_device!(BindGroupLayout);
809crate::impl_storage_item!(BindGroupLayout);
810
811impl BindGroupLayout {
812    pub(crate) fn raw(&self) -> &dyn hal::DynBindGroupLayout {
813        match &self.raw {
814            RawBindGroupLayout::Owning(raw) => raw.as_ref(),
815            RawBindGroupLayout::RefDeviceEmptyBGL => self.device.empty_bgl.as_ref(),
816        }
817    }
818
819    fn empty(device: &Arc<Device>, exclusive_pipeline: ExclusivePipeline) -> Arc<Self> {
820        Arc::new(Self {
821            raw: RawBindGroupLayout::RefDeviceEmptyBGL,
822            device: device.clone(),
823            entries: bgl::EntryMap::default(),
824            origin: bgl::Origin::Derived,
825            exclusive_pipeline: crate::OnceCellOrLock::from(exclusive_pipeline),
826            binding_count_validator: BindingTypeMaxCountValidator::default(),
827            label: String::new(),
828        })
829    }
830}
831
832#[derive(Clone, Debug, Error)]
833#[non_exhaustive]
834pub enum CreatePipelineLayoutError {
835    #[error(transparent)]
836    Device(#[from] DeviceError),
837    #[error(
838        "Immediate data has range bound {size} which is not aligned to IMMEDIATE_DATA_ALIGNMENT ({})",
839        wgt::IMMEDIATE_DATA_ALIGNMENT
840    )]
841    MisalignedImmediateSize { size: u32 },
842    #[error(transparent)]
843    MissingFeatures(#[from] MissingFeatures),
844    #[error(
845        "Immediate data has size {size} which exceeds device immediate data size limit 0..{max}"
846    )]
847    ImmediateRangeTooLarge { size: u32, max: u32 },
848    #[error(transparent)]
849    TooManyBindings(BindingTypeMaxCountError),
850    #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
851    TooManyGroups { actual: usize, max: usize },
852    #[error(transparent)]
853    InvalidResource(#[from] InvalidResourceError),
854    #[error("Bind group layout at index {index} has an exclusive pipeline: {pipeline}")]
855    BglHasExclusivePipeline { index: usize, pipeline: String },
856}
857
858impl WebGpuError for CreatePipelineLayoutError {
859    fn webgpu_error_type(&self) -> ErrorType {
860        match self {
861            Self::Device(e) => e.webgpu_error_type(),
862            Self::MissingFeatures(e) => e.webgpu_error_type(),
863            Self::InvalidResource(e) => e.webgpu_error_type(),
864            Self::TooManyBindings(e) => e.webgpu_error_type(),
865            Self::MisalignedImmediateSize { .. }
866            | Self::ImmediateRangeTooLarge { .. }
867            | Self::TooManyGroups { .. }
868            | Self::BglHasExclusivePipeline { .. } => ErrorType::Validation,
869        }
870    }
871}
872
873#[derive(Clone, Debug, Error)]
874#[non_exhaustive]
875pub enum ImmediateUploadError {
876    #[error(
877        "Start offset {start_offset} overruns the immediate data range with a size of {immediate_size}"
878    )]
879    StartOffsetOverrun {
880        start_offset: u32,
881        immediate_size: u32,
882    },
883    #[error(
884        "Provided immediate data start offset {0} does not respect \
885        `IMMEDIATE_DATA_ALIGNMENT` ({ida})",
886        ida = wgt::IMMEDIATE_DATA_ALIGNMENT
887    )]
888    StartOffsetUnaligned(u32),
889    #[error(
890        "Provided immediate data byte size {0} does not respect \
891        `IMMEDIATE_DATA_ALIGNMENT` ({ida})",
892        ida = wgt::IMMEDIATE_DATA_ALIGNMENT
893    )]
894    SizeUnaligned(u32),
895    #[error(
896        "Provided immediate data start offset {} + size {} overruns the immediate data range \
897        with a size of {}",
898        start_offset,
899        size,
900        immediate_size
901    )]
902    EndOffsetOverrun {
903        start_offset: u32,
904        size: u32,
905        immediate_size: u32,
906    },
907    #[error("Start index {start_index} overruns the value data range with {data_size} element(s)")]
908    ValueStartIndexOverrun { start_index: u32, data_size: usize },
909    #[error(
910        "Start index {} + count of {} overruns the value data range \
911        with {} element(s)",
912        start_index,
913        count,
914        data_size
915    )]
916    ValueEndIndexOverrun {
917        start_index: u32,
918        count: u32,
919        data_size: usize,
920    },
921}
922
923impl WebGpuError for ImmediateUploadError {
924    fn webgpu_error_type(&self) -> ErrorType {
925        ErrorType::Validation
926    }
927}
928
929/// Describes a pipeline layout.
930///
931/// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
932#[derive(Clone, Debug, PartialEq, Eq, Hash)]
933#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
934#[cfg_attr(feature = "serde", serde(bound = "BGL: Serialize"))]
935pub struct PipelineLayoutDescriptor<'a, BGL = BindGroupLayoutId>
936where
937    [Option<BGL>]: ToOwned,
938    <[Option<BGL>] as ToOwned>::Owned: fmt::Debug,
939{
940    /// Debug label of the pipeline layout.
941    ///
942    /// This will show up in graphics debuggers for easy identification.
943    pub label: Label<'a>,
944    /// Bind groups that this pipeline uses. The first entry will provide all the bindings for
945    /// "set = 0", second entry will provide all the bindings for "set = 1" etc.
946    #[cfg_attr(
947        feature = "serde",
948        serde(bound(deserialize = "<[Option<BGL>] as ToOwned>::Owned: Deserialize<'de>"))
949    )]
950    pub bind_group_layouts: Cow<'a, [Option<BGL>]>,
951    /// The number of bytes of immediate data that are allocated for use
952    /// in the shader. The `var<immediate>`s in the shader attached to
953    /// this pipeline must be equal or smaller than this size.
954    ///
955    /// If this value is non-zero, [`wgt::Features::IMMEDIATES`] must be enabled.
956    pub immediate_size: u32,
957}
958
959/// cbindgen:ignore
960pub type ResolvedPipelineLayoutDescriptor<'a, BGL = Arc<BindGroupLayout>> =
961    PipelineLayoutDescriptor<'a, BGL>;
962
963#[derive(Debug)]
964pub struct PipelineLayout {
965    pub(crate) raw: ManuallyDrop<Box<dyn hal::DynPipelineLayout>>,
966    pub(crate) device: Arc<Device>,
967    /// The `label` from the descriptor used to create the resource.
968    pub(crate) label: String,
969    pub(crate) bind_group_layouts: ArrayVec<Option<Arc<BindGroupLayout>>, { hal::MAX_BIND_GROUPS }>,
970    pub(crate) immediate_size: u32,
971}
972
973impl Drop for PipelineLayout {
974    fn drop(&mut self) {
975        resource_log!("Destroy raw {}", self.error_ident());
976        // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
977        let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
978        unsafe {
979            self.device.raw().destroy_pipeline_layout(raw);
980        }
981    }
982}
983
984impl PipelineLayout {
985    pub(crate) fn raw(&self) -> &dyn hal::DynPipelineLayout {
986        self.raw.as_ref()
987    }
988
989    pub(crate) fn get_bind_group_layout(
990        self: &Arc<Self>,
991        index: u32,
992        exclusive_pipeline_for_empty_bgl: ExclusivePipeline,
993    ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
994        let max_bind_groups = self.device.limits.max_bind_groups;
995        if index >= max_bind_groups {
996            return Err(GetBindGroupLayoutError::IndexOutOfRange {
997                index,
998                max: max_bind_groups,
999            });
1000        }
1001        Ok(self
1002            .bind_group_layouts
1003            .get(index as usize)
1004            .cloned()
1005            .flatten()
1006            .unwrap_or_else(|| {
1007                BindGroupLayout::empty(&self.device, exclusive_pipeline_for_empty_bgl)
1008            }))
1009    }
1010
1011    pub(crate) fn get_bgl_entry(
1012        &self,
1013        group: u32,
1014        binding: u32,
1015    ) -> Option<&wgt::BindGroupLayoutEntry> {
1016        let bgl = self.bind_group_layouts.get(group as usize)?;
1017        let bgl = bgl.as_ref()?;
1018        bgl.entries.get(binding)
1019    }
1020
1021    /// Validate immediates match up with expected ranges.
1022    pub(crate) fn validate_immediates_ranges(
1023        &self,
1024        offset: u32,
1025        size_bytes: u32,
1026    ) -> Result<(), ImmediateUploadError> {
1027        // Don't need to validate size against the immediate data size limit here,
1028        // as immediate data ranges are already validated to be within bounds,
1029        // and we validate that they are within the ranges.
1030
1031        if !offset.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
1032            return Err(ImmediateUploadError::StartOffsetUnaligned(offset));
1033        }
1034
1035        if !size_bytes.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
1036            return Err(ImmediateUploadError::SizeUnaligned(offset));
1037        }
1038
1039        if offset > self.immediate_size {
1040            return Err(ImmediateUploadError::StartOffsetOverrun {
1041                start_offset: offset,
1042                immediate_size: self.immediate_size,
1043            });
1044        }
1045
1046        if size_bytes > self.immediate_size - offset {
1047            return Err(ImmediateUploadError::EndOffsetOverrun {
1048                start_offset: offset,
1049                size: size_bytes,
1050                immediate_size: self.immediate_size,
1051            });
1052        }
1053
1054        Ok(())
1055    }
1056}
1057
1058crate::impl_resource_type!(PipelineLayout);
1059crate::impl_labeled!(PipelineLayout);
1060crate::impl_parent_device!(PipelineLayout);
1061crate::impl_storage_item!(PipelineLayout);
1062
1063#[repr(C)]
1064#[derive(Clone, Debug, Hash, Eq, PartialEq)]
1065#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1066pub struct BufferBinding<B = BufferId> {
1067    pub buffer: B,
1068    pub offset: wgt::BufferAddress,
1069
1070    /// Size of the binding. If `None`, the binding spans from `offset` to the
1071    /// end of the buffer.
1072    ///
1073    /// We use `BufferAddress` to allow a size of zero on this `wgpu_core` type,
1074    /// because JavaScript bindings cannot readily express `Option<NonZeroU64>`.
1075    /// The `wgpu` API uses `Option<BufferSize>` (i.e. `NonZeroU64`) for this
1076    /// field.
1077    pub size: Option<wgt::BufferAddress>,
1078}
1079
1080pub type ResolvedBufferBinding = BufferBinding<Arc<Buffer>>;
1081
1082// Note: Duplicated in `wgpu-rs` as `BindingResource`
1083// They're different enough that it doesn't make sense to share a common type
1084#[derive(Debug, Clone)]
1085#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1086pub enum BindingResource<
1087    'a,
1088    B = BufferId,
1089    S = SamplerId,
1090    TV = TextureViewId,
1091    TLAS = TlasId,
1092    ET = ExternalTextureId,
1093> where
1094    [BufferBinding<B>]: ToOwned,
1095    [S]: ToOwned,
1096    [TV]: ToOwned,
1097    [TLAS]: ToOwned,
1098    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
1099    <[S] as ToOwned>::Owned: fmt::Debug,
1100    <[TV] as ToOwned>::Owned: fmt::Debug,
1101    <[TLAS] as ToOwned>::Owned: fmt::Debug,
1102{
1103    Buffer(BufferBinding<B>),
1104    #[cfg_attr(
1105        feature = "serde",
1106        serde(bound(deserialize = "<[BufferBinding<B>] as ToOwned>::Owned: Deserialize<'de>"))
1107    )]
1108    BufferArray(Cow<'a, [BufferBinding<B>]>),
1109    Sampler(S),
1110    #[cfg_attr(
1111        feature = "serde",
1112        serde(bound(deserialize = "<[S] as ToOwned>::Owned: Deserialize<'de>"))
1113    )]
1114    SamplerArray(Cow<'a, [S]>),
1115    TextureView(TV),
1116    #[cfg_attr(
1117        feature = "serde",
1118        serde(bound(deserialize = "<[TV] as ToOwned>::Owned: Deserialize<'de>"))
1119    )]
1120    TextureViewArray(Cow<'a, [TV]>),
1121    AccelerationStructure(TLAS),
1122    #[cfg_attr(
1123        feature = "serde",
1124        serde(bound(deserialize = "<[TLAS] as ToOwned>::Owned: Deserialize<'de>"))
1125    )]
1126    AccelerationStructureArray(Cow<'a, [TLAS]>),
1127    ExternalTexture(ET),
1128}
1129
1130pub type ResolvedBindingResource<'a> = BindingResource<
1131    'a,
1132    Arc<Buffer>,
1133    Arc<Sampler>,
1134    Arc<TextureView>,
1135    Arc<Tlas>,
1136    Arc<ExternalTexture>,
1137>;
1138
1139#[derive(Clone, Debug, Error)]
1140#[non_exhaustive]
1141pub enum BindError {
1142    #[error(
1143        "Dynamic offsets not expected with null bind group at index {group}. However {actual} dynamic offset{s1} were provided.",
1144        s1 = if *.actual >= 2 { "s" } else { "" },
1145    )]
1146    DynamicOffsetCountNotZero { group: u32, actual: usize },
1147    #[error(
1148        "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
1149        s0 = if *.expected >= 2 { "s" } else { "" },
1150        s1 = if *.actual >= 2 { "s" } else { "" },
1151    )]
1152    MismatchedDynamicOffsetCount {
1153        bind_group: ResourceErrorIdent,
1154        group: u32,
1155        actual: usize,
1156        expected: usize,
1157    },
1158    #[error(
1159        "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
1160    )]
1161    UnalignedDynamicBinding {
1162        bind_group: ResourceErrorIdent,
1163        idx: usize,
1164        group: u32,
1165        binding: u32,
1166        offset: u32,
1167        alignment: u32,
1168        limit_name: &'static str,
1169    },
1170    #[error(
1171        "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \
1172         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",
1173    )]
1174    DynamicBindingOutOfBounds {
1175        bind_group: ResourceErrorIdent,
1176        idx: usize,
1177        group: u32,
1178        binding: u32,
1179        offset: u32,
1180        buffer_size: wgt::BufferAddress,
1181        binding_range: Range<wgt::BufferAddress>,
1182        maximum_dynamic_offset: wgt::BufferAddress,
1183    },
1184}
1185
1186impl WebGpuError for BindError {
1187    fn webgpu_error_type(&self) -> ErrorType {
1188        ErrorType::Validation
1189    }
1190}
1191
1192#[derive(Debug)]
1193pub struct BindGroupDynamicBindingData {
1194    /// The index of the binding.
1195    ///
1196    /// Used for more descriptive errors.
1197    pub(crate) binding_idx: u32,
1198    /// The size of the buffer.
1199    ///
1200    /// Used for more descriptive errors.
1201    pub(crate) buffer_size: wgt::BufferAddress,
1202    /// The range that the binding covers.
1203    ///
1204    /// Used for more descriptive errors.
1205    pub(crate) binding_range: Range<wgt::BufferAddress>,
1206    /// The maximum value the dynamic offset can have before running off the end of the buffer.
1207    pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
1208    /// The binding type.
1209    pub(crate) binding_type: wgt::BufferBindingType,
1210}
1211
1212pub(crate) fn buffer_binding_type_alignment(
1213    limits: &wgt::Limits,
1214    binding_type: wgt::BufferBindingType,
1215) -> (u32, &'static str) {
1216    match binding_type {
1217        wgt::BufferBindingType::Uniform => (
1218            limits.min_uniform_buffer_offset_alignment,
1219            "min_uniform_buffer_offset_alignment",
1220        ),
1221        wgt::BufferBindingType::Storage { .. } => (
1222            limits.min_storage_buffer_offset_alignment,
1223            "min_storage_buffer_offset_alignment",
1224        ),
1225    }
1226}
1227
1228pub(crate) fn buffer_binding_type_bounds_check_alignment(
1229    alignments: &hal::Alignments,
1230    binding_type: wgt::BufferBindingType,
1231) -> wgt::BufferAddress {
1232    match binding_type {
1233        wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(),
1234        wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT,
1235    }
1236}
1237
1238#[derive(Debug)]
1239pub(crate) struct BindGroupLateBufferBindingInfo {
1240    /// The normal binding index in the bind group.
1241    pub binding_index: u32,
1242    /// The size that exists at bind time.
1243    pub size: wgt::BufferSize,
1244}
1245
1246#[derive(Debug)]
1247pub struct BindGroup {
1248    pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>,
1249    pub(crate) device: Arc<Device>,
1250    pub(crate) layout: Arc<BindGroupLayout>,
1251    /// The `label` from the descriptor used to create the resource.
1252    pub(crate) label: String,
1253    pub(crate) tracking_data: TrackingData,
1254    pub(crate) used: BindGroupStates,
1255    pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
1256    pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
1257    /// INVARIANT: Sorted by binding index order.
1258    pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
1259    /// Actual binding sizes for buffers that don't have `min_binding_size`
1260    /// specified in BGL. Listed in the order of iteration of `BGL.entries`.
1261    pub(crate) late_buffer_binding_infos: Vec<BindGroupLateBufferBindingInfo>,
1262}
1263
1264impl Drop for BindGroup {
1265    fn drop(&mut self) {
1266        if let Some(raw) = self.raw.take() {
1267            resource_log!("Destroy raw {}", self.error_ident());
1268            unsafe {
1269                self.device.raw().destroy_bind_group(raw);
1270            }
1271        }
1272    }
1273}
1274
1275impl BindGroup {
1276    pub(crate) fn try_raw<'a>(
1277        &'a self,
1278        guard: &'a SnatchGuard,
1279    ) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> {
1280        // Clippy insist on writing it this way. The idea is to return None
1281        // if any of the raw buffer is not valid anymore.
1282        for buffer in &self.used_buffer_ranges {
1283            buffer.buffer.try_raw(guard)?;
1284        }
1285        for texture in &self.used_texture_ranges {
1286            texture.texture.try_raw(guard)?;
1287        }
1288
1289        self.raw
1290            .get(guard)
1291            .map(|raw| raw.as_ref())
1292            .ok_or_else(|| DestroyedResourceError(self.error_ident()))
1293    }
1294
1295    pub(crate) fn validate_dynamic_bindings(
1296        &self,
1297        bind_group_index: u32,
1298        offsets: &[wgt::DynamicOffset],
1299    ) -> Result<(), BindError> {
1300        if self.dynamic_binding_info.len() != offsets.len() {
1301            return Err(BindError::MismatchedDynamicOffsetCount {
1302                bind_group: self.error_ident(),
1303                group: bind_group_index,
1304                expected: self.dynamic_binding_info.len(),
1305                actual: offsets.len(),
1306            });
1307        }
1308
1309        for (idx, (info, &offset)) in self
1310            .dynamic_binding_info
1311            .iter()
1312            .zip(offsets.iter())
1313            .enumerate()
1314        {
1315            let (alignment, limit_name) =
1316                buffer_binding_type_alignment(&self.device.limits, info.binding_type);
1317            if !(offset as wgt::BufferAddress).is_multiple_of(alignment as u64) {
1318                return Err(BindError::UnalignedDynamicBinding {
1319                    bind_group: self.error_ident(),
1320                    group: bind_group_index,
1321                    binding: info.binding_idx,
1322                    idx,
1323                    offset,
1324                    alignment,
1325                    limit_name,
1326                });
1327            }
1328
1329            if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
1330                return Err(BindError::DynamicBindingOutOfBounds {
1331                    bind_group: self.error_ident(),
1332                    group: bind_group_index,
1333                    binding: info.binding_idx,
1334                    idx,
1335                    offset,
1336                    buffer_size: info.buffer_size,
1337                    binding_range: info.binding_range.clone(),
1338                    maximum_dynamic_offset: info.maximum_dynamic_offset,
1339                });
1340            }
1341        }
1342
1343        Ok(())
1344    }
1345}
1346
1347crate::impl_resource_type!(BindGroup);
1348crate::impl_labeled!(BindGroup);
1349crate::impl_parent_device!(BindGroup);
1350crate::impl_storage_item!(BindGroup);
1351crate::impl_trackable!(BindGroup);
1352
1353#[derive(Clone, Debug, Error)]
1354#[non_exhaustive]
1355pub enum GetBindGroupLayoutError {
1356    #[error("Bind group layout index {index} is greater than the device's configured `max_bind_groups` limit {max}")]
1357    IndexOutOfRange { index: u32, max: u32 },
1358    #[error(transparent)]
1359    InvalidResource(#[from] InvalidResourceError),
1360}
1361
1362impl WebGpuError for GetBindGroupLayoutError {
1363    fn webgpu_error_type(&self) -> ErrorType {
1364        match self {
1365            Self::IndexOutOfRange { .. } => ErrorType::Validation,
1366            Self::InvalidResource(e) => e.webgpu_error_type(),
1367        }
1368    }
1369}
1370
1371#[derive(Clone, Debug, Error, Eq, PartialEq)]
1372#[error(
1373    "In bind group index {group_index}, the buffer bound at binding index {binding_index} \
1374     is bound with size {bound_size} where the shader expects {shader_size}."
1375)]
1376pub struct LateMinBufferBindingSizeMismatch {
1377    pub group_index: u32,
1378    pub binding_index: u32,
1379    pub shader_size: wgt::BufferAddress,
1380    pub bound_size: wgt::BufferAddress,
1381}