1use alloc::{
2 borrow::{Cow, ToOwned},
3 boxed::Box,
4 string::String,
5 sync::{Arc, Weak},
6 vec::Vec,
7};
8use core::{fmt, mem::ManuallyDrop, ops::Range};
9
10use arrayvec::ArrayVec;
11use thiserror::Error;
12
13#[cfg(feature = "serde")]
14use serde::Deserialize;
15#[cfg(feature = "serde")]
16use serde::Serialize;
17
18use wgt::error::{ErrorType, WebGpuError};
19
20use crate::{
21 device::{
22 bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT,
23 },
24 id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId},
25 init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction},
26 pipeline::{ComputePipeline, RenderPipeline},
27 resource::{
28 Buffer, DestroyedResourceError, ExternalTexture, InvalidResourceError, Labeled,
29 MissingBufferUsageError, MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent,
30 Sampler, TextureView, Tlas, TrackingData,
31 },
32 resource_log,
33 snatch::{SnatchGuard, Snatchable},
34 track::{BindGroupStates, ResourceUsageCompatibilityError},
35 Label,
36};
37
38#[derive(Clone, Debug, Error)]
39#[non_exhaustive]
40pub enum BindGroupLayoutEntryError {
41 #[error("Cube dimension is not expected for texture storage")]
42 StorageTextureCube,
43 #[error("Atomic storage textures are not allowed by baseline webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")]
44 StorageTextureAtomic,
45 #[error("Arrays of bindings unsupported for this type of binding")]
46 ArrayUnsupported,
47 #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")]
48 SampleTypeFloatFilterableBindingMultisampled,
49 #[error("Multisampled texture binding view dimension must be 2d, got {0:?}")]
50 Non2DMultisampled(wgt::TextureViewDimension),
51 #[error(transparent)]
52 MissingFeatures(#[from] MissingFeatures),
53 #[error(transparent)]
54 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
55}
56
57#[derive(Clone, Debug, Error)]
58#[non_exhaustive]
59pub enum CreateBindGroupLayoutError {
60 #[error(transparent)]
61 Device(#[from] DeviceError),
62 #[error("Conflicting binding at index {0}")]
63 ConflictBinding(u32),
64 #[error("Binding {binding} entry is invalid")]
65 Entry {
66 binding: u32,
67 #[source]
68 error: BindGroupLayoutEntryError,
69 },
70 #[error(transparent)]
71 TooManyBindings(BindingTypeMaxCountError),
72 #[error("Bind groups may not contain both a binding array and a dynamically offset buffer")]
73 ContainsBothBindingArrayAndDynamicOffsetArray,
74 #[error("Bind groups may not contain both a binding array and a uniform buffer")]
75 ContainsBothBindingArrayAndUniformBuffer,
76 #[error("Binding index {binding} is greater than the maximum number {maximum}")]
77 InvalidBindingIndex { binding: u32, maximum: u32 },
78 #[error("Invalid visibility {0:?}")]
79 InvalidVisibility(wgt::ShaderStages),
80}
81
82impl WebGpuError for CreateBindGroupLayoutError {
83 fn webgpu_error_type(&self) -> ErrorType {
84 match self {
85 Self::Device(e) => e.webgpu_error_type(),
86
87 Self::ConflictBinding(_)
88 | Self::Entry { .. }
89 | Self::TooManyBindings(_)
90 | Self::InvalidBindingIndex { .. }
91 | Self::InvalidVisibility(_)
92 | Self::ContainsBothBindingArrayAndDynamicOffsetArray
93 | Self::ContainsBothBindingArrayAndUniformBuffer => ErrorType::Validation,
94 }
95 }
96}
97
98#[derive(Clone, Debug, Error)]
99#[non_exhaustive]
100pub enum BindingError {
101 #[error(transparent)]
102 DestroyedResource(#[from] DestroyedResourceError),
103 #[error("Buffer {buffer}: Binding with size {binding_size} at offset {offset} would overflow buffer size of {buffer_size}")]
104 BindingRangeTooLarge {
105 buffer: ResourceErrorIdent,
106 offset: wgt::BufferAddress,
107 binding_size: u64,
108 buffer_size: u64,
109 },
110 #[error("Buffer {buffer}: Binding offset {offset} is greater than buffer size {buffer_size}")]
111 BindingOffsetTooLarge {
112 buffer: ResourceErrorIdent,
113 offset: wgt::BufferAddress,
114 buffer_size: u64,
115 },
116}
117
118impl WebGpuError for BindingError {
119 fn webgpu_error_type(&self) -> ErrorType {
120 match self {
121 Self::DestroyedResource(e) => e.webgpu_error_type(),
122 Self::BindingRangeTooLarge { .. } | Self::BindingOffsetTooLarge { .. } => {
123 ErrorType::Validation
124 }
125 }
126 }
127}
128
129#[derive(Clone, Debug, Error)]
132#[non_exhaustive]
133pub enum CreateBindGroupError {
134 #[error(transparent)]
135 Device(#[from] DeviceError),
136 #[error(transparent)]
137 DestroyedResource(#[from] DestroyedResourceError),
138 #[error(transparent)]
139 BindingError(#[from] BindingError),
140 #[error(
141 "Binding count declared with at most {expected} items, but {actual} items were provided"
142 )]
143 BindingArrayPartialLengthMismatch { actual: usize, expected: usize },
144 #[error(
145 "Binding count declared with exactly {expected} items, but {actual} items were provided"
146 )]
147 BindingArrayLengthMismatch { actual: usize, expected: usize },
148 #[error("Array binding provided zero elements")]
149 BindingArrayZeroLength,
150 #[error("Binding size {actual} of {buffer} is less than minimum {min}")]
151 BindingSizeTooSmall {
152 buffer: ResourceErrorIdent,
153 actual: u64,
154 min: u64,
155 },
156 #[error("{0} binding size is zero")]
157 BindingZeroSize(ResourceErrorIdent),
158 #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
159 BindingsNumMismatch { actual: usize, expected: usize },
160 #[error("Binding {0} is used at least twice in the descriptor")]
161 DuplicateBinding(u32),
162 #[error("Unable to find a corresponding declaration for the given binding {0}")]
163 MissingBindingDeclaration(u32),
164 #[error(transparent)]
165 MissingBufferUsage(#[from] MissingBufferUsageError),
166 #[error(transparent)]
167 MissingTextureUsage(#[from] MissingTextureUsageError),
168 #[error("Binding declared as a single item, but bind group is using it as an array")]
169 SingleBindingExpected,
170 #[error("Effective buffer binding size {size} for storage buffers is expected to align to {alignment}, but size is {size}")]
171 UnalignedEffectiveBufferBindingSizeForStorage { alignment: u8, size: u64 },
172 #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")]
173 UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32),
174 #[error(
175 "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
176 )]
177 BufferRangeTooLarge {
178 binding: u32,
179 given: u32,
180 limit: u32,
181 },
182 #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
183 WrongBindingType {
184 binding: u32,
186 actual: wgt::BindingType,
188 expected: &'static str,
190 },
191 #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
192 InvalidTextureMultisample {
193 binding: u32,
194 layout_multisampled: bool,
195 view_samples: u32,
196 },
197 #[error(
198 "Texture binding {} expects sample type {:?}, but was given a view with format {:?} (sample type {:?})",
199 binding,
200 layout_sample_type,
201 view_format,
202 view_sample_type
203 )]
204 InvalidTextureSampleType {
205 binding: u32,
206 layout_sample_type: wgt::TextureSampleType,
207 view_format: wgt::TextureFormat,
208 view_sample_type: wgt::TextureSampleType,
209 },
210 #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
211 InvalidTextureDimension {
212 binding: u32,
213 layout_dimension: wgt::TextureViewDimension,
214 view_dimension: wgt::TextureViewDimension,
215 },
216 #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
217 InvalidStorageTextureFormat {
218 binding: u32,
219 layout_format: wgt::TextureFormat,
220 view_format: wgt::TextureFormat,
221 },
222 #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
223 InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 },
224 #[error("External texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
225 InvalidExternalTextureMipLevelCount { binding: u32, mip_level_count: u32 },
226 #[error("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = {format:?} at binding {binding}")]
227 InvalidExternalTextureFormat {
228 binding: u32,
229 format: wgt::TextureFormat,
230 },
231 #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
232 WrongSamplerComparison {
233 binding: u32,
234 layout_cmp: bool,
235 sampler_cmp: bool,
236 },
237 #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
238 WrongSamplerFiltering {
239 binding: u32,
240 layout_flt: bool,
241 sampler_flt: bool,
242 },
243 #[error("TLAS binding {binding} is required to support vertex returns but is missing flag AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN")]
244 MissingTLASVertexReturn { binding: u32 },
245 #[error("Bound texture views can not have both depth and stencil aspects enabled")]
246 DepthStencilAspect,
247 #[error("The adapter does not support read access for storage textures of format {0:?}")]
248 StorageReadNotSupported(wgt::TextureFormat),
249 #[error("The adapter does not support atomics for storage textures of format {0:?}")]
250 StorageAtomicNotSupported(wgt::TextureFormat),
251 #[error("The adapter does not support write access for storage textures of format {0:?}")]
252 StorageWriteNotSupported(wgt::TextureFormat),
253 #[error("The adapter does not support read-write access for storage textures of format {0:?}")]
254 StorageReadWriteNotSupported(wgt::TextureFormat),
255 #[error(transparent)]
256 ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError),
257 #[error(transparent)]
258 InvalidResource(#[from] InvalidResourceError),
259}
260
261impl WebGpuError for CreateBindGroupError {
262 fn webgpu_error_type(&self) -> ErrorType {
263 let e: &dyn WebGpuError = match self {
264 Self::Device(e) => e,
265 Self::DestroyedResource(e) => e,
266 Self::BindingError(e) => e,
267 Self::MissingBufferUsage(e) => e,
268 Self::MissingTextureUsage(e) => e,
269 Self::ResourceUsageCompatibility(e) => e,
270 Self::InvalidResource(e) => e,
271 Self::BindingArrayPartialLengthMismatch { .. }
272 | Self::BindingArrayLengthMismatch { .. }
273 | Self::BindingArrayZeroLength
274 | Self::BindingSizeTooSmall { .. }
275 | Self::BindingsNumMismatch { .. }
276 | Self::BindingZeroSize(_)
277 | Self::DuplicateBinding(_)
278 | Self::MissingBindingDeclaration(_)
279 | Self::SingleBindingExpected
280 | Self::UnalignedEffectiveBufferBindingSizeForStorage { .. }
281 | Self::UnalignedBufferOffset(_, _, _)
282 | Self::BufferRangeTooLarge { .. }
283 | Self::WrongBindingType { .. }
284 | Self::InvalidTextureMultisample { .. }
285 | Self::InvalidTextureSampleType { .. }
286 | Self::InvalidTextureDimension { .. }
287 | Self::InvalidStorageTextureFormat { .. }
288 | Self::InvalidStorageTextureMipLevelCount { .. }
289 | Self::WrongSamplerComparison { .. }
290 | Self::WrongSamplerFiltering { .. }
291 | Self::DepthStencilAspect
292 | Self::StorageReadNotSupported(_)
293 | Self::StorageWriteNotSupported(_)
294 | Self::StorageReadWriteNotSupported(_)
295 | Self::StorageAtomicNotSupported(_)
296 | Self::MissingTLASVertexReturn { .. }
297 | Self::InvalidExternalTextureMipLevelCount { .. }
298 | Self::InvalidExternalTextureFormat { .. } => return ErrorType::Validation,
299 };
300 e.webgpu_error_type()
301 }
302}
303
304#[derive(Clone, Debug, Error)]
305pub enum BindingZone {
306 #[error("Stage {0:?}")]
307 Stage(wgt::ShaderStages),
308 #[error("Whole pipeline")]
309 Pipeline,
310}
311
312#[derive(Clone, Debug, Error)]
313#[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())]
314pub struct BindingTypeMaxCountError {
315 pub kind: BindingTypeMaxCountErrorKind,
316 pub zone: BindingZone,
317 pub limit: u32,
318 pub count: u32,
319}
320
321impl WebGpuError for BindingTypeMaxCountError {
322 fn webgpu_error_type(&self) -> ErrorType {
323 ErrorType::Validation
324 }
325}
326
327#[derive(Clone, Debug)]
328pub enum BindingTypeMaxCountErrorKind {
329 DynamicUniformBuffers,
330 DynamicStorageBuffers,
331 SampledTextures,
332 Samplers,
333 StorageBuffers,
334 StorageTextures,
335 UniformBuffers,
336 BindingArrayElements,
337 BindingArraySamplerElements,
338 AccelerationStructures,
339}
340
341impl BindingTypeMaxCountErrorKind {
342 fn to_config_str(&self) -> &'static str {
343 match self {
344 BindingTypeMaxCountErrorKind::DynamicUniformBuffers => {
345 "max_dynamic_uniform_buffers_per_pipeline_layout"
346 }
347 BindingTypeMaxCountErrorKind::DynamicStorageBuffers => {
348 "max_dynamic_storage_buffers_per_pipeline_layout"
349 }
350 BindingTypeMaxCountErrorKind::SampledTextures => {
351 "max_sampled_textures_per_shader_stage"
352 }
353 BindingTypeMaxCountErrorKind::Samplers => "max_samplers_per_shader_stage",
354 BindingTypeMaxCountErrorKind::StorageBuffers => "max_storage_buffers_per_shader_stage",
355 BindingTypeMaxCountErrorKind::StorageTextures => {
356 "max_storage_textures_per_shader_stage"
357 }
358 BindingTypeMaxCountErrorKind::UniformBuffers => "max_uniform_buffers_per_shader_stage",
359 BindingTypeMaxCountErrorKind::BindingArrayElements => {
360 "max_binding_array_elements_per_shader_stage"
361 }
362 BindingTypeMaxCountErrorKind::BindingArraySamplerElements => {
363 "max_binding_array_sampler_elements_per_shader_stage"
364 }
365 BindingTypeMaxCountErrorKind::AccelerationStructures => {
366 "max_acceleration_structures_per_shader_stage"
367 }
368 }
369 }
370}
371
372#[derive(Debug, Default)]
373pub(crate) struct PerStageBindingTypeCounter {
374 vertex: u32,
375 fragment: u32,
376 compute: u32,
377}
378
379impl PerStageBindingTypeCounter {
380 pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
381 if stage.contains(wgt::ShaderStages::VERTEX) {
382 self.vertex += count;
383 }
384 if stage.contains(wgt::ShaderStages::FRAGMENT) {
385 self.fragment += count;
386 }
387 if stage.contains(wgt::ShaderStages::COMPUTE) {
388 self.compute += count;
389 }
390 }
391
392 pub(crate) fn max(&self) -> (BindingZone, u32) {
393 let max_value = self.vertex.max(self.fragment.max(self.compute));
394 let mut stage = wgt::ShaderStages::NONE;
395 if max_value == self.vertex {
396 stage |= wgt::ShaderStages::VERTEX
397 }
398 if max_value == self.fragment {
399 stage |= wgt::ShaderStages::FRAGMENT
400 }
401 if max_value == self.compute {
402 stage |= wgt::ShaderStages::COMPUTE
403 }
404 (BindingZone::Stage(stage), max_value)
405 }
406
407 pub(crate) fn merge(&mut self, other: &Self) {
408 self.vertex = self.vertex.max(other.vertex);
409 self.fragment = self.fragment.max(other.fragment);
410 self.compute = self.compute.max(other.compute);
411 }
412
413 pub(crate) fn validate(
414 &self,
415 limit: u32,
416 kind: BindingTypeMaxCountErrorKind,
417 ) -> Result<(), BindingTypeMaxCountError> {
418 let (zone, count) = self.max();
419 if limit < count {
420 Err(BindingTypeMaxCountError {
421 kind,
422 zone,
423 limit,
424 count,
425 })
426 } else {
427 Ok(())
428 }
429 }
430}
431
432#[derive(Debug, Default)]
433pub(crate) struct BindingTypeMaxCountValidator {
434 dynamic_uniform_buffers: u32,
435 dynamic_storage_buffers: u32,
436 sampled_textures: PerStageBindingTypeCounter,
437 samplers: PerStageBindingTypeCounter,
438 storage_buffers: PerStageBindingTypeCounter,
439 storage_textures: PerStageBindingTypeCounter,
440 uniform_buffers: PerStageBindingTypeCounter,
441 acceleration_structures: PerStageBindingTypeCounter,
442 binding_array_elements: PerStageBindingTypeCounter,
443 binding_array_sampler_elements: PerStageBindingTypeCounter,
444 has_bindless_array: bool,
445}
446
447impl BindingTypeMaxCountValidator {
448 pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
449 let count = binding.count.map_or(1, |count| count.get());
450
451 if binding.count.is_some() {
452 self.binding_array_elements.add(binding.visibility, count);
453 self.has_bindless_array = true;
454
455 if let wgt::BindingType::Sampler(_) = binding.ty {
456 self.binding_array_sampler_elements
457 .add(binding.visibility, count);
458 }
459 } else {
460 match binding.ty {
461 wgt::BindingType::Buffer {
462 ty: wgt::BufferBindingType::Uniform,
463 has_dynamic_offset,
464 ..
465 } => {
466 self.uniform_buffers.add(binding.visibility, count);
467 if has_dynamic_offset {
468 self.dynamic_uniform_buffers += count;
469 }
470 }
471 wgt::BindingType::Buffer {
472 ty: wgt::BufferBindingType::Storage { .. },
473 has_dynamic_offset,
474 ..
475 } => {
476 self.storage_buffers.add(binding.visibility, count);
477 if has_dynamic_offset {
478 self.dynamic_storage_buffers += count;
479 }
480 }
481 wgt::BindingType::Sampler { .. } => {
482 self.samplers.add(binding.visibility, count);
483 }
484 wgt::BindingType::Texture { .. } => {
485 self.sampled_textures.add(binding.visibility, count);
486 }
487 wgt::BindingType::StorageTexture { .. } => {
488 self.storage_textures.add(binding.visibility, count);
489 }
490 wgt::BindingType::AccelerationStructure { .. } => {
491 self.acceleration_structures.add(binding.visibility, count);
492 }
493 wgt::BindingType::ExternalTexture => {
494 self.sampled_textures.add(binding.visibility, count * 4);
503 self.samplers.add(binding.visibility, count);
504 self.uniform_buffers.add(binding.visibility, count);
505 }
506 }
507 }
508 }
509
510 pub(crate) fn merge(&mut self, other: &Self) {
511 self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
512 self.dynamic_storage_buffers += other.dynamic_storage_buffers;
513 self.sampled_textures.merge(&other.sampled_textures);
514 self.samplers.merge(&other.samplers);
515 self.storage_buffers.merge(&other.storage_buffers);
516 self.storage_textures.merge(&other.storage_textures);
517 self.uniform_buffers.merge(&other.uniform_buffers);
518 self.acceleration_structures
519 .merge(&other.acceleration_structures);
520 self.binding_array_elements
521 .merge(&other.binding_array_elements);
522 self.binding_array_sampler_elements
523 .merge(&other.binding_array_sampler_elements);
524 }
525
526 pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
527 if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
528 return Err(BindingTypeMaxCountError {
529 kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
530 zone: BindingZone::Pipeline,
531 limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
532 count: self.dynamic_uniform_buffers,
533 });
534 }
535 if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
536 return Err(BindingTypeMaxCountError {
537 kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
538 zone: BindingZone::Pipeline,
539 limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
540 count: self.dynamic_storage_buffers,
541 });
542 }
543 self.sampled_textures.validate(
544 limits.max_sampled_textures_per_shader_stage,
545 BindingTypeMaxCountErrorKind::SampledTextures,
546 )?;
547 self.samplers.validate(
548 limits.max_samplers_per_shader_stage,
549 BindingTypeMaxCountErrorKind::Samplers,
550 )?;
551 self.storage_buffers.validate(
552 limits.max_storage_buffers_per_shader_stage,
553 BindingTypeMaxCountErrorKind::StorageBuffers,
554 )?;
555 self.storage_textures.validate(
556 limits.max_storage_textures_per_shader_stage,
557 BindingTypeMaxCountErrorKind::StorageTextures,
558 )?;
559 self.uniform_buffers.validate(
560 limits.max_uniform_buffers_per_shader_stage,
561 BindingTypeMaxCountErrorKind::UniformBuffers,
562 )?;
563 self.binding_array_elements.validate(
564 limits.max_binding_array_elements_per_shader_stage,
565 BindingTypeMaxCountErrorKind::BindingArrayElements,
566 )?;
567 self.binding_array_sampler_elements.validate(
568 limits.max_binding_array_sampler_elements_per_shader_stage,
569 BindingTypeMaxCountErrorKind::BindingArraySamplerElements,
570 )?;
571 self.acceleration_structures.validate(
572 limits.max_acceleration_structures_per_shader_stage,
573 BindingTypeMaxCountErrorKind::AccelerationStructures,
574 )?;
575 Ok(())
576 }
577
578 pub(crate) fn validate_binding_arrays(&self) -> Result<(), CreateBindGroupLayoutError> {
583 let has_dynamic_offset_array =
584 self.dynamic_uniform_buffers > 0 || self.dynamic_storage_buffers > 0;
585 let has_uniform_buffer = self.uniform_buffers.max().1 > 0;
586 if self.has_bindless_array && has_dynamic_offset_array {
587 return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndDynamicOffsetArray);
588 }
589 if self.has_bindless_array && has_uniform_buffer {
590 return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndUniformBuffer);
591 }
592 Ok(())
593 }
594}
595
596#[derive(Clone, Debug)]
599#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
600pub struct BindGroupEntry<
601 'a,
602 B = BufferId,
603 S = SamplerId,
604 TV = TextureViewId,
605 TLAS = TlasId,
606 ET = ExternalTextureId,
607> where
608 [BufferBinding<B>]: ToOwned,
609 [S]: ToOwned,
610 [TV]: ToOwned,
611 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
612 <[S] as ToOwned>::Owned: fmt::Debug,
613 <[TV] as ToOwned>::Owned: fmt::Debug,
614{
615 pub binding: u32,
618 #[cfg_attr(
619 feature = "serde",
620 serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS, ET>: Deserialize<'de>"))
621 )]
622 pub resource: BindingResource<'a, B, S, TV, TLAS, ET>,
624}
625
626pub type ResolvedBindGroupEntry<'a> = BindGroupEntry<
628 'a,
629 Arc<Buffer>,
630 Arc<Sampler>,
631 Arc<TextureView>,
632 Arc<Tlas>,
633 Arc<ExternalTexture>,
634>;
635
636#[derive(Clone, Debug)]
638#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
639pub struct BindGroupDescriptor<
640 'a,
641 BGL = BindGroupLayoutId,
642 B = BufferId,
643 S = SamplerId,
644 TV = TextureViewId,
645 TLAS = TlasId,
646 ET = ExternalTextureId,
647> where
648 [BufferBinding<B>]: ToOwned,
649 [S]: ToOwned,
650 [TV]: ToOwned,
651 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
652 <[S] as ToOwned>::Owned: fmt::Debug,
653 <[TV] as ToOwned>::Owned: fmt::Debug,
654 [BindGroupEntry<'a, B, S, TV, TLAS, ET>]: ToOwned,
655 <[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: fmt::Debug,
656{
657 pub label: Label<'a>,
661 pub layout: BGL,
663 #[cfg_attr(
664 feature = "serde",
665 serde(bound(
666 deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS, ET>] as ToOwned>::Owned: Deserialize<'de>"
667 ))
668 )]
669 #[allow(clippy::type_complexity)]
671 pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS, ET>]>,
672}
673
674pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor<
676 'a,
677 Arc<BindGroupLayout>,
678 Arc<Buffer>,
679 Arc<Sampler>,
680 Arc<TextureView>,
681 Arc<Tlas>,
682 Arc<ExternalTexture>,
683>;
684
685#[derive(Clone, Debug)]
687#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
688pub struct BindGroupLayoutDescriptor<'a> {
689 pub label: Label<'a>,
693 pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
695}
696
697#[derive(Debug)]
701pub(crate) enum ExclusivePipeline {
702 None,
703 Render(Weak<RenderPipeline>),
704 Compute(Weak<ComputePipeline>),
705}
706
707impl fmt::Display for ExclusivePipeline {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709 match self {
710 ExclusivePipeline::None => f.write_str("None"),
711 ExclusivePipeline::Render(p) => {
712 if let Some(p) = p.upgrade() {
713 p.error_ident().fmt(f)
714 } else {
715 f.write_str("RenderPipeline")
716 }
717 }
718 ExclusivePipeline::Compute(p) => {
719 if let Some(p) = p.upgrade() {
720 p.error_ident().fmt(f)
721 } else {
722 f.write_str("ComputePipeline")
723 }
724 }
725 }
726 }
727}
728
729#[derive(Debug)]
731pub struct BindGroupLayout {
732 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>,
733 pub(crate) device: Arc<Device>,
734 pub(crate) entries: bgl::EntryMap,
735 pub(crate) origin: bgl::Origin,
742 pub(crate) exclusive_pipeline: crate::OnceCellOrLock<ExclusivePipeline>,
743 #[allow(unused)]
744 pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
745 pub(crate) label: String,
747}
748
749impl Drop for BindGroupLayout {
750 fn drop(&mut self) {
751 resource_log!("Destroy raw {}", self.error_ident());
752 if matches!(self.origin, bgl::Origin::Pool) {
753 self.device.bgl_pool.remove(&self.entries);
754 }
755 let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
757 unsafe {
758 self.device.raw().destroy_bind_group_layout(raw);
759 }
760 }
761}
762
763crate::impl_resource_type!(BindGroupLayout);
764crate::impl_labeled!(BindGroupLayout);
765crate::impl_parent_device!(BindGroupLayout);
766crate::impl_storage_item!(BindGroupLayout);
767
768impl BindGroupLayout {
769 pub(crate) fn raw(&self) -> &dyn hal::DynBindGroupLayout {
770 self.raw.as_ref()
771 }
772}
773
774#[derive(Clone, Debug, Error)]
775#[non_exhaustive]
776pub enum CreatePipelineLayoutError {
777 #[error(transparent)]
778 Device(#[from] DeviceError),
779 #[error(
780 "Push constant at index {index} has range bound {bound} not aligned to {}",
781 wgt::PUSH_CONSTANT_ALIGNMENT
782 )]
783 MisalignedPushConstantRange { index: usize, bound: u32 },
784 #[error(transparent)]
785 MissingFeatures(#[from] MissingFeatures),
786 #[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")]
787 MoreThanOnePushConstantRangePerStage {
788 index: usize,
789 provided: wgt::ShaderStages,
790 intersected: wgt::ShaderStages,
791 },
792 #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
793 PushConstantRangeTooLarge {
794 index: usize,
795 range: Range<u32>,
796 max: u32,
797 },
798 #[error(transparent)]
799 TooManyBindings(BindingTypeMaxCountError),
800 #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
801 TooManyGroups { actual: usize, max: usize },
802 #[error(transparent)]
803 InvalidResource(#[from] InvalidResourceError),
804}
805
806impl WebGpuError for CreatePipelineLayoutError {
807 fn webgpu_error_type(&self) -> ErrorType {
808 let e: &dyn WebGpuError = match self {
809 Self::Device(e) => e,
810 Self::MissingFeatures(e) => e,
811 Self::InvalidResource(e) => e,
812 Self::TooManyBindings(e) => e,
813 Self::MisalignedPushConstantRange { .. }
814 | Self::MoreThanOnePushConstantRangePerStage { .. }
815 | Self::PushConstantRangeTooLarge { .. }
816 | Self::TooManyGroups { .. } => return ErrorType::Validation,
817 };
818 e.webgpu_error_type()
819 }
820}
821
822#[derive(Clone, Debug, Error)]
823#[non_exhaustive]
824pub enum PushConstantUploadError {
825 #[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)]
826 TooLarge {
827 offset: u32,
828 end_offset: u32,
829 idx: usize,
830 range: wgt::PushConstantRange,
831 },
832 #[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")]
833 PartialRangeMatch {
834 actual: wgt::ShaderStages,
835 idx: usize,
836 matched: wgt::ShaderStages,
837 },
838 #[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")]
839 MissingStages {
840 actual: wgt::ShaderStages,
841 idx: usize,
842 missing: wgt::ShaderStages,
843 },
844 #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
845 UnmatchedStages {
846 actual: wgt::ShaderStages,
847 unmatched: wgt::ShaderStages,
848 },
849 #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
850 Unaligned(u32),
851}
852
853impl WebGpuError for PushConstantUploadError {
854 fn webgpu_error_type(&self) -> ErrorType {
855 ErrorType::Validation
856 }
857}
858
859#[derive(Clone, Debug, PartialEq, Eq, Hash)]
863#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
864#[cfg_attr(feature = "serde", serde(bound = "BGL: Serialize"))]
865pub struct PipelineLayoutDescriptor<'a, BGL = BindGroupLayoutId>
866where
867 [BGL]: ToOwned,
868 <[BGL] as ToOwned>::Owned: fmt::Debug,
869{
870 pub label: Label<'a>,
874 #[cfg_attr(
877 feature = "serde",
878 serde(bound(deserialize = "<[BGL] as ToOwned>::Owned: Deserialize<'de>"))
879 )]
880 pub bind_group_layouts: Cow<'a, [BGL]>,
881 pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
889}
890
891pub type ResolvedPipelineLayoutDescriptor<'a, BGL = Arc<BindGroupLayout>> =
893 PipelineLayoutDescriptor<'a, BGL>;
894
895#[derive(Debug)]
896pub struct PipelineLayout {
897 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynPipelineLayout>>,
898 pub(crate) device: Arc<Device>,
899 pub(crate) label: String,
901 pub(crate) bind_group_layouts: ArrayVec<Arc<BindGroupLayout>, { hal::MAX_BIND_GROUPS }>,
902 pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
903}
904
905impl Drop for PipelineLayout {
906 fn drop(&mut self) {
907 resource_log!("Destroy raw {}", self.error_ident());
908 let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
910 unsafe {
911 self.device.raw().destroy_pipeline_layout(raw);
912 }
913 }
914}
915
916impl PipelineLayout {
917 pub(crate) fn raw(&self) -> &dyn hal::DynPipelineLayout {
918 self.raw.as_ref()
919 }
920
921 pub(crate) fn get_binding_maps(&self) -> ArrayVec<&bgl::EntryMap, { hal::MAX_BIND_GROUPS }> {
922 self.bind_group_layouts
923 .iter()
924 .map(|bgl| &bgl.entries)
925 .collect()
926 }
927
928 pub(crate) fn validate_push_constant_ranges(
930 &self,
931 stages: wgt::ShaderStages,
932 offset: u32,
933 end_offset: u32,
934 ) -> Result<(), PushConstantUploadError> {
935 if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
940 return Err(PushConstantUploadError::Unaligned(offset));
941 }
942
943 let mut used_stages = wgt::ShaderStages::NONE;
963 for (idx, range) in self.push_constant_ranges.iter().enumerate() {
964 if stages.contains(range.stages) {
966 if !(range.range.start <= offset && end_offset <= range.range.end) {
967 return Err(PushConstantUploadError::TooLarge {
968 offset,
969 end_offset,
970 idx,
971 range: range.clone(),
972 });
973 }
974 used_stages |= range.stages;
975 } else if stages.intersects(range.stages) {
976 return Err(PushConstantUploadError::PartialRangeMatch {
979 actual: stages,
980 idx,
981 matched: range.stages,
982 });
983 }
984
985 if offset < range.range.end && range.range.start < end_offset {
987 if !stages.contains(range.stages) {
989 return Err(PushConstantUploadError::MissingStages {
990 actual: stages,
991 idx,
992 missing: stages,
993 });
994 }
995 }
996 }
997 if used_stages != stages {
998 return Err(PushConstantUploadError::UnmatchedStages {
999 actual: stages,
1000 unmatched: stages - used_stages,
1001 });
1002 }
1003 Ok(())
1004 }
1005}
1006
1007crate::impl_resource_type!(PipelineLayout);
1008crate::impl_labeled!(PipelineLayout);
1009crate::impl_parent_device!(PipelineLayout);
1010crate::impl_storage_item!(PipelineLayout);
1011
1012#[repr(C)]
1013#[derive(Clone, Debug, Hash, Eq, PartialEq)]
1014#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1015pub struct BufferBinding<B = BufferId> {
1016 pub buffer: B,
1017 pub offset: wgt::BufferAddress,
1018 pub size: Option<wgt::BufferSize>,
1019}
1020
1021pub type ResolvedBufferBinding = BufferBinding<Arc<Buffer>>;
1022
1023#[derive(Debug, Clone)]
1026#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1027pub enum BindingResource<
1028 'a,
1029 B = BufferId,
1030 S = SamplerId,
1031 TV = TextureViewId,
1032 TLAS = TlasId,
1033 ET = ExternalTextureId,
1034> where
1035 [BufferBinding<B>]: ToOwned,
1036 [S]: ToOwned,
1037 [TV]: ToOwned,
1038 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
1039 <[S] as ToOwned>::Owned: fmt::Debug,
1040 <[TV] as ToOwned>::Owned: fmt::Debug,
1041{
1042 Buffer(BufferBinding<B>),
1043 #[cfg_attr(
1044 feature = "serde",
1045 serde(bound(deserialize = "<[BufferBinding<B>] as ToOwned>::Owned: Deserialize<'de>"))
1046 )]
1047 BufferArray(Cow<'a, [BufferBinding<B>]>),
1048 Sampler(S),
1049 #[cfg_attr(
1050 feature = "serde",
1051 serde(bound(deserialize = "<[S] as ToOwned>::Owned: Deserialize<'de>"))
1052 )]
1053 SamplerArray(Cow<'a, [S]>),
1054 TextureView(TV),
1055 #[cfg_attr(
1056 feature = "serde",
1057 serde(bound(deserialize = "<[TV] as ToOwned>::Owned: Deserialize<'de>"))
1058 )]
1059 TextureViewArray(Cow<'a, [TV]>),
1060 AccelerationStructure(TLAS),
1061 ExternalTexture(ET),
1062}
1063
1064pub type ResolvedBindingResource<'a> = BindingResource<
1065 'a,
1066 Arc<Buffer>,
1067 Arc<Sampler>,
1068 Arc<TextureView>,
1069 Arc<Tlas>,
1070 Arc<ExternalTexture>,
1071>;
1072
1073#[derive(Clone, Debug, Error)]
1074#[non_exhaustive]
1075pub enum BindError {
1076 #[error(
1077 "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
1078 s0 = if *.expected >= 2 { "s" } else { "" },
1079 s1 = if *.actual >= 2 { "s" } else { "" },
1080 )]
1081 MismatchedDynamicOffsetCount {
1082 bind_group: ResourceErrorIdent,
1083 group: u32,
1084 actual: usize,
1085 expected: usize,
1086 },
1087 #[error(
1088 "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
1089 )]
1090 UnalignedDynamicBinding {
1091 bind_group: ResourceErrorIdent,
1092 idx: usize,
1093 group: u32,
1094 binding: u32,
1095 offset: u32,
1096 alignment: u32,
1097 limit_name: &'static str,
1098 },
1099 #[error(
1100 "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \
1101 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",
1102 )]
1103 DynamicBindingOutOfBounds {
1104 bind_group: ResourceErrorIdent,
1105 idx: usize,
1106 group: u32,
1107 binding: u32,
1108 offset: u32,
1109 buffer_size: wgt::BufferAddress,
1110 binding_range: Range<wgt::BufferAddress>,
1111 maximum_dynamic_offset: wgt::BufferAddress,
1112 },
1113}
1114
1115impl WebGpuError for BindError {
1116 fn webgpu_error_type(&self) -> ErrorType {
1117 ErrorType::Validation
1118 }
1119}
1120
1121#[derive(Debug)]
1122pub struct BindGroupDynamicBindingData {
1123 pub(crate) binding_idx: u32,
1127 pub(crate) buffer_size: wgt::BufferAddress,
1131 pub(crate) binding_range: Range<wgt::BufferAddress>,
1135 pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
1137 pub(crate) binding_type: wgt::BufferBindingType,
1139}
1140
1141pub(crate) fn buffer_binding_type_alignment(
1142 limits: &wgt::Limits,
1143 binding_type: wgt::BufferBindingType,
1144) -> (u32, &'static str) {
1145 match binding_type {
1146 wgt::BufferBindingType::Uniform => (
1147 limits.min_uniform_buffer_offset_alignment,
1148 "min_uniform_buffer_offset_alignment",
1149 ),
1150 wgt::BufferBindingType::Storage { .. } => (
1151 limits.min_storage_buffer_offset_alignment,
1152 "min_storage_buffer_offset_alignment",
1153 ),
1154 }
1155}
1156
1157pub(crate) fn buffer_binding_type_bounds_check_alignment(
1158 alignments: &hal::Alignments,
1159 binding_type: wgt::BufferBindingType,
1160) -> wgt::BufferAddress {
1161 match binding_type {
1162 wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(),
1163 wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT,
1164 }
1165}
1166
1167#[derive(Debug)]
1168pub struct BindGroup {
1169 pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>,
1170 pub(crate) device: Arc<Device>,
1171 pub(crate) layout: Arc<BindGroupLayout>,
1172 pub(crate) label: String,
1174 pub(crate) tracking_data: TrackingData,
1175 pub(crate) used: BindGroupStates,
1176 pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
1177 pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
1178 pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
1179 pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
1182}
1183
1184impl Drop for BindGroup {
1185 fn drop(&mut self) {
1186 if let Some(raw) = self.raw.take() {
1187 resource_log!("Destroy raw {}", self.error_ident());
1188 unsafe {
1189 self.device.raw().destroy_bind_group(raw);
1190 }
1191 }
1192 }
1193}
1194
1195impl BindGroup {
1196 pub(crate) fn try_raw<'a>(
1197 &'a self,
1198 guard: &'a SnatchGuard,
1199 ) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> {
1200 for buffer in &self.used_buffer_ranges {
1203 buffer.buffer.try_raw(guard)?;
1204 }
1205 for texture in &self.used_texture_ranges {
1206 texture.texture.try_raw(guard)?;
1207 }
1208
1209 self.raw
1210 .get(guard)
1211 .map(|raw| raw.as_ref())
1212 .ok_or_else(|| DestroyedResourceError(self.error_ident()))
1213 }
1214
1215 pub(crate) fn validate_dynamic_bindings(
1216 &self,
1217 bind_group_index: u32,
1218 offsets: &[wgt::DynamicOffset],
1219 ) -> Result<(), BindError> {
1220 if self.dynamic_binding_info.len() != offsets.len() {
1221 return Err(BindError::MismatchedDynamicOffsetCount {
1222 bind_group: self.error_ident(),
1223 group: bind_group_index,
1224 expected: self.dynamic_binding_info.len(),
1225 actual: offsets.len(),
1226 });
1227 }
1228
1229 for (idx, (info, &offset)) in self
1230 .dynamic_binding_info
1231 .iter()
1232 .zip(offsets.iter())
1233 .enumerate()
1234 {
1235 let (alignment, limit_name) =
1236 buffer_binding_type_alignment(&self.device.limits, info.binding_type);
1237 if offset as wgt::BufferAddress % alignment as u64 != 0 {
1238 return Err(BindError::UnalignedDynamicBinding {
1239 bind_group: self.error_ident(),
1240 group: bind_group_index,
1241 binding: info.binding_idx,
1242 idx,
1243 offset,
1244 alignment,
1245 limit_name,
1246 });
1247 }
1248
1249 if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
1250 return Err(BindError::DynamicBindingOutOfBounds {
1251 bind_group: self.error_ident(),
1252 group: bind_group_index,
1253 binding: info.binding_idx,
1254 idx,
1255 offset,
1256 buffer_size: info.buffer_size,
1257 binding_range: info.binding_range.clone(),
1258 maximum_dynamic_offset: info.maximum_dynamic_offset,
1259 });
1260 }
1261 }
1262
1263 Ok(())
1264 }
1265}
1266
1267crate::impl_resource_type!(BindGroup);
1268crate::impl_labeled!(BindGroup);
1269crate::impl_parent_device!(BindGroup);
1270crate::impl_storage_item!(BindGroup);
1271crate::impl_trackable!(BindGroup);
1272
1273#[derive(Clone, Debug, Error)]
1274#[non_exhaustive]
1275pub enum GetBindGroupLayoutError {
1276 #[error("Invalid group index {0}")]
1277 InvalidGroupIndex(u32),
1278 #[error(transparent)]
1279 InvalidResource(#[from] InvalidResourceError),
1280}
1281
1282impl WebGpuError for GetBindGroupLayoutError {
1283 fn webgpu_error_type(&self) -> ErrorType {
1284 match self {
1285 Self::InvalidGroupIndex(_) => ErrorType::Validation,
1286 Self::InvalidResource(e) => e.webgpu_error_type(),
1287 }
1288 }
1289}
1290
1291#[derive(Clone, Debug, Error, Eq, PartialEq)]
1292#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
1293pub struct LateMinBufferBindingSizeMismatch {
1294 pub group_index: u32,
1295 pub compact_index: usize,
1296 pub shader_size: wgt::BufferAddress,
1297 pub bound_size: wgt::BufferAddress,
1298}