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, SamplerId, TextureViewId, TlasId},
25 init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction},
26 pipeline::{ComputePipeline, RenderPipeline},
27 resource::{
28 Buffer, DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError,
29 MissingTextureUsageError, RawResourceAccess, ResourceErrorIdent, Sampler, TextureView,
30 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)]
101#[non_exhaustive]
102pub enum CreateBindGroupError {
103 #[error(transparent)]
104 Device(#[from] DeviceError),
105 #[error(transparent)]
106 DestroyedResource(#[from] DestroyedResourceError),
107 #[error(
108 "Binding count declared with at most {expected} items, but {actual} items were provided"
109 )]
110 BindingArrayPartialLengthMismatch { actual: usize, expected: usize },
111 #[error(
112 "Binding count declared with exactly {expected} items, but {actual} items were provided"
113 )]
114 BindingArrayLengthMismatch { actual: usize, expected: usize },
115 #[error("Array binding provided zero elements")]
116 BindingArrayZeroLength,
117 #[error("The bound range {range:?} of {buffer} overflows its size ({size})")]
118 BindingRangeTooLarge {
119 buffer: ResourceErrorIdent,
120 range: Range<wgt::BufferAddress>,
121 size: u64,
122 },
123 #[error("Binding size {actual} of {buffer} is less than minimum {min}")]
124 BindingSizeTooSmall {
125 buffer: ResourceErrorIdent,
126 actual: u64,
127 min: u64,
128 },
129 #[error("{0} binding size is zero")]
130 BindingZeroSize(ResourceErrorIdent),
131 #[error("Number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
132 BindingsNumMismatch { actual: usize, expected: usize },
133 #[error("Binding {0} is used at least twice in the descriptor")]
134 DuplicateBinding(u32),
135 #[error("Unable to find a corresponding declaration for the given binding {0}")]
136 MissingBindingDeclaration(u32),
137 #[error(transparent)]
138 MissingBufferUsage(#[from] MissingBufferUsageError),
139 #[error(transparent)]
140 MissingTextureUsage(#[from] MissingTextureUsageError),
141 #[error("Binding declared as a single item, but bind group is using it as an array")]
142 SingleBindingExpected,
143 #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")]
144 UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32),
145 #[error(
146 "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}"
147 )]
148 BufferRangeTooLarge {
149 binding: u32,
150 given: u32,
151 limit: u32,
152 },
153 #[error("Binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
154 WrongBindingType {
155 binding: u32,
157 actual: wgt::BindingType,
159 expected: &'static str,
161 },
162 #[error("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")]
163 InvalidTextureMultisample {
164 binding: u32,
165 layout_multisampled: bool,
166 view_samples: u32,
167 },
168 #[error(
169 "Texture binding {} expects sample type {:?}, but was given a view with format {:?} (sample type {:?})",
170 binding,
171 layout_sample_type,
172 view_format,
173 view_sample_type
174 )]
175 InvalidTextureSampleType {
176 binding: u32,
177 layout_sample_type: wgt::TextureSampleType,
178 view_format: wgt::TextureFormat,
179 view_sample_type: wgt::TextureSampleType,
180 },
181 #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")]
182 InvalidTextureDimension {
183 binding: u32,
184 layout_dimension: wgt::TextureViewDimension,
185 view_dimension: wgt::TextureViewDimension,
186 },
187 #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")]
188 InvalidStorageTextureFormat {
189 binding: u32,
190 layout_format: wgt::TextureFormat,
191 view_format: wgt::TextureFormat,
192 },
193 #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
194 InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 },
195 #[error("External texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")]
196 InvalidExternalTextureMipLevelCount { binding: u32, mip_level_count: u32 },
197 #[error("External texture bindings must have a format of `rgba8unorm`, `bgra8unorm`, or `rgba16float, but given a view with format = {format:?} at binding {binding}")]
198 InvalidExternalTextureFormat {
199 binding: u32,
200 format: wgt::TextureFormat,
201 },
202 #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")]
203 WrongSamplerComparison {
204 binding: u32,
205 layout_cmp: bool,
206 sampler_cmp: bool,
207 },
208 #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")]
209 WrongSamplerFiltering {
210 binding: u32,
211 layout_flt: bool,
212 sampler_flt: bool,
213 },
214 #[error("TLAS binding {binding} is required to support vertex returns but is missing flag AccelerationStructureFlags::ALLOW_RAY_HIT_VERTEX_RETURN")]
215 MissingTLASVertexReturn { binding: u32 },
216 #[error("Bound texture views can not have both depth and stencil aspects enabled")]
217 DepthStencilAspect,
218 #[error("The adapter does not support read access for storage textures of format {0:?}")]
219 StorageReadNotSupported(wgt::TextureFormat),
220 #[error("The adapter does not support atomics for storage textures of format {0:?}")]
221 StorageAtomicNotSupported(wgt::TextureFormat),
222 #[error("The adapter does not support write access for storage textures of format {0:?}")]
223 StorageWriteNotSupported(wgt::TextureFormat),
224 #[error("The adapter does not support read-write access for storage textures of format {0:?}")]
225 StorageReadWriteNotSupported(wgt::TextureFormat),
226 #[error(transparent)]
227 ResourceUsageCompatibility(#[from] ResourceUsageCompatibilityError),
228 #[error(transparent)]
229 InvalidResource(#[from] InvalidResourceError),
230}
231
232impl WebGpuError for CreateBindGroupError {
233 fn webgpu_error_type(&self) -> ErrorType {
234 let e: &dyn WebGpuError = match self {
235 Self::Device(e) => e,
236 Self::DestroyedResource(e) => e,
237 Self::MissingBufferUsage(e) => e,
238 Self::MissingTextureUsage(e) => e,
239 Self::ResourceUsageCompatibility(e) => e,
240 Self::InvalidResource(e) => e,
241 Self::BindingArrayPartialLengthMismatch { .. }
242 | Self::BindingArrayLengthMismatch { .. }
243 | Self::BindingArrayZeroLength
244 | Self::BindingRangeTooLarge { .. }
245 | Self::BindingSizeTooSmall { .. }
246 | Self::BindingsNumMismatch { .. }
247 | Self::BindingZeroSize(_)
248 | Self::DuplicateBinding(_)
249 | Self::MissingBindingDeclaration(_)
250 | Self::SingleBindingExpected
251 | Self::UnalignedBufferOffset(_, _, _)
252 | Self::BufferRangeTooLarge { .. }
253 | Self::WrongBindingType { .. }
254 | Self::InvalidTextureMultisample { .. }
255 | Self::InvalidTextureSampleType { .. }
256 | Self::InvalidTextureDimension { .. }
257 | Self::InvalidStorageTextureFormat { .. }
258 | Self::InvalidStorageTextureMipLevelCount { .. }
259 | Self::WrongSamplerComparison { .. }
260 | Self::WrongSamplerFiltering { .. }
261 | Self::DepthStencilAspect
262 | Self::StorageReadNotSupported(_)
263 | Self::StorageWriteNotSupported(_)
264 | Self::StorageReadWriteNotSupported(_)
265 | Self::StorageAtomicNotSupported(_)
266 | Self::MissingTLASVertexReturn { .. }
267 | Self::InvalidExternalTextureMipLevelCount { .. }
268 | Self::InvalidExternalTextureFormat { .. } => return ErrorType::Validation,
269 };
270 e.webgpu_error_type()
271 }
272}
273
274#[derive(Clone, Debug, Error)]
275pub enum BindingZone {
276 #[error("Stage {0:?}")]
277 Stage(wgt::ShaderStages),
278 #[error("Whole pipeline")]
279 Pipeline,
280}
281
282#[derive(Clone, Debug, Error)]
283#[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())]
284pub struct BindingTypeMaxCountError {
285 pub kind: BindingTypeMaxCountErrorKind,
286 pub zone: BindingZone,
287 pub limit: u32,
288 pub count: u32,
289}
290
291impl WebGpuError for BindingTypeMaxCountError {
292 fn webgpu_error_type(&self) -> ErrorType {
293 ErrorType::Validation
294 }
295}
296
297#[derive(Clone, Debug)]
298pub enum BindingTypeMaxCountErrorKind {
299 DynamicUniformBuffers,
300 DynamicStorageBuffers,
301 SampledTextures,
302 Samplers,
303 StorageBuffers,
304 StorageTextures,
305 UniformBuffers,
306 BindingArrayElements,
307 BindingArraySamplerElements,
308 AccelerationStructures,
309}
310
311impl BindingTypeMaxCountErrorKind {
312 fn to_config_str(&self) -> &'static str {
313 match self {
314 BindingTypeMaxCountErrorKind::DynamicUniformBuffers => {
315 "max_dynamic_uniform_buffers_per_pipeline_layout"
316 }
317 BindingTypeMaxCountErrorKind::DynamicStorageBuffers => {
318 "max_dynamic_storage_buffers_per_pipeline_layout"
319 }
320 BindingTypeMaxCountErrorKind::SampledTextures => {
321 "max_sampled_textures_per_shader_stage"
322 }
323 BindingTypeMaxCountErrorKind::Samplers => "max_samplers_per_shader_stage",
324 BindingTypeMaxCountErrorKind::StorageBuffers => "max_storage_buffers_per_shader_stage",
325 BindingTypeMaxCountErrorKind::StorageTextures => {
326 "max_storage_textures_per_shader_stage"
327 }
328 BindingTypeMaxCountErrorKind::UniformBuffers => "max_uniform_buffers_per_shader_stage",
329 BindingTypeMaxCountErrorKind::BindingArrayElements => {
330 "max_binding_array_elements_per_shader_stage"
331 }
332 BindingTypeMaxCountErrorKind::BindingArraySamplerElements => {
333 "max_binding_array_sampler_elements_per_shader_stage"
334 }
335 BindingTypeMaxCountErrorKind::AccelerationStructures => {
336 "max_acceleration_structures_per_shader_stage"
337 }
338 }
339 }
340}
341
342#[derive(Debug, Default)]
343pub(crate) struct PerStageBindingTypeCounter {
344 vertex: u32,
345 fragment: u32,
346 compute: u32,
347}
348
349impl PerStageBindingTypeCounter {
350 pub(crate) fn add(&mut self, stage: wgt::ShaderStages, count: u32) {
351 if stage.contains(wgt::ShaderStages::VERTEX) {
352 self.vertex += count;
353 }
354 if stage.contains(wgt::ShaderStages::FRAGMENT) {
355 self.fragment += count;
356 }
357 if stage.contains(wgt::ShaderStages::COMPUTE) {
358 self.compute += count;
359 }
360 }
361
362 pub(crate) fn max(&self) -> (BindingZone, u32) {
363 let max_value = self.vertex.max(self.fragment.max(self.compute));
364 let mut stage = wgt::ShaderStages::NONE;
365 if max_value == self.vertex {
366 stage |= wgt::ShaderStages::VERTEX
367 }
368 if max_value == self.fragment {
369 stage |= wgt::ShaderStages::FRAGMENT
370 }
371 if max_value == self.compute {
372 stage |= wgt::ShaderStages::COMPUTE
373 }
374 (BindingZone::Stage(stage), max_value)
375 }
376
377 pub(crate) fn merge(&mut self, other: &Self) {
378 self.vertex = self.vertex.max(other.vertex);
379 self.fragment = self.fragment.max(other.fragment);
380 self.compute = self.compute.max(other.compute);
381 }
382
383 pub(crate) fn validate(
384 &self,
385 limit: u32,
386 kind: BindingTypeMaxCountErrorKind,
387 ) -> Result<(), BindingTypeMaxCountError> {
388 let (zone, count) = self.max();
389 if limit < count {
390 Err(BindingTypeMaxCountError {
391 kind,
392 zone,
393 limit,
394 count,
395 })
396 } else {
397 Ok(())
398 }
399 }
400}
401
402#[derive(Debug, Default)]
403pub(crate) struct BindingTypeMaxCountValidator {
404 dynamic_uniform_buffers: u32,
405 dynamic_storage_buffers: u32,
406 sampled_textures: PerStageBindingTypeCounter,
407 samplers: PerStageBindingTypeCounter,
408 storage_buffers: PerStageBindingTypeCounter,
409 storage_textures: PerStageBindingTypeCounter,
410 uniform_buffers: PerStageBindingTypeCounter,
411 acceleration_structures: PerStageBindingTypeCounter,
412 binding_array_elements: PerStageBindingTypeCounter,
413 binding_array_sampler_elements: PerStageBindingTypeCounter,
414 has_bindless_array: bool,
415}
416
417impl BindingTypeMaxCountValidator {
418 pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
419 let count = binding.count.map_or(1, |count| count.get());
420
421 if binding.count.is_some() {
422 self.binding_array_elements.add(binding.visibility, count);
423 self.has_bindless_array = true;
424
425 if let wgt::BindingType::Sampler(_) = binding.ty {
426 self.binding_array_sampler_elements
427 .add(binding.visibility, count);
428 }
429 } else {
430 match binding.ty {
431 wgt::BindingType::Buffer {
432 ty: wgt::BufferBindingType::Uniform,
433 has_dynamic_offset,
434 ..
435 } => {
436 self.uniform_buffers.add(binding.visibility, count);
437 if has_dynamic_offset {
438 self.dynamic_uniform_buffers += count;
439 }
440 }
441 wgt::BindingType::Buffer {
442 ty: wgt::BufferBindingType::Storage { .. },
443 has_dynamic_offset,
444 ..
445 } => {
446 self.storage_buffers.add(binding.visibility, count);
447 if has_dynamic_offset {
448 self.dynamic_storage_buffers += count;
449 }
450 }
451 wgt::BindingType::Sampler { .. } => {
452 self.samplers.add(binding.visibility, count);
453 }
454 wgt::BindingType::Texture { .. } => {
455 self.sampled_textures.add(binding.visibility, count);
456 }
457 wgt::BindingType::StorageTexture { .. } => {
458 self.storage_textures.add(binding.visibility, count);
459 }
460 wgt::BindingType::AccelerationStructure { .. } => {
461 self.acceleration_structures.add(binding.visibility, count);
462 }
463 wgt::BindingType::ExternalTexture => {
464 self.sampled_textures.add(binding.visibility, count * 4);
473 self.samplers.add(binding.visibility, count);
474 self.uniform_buffers.add(binding.visibility, count);
475 }
476 }
477 }
478 }
479
480 pub(crate) fn merge(&mut self, other: &Self) {
481 self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
482 self.dynamic_storage_buffers += other.dynamic_storage_buffers;
483 self.sampled_textures.merge(&other.sampled_textures);
484 self.samplers.merge(&other.samplers);
485 self.storage_buffers.merge(&other.storage_buffers);
486 self.storage_textures.merge(&other.storage_textures);
487 self.uniform_buffers.merge(&other.uniform_buffers);
488 self.acceleration_structures
489 .merge(&other.acceleration_structures);
490 self.binding_array_elements
491 .merge(&other.binding_array_elements);
492 self.binding_array_sampler_elements
493 .merge(&other.binding_array_sampler_elements);
494 }
495
496 pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
497 if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
498 return Err(BindingTypeMaxCountError {
499 kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
500 zone: BindingZone::Pipeline,
501 limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout,
502 count: self.dynamic_uniform_buffers,
503 });
504 }
505 if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
506 return Err(BindingTypeMaxCountError {
507 kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
508 zone: BindingZone::Pipeline,
509 limit: limits.max_dynamic_storage_buffers_per_pipeline_layout,
510 count: self.dynamic_storage_buffers,
511 });
512 }
513 self.sampled_textures.validate(
514 limits.max_sampled_textures_per_shader_stage,
515 BindingTypeMaxCountErrorKind::SampledTextures,
516 )?;
517 self.samplers.validate(
518 limits.max_samplers_per_shader_stage,
519 BindingTypeMaxCountErrorKind::Samplers,
520 )?;
521 self.storage_buffers.validate(
522 limits.max_storage_buffers_per_shader_stage,
523 BindingTypeMaxCountErrorKind::StorageBuffers,
524 )?;
525 self.storage_textures.validate(
526 limits.max_storage_textures_per_shader_stage,
527 BindingTypeMaxCountErrorKind::StorageTextures,
528 )?;
529 self.uniform_buffers.validate(
530 limits.max_uniform_buffers_per_shader_stage,
531 BindingTypeMaxCountErrorKind::UniformBuffers,
532 )?;
533 self.binding_array_elements.validate(
534 limits.max_binding_array_elements_per_shader_stage,
535 BindingTypeMaxCountErrorKind::BindingArrayElements,
536 )?;
537 self.binding_array_sampler_elements.validate(
538 limits.max_binding_array_sampler_elements_per_shader_stage,
539 BindingTypeMaxCountErrorKind::BindingArraySamplerElements,
540 )?;
541 self.acceleration_structures.validate(
542 limits.max_acceleration_structures_per_shader_stage,
543 BindingTypeMaxCountErrorKind::AccelerationStructures,
544 )?;
545 Ok(())
546 }
547
548 pub(crate) fn validate_binding_arrays(&self) -> Result<(), CreateBindGroupLayoutError> {
553 let has_dynamic_offset_array =
554 self.dynamic_uniform_buffers > 0 || self.dynamic_storage_buffers > 0;
555 let has_uniform_buffer = self.uniform_buffers.max().1 > 0;
556 if self.has_bindless_array && has_dynamic_offset_array {
557 return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndDynamicOffsetArray);
558 }
559 if self.has_bindless_array && has_uniform_buffer {
560 return Err(CreateBindGroupLayoutError::ContainsBothBindingArrayAndUniformBuffer);
561 }
562 Ok(())
563 }
564}
565
566#[derive(Clone, Debug)]
569#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
570pub struct BindGroupEntry<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId>
571where
572 [BufferBinding<B>]: ToOwned,
573 [S]: ToOwned,
574 [TV]: ToOwned,
575 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
576 <[S] as ToOwned>::Owned: fmt::Debug,
577 <[TV] as ToOwned>::Owned: fmt::Debug,
578{
579 pub binding: u32,
582 #[cfg_attr(
583 feature = "serde",
584 serde(bound(deserialize = "BindingResource<'a, B, S, TV, TLAS>: Deserialize<'de>"))
585 )]
586 pub resource: BindingResource<'a, B, S, TV, TLAS>,
588}
589
590pub type ResolvedBindGroupEntry<'a> =
592 BindGroupEntry<'a, Arc<Buffer>, Arc<Sampler>, Arc<TextureView>, Arc<Tlas>>;
593
594#[derive(Clone, Debug)]
596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
597pub struct BindGroupDescriptor<
598 'a,
599 BGL = BindGroupLayoutId,
600 B = BufferId,
601 S = SamplerId,
602 TV = TextureViewId,
603 TLAS = TlasId,
604> where
605 [BufferBinding<B>]: ToOwned,
606 [S]: ToOwned,
607 [TV]: ToOwned,
608 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
609 <[S] as ToOwned>::Owned: fmt::Debug,
610 <[TV] as ToOwned>::Owned: fmt::Debug,
611 [BindGroupEntry<'a, B, S, TV, TLAS>]: ToOwned,
612 <[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: fmt::Debug,
613{
614 pub label: Label<'a>,
618 pub layout: BGL,
620 #[cfg_attr(
621 feature = "serde",
622 serde(bound(
623 deserialize = "<[BindGroupEntry<'a, B, S, TV, TLAS>] as ToOwned>::Owned: Deserialize<'de>"
624 ))
625 )]
626 pub entries: Cow<'a, [BindGroupEntry<'a, B, S, TV, TLAS>]>,
628}
629
630pub type ResolvedBindGroupDescriptor<'a> = BindGroupDescriptor<
632 'a,
633 Arc<BindGroupLayout>,
634 Arc<Buffer>,
635 Arc<Sampler>,
636 Arc<TextureView>,
637 Arc<Tlas>,
638>;
639
640#[derive(Clone, Debug)]
642#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
643pub struct BindGroupLayoutDescriptor<'a> {
644 pub label: Label<'a>,
648 pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
650}
651
652#[derive(Debug)]
656pub(crate) enum ExclusivePipeline {
657 None,
658 Render(Weak<RenderPipeline>),
659 Compute(Weak<ComputePipeline>),
660}
661
662impl fmt::Display for ExclusivePipeline {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 match self {
665 ExclusivePipeline::None => f.write_str("None"),
666 ExclusivePipeline::Render(p) => {
667 if let Some(p) = p.upgrade() {
668 p.error_ident().fmt(f)
669 } else {
670 f.write_str("RenderPipeline")
671 }
672 }
673 ExclusivePipeline::Compute(p) => {
674 if let Some(p) = p.upgrade() {
675 p.error_ident().fmt(f)
676 } else {
677 f.write_str("ComputePipeline")
678 }
679 }
680 }
681 }
682}
683
684#[derive(Debug)]
686pub struct BindGroupLayout {
687 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>,
688 pub(crate) device: Arc<Device>,
689 pub(crate) entries: bgl::EntryMap,
690 pub(crate) origin: bgl::Origin,
697 pub(crate) exclusive_pipeline: crate::OnceCellOrLock<ExclusivePipeline>,
698 #[allow(unused)]
699 pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
700 pub(crate) label: String,
702}
703
704impl Drop for BindGroupLayout {
705 fn drop(&mut self) {
706 resource_log!("Destroy raw {}", self.error_ident());
707 if matches!(self.origin, bgl::Origin::Pool) {
708 self.device.bgl_pool.remove(&self.entries);
709 }
710 let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
712 unsafe {
713 self.device.raw().destroy_bind_group_layout(raw);
714 }
715 }
716}
717
718crate::impl_resource_type!(BindGroupLayout);
719crate::impl_labeled!(BindGroupLayout);
720crate::impl_parent_device!(BindGroupLayout);
721crate::impl_storage_item!(BindGroupLayout);
722
723impl BindGroupLayout {
724 pub(crate) fn raw(&self) -> &dyn hal::DynBindGroupLayout {
725 self.raw.as_ref()
726 }
727}
728
729#[derive(Clone, Debug, Error)]
730#[non_exhaustive]
731pub enum CreatePipelineLayoutError {
732 #[error(transparent)]
733 Device(#[from] DeviceError),
734 #[error(
735 "Push constant at index {index} has range bound {bound} not aligned to {}",
736 wgt::PUSH_CONSTANT_ALIGNMENT
737 )]
738 MisalignedPushConstantRange { index: usize, bound: u32 },
739 #[error(transparent)]
740 MissingFeatures(#[from] MissingFeatures),
741 #[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")]
742 MoreThanOnePushConstantRangePerStage {
743 index: usize,
744 provided: wgt::ShaderStages,
745 intersected: wgt::ShaderStages,
746 },
747 #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
748 PushConstantRangeTooLarge {
749 index: usize,
750 range: Range<u32>,
751 max: u32,
752 },
753 #[error(transparent)]
754 TooManyBindings(BindingTypeMaxCountError),
755 #[error("Bind group layout count {actual} exceeds device bind group limit {max}")]
756 TooManyGroups { actual: usize, max: usize },
757 #[error(transparent)]
758 InvalidResource(#[from] InvalidResourceError),
759}
760
761impl WebGpuError for CreatePipelineLayoutError {
762 fn webgpu_error_type(&self) -> ErrorType {
763 let e: &dyn WebGpuError = match self {
764 Self::Device(e) => e,
765 Self::MissingFeatures(e) => e,
766 Self::InvalidResource(e) => e,
767 Self::TooManyBindings(e) => e,
768 Self::MisalignedPushConstantRange { .. }
769 | Self::MoreThanOnePushConstantRangePerStage { .. }
770 | Self::PushConstantRangeTooLarge { .. }
771 | Self::TooManyGroups { .. } => return ErrorType::Validation,
772 };
773 e.webgpu_error_type()
774 }
775}
776
777#[derive(Clone, Debug, Error)]
778#[non_exhaustive]
779pub enum PushConstantUploadError {
780 #[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)]
781 TooLarge {
782 offset: u32,
783 end_offset: u32,
784 idx: usize,
785 range: wgt::PushConstantRange,
786 },
787 #[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")]
788 PartialRangeMatch {
789 actual: wgt::ShaderStages,
790 idx: usize,
791 matched: wgt::ShaderStages,
792 },
793 #[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")]
794 MissingStages {
795 actual: wgt::ShaderStages,
796 idx: usize,
797 missing: wgt::ShaderStages,
798 },
799 #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
800 UnmatchedStages {
801 actual: wgt::ShaderStages,
802 unmatched: wgt::ShaderStages,
803 },
804 #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
805 Unaligned(u32),
806}
807
808impl WebGpuError for PushConstantUploadError {
809 fn webgpu_error_type(&self) -> ErrorType {
810 ErrorType::Validation
811 }
812}
813
814#[derive(Clone, Debug, PartialEq, Eq, Hash)]
818#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
819#[cfg_attr(feature = "serde", serde(bound = "BGL: Serialize"))]
820pub struct PipelineLayoutDescriptor<'a, BGL = BindGroupLayoutId>
821where
822 [BGL]: ToOwned,
823 <[BGL] as ToOwned>::Owned: fmt::Debug,
824{
825 pub label: Label<'a>,
829 #[cfg_attr(
832 feature = "serde",
833 serde(bound(deserialize = "<[BGL] as ToOwned>::Owned: Deserialize<'de>"))
834 )]
835 pub bind_group_layouts: Cow<'a, [BGL]>,
836 pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
844}
845
846pub type ResolvedPipelineLayoutDescriptor<'a> = PipelineLayoutDescriptor<'a, Arc<BindGroupLayout>>;
848
849#[derive(Debug)]
850pub struct PipelineLayout {
851 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynPipelineLayout>>,
852 pub(crate) device: Arc<Device>,
853 pub(crate) label: String,
855 pub(crate) bind_group_layouts: ArrayVec<Arc<BindGroupLayout>, { hal::MAX_BIND_GROUPS }>,
856 pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
857}
858
859impl Drop for PipelineLayout {
860 fn drop(&mut self) {
861 resource_log!("Destroy raw {}", self.error_ident());
862 let raw = unsafe { ManuallyDrop::take(&mut self.raw) };
864 unsafe {
865 self.device.raw().destroy_pipeline_layout(raw);
866 }
867 }
868}
869
870impl PipelineLayout {
871 pub(crate) fn raw(&self) -> &dyn hal::DynPipelineLayout {
872 self.raw.as_ref()
873 }
874
875 pub(crate) fn get_binding_maps(&self) -> ArrayVec<&bgl::EntryMap, { hal::MAX_BIND_GROUPS }> {
876 self.bind_group_layouts
877 .iter()
878 .map(|bgl| &bgl.entries)
879 .collect()
880 }
881
882 pub(crate) fn validate_push_constant_ranges(
884 &self,
885 stages: wgt::ShaderStages,
886 offset: u32,
887 end_offset: u32,
888 ) -> Result<(), PushConstantUploadError> {
889 if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
894 return Err(PushConstantUploadError::Unaligned(offset));
895 }
896
897 let mut used_stages = wgt::ShaderStages::NONE;
917 for (idx, range) in self.push_constant_ranges.iter().enumerate() {
918 if stages.contains(range.stages) {
920 if !(range.range.start <= offset && end_offset <= range.range.end) {
921 return Err(PushConstantUploadError::TooLarge {
922 offset,
923 end_offset,
924 idx,
925 range: range.clone(),
926 });
927 }
928 used_stages |= range.stages;
929 } else if stages.intersects(range.stages) {
930 return Err(PushConstantUploadError::PartialRangeMatch {
933 actual: stages,
934 idx,
935 matched: range.stages,
936 });
937 }
938
939 if offset < range.range.end && range.range.start < end_offset {
941 if !stages.contains(range.stages) {
943 return Err(PushConstantUploadError::MissingStages {
944 actual: stages,
945 idx,
946 missing: stages,
947 });
948 }
949 }
950 }
951 if used_stages != stages {
952 return Err(PushConstantUploadError::UnmatchedStages {
953 actual: stages,
954 unmatched: stages - used_stages,
955 });
956 }
957 Ok(())
958 }
959}
960
961crate::impl_resource_type!(PipelineLayout);
962crate::impl_labeled!(PipelineLayout);
963crate::impl_parent_device!(PipelineLayout);
964crate::impl_storage_item!(PipelineLayout);
965
966#[repr(C)]
967#[derive(Clone, Debug, Hash, Eq, PartialEq)]
968#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
969pub struct BufferBinding<B = BufferId> {
970 pub buffer: B,
971 pub offset: wgt::BufferAddress,
972 pub size: Option<wgt::BufferSize>,
973}
974
975pub type ResolvedBufferBinding = BufferBinding<Arc<Buffer>>;
976
977#[derive(Debug, Clone)]
980#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
981pub enum BindingResource<'a, B = BufferId, S = SamplerId, TV = TextureViewId, TLAS = TlasId>
982where
983 [BufferBinding<B>]: ToOwned,
984 [S]: ToOwned,
985 [TV]: ToOwned,
986 <[BufferBinding<B>] as ToOwned>::Owned: fmt::Debug,
987 <[S] as ToOwned>::Owned: fmt::Debug,
988 <[TV] as ToOwned>::Owned: fmt::Debug,
989{
990 Buffer(BufferBinding<B>),
991 #[cfg_attr(
992 feature = "serde",
993 serde(bound(deserialize = "<[BufferBinding<B>] as ToOwned>::Owned: Deserialize<'de>"))
994 )]
995 BufferArray(Cow<'a, [BufferBinding<B>]>),
996 Sampler(S),
997 #[cfg_attr(
998 feature = "serde",
999 serde(bound(deserialize = "<[S] as ToOwned>::Owned: Deserialize<'de>"))
1000 )]
1001 SamplerArray(Cow<'a, [S]>),
1002 TextureView(TV),
1003 #[cfg_attr(
1004 feature = "serde",
1005 serde(bound(deserialize = "<[TV] as ToOwned>::Owned: Deserialize<'de>"))
1006 )]
1007 TextureViewArray(Cow<'a, [TV]>),
1008 AccelerationStructure(TLAS),
1009}
1010
1011pub type ResolvedBindingResource<'a> =
1012 BindingResource<'a, Arc<Buffer>, Arc<Sampler>, Arc<TextureView>, Arc<Tlas>>;
1013
1014#[derive(Clone, Debug, Error)]
1015#[non_exhaustive]
1016pub enum BindError {
1017 #[error(
1018 "{bind_group} {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.",
1019 s0 = if *.expected >= 2 { "s" } else { "" },
1020 s1 = if *.actual >= 2 { "s" } else { "" },
1021 )]
1022 MismatchedDynamicOffsetCount {
1023 bind_group: ResourceErrorIdent,
1024 group: u32,
1025 actual: usize,
1026 expected: usize,
1027 },
1028 #[error(
1029 "Dynamic binding index {idx} (targeting {bind_group} {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}"
1030 )]
1031 UnalignedDynamicBinding {
1032 bind_group: ResourceErrorIdent,
1033 idx: usize,
1034 group: u32,
1035 binding: u32,
1036 offset: u32,
1037 alignment: u32,
1038 limit_name: &'static str,
1039 },
1040 #[error(
1041 "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to {bind_group} {group} -> binding {binding}. \
1042 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",
1043 )]
1044 DynamicBindingOutOfBounds {
1045 bind_group: ResourceErrorIdent,
1046 idx: usize,
1047 group: u32,
1048 binding: u32,
1049 offset: u32,
1050 buffer_size: wgt::BufferAddress,
1051 binding_range: Range<wgt::BufferAddress>,
1052 maximum_dynamic_offset: wgt::BufferAddress,
1053 },
1054}
1055
1056impl WebGpuError for BindError {
1057 fn webgpu_error_type(&self) -> ErrorType {
1058 ErrorType::Validation
1059 }
1060}
1061
1062#[derive(Debug)]
1063pub struct BindGroupDynamicBindingData {
1064 pub(crate) binding_idx: u32,
1068 pub(crate) buffer_size: wgt::BufferAddress,
1072 pub(crate) binding_range: Range<wgt::BufferAddress>,
1076 pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
1078 pub(crate) binding_type: wgt::BufferBindingType,
1080}
1081
1082pub(crate) fn buffer_binding_type_alignment(
1083 limits: &wgt::Limits,
1084 binding_type: wgt::BufferBindingType,
1085) -> (u32, &'static str) {
1086 match binding_type {
1087 wgt::BufferBindingType::Uniform => (
1088 limits.min_uniform_buffer_offset_alignment,
1089 "min_uniform_buffer_offset_alignment",
1090 ),
1091 wgt::BufferBindingType::Storage { .. } => (
1092 limits.min_storage_buffer_offset_alignment,
1093 "min_storage_buffer_offset_alignment",
1094 ),
1095 }
1096}
1097
1098pub(crate) fn buffer_binding_type_bounds_check_alignment(
1099 alignments: &hal::Alignments,
1100 binding_type: wgt::BufferBindingType,
1101) -> wgt::BufferAddress {
1102 match binding_type {
1103 wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(),
1104 wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT,
1105 }
1106}
1107
1108#[derive(Debug)]
1109pub struct BindGroup {
1110 pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>,
1111 pub(crate) device: Arc<Device>,
1112 pub(crate) layout: Arc<BindGroupLayout>,
1113 pub(crate) label: String,
1115 pub(crate) tracking_data: TrackingData,
1116 pub(crate) used: BindGroupStates,
1117 pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>,
1118 pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>,
1119 pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
1120 pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>,
1123}
1124
1125impl Drop for BindGroup {
1126 fn drop(&mut self) {
1127 if let Some(raw) = self.raw.take() {
1128 resource_log!("Destroy raw {}", self.error_ident());
1129 unsafe {
1130 self.device.raw().destroy_bind_group(raw);
1131 }
1132 }
1133 }
1134}
1135
1136impl BindGroup {
1137 pub(crate) fn try_raw<'a>(
1138 &'a self,
1139 guard: &'a SnatchGuard,
1140 ) -> Result<&'a dyn hal::DynBindGroup, DestroyedResourceError> {
1141 for buffer in &self.used_buffer_ranges {
1144 buffer.buffer.try_raw(guard)?;
1145 }
1146 for texture in &self.used_texture_ranges {
1147 texture.texture.try_raw(guard)?;
1148 }
1149
1150 self.raw
1151 .get(guard)
1152 .map(|raw| raw.as_ref())
1153 .ok_or_else(|| DestroyedResourceError(self.error_ident()))
1154 }
1155
1156 pub(crate) fn validate_dynamic_bindings(
1157 &self,
1158 bind_group_index: u32,
1159 offsets: &[wgt::DynamicOffset],
1160 ) -> Result<(), BindError> {
1161 if self.dynamic_binding_info.len() != offsets.len() {
1162 return Err(BindError::MismatchedDynamicOffsetCount {
1163 bind_group: self.error_ident(),
1164 group: bind_group_index,
1165 expected: self.dynamic_binding_info.len(),
1166 actual: offsets.len(),
1167 });
1168 }
1169
1170 for (idx, (info, &offset)) in self
1171 .dynamic_binding_info
1172 .iter()
1173 .zip(offsets.iter())
1174 .enumerate()
1175 {
1176 let (alignment, limit_name) =
1177 buffer_binding_type_alignment(&self.device.limits, info.binding_type);
1178 if offset as wgt::BufferAddress % alignment as u64 != 0 {
1179 return Err(BindError::UnalignedDynamicBinding {
1180 bind_group: self.error_ident(),
1181 group: bind_group_index,
1182 binding: info.binding_idx,
1183 idx,
1184 offset,
1185 alignment,
1186 limit_name,
1187 });
1188 }
1189
1190 if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
1191 return Err(BindError::DynamicBindingOutOfBounds {
1192 bind_group: self.error_ident(),
1193 group: bind_group_index,
1194 binding: info.binding_idx,
1195 idx,
1196 offset,
1197 buffer_size: info.buffer_size,
1198 binding_range: info.binding_range.clone(),
1199 maximum_dynamic_offset: info.maximum_dynamic_offset,
1200 });
1201 }
1202 }
1203
1204 Ok(())
1205 }
1206}
1207
1208crate::impl_resource_type!(BindGroup);
1209crate::impl_labeled!(BindGroup);
1210crate::impl_parent_device!(BindGroup);
1211crate::impl_storage_item!(BindGroup);
1212crate::impl_trackable!(BindGroup);
1213
1214#[derive(Clone, Debug, Error)]
1215#[non_exhaustive]
1216pub enum GetBindGroupLayoutError {
1217 #[error("Invalid group index {0}")]
1218 InvalidGroupIndex(u32),
1219 #[error(transparent)]
1220 InvalidResource(#[from] InvalidResourceError),
1221}
1222
1223impl WebGpuError for GetBindGroupLayoutError {
1224 fn webgpu_error_type(&self) -> ErrorType {
1225 match self {
1226 Self::InvalidGroupIndex(_) => ErrorType::Validation,
1227 Self::InvalidResource(e) => e.webgpu_error_type(),
1228 }
1229 }
1230}
1231
1232#[derive(Clone, Debug, Error, Eq, PartialEq)]
1233#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")]
1234pub struct LateMinBufferBindingSizeMismatch {
1235 pub group_index: u32,
1236 pub compact_index: usize,
1237 pub shader_size: wgt::BufferAddress,
1238 pub bound_size: wgt::BufferAddress,
1239}