1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use crate::{
6     device::{
7         descriptor::{DescriptorSet, DescriptorTotalCount},
8         DeviceError, MissingFeatures, SHADER_STAGE_COUNT,
9     },
10     hub::Resource,
11     id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureViewId, Valid},
12     memory_init_tracker::MemoryInitTrackerAction,
13     track::{TrackerSet, UsageConflict, DUMMY_SELECTOR},
14     validation::{MissingBufferUsageError, MissingTextureUsageError},
15     FastHashMap, Label, LifeGuard, MultiRefCount, Stored, MAX_BIND_GROUPS,
16 };
17 
18 use arrayvec::ArrayVec;
19 
20 #[cfg(feature = "replay")]
21 use serde::Deserialize;
22 #[cfg(feature = "trace")]
23 use serde::Serialize;
24 
25 use std::{
26     borrow::{Borrow, Cow},
27     ops::Range,
28 };
29 
30 use thiserror::Error;
31 
32 #[derive(Clone, Debug, Error)]
33 pub enum BindGroupLayoutEntryError {
34     #[error("arrays of bindings unsupported for this type of binding")]
35     ArrayUnsupported,
36     #[error(transparent)]
37     MissingFeatures(#[from] MissingFeatures),
38 }
39 
40 #[derive(Clone, Debug, Error)]
41 pub enum CreateBindGroupLayoutError {
42     #[error(transparent)]
43     Device(#[from] DeviceError),
44     #[error("conflicting binding at index {0}")]
45     ConflictBinding(u32),
46     #[error("binding {binding} entry is invalid")]
47     Entry {
48         binding: u32,
49         #[source]
50         error: BindGroupLayoutEntryError,
51     },
52     #[error(transparent)]
53     TooManyBindings(BindingTypeMaxCountError),
54 }
55 
56 //TODO: refactor this to move out `enum BindingError`.
57 
58 #[derive(Clone, Debug, Error)]
59 pub enum CreateBindGroupError {
60     #[error(transparent)]
61     Device(#[from] DeviceError),
62     #[error("bind group layout is invalid")]
63     InvalidLayout,
64     #[error("buffer {0:?} is invalid or destroyed")]
65     InvalidBuffer(BufferId),
66     #[error("texture view {0:?} is invalid")]
67     InvalidTextureView(TextureViewId),
68     #[error("sampler {0:?} is invalid")]
69     InvalidSampler(SamplerId),
70     #[error("binding count declared with {expected} items, but {actual} items were provided")]
71     BindingArrayLengthMismatch { actual: usize, expected: usize },
72     #[error("bound buffer range {range:?} does not fit in buffer of size {size}")]
73     BindingRangeTooLarge {
74         buffer: BufferId,
75         range: Range<wgt::BufferAddress>,
76         size: u64,
77     },
78     #[error("buffer binding size {actual} is less than minimum {min}")]
79     BindingSizeTooSmall {
80         buffer: BufferId,
81         actual: u64,
82         min: u64,
83     },
84     #[error("buffer binding size is zero")]
85     BindingZeroSize(BufferId),
86     #[error("number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
87     BindingsNumMismatch { actual: usize, expected: usize },
88     #[error("binding {0} is used at least twice in the descriptor")]
89     DuplicateBinding(u32),
90     #[error("unable to find a corresponding declaration for the given binding {0}")]
91     MissingBindingDeclaration(u32),
92     #[error(transparent)]
93     MissingBufferUsage(#[from] MissingBufferUsageError),
94     #[error(transparent)]
95     MissingTextureUsage(#[from] MissingTextureUsageError),
96     #[error("binding declared as a single item, but bind group is using it as an array")]
97     SingleBindingExpected,
98     #[error("unable to create a bind group with a swap chain image")]
99     SwapChainImage,
100     #[error("buffer offset {0} does not respect `BIND_BUFFER_ALIGNMENT`")]
101     UnalignedBufferOffset(wgt::BufferAddress),
102     #[error(
103         "buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
104     )]
105     BufferRangeTooLarge {
106         binding: u32,
107         given: u32,
108         limit: u32,
109     },
110     #[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
111     WrongBindingType {
112         // Index of the binding
113         binding: u32,
114         // The type given to the function
115         actual: wgt::BindingType,
116         // Human-readable description of expected types
117         expected: &'static str,
118     },
119     #[error("texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
120     InvalidTextureMultisample {
121         binding: u32,
122         layout_multisampled: bool,
123         view_samples: u32,
124     },
125     #[error("texture binding {binding} expects sample type = {layout_sample_type:?}, but given a view with format = {view_format:?}")]
126     InvalidTextureSampleType {
127         binding: u32,
128         layout_sample_type: wgt::TextureSampleType,
129         view_format: wgt::TextureFormat,
130     },
131     #[error("texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
132     InvalidTextureDimension {
133         binding: u32,
134         layout_dimension: wgt::TextureViewDimension,
135         view_dimension: wgt::TextureViewDimension,
136     },
137     #[error("storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
138     InvalidStorageTextureFormat {
139         binding: u32,
140         layout_format: wgt::TextureFormat,
141         view_format: wgt::TextureFormat,
142     },
143     #[error("sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
144     WrongSamplerComparison {
145         binding: u32,
146         layout_cmp: bool,
147         sampler_cmp: bool,
148     },
149     #[error("sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
150     WrongSamplerFiltering {
151         binding: u32,
152         layout_flt: bool,
153         sampler_flt: bool,
154     },
155     #[error("bound texture views can not have both depth and stencil aspects enabled")]
156     DepthStencilAspect,
157     #[error("the adapter does not support simultaneous read + write storage texture access for the format {0:?}")]
158     StorageReadWriteNotSupported(wgt::TextureFormat),
159     #[error(transparent)]
160     ResourceUsageConflict(#[from] UsageConflict),
161 }
162 
163 #[derive(Clone, Debug, Error)]
164 pub enum BindingZone {
165     #[error("stage {0:?}")]
166     Stage(wgt::ShaderStage),
167     #[error("whole pipeline")]
168     Pipeline,
169 }
170 
171 #[derive(Clone, Debug, Error)]
172 #[error("too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}")]
173 pub struct BindingTypeMaxCountError {
174     pub kind: BindingTypeMaxCountErrorKind,
175     pub zone: BindingZone,
176     pub limit: u32,
177     pub count: u32,
178 }
179 
180 #[derive(Clone, Debug)]
181 pub enum BindingTypeMaxCountErrorKind {
182     DynamicUniformBuffers,
183     DynamicStorageBuffers,
184     SampledTextures,
185     Samplers,
186     StorageBuffers,
187     StorageTextures,
188     UniformBuffers,
189 }
190 
191 #[derive(Debug, Default)]
192 pub(crate) struct PerStageBindingTypeCounter {
193     vertex: u32,
194     fragment: u32,
195     compute: u32,
196 }
197 
198 impl PerStageBindingTypeCounter {
add(&mut self, stage: wgt::ShaderStage, count: u32)199     pub(crate) fn add(&mut self, stage: wgt::ShaderStage, count: u32) {
200         if stage.contains(wgt::ShaderStage::VERTEX) {
201             self.vertex += count;
202         }
203         if stage.contains(wgt::ShaderStage::FRAGMENT) {
204             self.fragment += count;
205         }
206         if stage.contains(wgt::ShaderStage::COMPUTE) {
207             self.compute += count;
208         }
209     }
210 
max(&self) -> (BindingZone, u32)211     pub(crate) fn max(&self) -> (BindingZone, u32) {
212         let max_value = self.vertex.max(self.fragment.max(self.compute));
213         let mut stage = wgt::ShaderStage::NONE;
214         if max_value == self.vertex {
215             stage |= wgt::ShaderStage::VERTEX
216         }
217         if max_value == self.fragment {
218             stage |= wgt::ShaderStage::FRAGMENT
219         }
220         if max_value == self.compute {
221             stage |= wgt::ShaderStage::COMPUTE
222         }
223         (BindingZone::Stage(stage), max_value)
224     }
225 
merge(&mut self, other: &Self)226     pub(crate) fn merge(&mut self, other: &Self) {
227         self.vertex = self.vertex.max(other.vertex);
228         self.fragment = self.fragment.max(other.fragment);
229         self.compute = self.compute.max(other.compute);
230     }
231 
validate( &self, limit: u32, kind: BindingTypeMaxCountErrorKind, ) -> Result<(), BindingTypeMaxCountError>232     pub(crate) fn validate(
233         &self,
234         limit: u32,
235         kind: BindingTypeMaxCountErrorKind,
236     ) -> Result<(), BindingTypeMaxCountError> {
237         let (zone, count) = self.max();
238         if limit < count {
239             Err(BindingTypeMaxCountError {
240                 kind,
241                 zone,
242                 limit,
243                 count,
244             })
245         } else {
246             Ok(())
247         }
248     }
249 }
250 
251 #[derive(Debug, Default)]
252 pub(crate) struct BindingTypeMaxCountValidator {
253     dynamic_uniform_buffers: u32,
254     dynamic_storage_buffers: u32,
255     sampled_textures: PerStageBindingTypeCounter,
256     samplers: PerStageBindingTypeCounter,
257     storage_buffers: PerStageBindingTypeCounter,
258     storage_textures: PerStageBindingTypeCounter,
259     uniform_buffers: PerStageBindingTypeCounter,
260 }
261 
262 impl BindingTypeMaxCountValidator {
add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry)263     pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
264         let count = binding.count.map_or(1, |count| count.get());
265         match binding.ty {
266             wgt::BindingType::Buffer {
267                 ty: wgt::BufferBindingType::Uniform,
268                 has_dynamic_offset,
269                 ..
270             } => {
271                 self.uniform_buffers.add(binding.visibility, count);
272                 if has_dynamic_offset {
273                     self.dynamic_uniform_buffers += count;
274                 }
275             }
276             wgt::BindingType::Buffer {
277                 ty: wgt::BufferBindingType::Storage { .. },
278                 has_dynamic_offset,
279                 ..
280             } => {
281                 self.storage_buffers.add(binding.visibility, count);
282                 if has_dynamic_offset {
283                     self.dynamic_storage_buffers += count;
284                 }
285             }
286             wgt::BindingType::Sampler { .. } => {
287                 self.samplers.add(binding.visibility, count);
288             }
289             wgt::BindingType::Texture { .. } => {
290                 self.sampled_textures.add(binding.visibility, count);
291             }
292             wgt::BindingType::StorageTexture { .. } => {
293                 self.storage_textures.add(binding.visibility, count);
294             }
295         }
296     }
297 
merge(&mut self, other: &Self)298     pub(crate) fn merge(&mut self, other: &Self) {
299         self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
300         self.dynamic_storage_buffers += other.dynamic_storage_buffers;
301         self.sampled_textures.merge(&other.sampled_textures);
302         self.samplers.merge(&other.samplers);
303         self.storage_buffers.merge(&other.storage_buffers);
304         self.storage_textures.merge(&other.storage_textures);
305         self.uniform_buffers.merge(&other.uniform_buffers);
306     }
307 
validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError>308     pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
309         if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
310             return Err(BindingTypeMaxCountError {
311                 kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
312                 zone: BindingZone::Pipeline,
313                 limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
314                 count: self.dynamic_uniform_buffers,
315             });
316         }
317         if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
318             return Err(BindingTypeMaxCountError {
319                 kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
320                 zone: BindingZone::Pipeline,
321                 limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
322                 count: self.dynamic_storage_buffers,
323             });
324         }
325         self.sampled_textures.validate(
326             limits.max_sampled_textures_per_shader_stage,
327             BindingTypeMaxCountErrorKind::SampledTextures,
328         )?;
329         self.storage_buffers.validate(
330             limits.max_storage_buffers_per_shader_stage,
331             BindingTypeMaxCountErrorKind::StorageBuffers,
332         )?;
333         self.samplers.validate(
334             limits.max_samplers_per_shader_stage,
335             BindingTypeMaxCountErrorKind::Samplers,
336         )?;
337         self.storage_buffers.validate(
338             limits.max_storage_buffers_per_shader_stage,
339             BindingTypeMaxCountErrorKind::StorageBuffers,
340         )?;
341         self.storage_textures.validate(
342             limits.max_storage_textures_per_shader_stage,
343             BindingTypeMaxCountErrorKind::StorageTextures,
344         )?;
345         self.uniform_buffers.validate(
346             limits.max_uniform_buffers_per_shader_stage,
347             BindingTypeMaxCountErrorKind::UniformBuffers,
348         )?;
349         Ok(())
350     }
351 }
352 
353 /// Bindable resource and the slot to bind it to.
354 #[derive(Clone, Debug)]
355 #[cfg_attr(feature = "trace", derive(Serialize))]
356 #[cfg_attr(feature = "replay", derive(Deserialize))]
357 pub struct BindGroupEntry<'a> {
358     /// Slot for which binding provides resource. Corresponds to an entry of the same
359     /// binding index in the [`BindGroupLayoutDescriptor`].
360     pub binding: u32,
361     /// Resource to attach to the binding
362     pub resource: BindingResource<'a>,
363 }
364 
365 /// Describes a group of bindings and the resources to be bound.
366 #[derive(Clone, Debug)]
367 #[cfg_attr(feature = "trace", derive(Serialize))]
368 #[cfg_attr(feature = "replay", derive(Deserialize))]
369 pub struct BindGroupDescriptor<'a> {
370     /// Debug label of the bind group. This will show up in graphics debuggers for easy identification.
371     pub label: Label<'a>,
372     /// The [`BindGroupLayout`] that corresponds to this bind group.
373     pub layout: BindGroupLayoutId,
374     /// The resources to bind to this bind group.
375     pub entries: Cow<'a, [BindGroupEntry<'a>]>,
376 }
377 
378 /// Describes a [`BindGroupLayout`].
379 #[derive(Clone, Debug)]
380 #[cfg_attr(feature = "trace", derive(serde::Serialize))]
381 #[cfg_attr(feature = "replay", derive(serde::Deserialize))]
382 pub struct BindGroupLayoutDescriptor<'a> {
383     /// Debug label of the bind group layout. This will show up in graphics debuggers for easy identification.
384     pub label: Label<'a>,
385     /// Array of entries in this BindGroupLayout
386     pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
387 }
388 
389 pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
390 
391 #[derive(Debug)]
392 pub struct BindGroupLayout<B: hal::Backend> {
393     pub(crate) raw: B::DescriptorSetLayout,
394     pub(crate) device_id: Stored<DeviceId>,
395     pub(crate) multi_ref_count: MultiRefCount,
396     pub(crate) entries: BindEntryMap,
397     pub(crate) desc_count: DescriptorTotalCount,
398     pub(crate) dynamic_count: usize,
399     pub(crate) count_validator: BindingTypeMaxCountValidator,
400     #[cfg(debug_assertions)]
401     pub(crate) label: String,
402 }
403 
404 impl<B: hal::Backend> Resource for BindGroupLayout<B> {
405     const TYPE: &'static str = "BindGroupLayout";
406 
life_guard(&self) -> &LifeGuard407     fn life_guard(&self) -> &LifeGuard {
408         unreachable!()
409     }
410 
label(&self) -> &str411     fn label(&self) -> &str {
412         #[cfg(debug_assertions)]
413         return &self.label;
414         #[cfg(not(debug_assertions))]
415         return "";
416     }
417 }
418 
419 #[derive(Clone, Debug, Error)]
420 pub enum CreatePipelineLayoutError {
421     #[error(transparent)]
422     Device(#[from] DeviceError),
423     #[error("bind group layout {0:?} is invalid")]
424     InvalidBindGroupLayout(BindGroupLayoutId),
425     #[error(
426         "push constant at index {index} has range bound {bound} not aligned to {}",
427         wgt::PUSH_CONSTANT_ALIGNMENT
428     )]
429     MisalignedPushConstantRange { index: usize, bound: u32 },
430     #[error(transparent)]
431     MissingFeatures(#[from] MissingFeatures),
432     #[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")]
433     MoreThanOnePushConstantRangePerStage {
434         index: usize,
435         provided: wgt::ShaderStage,
436         intersected: wgt::ShaderStage,
437     },
438     #[error("push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
439     PushConstantRangeTooLarge {
440         index: usize,
441         range: Range<u32>,
442         max: u32,
443     },
444     #[error(transparent)]
445     TooManyBindings(BindingTypeMaxCountError),
446     #[error("bind group layout count {actual} exceeds device bind group limit {max}")]
447     TooManyGroups { actual: usize, max: usize },
448 }
449 
450 #[derive(Clone, Debug, Error)]
451 pub enum PushConstantUploadError {
452     #[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)]
453     TooLarge {
454         offset: u32,
455         end_offset: u32,
456         idx: usize,
457         range: wgt::PushConstantRange,
458     },
459     #[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")]
460     PartialRangeMatch {
461         actual: wgt::ShaderStage,
462         idx: usize,
463         matched: wgt::ShaderStage,
464     },
465     #[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")]
466     MissingStages {
467         actual: wgt::ShaderStage,
468         idx: usize,
469         missing: wgt::ShaderStage,
470     },
471     #[error("provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
472     UnmatchedStages {
473         actual: wgt::ShaderStage,
474         unmatched: wgt::ShaderStage,
475     },
476     #[error("provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
477     Unaligned(u32),
478 }
479 
480 /// Describes a pipeline layout.
481 ///
482 /// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
483 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
484 #[cfg_attr(feature = "trace", derive(Serialize))]
485 #[cfg_attr(feature = "replay", derive(Deserialize))]
486 pub struct PipelineLayoutDescriptor<'a> {
487     /// Debug label of the pipeine layout. This will show up in graphics debuggers for easy identification.
488     pub label: Label<'a>,
489     /// Bind groups that this pipeline uses. The first entry will provide all the bindings for
490     /// "set = 0", second entry will provide all the bindings for "set = 1" etc.
491     pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
492     /// Set of push constant ranges this pipeline uses. Each shader stage that uses push constants
493     /// must define the range in push constant memory that corresponds to its single `layout(push_constant)`
494     /// uniform block.
495     ///
496     /// If this array is non-empty, the [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) must be enabled.
497     pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
498 }
499 
500 #[derive(Debug)]
501 pub struct PipelineLayout<B: hal::Backend> {
502     pub(crate) raw: B::PipelineLayout,
503     pub(crate) device_id: Stored<DeviceId>,
504     pub(crate) life_guard: LifeGuard,
505     pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; MAX_BIND_GROUPS]>,
506     pub(crate) push_constant_ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>,
507 }
508 
509 impl<B: hal::Backend> PipelineLayout<B> {
510     /// Validate push constants match up with expected ranges.
validate_push_constant_ranges( &self, stages: wgt::ShaderStage, offset: u32, end_offset: u32, ) -> Result<(), PushConstantUploadError>511     pub(crate) fn validate_push_constant_ranges(
512         &self,
513         stages: wgt::ShaderStage,
514         offset: u32,
515         end_offset: u32,
516     ) -> Result<(), PushConstantUploadError> {
517         // Don't need to validate size against the push constant size limit here,
518         // as push constant ranges are already validated to be within bounds,
519         // and we validate that they are within the ranges.
520 
521         if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
522             return Err(PushConstantUploadError::Unaligned(offset));
523         }
524 
525         // Push constant validation looks very complicated on the surface, but
526         // the problem can be range-reduced pretty well.
527         //
528         // Push constants require (summarized from the vulkan spec):
529         // 1. For each byte in the range and for each shader stage in stageFlags,
530         //    there must be a push constant range in the layout that includes that
531         //    byte and that stage.
532         // 2. For each byte in the range and for each push constant range that overlaps that byte,
533         //    `stage` must include all stages in that push constant range’s `stage`.
534         //
535         // However there are some additional constraints that help us:
536         // 3. All push constant ranges are the only range that can access that stage.
537         //    i.e. if one range has VERTEX, no other range has VERTEX
538         //
539         // Therefore we can simplify the checks in the following ways:
540         // - Because 3 guarantees that the push constant range has a unique stage,
541         //   when we check for 1, we can simply check that our entire updated range
542         //   is within a push constant range. i.e. our range for a specific stage cannot
543         //   intersect more than one push constant range.
544         let mut used_stages = wgt::ShaderStage::NONE;
545         for (idx, range) in self.push_constant_ranges.iter().enumerate() {
546             // contains not intersects due to 2
547             if stages.contains(range.stages) {
548                 if !(range.range.start <= offset && end_offset <= range.range.end) {
549                     return Err(PushConstantUploadError::TooLarge {
550                         offset,
551                         end_offset,
552                         idx,
553                         range: range.clone(),
554                     });
555                 }
556                 used_stages |= range.stages;
557             } else if stages.intersects(range.stages) {
558                 // Will be caught by used stages check below, but we can do this because of 1
559                 // and is more helpful to the user.
560                 return Err(PushConstantUploadError::PartialRangeMatch {
561                     actual: stages,
562                     idx,
563                     matched: range.stages,
564                 });
565             }
566 
567             // The push constant range intersects range we are uploading
568             if offset < range.range.end && range.range.start < end_offset {
569                 // But requires stages we don't provide
570                 if !stages.contains(range.stages) {
571                     return Err(PushConstantUploadError::MissingStages {
572                         actual: stages,
573                         idx,
574                         missing: stages,
575                     });
576                 }
577             }
578         }
579         if used_stages != stages {
580             return Err(PushConstantUploadError::UnmatchedStages {
581                 actual: stages,
582                 unmatched: stages - used_stages,
583             });
584         }
585         Ok(())
586     }
587 }
588 
589 impl<B: hal::Backend> Resource for PipelineLayout<B> {
590     const TYPE: &'static str = "PipelineLayout";
591 
life_guard(&self) -> &LifeGuard592     fn life_guard(&self) -> &LifeGuard {
593         &self.life_guard
594     }
595 }
596 
597 #[repr(C)]
598 #[derive(Clone, Debug, Hash, PartialEq)]
599 #[cfg_attr(feature = "trace", derive(Serialize))]
600 #[cfg_attr(feature = "replay", derive(Deserialize))]
601 pub struct BufferBinding {
602     pub buffer_id: BufferId,
603     pub offset: wgt::BufferAddress,
604     pub size: Option<wgt::BufferSize>,
605 }
606 
607 // Note: Duplicated in `wgpu-rs` as `BindingResource`
608 // They're different enough that it doesn't make sense to share a common type
609 #[derive(Debug, Clone)]
610 #[cfg_attr(feature = "trace", derive(serde::Serialize))]
611 #[cfg_attr(feature = "replay", derive(serde::Deserialize))]
612 pub enum BindingResource<'a> {
613     Buffer(BufferBinding),
614     BufferArray(Cow<'a, [BufferBinding]>),
615     Sampler(SamplerId),
616     TextureView(TextureViewId),
617     TextureViewArray(Cow<'a, [TextureViewId]>),
618 }
619 
620 #[derive(Clone, Debug, Error)]
621 pub enum BindError {
622     #[error("number of dynamic offsets ({actual}) doesn't match the number of dynamic bindings in the bind group layout ({expected})")]
623     MismatchedDynamicOffsetCount { actual: usize, expected: usize },
624     #[error(
625         "dynamic binding at index {idx}: offset {offset} does not respect `BIND_BUFFER_ALIGNMENT`"
626     )]
627     UnalignedDynamicBinding { idx: usize, offset: u32 },
628     #[error("dynamic binding at index {idx} with offset {offset} would overrun the buffer (limit: {max})")]
629     DynamicBindingOutOfBounds { idx: usize, offset: u32, max: u64 },
630 }
631 
632 #[derive(Debug)]
633 pub struct BindGroupDynamicBindingData {
634     /// The maximum value the dynamic offset can have before running off the end of the buffer.
635     pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
636 }
637 
638 #[derive(Debug)]
639 pub struct BindGroup<B: hal::Backend> {
640     pub(crate) raw: DescriptorSet<B>,
641     pub(crate) device_id: Stored<DeviceId>,
642     pub(crate) layout_id: Valid<BindGroupLayoutId>,
643     pub(crate) life_guard: LifeGuard,
644     pub(crate) used: TrackerSet,
645     pub(crate) used_buffer_ranges: Vec<MemoryInitTrackerAction<BufferId>>,
646     pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
647 }
648 
649 impl<B: hal::Backend> BindGroup<B> {
validate_dynamic_bindings( &self, offsets: &[wgt::DynamicOffset], ) -> Result<(), BindError>650     pub(crate) fn validate_dynamic_bindings(
651         &self,
652         offsets: &[wgt::DynamicOffset],
653     ) -> Result<(), BindError> {
654         if self.dynamic_binding_info.len() != offsets.len() {
655             return Err(BindError::MismatchedDynamicOffsetCount {
656                 expected: self.dynamic_binding_info.len(),
657                 actual: offsets.len(),
658             });
659         }
660 
661         for (idx, (info, &offset)) in self
662             .dynamic_binding_info
663             .iter()
664             .zip(offsets.iter())
665             .enumerate()
666         {
667             if offset as wgt::BufferAddress % wgt::BIND_BUFFER_ALIGNMENT != 0 {
668                 return Err(BindError::UnalignedDynamicBinding { idx, offset });
669             }
670 
671             if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
672                 return Err(BindError::DynamicBindingOutOfBounds {
673                     idx,
674                     offset,
675                     max: info.maximum_dynamic_offset,
676                 });
677             }
678         }
679 
680         Ok(())
681     }
682 }
683 
684 impl<B: hal::Backend> Borrow<()> for BindGroup<B> {
borrow(&self) -> &()685     fn borrow(&self) -> &() {
686         &DUMMY_SELECTOR
687     }
688 }
689 
690 impl<B: hal::Backend> Resource for BindGroup<B> {
691     const TYPE: &'static str = "BindGroup";
692 
life_guard(&self) -> &LifeGuard693     fn life_guard(&self) -> &LifeGuard {
694         &self.life_guard
695     }
696 }
697 
698 #[derive(Clone, Debug, Error)]
699 pub enum GetBindGroupLayoutError {
700     #[error("pipeline is invalid")]
701     InvalidPipeline,
702     #[error("invalid group index {0}")]
703     InvalidGroupIndex(u32),
704 }
705