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