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