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
439pub type BindGroupLayouts<A> = crate::storage::Storage<BindGroupLayout<A>, BindGroupLayoutId>;
440
441pub struct BindGroupLayout<A: hal::Api> {
450 pub(crate) device_id: Stored<DeviceId>,
451 pub(crate) multi_ref_count: MultiRefCount,
452 pub(crate) inner: BglOrDuplicate<A>,
458}
459
460pub(crate) enum BglOrDuplicate<A: hal::Api> {
461 Inner(BindGroupLayoutInner<A>),
462 Duplicate(Valid<BindGroupLayoutId>),
463}
464
465pub struct BindGroupLayoutInner<A: hal::Api> {
466 pub(crate) raw: A::BindGroupLayout,
467 pub(crate) entries: BindEntryMap,
468 #[allow(unused)]
469 pub(crate) dynamic_count: usize,
470 pub(crate) count_validator: BindingTypeMaxCountValidator,
471 #[cfg(debug_assertions)]
472 pub(crate) label: String,
473}
474
475impl<A: hal::Api> BindGroupLayout<A> {
476 #[track_caller]
477 pub(crate) fn assume_deduplicated(&self) -> &BindGroupLayoutInner<A> {
478 self.as_inner().unwrap()
479 }
480
481 pub(crate) fn as_inner(&self) -> Option<&BindGroupLayoutInner<A>> {
482 match self.inner {
483 BglOrDuplicate::Inner(ref inner) => Some(inner),
484 BglOrDuplicate::Duplicate(_) => None,
485 }
486 }
487
488 pub(crate) fn into_inner(self) -> Option<BindGroupLayoutInner<A>> {
489 match self.inner {
490 BglOrDuplicate::Inner(inner) => Some(inner),
491 BglOrDuplicate::Duplicate(_) => None,
492 }
493 }
494
495 pub(crate) fn as_duplicate(&self) -> Option<Valid<BindGroupLayoutId>> {
496 match self.inner {
497 BglOrDuplicate::Duplicate(id) => Some(id),
498 BglOrDuplicate::Inner(_) => None,
499 }
500 }
501}
502
503impl<A: hal::Api> Resource for BindGroupLayout<A> {
504 const TYPE: &'static str = "BindGroupLayout";
505
506 fn life_guard(&self) -> &LifeGuard {
507 unreachable!()
508 }
509
510 fn label(&self) -> &str {
511 #[cfg(debug_assertions)]
512 return self.as_inner().map_or("", |inner| &inner.label);
513 #[cfg(not(debug_assertions))]
514 return "";
515 }
516}
517
518pub(crate) fn try_get_bind_group_layout<A: HalApi>(
520 layouts: &BindGroupLayouts<A>,
521 id: BindGroupLayoutId,
522) -> Option<&BindGroupLayout<A>> {
523 let layout = layouts.get(id).ok()?;
524 if let BglOrDuplicate::Duplicate(original_id) = layout.inner {
525 return Some(&layouts[original_id]);
526 }
527
528 Some(layout)
529}
530
531pub(crate) fn get_bind_group_layout<A: HalApi>(
532 layouts: &BindGroupLayouts<A>,
533 id: Valid<BindGroupLayoutId>,
534) -> (Valid<BindGroupLayoutId>, &BindGroupLayout<A>) {
535 let layout = &layouts[id];
536 layout
537 .as_duplicate()
538 .map_or((id, layout), |deduped| (deduped, &layouts[deduped]))
539}
540
541#[derive(Clone, Debug, Error)]
542#[non_exhaustive]
543pub enum CreatePipelineLayoutError {
544 #[error(transparent)]
545 Device(#[from] DeviceError),
546 #[error("Bind group layout {0:?} is invalid")]
547 InvalidBindGroupLayout(BindGroupLayoutId),
548 #[error(
549 "Push constant at index {index} has range bound {bound} not aligned to {}",
550 wgt::PUSH_CONSTANT_ALIGNMENT
551 )]
552 MisalignedPushConstantRange { index: usize, bound: u32 },
553 #[error(transparent)]
554 MissingFeatures(#[from] MissingFeatures),
555 #[error("Push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")]
556 MoreThanOnePushConstantRangePerStage {
557 index: usize,
558 provided: wgt::ShaderStages,
559 intersected: wgt::ShaderStages,
560 },
561 #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
562 PushConstantRangeTooLarge {
563 index: usize,
564 range: Range<u32>,
565 max: u32,
566 },
567 #[error(transparent)]
568 TooManyBindings(BindingTypeMaxCountError),
569 #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
570 TooManyGroups { actual: usize, max: usize },
571}
572
573impl PrettyError for CreatePipelineLayoutError {
574 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
575 fmt.error(self);
576 if let Self::InvalidBindGroupLayout(id) = *self {
577 fmt.bind_group_layout_label(&id);
578 };
579 }
580}
581
582#[derive(Clone, Debug, Error)]
583#[non_exhaustive]
584pub enum PushConstantUploadError {
585 #[error("Provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)]
586 TooLarge {
587 offset: u32,
588 end_offset: u32,
589 idx: usize,
590 range: wgt::PushConstantRange,
591 },
592 #[error("Provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")]
593 PartialRangeMatch {
594 actual: wgt::ShaderStages,
595 idx: usize,
596 matched: wgt::ShaderStages,
597 },
598 #[error("Provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")]
599 MissingStages {
600 actual: wgt::ShaderStages,
601 idx: usize,
602 missing: wgt::ShaderStages,
603 },
604 #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
605 UnmatchedStages {
606 actual: wgt::ShaderStages,
607 unmatched: wgt::ShaderStages,
608 },
609 #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
610 Unaligned(u32),
611}
612
613#[derive(Clone, Debug, PartialEq, Eq, Hash)]
617#[cfg_attr(feature = "trace", derive(Serialize))]
618#[cfg_attr(feature = "replay", derive(Deserialize))]
619pub struct PipelineLayoutDescriptor<'a> {
620 pub label: Label<'a>,
624 pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
627 pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
635}
636
637#[derive(Debug)]
638pub struct PipelineLayout<A: hal::Api> {
639 pub(crate) raw: A::PipelineLayout,
640 pub(crate) device_id: Stored<DeviceId>,
641 pub(crate) life_guard: LifeGuard,
642 pub(crate) bind_group_layout_ids: ArrayVec<Valid<BindGroupLayoutId>, { hal::MAX_BIND_GROUPS }>,
643 pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
644}
645
646impl<A: hal::Api> PipelineLayout<A> {
647 pub(crate) fn validate_push_constant_ranges(
649 &self,
650 stages: wgt::ShaderStages,
651 offset: u32,
652 end_offset: u32,
653 ) -> Result<(), PushConstantUploadError> {
654 if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
659 return Err(PushConstantUploadError::Unaligned(offset));
660 }
661
662 let mut used_stages = wgt::ShaderStages::NONE;
682 for (idx, range) in self.push_constant_ranges.iter().enumerate() {
683 if stages.contains(range.stages) {
685 if !(range.range.start <= offset && end_offset <= range.range.end) {
686 return Err(PushConstantUploadError::TooLarge {
687 offset,
688 end_offset,
689 idx,
690 range: range.clone(),
691 });
692 }
693 used_stages |= range.stages;
694 } else if stages.intersects(range.stages) {
695 return Err(PushConstantUploadError::PartialRangeMatch {
698 actual: stages,
699 idx,
700 matched: range.stages,
701 });
702 }
703
704 if offset < range.range.end && range.range.start < end_offset {
706 if !stages.contains(range.stages) {
708 return Err(PushConstantUploadError::MissingStages {
709 actual: stages,
710 idx,
711 missing: stages,
712 });
713 }
714 }
715 }
716 if used_stages != stages {
717 return Err(PushConstantUploadError::UnmatchedStages {
718 actual: stages,
719 unmatched: stages - used_stages,
720 });
721 }
722 Ok(())
723 }
724}
725
726impl<A: hal::Api> Resource for PipelineLayout<A> {
727 const TYPE: &'static str = "PipelineLayout";
728
729 fn life_guard(&self) -> &LifeGuard {
730 &self.life_guard
731 }
732}
733
734#[repr(C)]
735#[derive(Clone, Debug, Hash, Eq, PartialEq)]
736#[cfg_attr(feature = "trace", derive(Serialize))]
737#[cfg_attr(feature = "replay", derive(Deserialize))]
738pub struct BufferBinding {
739 pub buffer_id: BufferId,
740 pub offset: wgt::BufferAddress,
741 pub size: Option<wgt::BufferSize>,
742}
743
744#[derive(Debug, Clone)]
747#[cfg_attr(feature = "trace", derive(serde::Serialize))]
748#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
749pub enum BindingResource<'a> {
750 Buffer(BufferBinding),
751 BufferArray(Cow<'a, [BufferBinding]>),
752 Sampler(SamplerId),
753 SamplerArray(Cow<'a, [SamplerId]>),
754 TextureView(TextureViewId),
755 TextureViewArray(Cow<'a, [TextureViewId]>),
756}
757
758#[derive(Clone, Debug, Error)]
759#[non_exhaustive]
760pub enum BindError {
761 #[error(
762 "Bind group {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
763 s0 = if *.expected >= 2 { "s" } else { "" },
764 s1 = if *.actual >= 2 { "s" } else { "" },
765 )]
766 MismatchedDynamicOffsetCount {
767 group: u32,
768 actual: usize,
769 expected: usize,
770 },
771 #[error(
772 "Dynamic binding index {idx} (targeting bind group {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
773 )]
774 UnalignedDynamicBinding {
775 idx: usize,
776 group: u32,
777 binding: u32,
778 offset: u32,
779 alignment: u32,
780 limit_name: &'static str,
781 },
782 #[error(
783 "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to bind group {group} -> binding {binding}. \
784 Buffer size is {buffer_size} bytes, the binding binds bytes {binding_range:?}, meaning the maximum the binding can be offset is {maximum_dynamic_offset} bytes",
785 )]
786 DynamicBindingOutOfBounds {
787 idx: usize,
788 group: u32,
789 binding: u32,
790 offset: u32,
791 buffer_size: wgt::BufferAddress,
792 binding_range: Range<wgt::BufferAddress>,
793 maximum_dynamic_offset: wgt::BufferAddress,
794 },
795}
796
797#[derive(Debug)]
798pub struct BindGroupDynamicBindingData {
799 pub(crate) binding_idx: u32,
803 pub(crate) buffer_size: wgt::BufferAddress,
807 pub(crate) binding_range: Range<wgt::BufferAddress>,
811 pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
813 pub(crate) binding_type: wgt::BufferBindingType,
815}
816
817pub(crate) fn buffer_binding_type_alignment(
818 limits: &wgt::Limits,
819 binding_type: wgt::BufferBindingType,
820) -> (u32, &'static str) {
821 match binding_type {
822 wgt::BufferBindingType::Uniform => (
823 limits.min_uniform_buffer_offset_alignment,
824 "min_uniform_buffer_offset_alignment",
825 ),
826 wgt::BufferBindingType::Storage { .. } => (
827 limits.min_storage_buffer_offset_alignment,
828 "min_storage_buffer_offset_alignment",
829 ),
830 }
831}
832
833pub struct BindGroup<A: HalApi> {
834 pub(crate) raw: A::BindGroup,
835 pub(crate) device_id: Stored<DeviceId>,
836 pub(crate) layout_id: Valid<BindGroupLayoutId>,
837 pub(crate) life_guard: LifeGuard,
838 pub(crate) used: BindGroupStates<A>,
839 pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
840 pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
841 pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
842 pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
845}
846
847impl<A: HalApi> BindGroup<A> {
848 pub(crate) fn validate_dynamic_bindings(
849 &self,
850 bind_group_index: u32,
851 offsets: &[wgt::DynamicOffset],
852 limits: &wgt::Limits,
853 ) -> Result<(), BindError> {
854 if self.dynamic_binding_info.len() != offsets.len() {
855 return Err(BindError::MismatchedDynamicOffsetCount {
856 group: bind_group_index,
857 expected: self.dynamic_binding_info.len(),
858 actual: offsets.len(),
859 });
860 }
861
862 for (idx, (info, &offset)) in self
863 .dynamic_binding_info
864 .iter()
865 .zip(offsets.iter())
866 .enumerate()
867 {
868 let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type);
869 if offset as wgt::BufferAddress % alignment as u64 != 0 {
870 return Err(BindError::UnalignedDynamicBinding {
871 group: bind_group_index,
872 binding: info.binding_idx,
873 idx,
874 offset,
875 alignment,
876 limit_name,
877 });
878 }
879
880 if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
881 return Err(BindError::DynamicBindingOutOfBounds {
882 group: bind_group_index,
883 binding: info.binding_idx,
884 idx,
885 offset,
886 buffer_size: info.buffer_size,
887 binding_range: info.binding_range.clone(),
888 maximum_dynamic_offset: info.maximum_dynamic_offset,
889 });
890 }
891 }
892
893 Ok(())
894 }
895}
896
897impl<A: HalApi> Resource for BindGroup<A> {
898 const TYPE: &'static str = "BindGroup";
899
900 fn life_guard(&self) -> &LifeGuard {
901 &self.life_guard
902 }
903}
904
905#[derive(Clone, Debug, Error)]
906#[non_exhaustive]
907pub enum GetBindGroupLayoutError {
908 #[error("Pipeline is invalid")]
909 InvalidPipeline,
910 #[error("Invalid group index {0}")]
911 InvalidGroupIndex(u32),
912}
913
914#[derive(Clone, Debug, Error, Eq, PartialEq)]
915#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
916pub struct LateMinBufferBindingSizeMismatch {
917 pub group_index: u32,
918 pub compact_index: usize,
919 pub shader_size: wgt::BufferAddress,
920 pub bound_size: wgt::BufferAddress,
921}