wgpu_core/
binding_model.rs

1use crate::{
2    device::{DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT},
3    error::{ErrorFormatter, PrettyError},
4    hal_api::HalApi,
5    id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureId, TextureViewId, Valid},
6    init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction},
7    resource::Resource,
8    track::{BindGroupStates, UsageConflict},
9    validation::{MissingBufferUsageError, MissingTextureUsageError},
10    FastHashMap, Label, LifeGuard, MultiRefCount, Stored,
11};
12
13use arrayvec::ArrayVec;
14
15#[cfg(feature = "replay")]
16use serde::Deserialize;
17#[cfg(feature = "trace")]
18use serde::Serialize;
19
20use std::{borrow::Cow, ops::Range};
21
22use thiserror::Error;
23
24#[derive(Clone, Debug, Error)]
25#[non_exhaustive]
26pub enum BindGroupLayoutEntryError {
27    #[error("Cube dimension is not expected for texture storage")]
28    StorageTextureCube,
29    #[error("Read-write and read-only storage textures are not allowed by webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
30    StorageTextureReadWrite,
31    #[error("Arrays of bindings unsupported for this type of binding")]
32    ArrayUnsupported,
33    #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")]
34    SampleTypeFloatFilterableBindingMultisampled,
35    #[error(transparent)]
36    MissingFeatures(#[from] MissingFeatures),
37    #[error(transparent)]
38    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
39}
40
41#[derive(Clone, Debug, Error)]
42#[non_exhaustive]
43pub enum CreateBindGroupLayoutError {
44    #[error(transparent)]
45    Device(#[from] DeviceError),
46    #[error("Conflicting binding at index {0}")]
47    ConflictBinding(u32),
48    #[error("Binding {binding} entry is invalid")]
49    Entry {
50        binding: u32,
51        #[source]
52        error: BindGroupLayoutEntryError,
53    },
54    #[error(transparent)]
55    TooManyBindings(BindingTypeMaxCountError),
56    #[error("Binding index {binding} is greater than the maximum index {maximum}")]
57    InvalidBindingIndex { binding: u32, maximum: u32 },
58    #[error("Invalid visibility {0:?}")]
59    InvalidVisibility(wgt::ShaderStages),
60}
61
62//TODO: refactor this to move out `enum BindingError`.
63
64#[derive(Clone, Debug, Error)]
65#[non_exhaustive]
66pub enum CreateBindGroupError {
67    #[error(transparent)]
68    Device(#[from] DeviceError),
69    #[error("Bind group layout is invalid")]
70    InvalidLayout,
71    #[error("Buffer {0:?} is invalid or destroyed")]
72    InvalidBuffer(BufferId),
73    #[error("Texture view {0:?} is invalid")]
74    InvalidTextureView(TextureViewId),
75    #[error("Texture {0:?} is invalid")]
76    InvalidTexture(TextureId),
77    #[error("Sampler {0:?} is invalid")]
78    InvalidSampler(SamplerId),
79    #[error(
80        "Binding count declared with at most {expected} items, but {actual} items were provided"
81    )]
82    BindingArrayPartialLengthMismatch { actual: usize, expected: usize },
83    #[error(
84        "Binding count declared with exactly {expected} items, but {actual} items were provided"
85    )]
86    BindingArrayLengthMismatch { actual: usize, expected: usize },
87    #[error("Array binding provided zero elements")]
88    BindingArrayZeroLength,
89    #[error("Bound buffer range {range:?} does not fit in buffer of size {size}")]
90    BindingRangeTooLarge {
91        buffer: BufferId,
92        range: Range<wgt::BufferAddress>,
93        size: u64,
94    },
95    #[error("Buffer binding size {actual} is less than minimum {min}")]
96    BindingSizeTooSmall {
97        buffer: BufferId,
98        actual: u64,
99        min: u64,
100    },
101    #[error("Buffer binding size is zero")]
102    BindingZeroSize(BufferId),
103    #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
104    BindingsNumMismatch { actual: usize, expected: usize },
105    #[error("Binding {0} is used at least twice in the descriptor")]
106    DuplicateBinding(u32),
107    #[error("Unable to find a corresponding declaration for the given binding {0}")]
108    MissingBindingDeclaration(u32),
109    #[error(transparent)]
110    MissingBufferUsage(#[from] MissingBufferUsageError),
111    #[error(transparent)]
112    MissingTextureUsage(#[from] MissingTextureUsageError),
113    #[error("Binding declared as a single item, but bind group is using it as an array")]
114    SingleBindingExpected,
115    #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")]
116    UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32),
117    #[error(
118        "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
119    )]
120    BufferRangeTooLarge {
121        binding: u32,
122        given: u32,
123        limit: u32,
124    },
125    #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
126    WrongBindingType {
127        // Index of the binding
128        binding: u32,
129        // The type given to the function
130        actual: wgt::BindingType,
131        // Human-readable description of expected types
132        expected: &'static str,
133    },
134    #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
135    InvalidTextureMultisample {
136        binding: u32,
137        layout_multisampled: bool,
138        view_samples: u32,
139    },
140    #[error("Texture binding {binding} expects sample type = {layout_sample_type:?}, but given a view with format = {view_format:?}")]
141    InvalidTextureSampleType {
142        binding: u32,
143        layout_sample_type: wgt::TextureSampleType,
144        view_format: wgt::TextureFormat,
145    },
146    #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
147    InvalidTextureDimension {
148        binding: u32,
149        layout_dimension: wgt::TextureViewDimension,
150        view_dimension: wgt::TextureViewDimension,
151    },
152    #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
153    InvalidStorageTextureFormat {
154        binding: u32,
155        layout_format: wgt::TextureFormat,
156        view_format: wgt::TextureFormat,
157    },
158    #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
159    InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 },
160    #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
161    WrongSamplerComparison {
162        binding: u32,
163        layout_cmp: bool,
164        sampler_cmp: bool,
165    },
166    #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
167    WrongSamplerFiltering {
168        binding: u32,
169        layout_flt: bool,
170        sampler_flt: bool,
171    },
172    #[error("Bound texture views can not have both depth and stencil aspects enabled")]
173    DepthStencilAspect,
174    #[error("The adapter does not support read access for storages texture of format {0:?}")]
175    StorageReadNotSupported(wgt::TextureFormat),
176    #[error(transparent)]
177    ResourceUsageConflict(#[from] UsageConflict),
178}
179
180impl PrettyError for CreateBindGroupError {
181    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
182        fmt.error(self);
183        match *self {
184            Self::BindingZeroSize(id) => {
185                fmt.buffer_label(&id);
186            }
187            Self::BindingRangeTooLarge { buffer, .. } => {
188                fmt.buffer_label(&buffer);
189            }
190            Self::BindingSizeTooSmall { buffer, .. } => {
191                fmt.buffer_label(&buffer);
192            }
193            Self::InvalidBuffer(id) => {
194                fmt.buffer_label(&id);
195            }
196            Self::InvalidTextureView(id) => {
197                fmt.texture_view_label(&id);
198            }
199            Self::InvalidSampler(id) => {
200                fmt.sampler_label(&id);
201            }
202            _ => {}
203        };
204    }
205}
206
207#[derive(Clone, Debug, Error)]
208pub enum BindingZone {
209    #[error("Stage {0:?}")]
210    Stage(wgt::ShaderStages),
211    #[error("Whole pipeline")]
212    Pipeline,
213}
214
215#[derive(Clone, Debug, Error)]
216#[error("Too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}")]
217pub struct BindingTypeMaxCountError {
218    pub kind: BindingTypeMaxCountErrorKind,
219    pub zone: BindingZone,
220    pub limit: u32,
221    pub count: u32,
222}
223
224#[derive(Clone, Debug)]
225pub enum BindingTypeMaxCountErrorKind {
226    DynamicUniformBuffers,
227    DynamicStorageBuffers,
228    SampledTextures,
229    Samplers,
230    StorageBuffers,
231    StorageTextures,
232    UniformBuffers,
233}
234
235#[derive(Debug, Default)]
236pub(crate) struct PerStageBindingTypeCounter {
237    vertex: u32,
238    fragment: u32,
239    compute: u32,
240}
241
242impl PerStageBindingTypeCounter {
243    pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
244        if stage.contains(wgt::ShaderStages::VERTEX) {
245            self.vertex += count;
246        }
247        if stage.contains(wgt::ShaderStages::FRAGMENT) {
248            self.fragment += count;
249        }
250        if stage.contains(wgt::ShaderStages::COMPUTE) {
251            self.compute += count;
252        }
253    }
254
255    pub(crate) fn max(&self) -> (BindingZone, u32) {
256        let max_value = self.vertex.max(self.fragment.max(self.compute));
257        let mut stage = wgt::ShaderStages::NONE;
258        if max_value == self.vertex {
259            stage |= wgt::ShaderStages::VERTEX
260        }
261        if max_value == self.fragment {
262            stage |= wgt::ShaderStages::FRAGMENT
263        }
264        if max_value == self.compute {
265            stage |= wgt::ShaderStages::COMPUTE
266        }
267        (BindingZone::Stage(stage), max_value)
268    }
269
270    pub(crate) fn merge(&mut self, other: &Self) {
271        self.vertex = self.vertex.max(other.vertex);
272        self.fragment = self.fragment.max(other.fragment);
273        self.compute = self.compute.max(other.compute);
274    }
275
276    pub(crate) fn validate(
277        &self,
278        limit: u32,
279        kind: BindingTypeMaxCountErrorKind,
280    ) -> Result<(), BindingTypeMaxCountError> {
281        let (zone, count) = self.max();
282        if limit < count {
283            Err(BindingTypeMaxCountError {
284                kind,
285                zone,
286                limit,
287                count,
288            })
289        } else {
290            Ok(())
291        }
292    }
293}
294
295#[derive(Debug, Default)]
296pub(crate) struct BindingTypeMaxCountValidator {
297    dynamic_uniform_buffers: u32,
298    dynamic_storage_buffers: u32,
299    sampled_textures: PerStageBindingTypeCounter,
300    samplers: PerStageBindingTypeCounter,
301    storage_buffers: PerStageBindingTypeCounter,
302    storage_textures: PerStageBindingTypeCounter,
303    uniform_buffers: PerStageBindingTypeCounter,
304}
305
306impl BindingTypeMaxCountValidator {
307    pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
308        let count = binding.count.map_or(1, |count| count.get());
309        match binding.ty {
310            wgt::BindingType::Buffer {
311                ty: wgt::BufferBindingType::Uniform,
312                has_dynamic_offset,
313                ..
314            } => {
315                self.uniform_buffers.add(binding.visibility, count);
316                if has_dynamic_offset {
317                    self.dynamic_uniform_buffers += count;
318                }
319            }
320            wgt::BindingType::Buffer {
321                ty: wgt::BufferBindingType::Storage { .. },
322                has_dynamic_offset,
323                ..
324            } => {
325                self.storage_buffers.add(binding.visibility, count);
326                if has_dynamic_offset {
327                    self.dynamic_storage_buffers += count;
328                }
329            }
330            wgt::BindingType::Sampler { .. } => {
331                self.samplers.add(binding.visibility, count);
332            }
333            wgt::BindingType::Texture { .. } => {
334                self.sampled_textures.add(binding.visibility, count);
335            }
336            wgt::BindingType::StorageTexture { .. } => {
337                self.storage_textures.add(binding.visibility, count);
338            }
339        }
340    }
341
342    pub(crate) fn merge(&mut self, other: &Self) {
343        self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
344        self.dynamic_storage_buffers += other.dynamic_storage_buffers;
345        self.sampled_textures.merge(&other.sampled_textures);
346        self.samplers.merge(&other.samplers);
347        self.storage_buffers.merge(&other.storage_buffers);
348        self.storage_textures.merge(&other.storage_textures);
349        self.uniform_buffers.merge(&other.uniform_buffers);
350    }
351
352    pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
353        if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
354            return Err(BindingTypeMaxCountError {
355                kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
356                zone: BindingZone::Pipeline,
357                limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
358                count: self.dynamic_uniform_buffers,
359            });
360        }
361        if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
362            return Err(BindingTypeMaxCountError {
363                kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
364                zone: BindingZone::Pipeline,
365                limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
366                count: self.dynamic_storage_buffers,
367            });
368        }
369        self.sampled_textures.validate(
370            limits.max_sampled_textures_per_shader_stage,
371            BindingTypeMaxCountErrorKind::SampledTextures,
372        )?;
373        self.storage_buffers.validate(
374            limits.max_storage_buffers_per_shader_stage,
375            BindingTypeMaxCountErrorKind::StorageBuffers,
376        )?;
377        self.samplers.validate(
378            limits.max_samplers_per_shader_stage,
379            BindingTypeMaxCountErrorKind::Samplers,
380        )?;
381        self.storage_buffers.validate(
382            limits.max_storage_buffers_per_shader_stage,
383            BindingTypeMaxCountErrorKind::StorageBuffers,
384        )?;
385        self.storage_textures.validate(
386            limits.max_storage_textures_per_shader_stage,
387            BindingTypeMaxCountErrorKind::StorageTextures,
388        )?;
389        self.uniform_buffers.validate(
390            limits.max_uniform_buffers_per_shader_stage,
391            BindingTypeMaxCountErrorKind::UniformBuffers,
392        )?;
393        Ok(())
394    }
395}
396
397/// Bindable resource and the slot to bind it to.
398#[derive(Clone, Debug)]
399#[cfg_attr(feature = "trace", derive(Serialize))]
400#[cfg_attr(feature = "replay", derive(Deserialize))]
401pub struct BindGroupEntry<'a> {
402    /// Slot for which binding provides resource. Corresponds to an entry of the same
403    /// binding index in the [`BindGroupLayoutDescriptor`].
404    pub binding: u32,
405    /// Resource to attach to the binding
406    pub resource: BindingResource<'a>,
407}
408
409/// Describes a group of bindings and the resources to be bound.
410#[derive(Clone, Debug)]
411#[cfg_attr(feature = "trace", derive(Serialize))]
412#[cfg_attr(feature = "replay", derive(Deserialize))]
413pub struct BindGroupDescriptor<'a> {
414    /// Debug label of the bind group.
415    ///
416    /// This will show up in graphics debuggers for easy identification.
417    pub label: Label<'a>,
418    /// The [`BindGroupLayout`] that corresponds to this bind group.
419    pub layout: BindGroupLayoutId,
420    /// The resources to bind to this bind group.
421    pub entries: Cow<'a, [BindGroupEntry<'a>]>,
422}
423
424/// Describes a [`BindGroupLayout`].
425#[derive(Clone, Debug)]
426#[cfg_attr(feature = "trace", derive(serde::Serialize))]
427#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
428pub struct BindGroupLayoutDescriptor<'a> {
429    /// Debug label of the bind group layout.
430    ///
431    /// This will show up in graphics debuggers for easy identification.
432    pub label: Label<'a>,
433    /// Array of entries in this BindGroupLayout
434    pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
435}
436
437pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
438
439/// Bind group layout.
440///
441/// The lifetime of BGLs is a bit special. They are only referenced on CPU
442/// without considering GPU operations. And on CPU they get manual
443/// inc-refs and dec-refs. In particular, the following objects depend on them:
444///  - produced bind groups
445///  - produced pipeline layouts
446///  - pipelines with implicit layouts
447#[derive(Debug)]
448pub struct BindGroupLayout<A: hal::Api> {
449    pub(crate) raw: A::BindGroupLayout,
450    pub(crate) device_id: Stored<DeviceId>,
451    pub(crate) multi_ref_count: MultiRefCount,
452    pub(crate) entries: BindEntryMap,
453    #[allow(unused)]
454    pub(crate) dynamic_count: usize,
455    pub(crate) count_validator: BindingTypeMaxCountValidator,
456    #[cfg(debug_assertions)]
457    pub(crate) label: String,
458}
459
460impl<A: hal::Api> Resource for BindGroupLayout<A> {
461    const TYPE: &'static str = "BindGroupLayout";
462
463    fn life_guard(&self) -> &LifeGuard {
464        unreachable!()
465    }
466
467    fn label(&self) -> &str {
468        #[cfg(debug_assertions)]
469        return &self.label;
470        #[cfg(not(debug_assertions))]
471        return "";
472    }
473}
474
475#[derive(Clone, Debug, Error)]
476#[non_exhaustive]
477pub enum CreatePipelineLayoutError {
478    #[error(transparent)]
479    Device(#[from] DeviceError),
480    #[error("Bind group layout {0:?} is invalid")]
481    InvalidBindGroupLayout(BindGroupLayoutId),
482    #[error(
483        "Push constant at index {index} has range bound {bound} not aligned to {}",
484        wgt::PUSH_CONSTANT_ALIGNMENT
485    )]
486    MisalignedPushConstantRange { index: usize, bound: u32 },
487    #[error(transparent)]
488    MissingFeatures(#[from] MissingFeatures),
489    #[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")]
490    MoreThanOnePushConstantRangePerStage {
491        index: usize,
492        provided: wgt::ShaderStages,
493        intersected: wgt::ShaderStages,
494    },
495    #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
496    PushConstantRangeTooLarge {
497        index: usize,
498        range: Range<u32>,
499        max: u32,
500    },
501    #[error(transparent)]
502    TooManyBindings(BindingTypeMaxCountError),
503    #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
504    TooManyGroups { actual: usize, max: usize },
505}
506
507impl PrettyError for CreatePipelineLayoutError {
508    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
509        fmt.error(self);
510        if let Self::InvalidBindGroupLayout(id) = *self {
511            fmt.bind_group_layout_label(&id);
512        };
513    }
514}
515
516#[derive(Clone, Debug, Error)]
517#[non_exhaustive]
518pub enum PushConstantUploadError {
519    #[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)]
520    TooLarge {
521        offset: u32,
522        end_offset: u32,
523        idx: usize,
524        range: wgt::PushConstantRange,
525    },
526    #[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")]
527    PartialRangeMatch {
528        actual: wgt::ShaderStages,
529        idx: usize,
530        matched: wgt::ShaderStages,
531    },
532    #[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")]
533    MissingStages {
534        actual: wgt::ShaderStages,
535        idx: usize,
536        missing: wgt::ShaderStages,
537    },
538    #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
539    UnmatchedStages {
540        actual: wgt::ShaderStages,
541        unmatched: wgt::ShaderStages,
542    },
543    #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
544    Unaligned(u32),
545}
546
547/// Describes a pipeline layout.
548///
549/// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
550#[derive(Clone, Debug, PartialEq, Eq, Hash)]
551#[cfg_attr(feature = "trace", derive(Serialize))]
552#[cfg_attr(feature = "replay", derive(Deserialize))]
553pub struct PipelineLayoutDescriptor<'a> {
554    /// Debug label of the pipeine layout.
555    ///
556    /// This will show up in graphics debuggers for easy identification.
557    pub label: Label<'a>,
558    /// Bind groups that this pipeline uses. The first entry will provide all the bindings for
559    /// "set = 0", second entry will provide all the bindings for "set = 1" etc.
560    pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
561    /// Set of push constant ranges this pipeline uses. Each shader stage that
562    /// uses push constants must define the range in push constant memory that
563    /// corresponds to its single `layout(push_constant)` uniform block.
564    ///
565    /// If this array is non-empty, the
566    /// [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) feature must
567    /// be enabled.
568    pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
569}
570
571#[derive(Debug)]
572pub struct PipelineLayout<A: hal::Api> {
573    pub(crate) raw: A::PipelineLayout,
574    pub(crate) device_id: Stored<DeviceId>,
575    pub(crate) life_guard: LifeGuard,
576    pub(crate) bind_group_layout_ids: ArrayVec<Valid<BindGroupLayoutId>, { hal::MAX_BIND_GROUPS }>,
577    pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
578}
579
580impl<A: hal::Api> PipelineLayout<A> {
581    /// Validate push constants match up with expected ranges.
582    pub(crate) fn validate_push_constant_ranges(
583        &self,
584        stages: wgt::ShaderStages,
585        offset: u32,
586        end_offset: u32,
587    ) -> Result<(), PushConstantUploadError> {
588        // Don't need to validate size against the push constant size limit here,
589        // as push constant ranges are already validated to be within bounds,
590        // and we validate that they are within the ranges.
591
592        if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
593            return Err(PushConstantUploadError::Unaligned(offset));
594        }
595
596        // Push constant validation looks very complicated on the surface, but
597        // the problem can be range-reduced pretty well.
598        //
599        // Push constants require (summarized from the vulkan spec):
600        // 1. For each byte in the range and for each shader stage in stageFlags,
601        //    there must be a push constant range in the layout that includes that
602        //    byte and that stage.
603        // 2. For each byte in the range and for each push constant range that overlaps that byte,
604        //    `stage` must include all stages in that push constant range’s `stage`.
605        //
606        // However there are some additional constraints that help us:
607        // 3. All push constant ranges are the only range that can access that stage.
608        //    i.e. if one range has VERTEX, no other range has VERTEX
609        //
610        // Therefore we can simplify the checks in the following ways:
611        // - Because 3 guarantees that the push constant range has a unique stage,
612        //   when we check for 1, we can simply check that our entire updated range
613        //   is within a push constant range. i.e. our range for a specific stage cannot
614        //   intersect more than one push constant range.
615        let mut used_stages = wgt::ShaderStages::NONE;
616        for (idx, range) in self.push_constant_ranges.iter().enumerate() {
617            // contains not intersects due to 2
618            if stages.contains(range.stages) {
619                if !(range.range.start <= offset && end_offset <= range.range.end) {
620                    return Err(PushConstantUploadError::TooLarge {
621                        offset,
622                        end_offset,
623                        idx,
624                        range: range.clone(),
625                    });
626                }
627                used_stages |= range.stages;
628            } else if stages.intersects(range.stages) {
629                // Will be caught by used stages check below, but we can do this because of 1
630                // and is more helpful to the user.
631                return Err(PushConstantUploadError::PartialRangeMatch {
632                    actual: stages,
633                    idx,
634                    matched: range.stages,
635                });
636            }
637
638            // The push constant range intersects range we are uploading
639            if offset < range.range.end && range.range.start < end_offset {
640                // But requires stages we don't provide
641                if !stages.contains(range.stages) {
642                    return Err(PushConstantUploadError::MissingStages {
643                        actual: stages,
644                        idx,
645                        missing: stages,
646                    });
647                }
648            }
649        }
650        if used_stages != stages {
651            return Err(PushConstantUploadError::UnmatchedStages {
652                actual: stages,
653                unmatched: stages - used_stages,
654            });
655        }
656        Ok(())
657    }
658}
659
660impl<A: hal::Api> Resource for PipelineLayout<A> {
661    const TYPE: &'static str = "PipelineLayout";
662
663    fn life_guard(&self) -> &LifeGuard {
664        &self.life_guard
665    }
666}
667
668#[repr(C)]
669#[derive(Clone, Debug, Hash, Eq, PartialEq)]
670#[cfg_attr(feature = "trace", derive(Serialize))]
671#[cfg_attr(feature = "replay", derive(Deserialize))]
672pub struct BufferBinding {
673    pub buffer_id: BufferId,
674    pub offset: wgt::BufferAddress,
675    pub size: Option<wgt::BufferSize>,
676}
677
678// Note: Duplicated in `wgpu-rs` as `BindingResource`
679// They're different enough that it doesn't make sense to share a common type
680#[derive(Debug, Clone)]
681#[cfg_attr(feature = "trace", derive(serde::Serialize))]
682#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
683pub enum BindingResource<'a> {
684    Buffer(BufferBinding),
685    BufferArray(Cow<'a, [BufferBinding]>),
686    Sampler(SamplerId),
687    SamplerArray(Cow<'a, [SamplerId]>),
688    TextureView(TextureViewId),
689    TextureViewArray(Cow<'a, [TextureViewId]>),
690}
691
692#[derive(Clone, Debug, Error)]
693#[non_exhaustive]
694pub enum BindError {
695    #[error(
696        "Bind group {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
697        s0 = if *.expected >= 2 { "s" } else { "" },
698        s1 = if *.actual >= 2 { "s" } else { "" },
699    )]
700    MismatchedDynamicOffsetCount {
701        group: u32,
702        actual: usize,
703        expected: usize,
704    },
705    #[error(
706        "Dynamic binding index {idx} (targeting bind group {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
707    )]
708    UnalignedDynamicBinding {
709        idx: usize,
710        group: u32,
711        binding: u32,
712        offset: u32,
713        alignment: u32,
714        limit_name: &'static str,
715    },
716    #[error(
717        "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to bind group {group} -> binding {binding}. \
718         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",
719    )]
720    DynamicBindingOutOfBounds {
721        idx: usize,
722        group: u32,
723        binding: u32,
724        offset: u32,
725        buffer_size: wgt::BufferAddress,
726        binding_range: Range<wgt::BufferAddress>,
727        maximum_dynamic_offset: wgt::BufferAddress,
728    },
729}
730
731#[derive(Debug)]
732pub struct BindGroupDynamicBindingData {
733    /// The index of the binding.
734    ///
735    /// Used for more descriptive errors.
736    pub(crate) binding_idx: u32,
737    /// The size of the buffer.
738    ///
739    /// Used for more descriptive errors.
740    pub(crate) buffer_size: wgt::BufferAddress,
741    /// The range that the binding covers.
742    ///
743    /// Used for more descriptive errors.
744    pub(crate) binding_range: Range<wgt::BufferAddress>,
745    /// The maximum value the dynamic offset can have before running off the end of the buffer.
746    pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
747    /// The binding type.
748    pub(crate) binding_type: wgt::BufferBindingType,
749}
750
751pub(crate) fn buffer_binding_type_alignment(
752    limits: &wgt::Limits,
753    binding_type: wgt::BufferBindingType,
754) -> (u32, &'static str) {
755    match binding_type {
756        wgt::BufferBindingType::Uniform => (
757            limits.min_uniform_buffer_offset_alignment,
758            "min_uniform_buffer_offset_alignment",
759        ),
760        wgt::BufferBindingType::Storage { .. } => (
761            limits.min_storage_buffer_offset_alignment,
762            "min_storage_buffer_offset_alignment",
763        ),
764    }
765}
766
767pub struct BindGroup<A: HalApi> {
768    pub(crate) raw: A::BindGroup,
769    pub(crate) device_id: Stored<DeviceId>,
770    pub(crate) layout_id: Valid<BindGroupLayoutId>,
771    pub(crate) life_guard: LifeGuard,
772    pub(crate) used: BindGroupStates<A>,
773    pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
774    pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
775    pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
776    /// Actual binding sizes for buffers that don't have `min_binding_size`
777    /// specified in BGL. Listed in the order of iteration of `BGL.entries`.
778    pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
779}
780
781impl<A: HalApi> BindGroup<A> {
782    pub(crate) fn validate_dynamic_bindings(
783        &self,
784        bind_group_index: u32,
785        offsets: &[wgt::DynamicOffset],
786        limits: &wgt::Limits,
787    ) -> Result<(), BindError> {
788        if self.dynamic_binding_info.len() != offsets.len() {
789            return Err(BindError::MismatchedDynamicOffsetCount {
790                group: bind_group_index,
791                expected: self.dynamic_binding_info.len(),
792                actual: offsets.len(),
793            });
794        }
795
796        for (idx, (info, &offset)) in self
797            .dynamic_binding_info
798            .iter()
799            .zip(offsets.iter())
800            .enumerate()
801        {
802            let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type);
803            if offset as wgt::BufferAddress % alignment as u64 != 0 {
804                return Err(BindError::UnalignedDynamicBinding {
805                    group: bind_group_index,
806                    binding: info.binding_idx,
807                    idx,
808                    offset,
809                    alignment,
810                    limit_name,
811                });
812            }
813
814            if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
815                return Err(BindError::DynamicBindingOutOfBounds {
816                    group: bind_group_index,
817                    binding: info.binding_idx,
818                    idx,
819                    offset,
820                    buffer_size: info.buffer_size,
821                    binding_range: info.binding_range.clone(),
822                    maximum_dynamic_offset: info.maximum_dynamic_offset,
823                });
824            }
825        }
826
827        Ok(())
828    }
829}
830
831impl<A: HalApi> Resource for BindGroup<A> {
832    const TYPE: &'static str = "BindGroup";
833
834    fn life_guard(&self) -> &LifeGuard {
835        &self.life_guard
836    }
837}
838
839#[derive(Clone, Debug, Error)]
840#[non_exhaustive]
841pub enum GetBindGroupLayoutError {
842    #[error("Pipeline is invalid")]
843    InvalidPipeline,
844    #[error("Invalid group index {0}")]
845    InvalidGroupIndex(u32),
846}
847
848#[derive(Clone, Debug, Error, Eq, PartialEq)]
849#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
850pub struct LateMinBufferBindingSizeMismatch {
851    pub group_index: u32,
852    pub compact_index: usize,
853    pub shader_size: wgt::BufferAddress,
854    pub bound_size: wgt::BufferAddress,
855}