li_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
439pub type BindGroupLayouts<A> = crate::storage::Storage<BindGroupLayout<A>, BindGroupLayoutId>;
440
441/// Bind group layout.
442///
443/// The lifetime of BGLs is a bit special. They are only referenced on CPU
444/// without considering GPU operations. And on CPU they get manual
445/// inc-refs and dec-refs. In particular, the following objects depend on them:
446///  - produced bind groups
447///  - produced pipeline layouts
448///  - pipelines with implicit layouts
449pub struct BindGroupLayout<A: hal::Api> {
450    pub(crate) device_id: Stored<DeviceId>,
451    pub(crate) multi_ref_count: MultiRefCount,
452    // When a layout created and there already exists a compatible layout the new layout
453    // keeps a reference to the older compatible one. In some places we substitute the
454    // bind group layout id with its compatible sibling.
455    // Since this substitution can come at a cost, it is skipped when wgpu-core generates
456    // its own resource IDs.
457    pub(crate) inner: BglOrDuplicate<A>,
458}
459
460pub(crate) enum BglOrDuplicate<A: hal::Api> {
461    Inner(BindGroupLayoutInner<A>),
462    Duplicate(Valid<BindGroupLayoutId>),
463}
464
465pub struct BindGroupLayoutInner<A: hal::Api> {
466    pub(crate) raw: A::BindGroupLayout,
467    pub(crate) entries: BindEntryMap,
468    #[allow(unused)]
469    pub(crate) dynamic_count: usize,
470    pub(crate) count_validator: BindingTypeMaxCountValidator,
471    #[cfg(debug_assertions)]
472    pub(crate) label: String,
473}
474
475impl<A: hal::Api> BindGroupLayout<A> {
476    #[track_caller]
477    pub(crate) fn assume_deduplicated(&self) -> &BindGroupLayoutInner<A> {
478        self.as_inner().unwrap()
479    }
480
481    pub(crate) fn as_inner(&self) -> Option<&BindGroupLayoutInner<A>> {
482        match self.inner {
483            BglOrDuplicate::Inner(ref inner) => Some(inner),
484            BglOrDuplicate::Duplicate(_) => None,
485        }
486    }
487
488    pub(crate) fn into_inner(self) -> Option<BindGroupLayoutInner<A>> {
489        match self.inner {
490            BglOrDuplicate::Inner(inner) => Some(inner),
491            BglOrDuplicate::Duplicate(_) => None,
492        }
493    }
494
495    pub(crate) fn as_duplicate(&self) -> Option<Valid<BindGroupLayoutId>> {
496        match self.inner {
497            BglOrDuplicate::Duplicate(id) => Some(id),
498            BglOrDuplicate::Inner(_) => None,
499        }
500    }
501}
502
503impl<A: hal::Api> Resource for BindGroupLayout<A> {
504    const TYPE: &'static str = "BindGroupLayout";
505
506    fn life_guard(&self) -> &LifeGuard {
507        unreachable!()
508    }
509
510    fn label(&self) -> &str {
511        #[cfg(debug_assertions)]
512        return self.as_inner().map_or("", |inner| &inner.label);
513        #[cfg(not(debug_assertions))]
514        return "";
515    }
516}
517
518// If a bindgroup needs to be substitued with its compatible equivalent, return the latter.
519pub(crate) fn try_get_bind_group_layout<A: HalApi>(
520    layouts: &BindGroupLayouts<A>,
521    id: BindGroupLayoutId,
522) -> Option<&BindGroupLayout<A>> {
523    let layout = layouts.get(id).ok()?;
524    if let BglOrDuplicate::Duplicate(original_id) = layout.inner {
525        return Some(&layouts[original_id]);
526    }
527
528    Some(layout)
529}
530
531pub(crate) fn get_bind_group_layout<A: HalApi>(
532    layouts: &BindGroupLayouts<A>,
533    id: Valid<BindGroupLayoutId>,
534) -> (Valid<BindGroupLayoutId>, &BindGroupLayout<A>) {
535    let layout = &layouts[id];
536    layout
537        .as_duplicate()
538        .map_or((id, layout), |deduped| (deduped, &layouts[deduped]))
539}
540
541#[derive(Clone, Debug, Error)]
542#[non_exhaustive]
543pub enum CreatePipelineLayoutError {
544    #[error(transparent)]
545    Device(#[from] DeviceError),
546    #[error("Bind group layout {0:?} is invalid")]
547    InvalidBindGroupLayout(BindGroupLayoutId),
548    #[error(
549        "Push constant at index {index} has range bound {bound} not aligned to {}",
550        wgt::PUSH_CONSTANT_ALIGNMENT
551    )]
552    MisalignedPushConstantRange { index: usize, bound: u32 },
553    #[error(transparent)]
554    MissingFeatures(#[from] MissingFeatures),
555    #[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")]
556    MoreThanOnePushConstantRangePerStage {
557        index: usize,
558        provided: wgt::ShaderStages,
559        intersected: wgt::ShaderStages,
560    },
561    #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
562    PushConstantRangeTooLarge {
563        index: usize,
564        range: Range<u32>,
565        max: u32,
566    },
567    #[error(transparent)]
568    TooManyBindings(BindingTypeMaxCountError),
569    #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
570    TooManyGroups { actual: usize, max: usize },
571}
572
573impl PrettyError for CreatePipelineLayoutError {
574    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
575        fmt.error(self);
576        if let Self::InvalidBindGroupLayout(id) = *self {
577            fmt.bind_group_layout_label(&id);
578        };
579    }
580}
581
582#[derive(Clone, Debug, Error)]
583#[non_exhaustive]
584pub enum PushConstantUploadError {
585    #[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)]
586    TooLarge {
587        offset: u32,
588        end_offset: u32,
589        idx: usize,
590        range: wgt::PushConstantRange,
591    },
592    #[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")]
593    PartialRangeMatch {
594        actual: wgt::ShaderStages,
595        idx: usize,
596        matched: wgt::ShaderStages,
597    },
598    #[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")]
599    MissingStages {
600        actual: wgt::ShaderStages,
601        idx: usize,
602        missing: wgt::ShaderStages,
603    },
604    #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
605    UnmatchedStages {
606        actual: wgt::ShaderStages,
607        unmatched: wgt::ShaderStages,
608    },
609    #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
610    Unaligned(u32),
611}
612
613/// Describes a pipeline layout.
614///
615/// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
616#[derive(Clone, Debug, PartialEq, Eq, Hash)]
617#[cfg_attr(feature = "trace", derive(Serialize))]
618#[cfg_attr(feature = "replay", derive(Deserialize))]
619pub struct PipelineLayoutDescriptor<'a> {
620    /// Debug label of the pipeine 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#[derive(Debug)]
638pub struct PipelineLayout<A: hal::Api> {
639    pub(crate) raw: A::PipelineLayout,
640    pub(crate) device_id: Stored<DeviceId>,
641    pub(crate) life_guard: LifeGuard,
642    pub(crate) bind_group_layout_ids: ArrayVec<Valid<BindGroupLayoutId>, { hal::MAX_BIND_GROUPS }>,
643    pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
644}
645
646impl<A: hal::Api> PipelineLayout<A> {
647    /// Validate push constants match up with expected ranges.
648    pub(crate) fn validate_push_constant_ranges(
649        &self,
650        stages: wgt::ShaderStages,
651        offset: u32,
652        end_offset: u32,
653    ) -> Result<(), PushConstantUploadError> {
654        // Don't need to validate size against the push constant size limit here,
655        // as push constant ranges are already validated to be within bounds,
656        // and we validate that they are within the ranges.
657
658        if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
659            return Err(PushConstantUploadError::Unaligned(offset));
660        }
661
662        // Push constant validation looks very complicated on the surface, but
663        // the problem can be range-reduced pretty well.
664        //
665        // Push constants require (summarized from the vulkan spec):
666        // 1. For each byte in the range and for each shader stage in stageFlags,
667        //    there must be a push constant range in the layout that includes that
668        //    byte and that stage.
669        // 2. For each byte in the range and for each push constant range that overlaps that byte,
670        //    `stage` must include all stages in that push constant range’s `stage`.
671        //
672        // However there are some additional constraints that help us:
673        // 3. All push constant ranges are the only range that can access that stage.
674        //    i.e. if one range has VERTEX, no other range has VERTEX
675        //
676        // Therefore we can simplify the checks in the following ways:
677        // - Because 3 guarantees that the push constant range has a unique stage,
678        //   when we check for 1, we can simply check that our entire updated range
679        //   is within a push constant range. i.e. our range for a specific stage cannot
680        //   intersect more than one push constant range.
681        let mut used_stages = wgt::ShaderStages::NONE;
682        for (idx, range) in self.push_constant_ranges.iter().enumerate() {
683            // contains not intersects due to 2
684            if stages.contains(range.stages) {
685                if !(range.range.start <= offset && end_offset <= range.range.end) {
686                    return Err(PushConstantUploadError::TooLarge {
687                        offset,
688                        end_offset,
689                        idx,
690                        range: range.clone(),
691                    });
692                }
693                used_stages |= range.stages;
694            } else if stages.intersects(range.stages) {
695                // Will be caught by used stages check below, but we can do this because of 1
696                // and is more helpful to the user.
697                return Err(PushConstantUploadError::PartialRangeMatch {
698                    actual: stages,
699                    idx,
700                    matched: range.stages,
701                });
702            }
703
704            // The push constant range intersects range we are uploading
705            if offset < range.range.end && range.range.start < end_offset {
706                // But requires stages we don't provide
707                if !stages.contains(range.stages) {
708                    return Err(PushConstantUploadError::MissingStages {
709                        actual: stages,
710                        idx,
711                        missing: stages,
712                    });
713                }
714            }
715        }
716        if used_stages != stages {
717            return Err(PushConstantUploadError::UnmatchedStages {
718                actual: stages,
719                unmatched: stages - used_stages,
720            });
721        }
722        Ok(())
723    }
724}
725
726impl<A: hal::Api> Resource for PipelineLayout<A> {
727    const TYPE: &'static str = "PipelineLayout";
728
729    fn life_guard(&self) -> &LifeGuard {
730        &self.life_guard
731    }
732}
733
734#[repr(C)]
735#[derive(Clone, Debug, Hash, Eq, PartialEq)]
736#[cfg_attr(feature = "trace", derive(Serialize))]
737#[cfg_attr(feature = "replay", derive(Deserialize))]
738pub struct BufferBinding {
739    pub buffer_id: BufferId,
740    pub offset: wgt::BufferAddress,
741    pub size: Option<wgt::BufferSize>,
742}
743
744// Note: Duplicated in `wgpu-rs` as `BindingResource`
745// They're different enough that it doesn't make sense to share a common type
746#[derive(Debug, Clone)]
747#[cfg_attr(feature = "trace", derive(serde::Serialize))]
748#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
749pub enum BindingResource<'a> {
750    Buffer(BufferBinding),
751    BufferArray(Cow<'a, [BufferBinding]>),
752    Sampler(SamplerId),
753    SamplerArray(Cow<'a, [SamplerId]>),
754    TextureView(TextureViewId),
755    TextureViewArray(Cow<'a, [TextureViewId]>),
756}
757
758#[derive(Clone, Debug, Error)]
759#[non_exhaustive]
760pub enum BindError {
761    #[error(
762        "Bind group {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
763        s0 = if *.expected >= 2 { "s" } else { "" },
764        s1 = if *.actual >= 2 { "s" } else { "" },
765    )]
766    MismatchedDynamicOffsetCount {
767        group: u32,
768        actual: usize,
769        expected: usize,
770    },
771    #[error(
772        "Dynamic binding index {idx} (targeting bind group {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
773    )]
774    UnalignedDynamicBinding {
775        idx: usize,
776        group: u32,
777        binding: u32,
778        offset: u32,
779        alignment: u32,
780        limit_name: &'static str,
781    },
782    #[error(
783        "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to bind group {group} -> binding {binding}. \
784         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",
785    )]
786    DynamicBindingOutOfBounds {
787        idx: usize,
788        group: u32,
789        binding: u32,
790        offset: u32,
791        buffer_size: wgt::BufferAddress,
792        binding_range: Range<wgt::BufferAddress>,
793        maximum_dynamic_offset: wgt::BufferAddress,
794    },
795}
796
797#[derive(Debug)]
798pub struct BindGroupDynamicBindingData {
799    /// The index of the binding.
800    ///
801    /// Used for more descriptive errors.
802    pub(crate) binding_idx: u32,
803    /// The size of the buffer.
804    ///
805    /// Used for more descriptive errors.
806    pub(crate) buffer_size: wgt::BufferAddress,
807    /// The range that the binding covers.
808    ///
809    /// Used for more descriptive errors.
810    pub(crate) binding_range: Range<wgt::BufferAddress>,
811    /// The maximum value the dynamic offset can have before running off the end of the buffer.
812    pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
813    /// The binding type.
814    pub(crate) binding_type: wgt::BufferBindingType,
815}
816
817pub(crate) fn buffer_binding_type_alignment(
818    limits: &wgt::Limits,
819    binding_type: wgt::BufferBindingType,
820) -> (u32, &'static str) {
821    match binding_type {
822        wgt::BufferBindingType::Uniform => (
823            limits.min_uniform_buffer_offset_alignment,
824            "min_uniform_buffer_offset_alignment",
825        ),
826        wgt::BufferBindingType::Storage { .. } => (
827            limits.min_storage_buffer_offset_alignment,
828            "min_storage_buffer_offset_alignment",
829        ),
830    }
831}
832
833pub struct BindGroup<A: HalApi> {
834    pub(crate) raw: A::BindGroup,
835    pub(crate) device_id: Stored<DeviceId>,
836    pub(crate) layout_id: Valid<BindGroupLayoutId>,
837    pub(crate) life_guard: LifeGuard,
838    pub(crate) used: BindGroupStates<A>,
839    pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
840    pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
841    pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
842    /// Actual binding sizes for buffers that don't have `min_binding_size`
843    /// specified in BGL. Listed in the order of iteration of `BGL.entries`.
844    pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
845}
846
847impl<A: HalApi> BindGroup<A> {
848    pub(crate) fn validate_dynamic_bindings(
849        &self,
850        bind_group_index: u32,
851        offsets: &[wgt::DynamicOffset],
852        limits: &wgt::Limits,
853    ) -> Result<(), BindError> {
854        if self.dynamic_binding_info.len() != offsets.len() {
855            return Err(BindError::MismatchedDynamicOffsetCount {
856                group: bind_group_index,
857                expected: self.dynamic_binding_info.len(),
858                actual: offsets.len(),
859            });
860        }
861
862        for (idx, (info, &offset)) in self
863            .dynamic_binding_info
864            .iter()
865            .zip(offsets.iter())
866            .enumerate()
867        {
868            let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type);
869            if offset as wgt::BufferAddress % alignment as u64 != 0 {
870                return Err(BindError::UnalignedDynamicBinding {
871                    group: bind_group_index,
872                    binding: info.binding_idx,
873                    idx,
874                    offset,
875                    alignment,
876                    limit_name,
877                });
878            }
879
880            if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
881                return Err(BindError::DynamicBindingOutOfBounds {
882                    group: bind_group_index,
883                    binding: info.binding_idx,
884                    idx,
885                    offset,
886                    buffer_size: info.buffer_size,
887                    binding_range: info.binding_range.clone(),
888                    maximum_dynamic_offset: info.maximum_dynamic_offset,
889                });
890            }
891        }
892
893        Ok(())
894    }
895}
896
897impl<A: HalApi> Resource for BindGroup<A> {
898    const TYPE: &'static str = "BindGroup";
899
900    fn life_guard(&self) -> &LifeGuard {
901        &self.life_guard
902    }
903}
904
905#[derive(Clone, Debug, Error)]
906#[non_exhaustive]
907pub enum GetBindGroupLayoutError {
908    #[error("Pipeline is invalid")]
909    InvalidPipeline,
910    #[error("Invalid group index {0}")]
911    InvalidGroupIndex(u32),
912}
913
914#[derive(Clone, Debug, Error, Eq, PartialEq)]
915#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
916pub struct LateMinBufferBindingSizeMismatch {
917    pub group_index: u32,
918    pub compact_index: usize,
919    pub shader_size: wgt::BufferAddress,
920    pub bound_size: wgt::BufferAddress,
921}