Skip to main content

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: u64,
185        limit: u64,
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        match self {
261            Self::Device(e) => e.webgpu_error_type(),
262            Self::DestroyedResource(e) => e.webgpu_error_type(),
263            Self::BindingError(e) => e.webgpu_error_type(),
264            Self::MissingBufferUsage(e) => e.webgpu_error_type(),
265            Self::MissingTextureUsage(e) => e.webgpu_error_type(),
266            Self::ResourceUsageCompatibility(e) => e.webgpu_error_type(),
267            Self::InvalidResource(e) => e.webgpu_error_type(),
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 { .. } => ErrorType::Validation,
292        }
293    }
294}
295
296#[derive(Clone, Debug, Error)]
297pub enum BindingZone {
298    #[error("Stage {0:?}")]
299    Stage(wgt::ShaderStages),
300    #[error("Whole pipeline")]
301    Pipeline,
302}
303
304#[derive(Clone, Debug, Error)]
305#[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())]
306pub struct BindingTypeMaxCountError {
307    pub kind: BindingTypeMaxCountErrorKind,
308    pub zone: BindingZone,
309    pub limit: u32,
310    pub count: u32,
311}
312
313impl WebGpuError for BindingTypeMaxCountError {
314    fn webgpu_error_type(&self) -> ErrorType {
315        ErrorType::Validation
316    }
317}
318
319#[derive(Clone, Debug)]
320pub enum BindingTypeMaxCountErrorKind {
321    DynamicUniformBuffers,
322    DynamicStorageBuffers,
323    SampledTextures,
324    Samplers,
325    StorageBuffers,
326    StorageTextures,
327    UniformBuffers,
328    BindingArrayElements,
329    BindingArraySamplerElements,
330    BindingArrayAccelerationStructureElements,
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::BindingArrayAccelerationStructureElements => {
359                "max_binding_array_acceleration_structure_elements_per_shader_stage"
360            }
361            BindingTypeMaxCountErrorKind::AccelerationStructures => {
362                "max_acceleration_structures_per_shader_stage"
363            }
364        }
365    }
366}
367
368#[derive(Debug, Default)]
369pub(crate) struct PerStageBindingTypeCounter {
370    vertex: u32,
371    fragment: u32,
372    compute: u32,
373}
374
375impl PerStageBindingTypeCounter {
376    pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
377        if stage.contains(wgt::ShaderStages::VERTEX) {
378            self.vertex += count;
379        }
380        if stage.contains(wgt::ShaderStages::FRAGMENT) {
381            self.fragment += count;
382        }
383        if stage.contains(wgt::ShaderStages::COMPUTE) {
384            self.compute += count;
385        }
386    }
387
388    pub(crate) fn max(&self) -> (BindingZone, u32) {
389        let max_value = self.vertex.max(self.fragment.max(self.compute));
390        let mut stage = wgt::ShaderStages::NONE;
391        if max_value == self.vertex {
392            stage |= wgt::ShaderStages::VERTEX
393        }
394        if max_value == self.fragment {
395            stage |= wgt::ShaderStages::FRAGMENT
396        }
397        if max_value == self.compute {
398            stage |= wgt::ShaderStages::COMPUTE
399        }
400        (BindingZone::Stage(stage), max_value)
401    }
402
403    pub(crate) fn merge(&mut self, other: &Self) {
404        self.vertex += other.vertex;
405        self.fragment += other.fragment;
406        self.compute += other.compute;
407    }
408
409    pub(crate) fn validate(
410        &self,
411        limit: u32,
412        kind: BindingTypeMaxCountErrorKind,
413    ) -> Result<(), BindingTypeMaxCountError> {
414        let (zone, count) = self.max();
415        if limit < count {
416            Err(BindingTypeMaxCountError {
417                kind,
418                zone,
419                limit,
420                count,
421            })
422        } else {
423            Ok(())
424        }
425    }
426}
427
428#[derive(Debug, Default)]
429pub(crate) struct BindingTypeMaxCountValidator {
430    dynamic_uniform_buffers: u32,
431    dynamic_storage_buffers: u32,
432    sampled_textures: PerStageBindingTypeCounter,
433    samplers: PerStageBindingTypeCounter,
434    storage_buffers: PerStageBindingTypeCounter,
435    storage_textures: PerStageBindingTypeCounter,
436    uniform_buffers: PerStageBindingTypeCounter,
437    acceleration_structures: PerStageBindingTypeCounter,
438    binding_array_elements: PerStageBindingTypeCounter,
439    binding_array_sampler_elements: PerStageBindingTypeCounter,
440    binding_array_acceleration_structure_elements: PerStageBindingTypeCounter,
441    has_bindless_array: bool,
442}
443
444impl BindingTypeMaxCountValidator {
445    pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
446        let count = binding.count.map_or(1, |count| count.get());
447
448        if binding.count.is_some() {
449            self.binding_array_elements.add(binding.visibility, count);
450            self.has_bindless_array = true;
451
452            match binding.ty {
453                wgt::BindingType::Sampler(_) => {
454                    self.binding_array_sampler_elements
455                        .add(binding.visibility, count);
456                }
457                wgt::BindingType::AccelerationStructure { .. } => {
458                    self.binding_array_acceleration_structure_elements
459                        .add(binding.visibility, count);
460                }
461                _ => {}
462            }
463        } else {
464            match binding.ty {
465                wgt::BindingType::Buffer {
466                    ty: wgt::BufferBindingType::Uniform,
467                    has_dynamic_offset,
468                    ..
469                } => {
470                    self.uniform_buffers.add(binding.visibility, count);
471                    if has_dynamic_offset {
472                        self.dynamic_uniform_buffers += count;
473                    }
474                }
475                wgt::BindingType::Buffer {
476                    ty: wgt::BufferBindingType::Storage { .. },
477                    has_dynamic_offset,
478                    ..
479                } => {
480                    self.storage_buffers.add(binding.visibility, count);
481                    if has_dynamic_offset {
482                        self.dynamic_storage_buffers += count;
483                    }
484                }
485                wgt::BindingType::Sampler { .. } => {
486                    self.samplers.add(binding.visibility, count);
487                }
488                wgt::BindingType::Texture { .. } => {
489                    self.sampled_textures.add(binding.visibility, count);
490                }
491                wgt::BindingType::StorageTexture { .. } => {
492                    self.storage_textures.add(binding.visibility, count);
493                }
494                wgt::BindingType::AccelerationStructure { .. } => {
495                    self.acceleration_structures.add(binding.visibility, count);
496                }
497                wgt::BindingType::ExternalTexture => {
498                    // https://www.w3.org/TR/webgpu/#gpuexternaltexture
499                    // In order to account for many possible representations,
500                    // the binding conservatively uses the following, for each
501                    // external texture:
502                    // * Three sampled textures for up to 3 planes
503                    // * One additional sampled texture for a 3D LUT
504                    // * One sampler to sample the LUT
505                    // * One uniform buffer for metadata
506                    self.sampled_textures.add(binding.visibility, count * 4);
507                    self.samplers.add(binding.visibility, count);
508                    self.uniform_buffers.add(binding.visibility, count);
509                }
510            }
511        }
512    }
513
514    pub(crate) fn merge(&mut self, other: &Self) {
515        self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
516        self.dynamic_storage_buffers += other.dynamic_storage_buffers;
517        self.sampled_textures.merge(&other.sampled_textures);
518        self.samplers.merge(&other.samplers);
519        self.storage_buffers.merge(&other.storage_buffers);
520        self.storage_textures.merge(&other.storage_textures);
521        self.uniform_buffers.merge(&other.uniform_buffers);
522        self.acceleration_structures
523            .merge(&other.acceleration_structures);
524        self.binding_array_elements
525            .merge(&other.binding_array_elements);
526        self.binding_array_sampler_elements
527            .merge(&other.binding_array_sampler_elements);
528        self.binding_array_acceleration_structure_elements
529            .merge(&other.binding_array_acceleration_structure_elements);
530    }
531
532    pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
533        if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
534            return Err(BindingTypeMaxCountError {
535                kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
536                zone: BindingZone::Pipeline,
537                limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
538                count: self.dynamic_uniform_buffers,
539            });
540        }
541        if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
542            return Err(BindingTypeMaxCountError {
543                kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
544                zone: BindingZone::Pipeline,
545                limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
546                count: self.dynamic_storage_buffers,
547            });
548        }
549        self.sampled_textures.validate(
550            limits.max_sampled_textures_per_shader_stage,
551            BindingTypeMaxCountErrorKind::SampledTextures,
552        )?;
553        self.samplers.validate(
554            limits.max_samplers_per_shader_stage,
555            BindingTypeMaxCountErrorKind::Samplers,
556        )?;
557        self.storage_buffers.validate(
558            limits.max_storage_buffers_per_shader_stage,
559            BindingTypeMaxCountErrorKind::StorageBuffers,
560        )?;
561        self.storage_textures.validate(
562            limits.max_storage_textures_per_shader_stage,
563            BindingTypeMaxCountErrorKind::StorageTextures,
564        )?;
565        self.uniform_buffers.validate(
566            limits.max_uniform_buffers_per_shader_stage,
567            BindingTypeMaxCountErrorKind::UniformBuffers,
568        )?;
569        self.binding_array_elements.validate(
570            limits.max_binding_array_elements_per_shader_stage,
571            BindingTypeMaxCountErrorKind::BindingArrayElements,
572        )?;
573        self.binding_array_sampler_elements.validate(
574            limits.max_binding_array_sampler_elements_per_shader_stage,
575            BindingTypeMaxCountErrorKind::BindingArraySamplerElements,
576        )?;
577        self.binding_array_acceleration_structure_elements
578            .validate(
579                limits.max_binding_array_acceleration_structure_elements_per_shader_stage,
580                BindingTypeMaxCountErrorKind::BindingArrayAccelerationStructureElements,
581            )?;
582        self.acceleration_structures.validate(
583            limits.max_acceleration_structures_per_shader_stage,
584            BindingTypeMaxCountErrorKind::AccelerationStructures,
585        )?;
586        Ok(())
587    }
588
589    /// Validate that the bind group layout does not contain both a binding array and a dynamic offset array.
590    ///
591    /// This allows us to use `UPDATE_AFTER_BIND` on vulkan for bindless arrays. Vulkan does not allow
592    /// `UPDATE_AFTER_BIND` on dynamic offset arrays. See <https://github.com/gfx-rs/wgpu/issues/6737>
593    pub(crate) fn validate_binding_arrays(&self) -> Result<(), CreateBindGroupLayoutError> {
594        let has_dynamic_offset_array =
595            self.dynamic_uniform_buffers > 0 || self.dynamic_storage_buffers > 0;
596        let has_uniform_buffer = self.uniform_buffers.max().1 > 0;
597        if self.has_bindless_array && has_dynamic_offset_array {
598            return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndDynamicOffsetArray);
599        }
600        if self.has_bindless_array && has_uniform_buffer {
601            return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndUniformBuffer);
602        }
603        Ok(())
604    }
605}
606
607/// Bindable resource and the slot to bind it to.
608/// cbindgen:ignore
609#[derive(Clone, Debug)]
610#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
611pub struct BindGroupEntry<
612    'a,
613    B = BufferId,
614    S = SamplerId,
615    TV = TextureViewId,
616    TLAS = TlasId,
617    ET = ExternalTextureId,
618> where
619    [BufferBinding<B>]: ToOwned,
620    [S]: ToOwned,
621    [TV]: ToOwned,
622    [TLAS]: ToOwned,
623    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
624    <[S] as ToOwned>::Owned: fmt::Debug,
625    <[TV] as ToOwned>::Owned: fmt::Debug,
626    <[TLAS] as ToOwned>::Owned: fmt::Debug,
627{
628    /// Slot for which binding provides resource. Corresponds to an entry of the same
629    /// binding index in the [`BindGroupLayoutDescriptor`].
630    pub binding: u32,
631    #[cfg_attr(
632        feature = "serde",
633        serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>"))
634    )]
635    /// Resource to attach to the binding
636    pub resource: BindingResource<'a, B, S, TV, TLAS, ET>,
637}
638
639/// cbindgen:ignore
640pub type ResolvedBindGroupEntry<'a> = BindGroupEntry<
641    'a,
642    Arc<Buffer>,
643    Arc<Sampler>,
644    Arc<TextureView>,
645    Arc<Tlas>,
646    Arc<ExternalTexture>,
647>;
648
649/// Describes a group of bindings and the resources to be bound.
650#[derive(Clone, Debug)]
651#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
652pub struct BindGroupDescriptor<
653    'a,
654    BGL = BindGroupLayoutId,
655    B = BufferId,
656    S = SamplerId,
657    TV = TextureViewId,
658    TLAS = TlasId,
659    ET = ExternalTextureId,
660> where
661    [BufferBinding<B>]: ToOwned,
662    [S]: ToOwned,
663    [TV]: ToOwned,
664    [TLAS]: ToOwned,
665    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
666    <[S] as ToOwned>::Owned: fmt::Debug,
667    <[TV] as ToOwned>::Owned: fmt::Debug,
668    <[TLAS] as ToOwned>::Owned: fmt::Debug,
669    [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned,
670    <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug,
671{
672    /// Debug label of the bind group.
673    ///
674    /// This will show up in graphics debuggers for easy identification.
675    pub label: Label<'a>,
676    /// The [`BindGroupLayout`] that corresponds to this bind group.
677    pub layout: BGL,
678    #[cfg_attr(
679        feature = "serde",
680        serde(bound(
681            deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>"
682        ))
683    )]
684    /// The resources to bind to this bind group.
685    #[allow(clippy::type_complexity)]
686    pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>,
687}
688
689/// cbindgen:ignore
690pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor<
691    'a,
692    Arc<BindGroupLayout>,
693    Arc<Buffer>,
694    Arc<Sampler>,
695    Arc<TextureView>,
696    Arc<Tlas>,
697    Arc<ExternalTexture>,
698>;
699
700/// Describes a [`BindGroupLayout`].
701#[derive(Clone, Debug)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub struct BindGroupLayoutDescriptor<'a> {
704    /// Debug label of the bind group layout.
705    ///
706    /// This will show up in graphics debuggers for easy identification.
707    pub label: Label<'a>,
708    /// Array of entries in this BindGroupLayout
709    pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
710}
711
712/// Used by [`BindGroupLayout`]. It indicates whether the BGL must be
713/// used with a specific pipeline. This constraint only happens when
714/// the BGLs have been derived from a pipeline without a layout.
715#[derive(Clone, Debug)]
716pub(crate) enum ExclusivePipeline {
717    None,
718    Render(Weak<RenderPipeline>),
719    Compute(Weak<ComputePipeline>),
720}
721
722impl fmt::Display for ExclusivePipeline {
723    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
724        match self {
725            ExclusivePipeline::None => f.write_str("None"),
726            ExclusivePipeline::Render(p) => {
727                if let Some(p) = p.upgrade() {
728                    p.error_ident().fmt(f)
729                } else {
730                    f.write_str("RenderPipeline")
731                }
732            }
733            ExclusivePipeline::Compute(p) => {
734                if let Some(p) = p.upgrade() {
735                    p.error_ident().fmt(f)
736                } else {
737                    f.write_str("ComputePipeline")
738                }
739            }
740        }
741    }
742}
743
744#[derive(Debug)]
745pub enum RawBindGroupLayout {
746    Owning(ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>),
747    /// The empty BGL was created by the device and will be destroyed by the device.
748    RefDeviceEmptyBGL,
749}
750
751/// Bind group layout.
752#[derive(Debug)]
753pub struct BindGroupLayout {
754    pub(crate) raw: RawBindGroupLayout,
755    pub(crate) device: Arc<Device>,
756    pub(crate) entries: bgl::EntryMap,
757    /// It is very important that we know if the bind group comes from the BGL pool.
758    ///
759    /// If it does, then we need to remove it from the pool when we drop it.
760    ///
761    /// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool
762    /// (derived BGLs) must not be removed.
763    pub(crate) origin: bgl::Origin,
764    pub(crate) exclusive_pipeline: crate::OnceCellOrLock<ExclusivePipeline>,
765    pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
766    /// The `label` from the descriptor used to create the resource.
767    pub(crate) label: String,
768}
769
770impl Drop for BindGroupLayout {
771    fn drop(&mut self) {
772        resource_log!("Destroy raw {}", self.error_ident());
773        if matches!(self.origin, bgl::Origin::Pool) {
774            self.device.bgl_pool.remove(&self.entries);
775        }
776        match self.raw {
777            RawBindGroupLayout::Owning(ref mut raw) => {
778                // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
779                let raw = unsafe { ManuallyDrop::take(raw) };
780                unsafe {
781                    self.device.raw().destroy_bind_group_layout(raw);
782                }
783            }
784            RawBindGroupLayout::RefDeviceEmptyBGL => {}
785        }
786    }
787}
788
789crate::impl_resource_type!(BindGroupLayout);
790crate::impl_labeled!(BindGroupLayout);
791crate::impl_parent_device!(BindGroupLayout);
792crate::impl_storage_item!(BindGroupLayout);
793
794impl BindGroupLayout {
795    pub(crate) fn raw(&self) -> &dyn hal::DynBindGroupLayout {
796        match &self.raw {
797            RawBindGroupLayout::Owning(raw) => raw.as_ref(),
798            RawBindGroupLayout::RefDeviceEmptyBGL => self.device.empty_bgl.as_ref(),
799        }
800    }
801
802    fn empty(device: &Arc<Device>) -> Arc<Self> {
803        let exclusive_pipeline = crate::OnceCellOrLock::new();
804        exclusive_pipeline.set(ExclusivePipeline::None).unwrap();
805        Arc::new(Self {
806            raw: RawBindGroupLayout::RefDeviceEmptyBGL,
807            device: device.clone(),
808            entries: bgl::EntryMap::default(),
809            origin: bgl::Origin::Derived,
810            exclusive_pipeline,
811            binding_count_validator: BindingTypeMaxCountValidator::default(),
812            label: String::new(),
813        })
814    }
815}
816
817#[derive(Clone, Debug, Error)]
818#[non_exhaustive]
819pub enum CreatePipelineLayoutError {
820    #[error(transparent)]
821    Device(#[from] DeviceError),
822    #[error(
823        "Immediate data has range bound {size} which is not aligned to IMMEDIATE_DATA_ALIGNMENT ({})",
824        wgt::IMMEDIATE_DATA_ALIGNMENT
825    )]
826    MisalignedImmediateSize { size: u32 },
827    #[error(transparent)]
828    MissingFeatures(#[from] MissingFeatures),
829    #[error(
830        "Immediate data has size {size} which exceeds device immediate data size limit 0..{max}"
831    )]
832    ImmediateRangeTooLarge { size: u32, max: u32 },
833    #[error(transparent)]
834    TooManyBindings(BindingTypeMaxCountError),
835    #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
836    TooManyGroups { actual: usize, max: usize },
837    #[error(transparent)]
838    InvalidResource(#[from] InvalidResourceError),
839    #[error("Bind group layout at index {index} has an exclusive pipeline: {pipeline}")]
840    BglHasExclusivePipeline { index: usize, pipeline: String },
841}
842
843impl WebGpuError for CreatePipelineLayoutError {
844    fn webgpu_error_type(&self) -> ErrorType {
845        match self {
846            Self::Device(e) => e.webgpu_error_type(),
847            Self::MissingFeatures(e) => e.webgpu_error_type(),
848            Self::InvalidResource(e) => e.webgpu_error_type(),
849            Self::TooManyBindings(e) => e.webgpu_error_type(),
850            Self::MisalignedImmediateSize { .. }
851            | Self::ImmediateRangeTooLarge { .. }
852            | Self::TooManyGroups { .. }
853            | Self::BglHasExclusivePipeline { .. } => ErrorType::Validation,
854        }
855    }
856}
857
858#[derive(Clone, Debug, Error)]
859#[non_exhaustive]
860pub enum ImmediateUploadError {
861    #[error(
862        "Start offset {start_offset} overruns the immediate data range with a size of {immediate_size}"
863    )]
864    StartOffsetOverrun {
865        start_offset: u32,
866        immediate_size: u32,
867    },
868    #[error(
869        "Provided immediate data start offset {0} does not respect \
870        `IMMEDIATE_DATA_ALIGNMENT` ({ida})",
871        ida = wgt::IMMEDIATE_DATA_ALIGNMENT
872    )]
873    StartOffsetUnaligned(u32),
874    #[error(
875        "Provided immediate data byte size {0} does not respect \
876        `IMMEDIATE_DATA_ALIGNMENT` ({ida})",
877        ida = wgt::IMMEDIATE_DATA_ALIGNMENT
878    )]
879    SizeUnaligned(u32),
880    #[error(
881        "Provided immediate data start offset {} + size {} overruns the immediate data range \
882        with a size of {}",
883        start_offset,
884        size,
885        immediate_size
886    )]
887    EndOffsetOverrun {
888        start_offset: u32,
889        size: u32,
890        immediate_size: u32,
891    },
892    #[error("Start index {start_index} overruns the value data range with {data_size} element(s)")]
893    ValueStartIndexOverrun { start_index: u32, data_size: usize },
894    #[error(
895        "Start index {} + count of {} overruns the value data range \
896        with {} element(s)",
897        start_index,
898        count,
899        data_size
900    )]
901    ValueEndIndexOverrun {
902        start_index: u32,
903        count: u32,
904        data_size: usize,
905    },
906}
907
908impl WebGpuError for ImmediateUploadError {
909    fn webgpu_error_type(&self) -> ErrorType {
910        ErrorType::Validation
911    }
912}
913
914/// Describes a pipeline layout.
915///
916/// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
917#[derive(Clone, Debug, PartialEq, Eq, Hash)]
918#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
919#[cfg_attr(feature = "serde", serde(bound = "BGL: Serialize"))]
920pub struct PipelineLayoutDescriptor<'a, BGL = BindGroupLayoutId>
921where
922    [Option<BGL>]: ToOwned,
923    <[Option<BGL>] as ToOwned>::Owned: fmt::Debug,
924{
925    /// Debug label of the pipeline layout.
926    ///
927    /// This will show up in graphics debuggers for easy identification.
928    pub label: Label<'a>,
929    /// Bind groups that this pipeline uses. The first entry will provide all the bindings for
930    /// "set = 0", second entry will provide all the bindings for "set = 1" etc.
931    #[cfg_attr(
932        feature = "serde",
933        serde(bound(deserialize = "<[Option<BGL>] as ToOwned>::Owned: Deserialize<'de>"))
934    )]
935    pub bind_group_layouts: Cow<'a, [Option<BGL>]>,
936    /// The number of bytes of immediate data that are allocated for use
937    /// in the shader. The `var<immediate>`s in the shader attached to
938    /// this pipeline must be equal or smaller than this size.
939    ///
940    /// If this value is non-zero, [`wgt::Features::IMMEDIATES`] must be enabled.
941    pub immediate_size: u32,
942}
943
944/// cbindgen:ignore
945pub type ResolvedPipelineLayoutDescriptor<'a, BGL = Arc<BindGroupLayout>> =
946    PipelineLayoutDescriptor<'a, BGL>;
947
948#[derive(Debug)]
949pub struct PipelineLayout {
950    pub(crate) raw: ManuallyDrop<Box<dyn hal::DynPipelineLayout>>,
951    pub(crate) device: Arc<Device>,
952    /// The `label` from the descriptor used to create the resource.
953    pub(crate) label: String,
954    pub(crate) bind_group_layouts: ArrayVec<Option<Arc<BindGroupLayout>>, { hal::MAX_BIND_GROUPS }>,
955    pub(crate) immediate_size: u32,
956}
957
958impl Drop for PipelineLayout {
959    fn drop(&mut self) {
960        resource_log!("Destroy raw {}", self.error_ident());
961        // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
962        let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
963        unsafe {
964            self.device.raw().destroy_pipeline_layout(raw);
965        }
966    }
967}
968
969impl PipelineLayout {
970    pub(crate) fn raw(&self) -> &dyn hal::DynPipelineLayout {
971        self.raw.as_ref()
972    }
973
974    pub fn get_bind_group_layout(
975        self: &Arc<Self>,
976        index: u32,
977    ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
978        let max_bind_groups = self.device.limits.max_bind_groups;
979        if index >= max_bind_groups {
980            return Err(GetBindGroupLayoutError::IndexOutOfRange {
981                index,
982                max: max_bind_groups,
983            });
984        }
985        Ok(self
986            .bind_group_layouts
987            .get(index as usize)
988            .cloned()
989            .flatten()
990            .unwrap_or_else(|| BindGroupLayout::empty(&self.device)))
991    }
992
993    pub(crate) fn get_bgl_entry(
994        &self,
995        group: u32,
996        binding: u32,
997    ) -> Option<&wgt::BindGroupLayoutEntry> {
998        let bgl = self.bind_group_layouts.get(group as usize)?;
999        let bgl = bgl.as_ref()?;
1000        bgl.entries.get(binding)
1001    }
1002
1003    /// Validate immediates match up with expected ranges.
1004    pub(crate) fn validate_immediates_ranges(
1005        &self,
1006        offset: u32,
1007        size_bytes: u32,
1008    ) -> Result<(), ImmediateUploadError> {
1009        // Don't need to validate size against the immediate data size limit here,
1010        // as immediate data ranges are already validated to be within bounds,
1011        // and we validate that they are within the ranges.
1012
1013        if !offset.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
1014            return Err(ImmediateUploadError::StartOffsetUnaligned(offset));
1015        }
1016
1017        if !size_bytes.is_multiple_of(wgt::IMMEDIATE_DATA_ALIGNMENT) {
1018            return Err(ImmediateUploadError::SizeUnaligned(offset));
1019        }
1020
1021        if offset > self.immediate_size {
1022            return Err(ImmediateUploadError::StartOffsetOverrun {
1023                start_offset: offset,
1024                immediate_size: self.immediate_size,
1025            });
1026        }
1027
1028        if size_bytes > self.immediate_size - offset {
1029            return Err(ImmediateUploadError::EndOffsetOverrun {
1030                start_offset: offset,
1031                size: size_bytes,
1032                immediate_size: self.immediate_size,
1033            });
1034        }
1035
1036        Ok(())
1037    }
1038}
1039
1040crate::impl_resource_type!(PipelineLayout);
1041crate::impl_labeled!(PipelineLayout);
1042crate::impl_parent_device!(PipelineLayout);
1043crate::impl_storage_item!(PipelineLayout);
1044
1045#[repr(C)]
1046#[derive(Clone, Debug, Hash, Eq, PartialEq)]
1047#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1048pub struct BufferBinding<B = BufferId> {
1049    pub buffer: B,
1050    pub offset: wgt::BufferAddress,
1051
1052    /// Size of the binding. If `None`, the binding spans from `offset` to the
1053    /// end of the buffer.
1054    ///
1055    /// We use `BufferAddress` to allow a size of zero on this `wgpu_core` type,
1056    /// because JavaScript bindings cannot readily express `Option<NonZeroU64>`.
1057    /// The `wgpu` API uses `Option<BufferSize>` (i.e. `NonZeroU64`) for this
1058    /// field.
1059    pub size: Option<wgt::BufferAddress>,
1060}
1061
1062pub type ResolvedBufferBinding = BufferBinding<Arc<Buffer>>;
1063
1064// Note: Duplicated in `wgpu-rs` as `BindingResource`
1065// They're different enough that it doesn't make sense to share a common type
1066#[derive(Debug, Clone)]
1067#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1068pub enum BindingResource<
1069    'a,
1070    B = BufferId,
1071    S = SamplerId,
1072    TV = TextureViewId,
1073    TLAS = TlasId,
1074    ET = ExternalTextureId,
1075> where
1076    [BufferBinding<B>]: ToOwned,
1077    [S]: ToOwned,
1078    [TV]: ToOwned,
1079    [TLAS]: ToOwned,
1080    <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
1081    <[S] as ToOwned>::Owned: fmt::Debug,
1082    <[TV] as ToOwned>::Owned: fmt::Debug,
1083    <[TLAS] as ToOwned>::Owned: fmt::Debug,
1084{
1085    Buffer(BufferBinding<B>),
1086    #[cfg_attr(
1087        feature = "serde",
1088        serde(bound(deserialize = "<[BufferBinding<B>] as ToOwned>::Owned: Deserialize<'de>"))
1089    )]
1090    BufferArray(Cow<'a, [BufferBinding<B>]>),
1091    Sampler(S),
1092    #[cfg_attr(
1093        feature = "serde",
1094        serde(bound(deserialize = "<[S] as ToOwned>::Owned: Deserialize<'de>"))
1095    )]
1096    SamplerArray(Cow<'a, [S]>),
1097    TextureView(TV),
1098    #[cfg_attr(
1099        feature = "serde",
1100        serde(bound(deserialize = "<[TV] as ToOwned>::Owned: Deserialize<'de>"))
1101    )]
1102    TextureViewArray(Cow<'a, [TV]>),
1103    AccelerationStructure(TLAS),
1104    #[cfg_attr(
1105        feature = "serde",
1106        serde(bound(deserialize = "<[TLAS] as ToOwned>::Owned: Deserialize<'de>"))
1107    )]
1108    AccelerationStructureArray(Cow<'a, [TLAS]>),
1109    ExternalTexture(ET),
1110}
1111
1112pub type ResolvedBindingResource<'a> = BindingResource<
1113    'a,
1114    Arc<Buffer>,
1115    Arc<Sampler>,
1116    Arc<TextureView>,
1117    Arc<Tlas>,
1118    Arc<ExternalTexture>,
1119>;
1120
1121#[derive(Clone, Debug, Error)]
1122#[non_exhaustive]
1123pub enum BindError {
1124    #[error(
1125        "Dynamic offsets not expected with null bind group at index {group}. However {actual} dynamic offset{s1} were provided.",
1126        s1 = if *.actual >= 2 { "s" } else { "" },
1127    )]
1128    DynamicOffsetCountNotZero { group: u32, actual: usize },
1129    #[error(
1130        "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
1131        s0 = if *.expected >= 2 { "s" } else { "" },
1132        s1 = if *.actual >= 2 { "s" } else { "" },
1133    )]
1134    MismatchedDynamicOffsetCount {
1135        bind_group: ResourceErrorIdent,
1136        group: u32,
1137        actual: usize,
1138        expected: usize,
1139    },
1140    #[error(
1141        "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
1142    )]
1143    UnalignedDynamicBinding {
1144        bind_group: ResourceErrorIdent,
1145        idx: usize,
1146        group: u32,
1147        binding: u32,
1148        offset: u32,
1149        alignment: u32,
1150        limit_name: &'static str,
1151    },
1152    #[error(
1153        "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \
1154         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",
1155    )]
1156    DynamicBindingOutOfBounds {
1157        bind_group: ResourceErrorIdent,
1158        idx: usize,
1159        group: u32,
1160        binding: u32,
1161        offset: u32,
1162        buffer_size: wgt::BufferAddress,
1163        binding_range: Range<wgt::BufferAddress>,
1164        maximum_dynamic_offset: wgt::BufferAddress,
1165    },
1166}
1167
1168impl WebGpuError for BindError {
1169    fn webgpu_error_type(&self) -> ErrorType {
1170        ErrorType::Validation
1171    }
1172}
1173
1174#[derive(Debug)]
1175pub struct BindGroupDynamicBindingData {
1176    /// The index of the binding.
1177    ///
1178    /// Used for more descriptive errors.
1179    pub(crate) binding_idx: u32,
1180    /// The size of the buffer.
1181    ///
1182    /// Used for more descriptive errors.
1183    pub(crate) buffer_size: wgt::BufferAddress,
1184    /// The range that the binding covers.
1185    ///
1186    /// Used for more descriptive errors.
1187    pub(crate) binding_range: Range<wgt::BufferAddress>,
1188    /// The maximum value the dynamic offset can have before running off the end of the buffer.
1189    pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
1190    /// The binding type.
1191    pub(crate) binding_type: wgt::BufferBindingType,
1192}
1193
1194pub(crate) fn buffer_binding_type_alignment(
1195    limits: &wgt::Limits,
1196    binding_type: wgt::BufferBindingType,
1197) -> (u32, &'static str) {
1198    match binding_type {
1199        wgt::BufferBindingType::Uniform => (
1200            limits.min_uniform_buffer_offset_alignment,
1201            "min_uniform_buffer_offset_alignment",
1202        ),
1203        wgt::BufferBindingType::Storage { .. } => (
1204            limits.min_storage_buffer_offset_alignment,
1205            "min_storage_buffer_offset_alignment",
1206        ),
1207    }
1208}
1209
1210pub(crate) fn buffer_binding_type_bounds_check_alignment(
1211    alignments: &hal::Alignments,
1212    binding_type: wgt::BufferBindingType,
1213) -> wgt::BufferAddress {
1214    match binding_type {
1215        wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(),
1216        wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT,
1217    }
1218}
1219
1220#[derive(Debug)]
1221pub(crate) struct BindGroupLateBufferBindingInfo {
1222    /// The normal binding index in the bind group.
1223    pub binding_index: u32,
1224    /// The size that exists at bind time.
1225    pub size: wgt::BufferSize,
1226}
1227
1228#[derive(Debug)]
1229pub struct BindGroup {
1230    pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>,
1231    pub(crate) device: Arc<Device>,
1232    pub(crate) layout: Arc<BindGroupLayout>,
1233    /// The `label` from the descriptor used to create the resource.
1234    pub(crate) label: String,
1235    pub(crate) tracking_data: TrackingData,
1236    pub(crate) used: BindGroupStates,
1237    pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
1238    pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
1239    pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
1240    /// Actual binding sizes for buffers that don't have `min_binding_size`
1241    /// specified in BGL. Listed in the order of iteration of `BGL.entries`.
1242    pub(crate) late_buffer_binding_infos: Vec<BindGroupLateBufferBindingInfo>,
1243}
1244
1245impl Drop for BindGroup {
1246    fn drop(&mut self) {
1247        if let Some(raw) = self.raw.take() {
1248            resource_log!("Destroy raw {}", self.error_ident());
1249            unsafe {
1250                self.device.raw().destroy_bind_group(raw);
1251            }
1252        }
1253    }
1254}
1255
1256impl BindGroup {
1257    pub(crate) fn try_raw<'a>(
1258        &'a self,
1259        guard: &'a SnatchGuard,
1260    ) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> {
1261        // Clippy insist on writing it this way. The idea is to return None
1262        // if any of the raw buffer is not valid anymore.
1263        for buffer in &self.used_buffer_ranges {
1264            buffer.buffer.try_raw(guard)?;
1265        }
1266        for texture in &self.used_texture_ranges {
1267            texture.texture.try_raw(guard)?;
1268        }
1269
1270        self.raw
1271            .get(guard)
1272            .map(|raw| raw.as_ref())
1273            .ok_or_else(|| DestroyedResourceError(self.error_ident()))
1274    }
1275
1276    pub(crate) fn validate_dynamic_bindings(
1277        &self,
1278        bind_group_index: u32,
1279        offsets: &[wgt::DynamicOffset],
1280    ) -> Result<(), BindError> {
1281        if self.dynamic_binding_info.len() != offsets.len() {
1282            return Err(BindError::MismatchedDynamicOffsetCount {
1283                bind_group: self.error_ident(),
1284                group: bind_group_index,
1285                expected: self.dynamic_binding_info.len(),
1286                actual: offsets.len(),
1287            });
1288        }
1289
1290        for (idx, (info, &offset)) in self
1291            .dynamic_binding_info
1292            .iter()
1293            .zip(offsets.iter())
1294            .enumerate()
1295        {
1296            let (alignment, limit_name) =
1297                buffer_binding_type_alignment(&self.device.limits, info.binding_type);
1298            if !(offset as wgt::BufferAddress).is_multiple_of(alignment as u64) {
1299                return Err(BindError::UnalignedDynamicBinding {
1300                    bind_group: self.error_ident(),
1301                    group: bind_group_index,
1302                    binding: info.binding_idx,
1303                    idx,
1304                    offset,
1305                    alignment,
1306                    limit_name,
1307                });
1308            }
1309
1310            if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
1311                return Err(BindError::DynamicBindingOutOfBounds {
1312                    bind_group: self.error_ident(),
1313                    group: bind_group_index,
1314                    binding: info.binding_idx,
1315                    idx,
1316                    offset,
1317                    buffer_size: info.buffer_size,
1318                    binding_range: info.binding_range.clone(),
1319                    maximum_dynamic_offset: info.maximum_dynamic_offset,
1320                });
1321            }
1322        }
1323
1324        Ok(())
1325    }
1326}
1327
1328crate::impl_resource_type!(BindGroup);
1329crate::impl_labeled!(BindGroup);
1330crate::impl_parent_device!(BindGroup);
1331crate::impl_storage_item!(BindGroup);
1332crate::impl_trackable!(BindGroup);
1333
1334#[derive(Clone, Debug, Error)]
1335#[non_exhaustive]
1336pub enum GetBindGroupLayoutError {
1337    #[error("Bind group layout index {index} is greater than the device's configured `max_bind_groups` limit {max}")]
1338    IndexOutOfRange { index: u32, max: u32 },
1339    #[error(transparent)]
1340    InvalidResource(#[from] InvalidResourceError),
1341}
1342
1343impl WebGpuError for GetBindGroupLayoutError {
1344    fn webgpu_error_type(&self) -> ErrorType {
1345        match self {
1346            Self::IndexOutOfRange { .. } => ErrorType::Validation,
1347            Self::InvalidResource(e) => e.webgpu_error_type(),
1348        }
1349    }
1350}
1351
1352#[derive(Clone, Debug, Error, Eq, PartialEq)]
1353#[error(
1354    "In bind group index {group_index}, the buffer bound at binding index {binding_index} \
1355     is bound with size {bound_size} where the shader expects {shader_size}."
1356)]
1357pub struct LateMinBufferBindingSizeMismatch {
1358    pub group_index: u32,
1359    pub binding_index: u32,
1360    pub shader_size: wgt::BufferAddress,
1361    pub bound_size: wgt::BufferAddress,
1362}