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#[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 binding: u32,
129 actual: wgt::BindingType,
131 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#[derive(Clone, Debug)]
399#[cfg_attr(feature = "trace", derive(Serialize))]
400#[cfg_attr(feature = "replay", derive(Deserialize))]
401pub struct BindGroupEntry<'a> {
402 pub binding: u32,
405 pub resource: BindingResource<'a>,
407}
408
409#[derive(Clone, Debug)]
411#[cfg_attr(feature = "trace", derive(Serialize))]
412#[cfg_attr(feature = "replay", derive(Deserialize))]
413pub struct BindGroupDescriptor<'a> {
414 pub label: Label<'a>,
418 pub layout: BindGroupLayoutId,
420 pub entries: Cow<'a, [BindGroupEntry<'a>]>,
422}
423
424#[derive(Clone, Debug)]
426#[cfg_attr(feature = "trace", derive(serde::Serialize))]
427#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
428pub struct BindGroupLayoutDescriptor<'a> {
429 pub label: Label<'a>,
433 pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
435}
436
437pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
438
439#[derive(Debug)]
448pub struct BindGroupLayout<A: hal::Api> {
449 pub(crate) raw: A::BindGroupLayout,
450 pub(crate) device_id: Stored<DeviceId>,
451 pub(crate) multi_ref_count: MultiRefCount,
452 pub(crate) entries: BindEntryMap,
453 #[allow(unused)]
454 pub(crate) dynamic_count: usize,
455 pub(crate) count_validator: BindingTypeMaxCountValidator,
456 #[cfg(debug_assertions)]
457 pub(crate) label: String,
458}
459
460impl<A: hal::Api> Resource for BindGroupLayout<A> {
461 const TYPE: &'static str = "BindGroupLayout";
462
463 fn life_guard(&self) -> &LifeGuard {
464 unreachable!()
465 }
466
467 fn label(&self) -> &str {
468 #[cfg(debug_assertions)]
469 return &self.label;
470 #[cfg(not(debug_assertions))]
471 return "";
472 }
473}
474
475#[derive(Clone, Debug, Error)]
476#[non_exhaustive]
477pub enum CreatePipelineLayoutError {
478 #[error(transparent)]
479 Device(#[from] DeviceError),
480 #[error("Bind group layout {0:?} is invalid")]
481 InvalidBindGroupLayout(BindGroupLayoutId),
482 #[error(
483 "Push constant at index {index} has range bound {bound} not aligned to {}",
484 wgt::PUSH_CONSTANT_ALIGNMENT
485 )]
486 MisalignedPushConstantRange { index: usize, bound: u32 },
487 #[error(transparent)]
488 MissingFeatures(#[from] MissingFeatures),
489 #[error("Push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")]
490 MoreThanOnePushConstantRangePerStage {
491 index: usize,
492 provided: wgt::ShaderStages,
493 intersected: wgt::ShaderStages,
494 },
495 #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
496 PushConstantRangeTooLarge {
497 index: usize,
498 range: Range<u32>,
499 max: u32,
500 },
501 #[error(transparent)]
502 TooManyBindings(BindingTypeMaxCountError),
503 #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
504 TooManyGroups { actual: usize, max: usize },
505}
506
507impl PrettyError for CreatePipelineLayoutError {
508 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
509 fmt.error(self);
510 if let Self::InvalidBindGroupLayout(id) = *self {
511 fmt.bind_group_layout_label(&id);
512 };
513 }
514}
515
516#[derive(Clone, Debug, Error)]
517#[non_exhaustive]
518pub enum PushConstantUploadError {
519 #[error("Provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)]
520 TooLarge {
521 offset: u32,
522 end_offset: u32,
523 idx: usize,
524 range: wgt::PushConstantRange,
525 },
526 #[error("Provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")]
527 PartialRangeMatch {
528 actual: wgt::ShaderStages,
529 idx: usize,
530 matched: wgt::ShaderStages,
531 },
532 #[error("Provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")]
533 MissingStages {
534 actual: wgt::ShaderStages,
535 idx: usize,
536 missing: wgt::ShaderStages,
537 },
538 #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
539 UnmatchedStages {
540 actual: wgt::ShaderStages,
541 unmatched: wgt::ShaderStages,
542 },
543 #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
544 Unaligned(u32),
545}
546
547#[derive(Clone, Debug, PartialEq, Eq, Hash)]
551#[cfg_attr(feature = "trace", derive(Serialize))]
552#[cfg_attr(feature = "replay", derive(Deserialize))]
553pub struct PipelineLayoutDescriptor<'a> {
554 pub label: Label<'a>,
558 pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
561 pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
569}
570
571#[derive(Debug)]
572pub struct PipelineLayout<A: hal::Api> {
573 pub(crate) raw: A::PipelineLayout,
574 pub(crate) device_id: Stored<DeviceId>,
575 pub(crate) life_guard: LifeGuard,
576 pub(crate) bind_group_layout_ids: ArrayVec<Valid<BindGroupLayoutId>, { hal::MAX_BIND_GROUPS }>,
577 pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
578}
579
580impl<A: hal::Api> PipelineLayout<A> {
581 pub(crate) fn validate_push_constant_ranges(
583 &self,
584 stages: wgt::ShaderStages,
585 offset: u32,
586 end_offset: u32,
587 ) -> Result<(), PushConstantUploadError> {
588 if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
593 return Err(PushConstantUploadError::Unaligned(offset));
594 }
595
596 let mut used_stages = wgt::ShaderStages::NONE;
616 for (idx, range) in self.push_constant_ranges.iter().enumerate() {
617 if stages.contains(range.stages) {
619 if !(range.range.start <= offset && end_offset <= range.range.end) {
620 return Err(PushConstantUploadError::TooLarge {
621 offset,
622 end_offset,
623 idx,
624 range: range.clone(),
625 });
626 }
627 used_stages |= range.stages;
628 } else if stages.intersects(range.stages) {
629 return Err(PushConstantUploadError::PartialRangeMatch {
632 actual: stages,
633 idx,
634 matched: range.stages,
635 });
636 }
637
638 if offset < range.range.end && range.range.start < end_offset {
640 if !stages.contains(range.stages) {
642 return Err(PushConstantUploadError::MissingStages {
643 actual: stages,
644 idx,
645 missing: stages,
646 });
647 }
648 }
649 }
650 if used_stages != stages {
651 return Err(PushConstantUploadError::UnmatchedStages {
652 actual: stages,
653 unmatched: stages - used_stages,
654 });
655 }
656 Ok(())
657 }
658}
659
660impl<A: hal::Api> Resource for PipelineLayout<A> {
661 const TYPE: &'static str = "PipelineLayout";
662
663 fn life_guard(&self) -> &LifeGuard {
664 &self.life_guard
665 }
666}
667
668#[repr(C)]
669#[derive(Clone, Debug, Hash, Eq, PartialEq)]
670#[cfg_attr(feature = "trace", derive(Serialize))]
671#[cfg_attr(feature = "replay", derive(Deserialize))]
672pub struct BufferBinding {
673 pub buffer_id: BufferId,
674 pub offset: wgt::BufferAddress,
675 pub size: Option<wgt::BufferSize>,
676}
677
678#[derive(Debug, Clone)]
681#[cfg_attr(feature = "trace", derive(serde::Serialize))]
682#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
683pub enum BindingResource<'a> {
684 Buffer(BufferBinding),
685 BufferArray(Cow<'a, [BufferBinding]>),
686 Sampler(SamplerId),
687 SamplerArray(Cow<'a, [SamplerId]>),
688 TextureView(TextureViewId),
689 TextureViewArray(Cow<'a, [TextureViewId]>),
690}
691
692#[derive(Clone, Debug, Error)]
693#[non_exhaustive]
694pub enum BindError {
695 #[error(
696 "Bind group {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
697 s0 = if *.expected >= 2 { "s" } else { "" },
698 s1 = if *.actual >= 2 { "s" } else { "" },
699 )]
700 MismatchedDynamicOffsetCount {
701 group: u32,
702 actual: usize,
703 expected: usize,
704 },
705 #[error(
706 "Dynamic binding index {idx} (targeting bind group {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
707 )]
708 UnalignedDynamicBinding {
709 idx: usize,
710 group: u32,
711 binding: u32,
712 offset: u32,
713 alignment: u32,
714 limit_name: &'static str,
715 },
716 #[error(
717 "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to bind group {group} -> binding {binding}. \
718 Buffer size is {buffer_size} bytes, the binding binds bytes {binding_range:?}, meaning the maximum the binding can be offset is {maximum_dynamic_offset} bytes",
719 )]
720 DynamicBindingOutOfBounds {
721 idx: usize,
722 group: u32,
723 binding: u32,
724 offset: u32,
725 buffer_size: wgt::BufferAddress,
726 binding_range: Range<wgt::BufferAddress>,
727 maximum_dynamic_offset: wgt::BufferAddress,
728 },
729}
730
731#[derive(Debug)]
732pub struct BindGroupDynamicBindingData {
733 pub(crate) binding_idx: u32,
737 pub(crate) buffer_size: wgt::BufferAddress,
741 pub(crate) binding_range: Range<wgt::BufferAddress>,
745 pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
747 pub(crate) binding_type: wgt::BufferBindingType,
749}
750
751pub(crate) fn buffer_binding_type_alignment(
752 limits: &wgt::Limits,
753 binding_type: wgt::BufferBindingType,
754) -> (u32, &'static str) {
755 match binding_type {
756 wgt::BufferBindingType::Uniform => (
757 limits.min_uniform_buffer_offset_alignment,
758 "min_uniform_buffer_offset_alignment",
759 ),
760 wgt::BufferBindingType::Storage { .. } => (
761 limits.min_storage_buffer_offset_alignment,
762 "min_storage_buffer_offset_alignment",
763 ),
764 }
765}
766
767pub struct BindGroup<A: HalApi> {
768 pub(crate) raw: A::BindGroup,
769 pub(crate) device_id: Stored<DeviceId>,
770 pub(crate) layout_id: Valid<BindGroupLayoutId>,
771 pub(crate) life_guard: LifeGuard,
772 pub(crate) used: BindGroupStates<A>,
773 pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
774 pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
775 pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
776 pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
779}
780
781impl<A: HalApi> BindGroup<A> {
782 pub(crate) fn validate_dynamic_bindings(
783 &self,
784 bind_group_index: u32,
785 offsets: &[wgt::DynamicOffset],
786 limits: &wgt::Limits,
787 ) -> Result<(), BindError> {
788 if self.dynamic_binding_info.len() != offsets.len() {
789 return Err(BindError::MismatchedDynamicOffsetCount {
790 group: bind_group_index,
791 expected: self.dynamic_binding_info.len(),
792 actual: offsets.len(),
793 });
794 }
795
796 for (idx, (info, &offset)) in self
797 .dynamic_binding_info
798 .iter()
799 .zip(offsets.iter())
800 .enumerate()
801 {
802 let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type);
803 if offset as wgt::BufferAddress % alignment as u64 != 0 {
804 return Err(BindError::UnalignedDynamicBinding {
805 group: bind_group_index,
806 binding: info.binding_idx,
807 idx,
808 offset,
809 alignment,
810 limit_name,
811 });
812 }
813
814 if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
815 return Err(BindError::DynamicBindingOutOfBounds {
816 group: bind_group_index,
817 binding: info.binding_idx,
818 idx,
819 offset,
820 buffer_size: info.buffer_size,
821 binding_range: info.binding_range.clone(),
822 maximum_dynamic_offset: info.maximum_dynamic_offset,
823 });
824 }
825 }
826
827 Ok(())
828 }
829}
830
831impl<A: HalApi> Resource for BindGroup<A> {
832 const TYPE: &'static str = "BindGroup";
833
834 fn life_guard(&self) -> &LifeGuard {
835 &self.life_guard
836 }
837}
838
839#[derive(Clone, Debug, Error)]
840#[non_exhaustive]
841pub enum GetBindGroupLayoutError {
842 #[error("Pipeline is invalid")]
843 InvalidPipeline,
844 #[error("Invalid group index {0}")]
845 InvalidGroupIndex(u32),
846}
847
848#[derive(Clone, Debug, Error, Eq, PartialEq)]
849#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
850pub struct LateMinBufferBindingSizeMismatch {
851 pub group_index: u32,
852 pub compact_index: usize,
853 pub shader_size: wgt::BufferAddress,
854 pub bound_size: wgt::BufferAddress,
855}