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