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