wgpu_core/
binding_model.rs

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