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