wgpu_core/
binding_model.rs

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