1use alloc::{
2 boxed::Box,
3 string::{String, ToString as _},
4 vec::Vec,
5};
6use core::fmt;
7
8use arrayvec::ArrayVec;
9use hashbrown::hash_map::Entry;
10use shader_io_deductions::{display_deductions_as_optional_list, MaxVertexShaderOutputDeduction};
11use thiserror::Error;
12use wgt::{
13 error::{ErrorType, WebGpuError},
14 BindGroupLayoutEntry, BindingType,
15};
16
17use crate::{
18 device::bgl, resource::InvalidResourceError,
19 validation::shader_io_deductions::MaxFragmentShaderInputDeduction, FastHashMap, FastHashSet,
20};
21
22pub mod shader_io_deductions;
23
24#[derive(Debug)]
25enum ResourceType {
26 Buffer {
27 size: wgt::BufferSize,
28 },
29 Texture {
30 dim: naga::ImageDimension,
31 arrayed: bool,
32 class: naga::ImageClass,
33 },
34 Sampler {
35 comparison: bool,
36 },
37 AccelerationStructure {
38 vertex_return: bool,
39 },
40}
41
42#[derive(Clone, Debug)]
43pub enum BindingTypeName {
44 Buffer,
45 Texture,
46 Sampler,
47 AccelerationStructure,
48 ExternalTexture,
49}
50
51impl From<&ResourceType> for BindingTypeName {
52 fn from(ty: &ResourceType) -> BindingTypeName {
53 match ty {
54 ResourceType::Buffer { .. } => BindingTypeName::Buffer,
55 ResourceType::Texture {
56 class: naga::ImageClass::External,
57 ..
58 } => BindingTypeName::ExternalTexture,
59 ResourceType::Texture { .. } => BindingTypeName::Texture,
60 ResourceType::Sampler { .. } => BindingTypeName::Sampler,
61 ResourceType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
62 }
63 }
64}
65
66impl From<&BindingType> for BindingTypeName {
67 fn from(ty: &BindingType) -> BindingTypeName {
68 match ty {
69 BindingType::Buffer { .. } => BindingTypeName::Buffer,
70 BindingType::Texture { .. } => BindingTypeName::Texture,
71 BindingType::StorageTexture { .. } => BindingTypeName::Texture,
72 BindingType::Sampler { .. } => BindingTypeName::Sampler,
73 BindingType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
74 BindingType::ExternalTexture => BindingTypeName::ExternalTexture,
75 }
76 }
77}
78
79#[derive(Debug)]
80struct Resource {
81 #[allow(unused)]
82 name: Option<String>,
83 bind: naga::ResourceBinding,
84 ty: ResourceType,
85 class: naga::AddressSpace,
86}
87
88#[derive(Clone, Copy, Debug)]
89enum NumericDimension {
90 Scalar,
91 Vector(naga::VectorSize),
92 Matrix(naga::VectorSize, naga::VectorSize),
93}
94
95impl fmt::Display for NumericDimension {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 match *self {
98 Self::Scalar => write!(f, ""),
99 Self::Vector(size) => write!(f, "x{}", size as u8),
100 Self::Matrix(columns, rows) => write!(f, "x{}{}", columns as u8, rows as u8),
101 }
102 }
103}
104
105#[derive(Clone, Copy, Debug)]
106pub struct NumericType {
107 dim: NumericDimension,
108 scalar: naga::Scalar,
109}
110
111impl fmt::Display for NumericType {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113 write!(
114 f,
115 "{:?}{}{}",
116 self.scalar.kind,
117 self.scalar.width * 8,
118 self.dim
119 )
120 }
121}
122
123#[derive(Clone, Debug)]
124pub struct InterfaceVar {
125 pub ty: NumericType,
126 interpolation: Option<naga::Interpolation>,
127 sampling: Option<naga::Sampling>,
128 per_primitive: bool,
129}
130
131impl InterfaceVar {
132 pub fn vertex_attribute(format: wgt::VertexFormat) -> Self {
133 InterfaceVar {
134 ty: NumericType::from_vertex_format(format),
135 interpolation: None,
136 sampling: None,
137 per_primitive: false,
138 }
139 }
140}
141
142impl fmt::Display for InterfaceVar {
143 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144 write!(
145 f,
146 "{} interpolated as {:?} with sampling {:?}",
147 self.ty, self.interpolation, self.sampling
148 )
149 }
150}
151
152#[derive(Debug)]
153enum Varying {
154 Local { location: u32, iv: InterfaceVar },
155 BuiltIn(naga::BuiltIn),
156}
157
158#[allow(unused)]
159#[derive(Debug)]
160struct SpecializationConstant {
161 id: u32,
162 ty: NumericType,
163}
164
165#[derive(Debug)]
166struct EntryPointMeshInfo {
167 max_vertices: u32,
168 max_primitives: u32,
169}
170
171#[derive(Debug, Default)]
172struct EntryPoint {
173 inputs: Vec<Varying>,
174 outputs: Vec<Varying>,
175 resources: Vec<naga::Handle<Resource>>,
176 #[allow(unused)]
177 spec_constants: Vec<SpecializationConstant>,
178 sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>,
179 workgroup_size: [u32; 3],
180 dual_source_blending: bool,
181 task_payload_size: Option<u32>,
182 mesh_info: Option<EntryPointMeshInfo>,
183}
184
185#[derive(Debug)]
186pub struct Interface {
187 limits: wgt::Limits,
188 resources: naga::Arena<Resource>,
189 entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>,
190}
191
192#[derive(Clone, Debug, Error)]
193#[non_exhaustive]
194pub enum BindingError {
195 #[error("Binding is missing from the pipeline layout")]
196 Missing,
197 #[error("Visibility flags don't include the shader stage")]
198 Invisible,
199 #[error(
200 "Type on the shader side ({shader:?}) does not match the pipeline binding ({binding:?})"
201 )]
202 WrongType {
203 binding: BindingTypeName,
204 shader: BindingTypeName,
205 },
206 #[error("Storage class {binding:?} doesn't match the shader {shader:?}")]
207 WrongAddressSpace {
208 binding: naga::AddressSpace,
209 shader: naga::AddressSpace,
210 },
211 #[error("Address space {space:?} is not a valid Buffer address space")]
212 WrongBufferAddressSpace { space: naga::AddressSpace },
213 #[error("Buffer structure size {buffer_size}, added to one element of an unbound array, if it's the last field, ended up greater than the given `min_binding_size`, which is {min_binding_size}")]
214 WrongBufferSize {
215 buffer_size: wgt::BufferSize,
216 min_binding_size: wgt::BufferSize,
217 },
218 #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")]
219 WrongTextureViewDimension {
220 dim: naga::ImageDimension,
221 is_array: bool,
222 binding: BindingType,
223 },
224 #[error("Texture class {binding:?} doesn't match the shader {shader:?}")]
225 WrongTextureClass {
226 binding: naga::ImageClass,
227 shader: naga::ImageClass,
228 },
229 #[error("Comparison flag doesn't match the shader")]
230 WrongSamplerComparison,
231 #[error("Derived bind group layout type is not consistent between stages")]
232 InconsistentlyDerivedType,
233 #[error("Texture format {0:?} is not supported for storage use")]
234 BadStorageFormat(wgt::TextureFormat),
235}
236
237impl WebGpuError for BindingError {
238 fn webgpu_error_type(&self) -> ErrorType {
239 ErrorType::Validation
240 }
241}
242
243#[derive(Clone, Debug, Error)]
244#[non_exhaustive]
245pub enum FilteringError {
246 #[error("Integer textures can't be sampled with a filtering sampler")]
247 Integer,
248 #[error("Non-filterable float textures can't be sampled with a filtering sampler")]
249 Float,
250}
251
252impl WebGpuError for FilteringError {
253 fn webgpu_error_type(&self) -> ErrorType {
254 ErrorType::Validation
255 }
256}
257
258#[derive(Clone, Debug, Error)]
259#[non_exhaustive]
260pub enum InputError {
261 #[error("Input is not provided by the earlier stage in the pipeline")]
262 Missing,
263 #[error("Input type is not compatible with the provided {0}")]
264 WrongType(NumericType),
265 #[error("Input interpolation doesn't match provided {0:?}")]
266 InterpolationMismatch(Option<naga::Interpolation>),
267 #[error("Input sampling doesn't match provided {0:?}")]
268 SamplingMismatch(Option<naga::Sampling>),
269 #[error("Pipeline input has per_primitive={pipeline_input}, but shader expects per_primitive={shader}")]
270 WrongPerPrimitive { pipeline_input: bool, shader: bool },
271}
272
273impl WebGpuError for InputError {
274 fn webgpu_error_type(&self) -> ErrorType {
275 ErrorType::Validation
276 }
277}
278
279#[derive(Clone, Debug, Error)]
281#[non_exhaustive]
282pub enum StageError {
283 #[error(
284 "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension
285 limit `Limits::{per_dimension_limit}` of {limit:?} and the total invocation limit `Limits::{total_limit}` of {total}"
286 )]
287 InvalidWorkgroupSize {
288 current: [u32; 3],
289 current_total: u32,
290 limit: [u32; 3],
291 total: u32,
292 per_dimension_limit: &'static str,
293 total_limit: &'static str,
294 },
295 #[error("Unable to find entry point '{0}'")]
296 MissingEntryPoint(String),
297 #[error("Shader global {0:?} is not available in the pipeline layout")]
298 Binding(naga::ResourceBinding, #[source] BindingError),
299 #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")]
300 Filtering {
301 texture: naga::ResourceBinding,
302 sampler: naga::ResourceBinding,
303 #[source]
304 error: FilteringError,
305 },
306 #[error("Location[{location}] {var} is not provided by the previous stage outputs")]
307 Input {
308 location: wgt::ShaderLocation,
309 var: InterfaceVar,
310 #[source]
311 error: InputError,
312 },
313 #[error(
314 "Unable to select an entry point: no entry point was found in the provided shader module"
315 )]
316 NoEntryPointFound,
317 #[error(
318 "Unable to select an entry point: \
319 multiple entry points were found in the provided shader module, \
320 but no entry point was specified"
321 )]
322 MultipleEntryPointsFound,
323 #[error(transparent)]
324 InvalidResource(#[from] InvalidResourceError),
325 #[error(
326 "vertex shader output location Location[{location}] ({var}) exceeds the \
327 `max_inter_stage_shader_variables` limit ({}, 0-based){}",
328 limit - 1,
330 display_deductions_as_optional_list(deductions, |d| d.for_location())
331 )]
332 VertexOutputLocationTooLarge {
333 location: u32,
334 var: InterfaceVar,
335 limit: u32,
336 deductions: Vec<MaxVertexShaderOutputDeduction>,
337 },
338 #[error(
339 "found {num_found} user-defined vertex shader output variables, which exceeds the \
340 `max_inter_stage_shader_variables` limit ({limit}){}",
341 display_deductions_as_optional_list(deductions, |d| d.for_variables())
342 )]
343 TooManyUserDefinedVertexOutputs {
344 num_found: u32,
345 limit: u32,
346 deductions: Vec<MaxVertexShaderOutputDeduction>,
347 },
348 #[error(
349 "fragment shader input location Location[{location}] ({var}) exceeds the \
350 `max_inter_stage_shader_variables` limit ({}, 0-based){}",
351 limit - 1,
353 display_deductions_as_optional_list(deductions, |d| d.for_variables())
357 )]
358 FragmentInputLocationTooLarge {
359 location: u32,
360 var: InterfaceVar,
361 limit: u32,
362 deductions: Vec<MaxFragmentShaderInputDeduction>,
363 },
364 #[error(
365 "found {num_found} user-defined fragment shader input variables, which exceeds the \
366 `max_inter_stage_shader_variables` limit ({limit}){}",
367 display_deductions_as_optional_list(deductions, |d| d.for_variables())
368 )]
369 TooManyUserDefinedFragmentInputs {
370 num_found: u32,
371 limit: u32,
372 deductions: Vec<MaxFragmentShaderInputDeduction>,
373 },
374 #[error(
375 "Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
376 )]
377 ColorAttachmentLocationTooLarge {
378 location: u32,
379 var: InterfaceVar,
380 limit: u32,
381 },
382 #[error("Mesh shaders are limited to {limit} output vertices by `Limits::max_mesh_output_vertices`, but the shader has a maximum number of {value}")]
383 TooManyMeshVertices { limit: u32, value: u32 },
384 #[error("Mesh shaders are limited to {limit} output primitives by `Limits::max_mesh_output_primitives`, but the shader has a maximum number of {value}")]
385 TooManyMeshPrimitives { limit: u32, value: u32 },
386 #[error("Mesh or task shaders are limited to {limit} bytes of task payload by `Limits::max_task_payload_size`, but the shader has a task payload of size {value}")]
387 TaskPayloadTooLarge { limit: u32, value: u32 },
388 #[error("Mesh shader's task payload has size ({shader:?}), which doesn't match the payload declared in the task stage ({input:?})")]
389 TaskPayloadMustMatch {
390 input: Option<u32>,
391 shader: Option<u32>,
392 },
393 #[error("Primitive index can only be used in a fragment shader if the preceding shader was a vertex shader or a mesh shader that writes to primitive index.")]
394 InvalidPrimitiveIndex,
395 #[error("If a mesh shader writes to primitive index, it must be read by the fragment shader.")]
396 MissingPrimitiveIndex,
397 #[error("DrawId cannot be used in the same pipeline as a task shader")]
398 DrawIdError,
399}
400
401impl WebGpuError for StageError {
402 fn webgpu_error_type(&self) -> ErrorType {
403 let e: &dyn WebGpuError = match self {
404 Self::Binding(_, e) => e,
405 Self::InvalidResource(e) => e,
406 Self::Filtering {
407 texture: _,
408 sampler: _,
409 error,
410 } => error,
411 Self::Input {
412 location: _,
413 var: _,
414 error,
415 } => error,
416 Self::InvalidWorkgroupSize { .. }
417 | Self::MissingEntryPoint(..)
418 | Self::NoEntryPointFound
419 | Self::MultipleEntryPointsFound
420 | Self::VertexOutputLocationTooLarge { .. }
421 | Self::TooManyUserDefinedVertexOutputs { .. }
422 | Self::FragmentInputLocationTooLarge { .. }
423 | Self::TooManyUserDefinedFragmentInputs { .. }
424 | Self::ColorAttachmentLocationTooLarge { .. }
425 | Self::TooManyMeshVertices { .. }
426 | Self::TooManyMeshPrimitives { .. }
427 | Self::TaskPayloadTooLarge { .. }
428 | Self::TaskPayloadMustMatch { .. }
429 | Self::InvalidPrimitiveIndex
430 | Self::MissingPrimitiveIndex
431 | Self::DrawIdError => return ErrorType::Validation,
432 };
433 e.webgpu_error_type()
434 }
435}
436
437pub fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
438 use naga::StorageFormat as Sf;
439 use wgt::TextureFormat as Tf;
440
441 Some(match format {
442 Tf::R8Unorm => Sf::R8Unorm,
443 Tf::R8Snorm => Sf::R8Snorm,
444 Tf::R8Uint => Sf::R8Uint,
445 Tf::R8Sint => Sf::R8Sint,
446
447 Tf::R16Uint => Sf::R16Uint,
448 Tf::R16Sint => Sf::R16Sint,
449 Tf::R16Float => Sf::R16Float,
450 Tf::Rg8Unorm => Sf::Rg8Unorm,
451 Tf::Rg8Snorm => Sf::Rg8Snorm,
452 Tf::Rg8Uint => Sf::Rg8Uint,
453 Tf::Rg8Sint => Sf::Rg8Sint,
454
455 Tf::R32Uint => Sf::R32Uint,
456 Tf::R32Sint => Sf::R32Sint,
457 Tf::R32Float => Sf::R32Float,
458 Tf::Rg16Uint => Sf::Rg16Uint,
459 Tf::Rg16Sint => Sf::Rg16Sint,
460 Tf::Rg16Float => Sf::Rg16Float,
461 Tf::Rgba8Unorm => Sf::Rgba8Unorm,
462 Tf::Rgba8Snorm => Sf::Rgba8Snorm,
463 Tf::Rgba8Uint => Sf::Rgba8Uint,
464 Tf::Rgba8Sint => Sf::Rgba8Sint,
465 Tf::Bgra8Unorm => Sf::Bgra8Unorm,
466
467 Tf::Rgb10a2Uint => Sf::Rgb10a2Uint,
468 Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm,
469 Tf::Rg11b10Ufloat => Sf::Rg11b10Ufloat,
470
471 Tf::R64Uint => Sf::R64Uint,
472 Tf::Rg32Uint => Sf::Rg32Uint,
473 Tf::Rg32Sint => Sf::Rg32Sint,
474 Tf::Rg32Float => Sf::Rg32Float,
475 Tf::Rgba16Uint => Sf::Rgba16Uint,
476 Tf::Rgba16Sint => Sf::Rgba16Sint,
477 Tf::Rgba16Float => Sf::Rgba16Float,
478
479 Tf::Rgba32Uint => Sf::Rgba32Uint,
480 Tf::Rgba32Sint => Sf::Rgba32Sint,
481 Tf::Rgba32Float => Sf::Rgba32Float,
482
483 Tf::R16Unorm => Sf::R16Unorm,
484 Tf::R16Snorm => Sf::R16Snorm,
485 Tf::Rg16Unorm => Sf::Rg16Unorm,
486 Tf::Rg16Snorm => Sf::Rg16Snorm,
487 Tf::Rgba16Unorm => Sf::Rgba16Unorm,
488 Tf::Rgba16Snorm => Sf::Rgba16Snorm,
489
490 _ => return None,
491 })
492}
493
494pub fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
495 use naga::StorageFormat as Sf;
496 use wgt::TextureFormat as Tf;
497
498 match format {
499 Sf::R8Unorm => Tf::R8Unorm,
500 Sf::R8Snorm => Tf::R8Snorm,
501 Sf::R8Uint => Tf::R8Uint,
502 Sf::R8Sint => Tf::R8Sint,
503
504 Sf::R16Uint => Tf::R16Uint,
505 Sf::R16Sint => Tf::R16Sint,
506 Sf::R16Float => Tf::R16Float,
507 Sf::Rg8Unorm => Tf::Rg8Unorm,
508 Sf::Rg8Snorm => Tf::Rg8Snorm,
509 Sf::Rg8Uint => Tf::Rg8Uint,
510 Sf::Rg8Sint => Tf::Rg8Sint,
511
512 Sf::R32Uint => Tf::R32Uint,
513 Sf::R32Sint => Tf::R32Sint,
514 Sf::R32Float => Tf::R32Float,
515 Sf::Rg16Uint => Tf::Rg16Uint,
516 Sf::Rg16Sint => Tf::Rg16Sint,
517 Sf::Rg16Float => Tf::Rg16Float,
518 Sf::Rgba8Unorm => Tf::Rgba8Unorm,
519 Sf::Rgba8Snorm => Tf::Rgba8Snorm,
520 Sf::Rgba8Uint => Tf::Rgba8Uint,
521 Sf::Rgba8Sint => Tf::Rgba8Sint,
522 Sf::Bgra8Unorm => Tf::Bgra8Unorm,
523
524 Sf::Rgb10a2Uint => Tf::Rgb10a2Uint,
525 Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
526 Sf::Rg11b10Ufloat => Tf::Rg11b10Ufloat,
527
528 Sf::R64Uint => Tf::R64Uint,
529 Sf::Rg32Uint => Tf::Rg32Uint,
530 Sf::Rg32Sint => Tf::Rg32Sint,
531 Sf::Rg32Float => Tf::Rg32Float,
532 Sf::Rgba16Uint => Tf::Rgba16Uint,
533 Sf::Rgba16Sint => Tf::Rgba16Sint,
534 Sf::Rgba16Float => Tf::Rgba16Float,
535
536 Sf::Rgba32Uint => Tf::Rgba32Uint,
537 Sf::Rgba32Sint => Tf::Rgba32Sint,
538 Sf::Rgba32Float => Tf::Rgba32Float,
539
540 Sf::R16Unorm => Tf::R16Unorm,
541 Sf::R16Snorm => Tf::R16Snorm,
542 Sf::Rg16Unorm => Tf::Rg16Unorm,
543 Sf::Rg16Snorm => Tf::Rg16Snorm,
544 Sf::Rgba16Unorm => Tf::Rgba16Unorm,
545 Sf::Rgba16Snorm => Tf::Rgba16Snorm,
546 }
547}
548
549impl Resource {
550 fn check_binding_use(&self, entry: &BindGroupLayoutEntry) -> Result<(), BindingError> {
551 match self.ty {
552 ResourceType::Buffer { size } => {
553 let min_size = match entry.ty {
554 BindingType::Buffer {
555 ty,
556 has_dynamic_offset: _,
557 min_binding_size,
558 } => {
559 let class = match ty {
560 wgt::BufferBindingType::Uniform => naga::AddressSpace::Uniform,
561 wgt::BufferBindingType::Storage { read_only } => {
562 let mut naga_access = naga::StorageAccess::LOAD;
563 naga_access.set(naga::StorageAccess::STORE, !read_only);
564 naga::AddressSpace::Storage {
565 access: naga_access,
566 }
567 }
568 };
569 if self.class != class {
570 return Err(BindingError::WrongAddressSpace {
571 binding: class,
572 shader: self.class,
573 });
574 }
575 min_binding_size
576 }
577 _ => {
578 return Err(BindingError::WrongType {
579 binding: (&entry.ty).into(),
580 shader: (&self.ty).into(),
581 })
582 }
583 };
584 match min_size {
585 Some(non_zero) if non_zero < size => {
586 return Err(BindingError::WrongBufferSize {
587 buffer_size: size,
588 min_binding_size: non_zero,
589 })
590 }
591 _ => (),
592 }
593 }
594 ResourceType::Sampler { comparison } => match entry.ty {
595 BindingType::Sampler(ty) => {
596 if (ty == wgt::SamplerBindingType::Comparison) != comparison {
597 return Err(BindingError::WrongSamplerComparison);
598 }
599 }
600 _ => {
601 return Err(BindingError::WrongType {
602 binding: (&entry.ty).into(),
603 shader: (&self.ty).into(),
604 })
605 }
606 },
607 ResourceType::Texture {
608 dim,
609 arrayed,
610 class,
611 } => {
612 let view_dimension = match entry.ty {
613 BindingType::Texture { view_dimension, .. }
614 | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
615 BindingType::ExternalTexture => wgt::TextureViewDimension::D2,
616 _ => {
617 return Err(BindingError::WrongTextureViewDimension {
618 dim,
619 is_array: false,
620 binding: entry.ty,
621 })
622 }
623 };
624 if arrayed {
625 match (dim, view_dimension) {
626 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
627 (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
628 _ => {
629 return Err(BindingError::WrongTextureViewDimension {
630 dim,
631 is_array: true,
632 binding: entry.ty,
633 })
634 }
635 }
636 } else {
637 match (dim, view_dimension) {
638 (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
639 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
640 (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
641 (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
642 _ => {
643 return Err(BindingError::WrongTextureViewDimension {
644 dim,
645 is_array: false,
646 binding: entry.ty,
647 })
648 }
649 }
650 }
651 let expected_class = match entry.ty {
652 BindingType::Texture {
653 sample_type,
654 view_dimension: _,
655 multisampled: multi,
656 } => match sample_type {
657 wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
658 kind: naga::ScalarKind::Float,
659 multi,
660 },
661 wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
662 kind: naga::ScalarKind::Sint,
663 multi,
664 },
665 wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
666 kind: naga::ScalarKind::Uint,
667 multi,
668 },
669 wgt::TextureSampleType::Depth => naga::ImageClass::Depth { multi },
670 },
671 BindingType::StorageTexture {
672 access,
673 format,
674 view_dimension: _,
675 } => {
676 let naga_format = map_storage_format_to_naga(format)
677 .ok_or(BindingError::BadStorageFormat(format))?;
678 let naga_access = match access {
679 wgt::StorageTextureAccess::ReadOnly => naga::StorageAccess::LOAD,
680 wgt::StorageTextureAccess::WriteOnly => naga::StorageAccess::STORE,
681 wgt::StorageTextureAccess::ReadWrite => {
682 naga::StorageAccess::LOAD | naga::StorageAccess::STORE
683 }
684 wgt::StorageTextureAccess::Atomic => {
685 naga::StorageAccess::ATOMIC
686 | naga::StorageAccess::LOAD
687 | naga::StorageAccess::STORE
688 }
689 };
690 naga::ImageClass::Storage {
691 format: naga_format,
692 access: naga_access,
693 }
694 }
695 BindingType::ExternalTexture => naga::ImageClass::External,
696 _ => {
697 return Err(BindingError::WrongType {
698 binding: (&entry.ty).into(),
699 shader: (&self.ty).into(),
700 })
701 }
702 };
703 if class != expected_class {
704 return Err(BindingError::WrongTextureClass {
705 binding: expected_class,
706 shader: class,
707 });
708 }
709 }
710 ResourceType::AccelerationStructure { vertex_return } => match entry.ty {
711 BindingType::AccelerationStructure {
712 vertex_return: entry_vertex_return,
713 } if vertex_return == entry_vertex_return => (),
714 _ => {
715 return Err(BindingError::WrongType {
716 binding: (&entry.ty).into(),
717 shader: (&self.ty).into(),
718 })
719 }
720 },
721 };
722
723 Ok(())
724 }
725
726 fn derive_binding_type(
727 &self,
728 is_reffed_by_sampler_in_entrypoint: bool,
729 ) -> Result<BindingType, BindingError> {
730 Ok(match self.ty {
731 ResourceType::Buffer { size } => BindingType::Buffer {
732 ty: match self.class {
733 naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform,
734 naga::AddressSpace::Storage { access } => wgt::BufferBindingType::Storage {
735 read_only: access == naga::StorageAccess::LOAD,
736 },
737 _ => return Err(BindingError::WrongBufferAddressSpace { space: self.class }),
738 },
739 has_dynamic_offset: false,
740 min_binding_size: Some(size),
741 },
742 ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison {
743 wgt::SamplerBindingType::Comparison
744 } else {
745 wgt::SamplerBindingType::Filtering
746 }),
747 ResourceType::Texture {
748 dim,
749 arrayed,
750 class,
751 } => {
752 let view_dimension = match dim {
753 naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
754 naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
755 naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
756 naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
757 naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
758 naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
759 };
760 match class {
761 naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
762 sample_type: match kind {
763 naga::ScalarKind::Float => wgt::TextureSampleType::Float {
764 filterable: is_reffed_by_sampler_in_entrypoint,
765 },
766 naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
767 naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
768 naga::ScalarKind::AbstractInt
769 | naga::ScalarKind::AbstractFloat
770 | naga::ScalarKind::Bool => unreachable!(),
771 },
772 view_dimension,
773 multisampled: multi,
774 },
775 naga::ImageClass::Depth { multi } => BindingType::Texture {
776 sample_type: wgt::TextureSampleType::Depth,
777 view_dimension,
778 multisampled: multi,
779 },
780 naga::ImageClass::Storage { format, access } => BindingType::StorageTexture {
781 access: {
782 const LOAD_STORE: naga::StorageAccess =
783 naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
784 match access {
785 naga::StorageAccess::LOAD => wgt::StorageTextureAccess::ReadOnly,
786 naga::StorageAccess::STORE => wgt::StorageTextureAccess::WriteOnly,
787 LOAD_STORE => wgt::StorageTextureAccess::ReadWrite,
788 _ if access.contains(naga::StorageAccess::ATOMIC) => {
789 wgt::StorageTextureAccess::Atomic
790 }
791 _ => unreachable!(),
792 }
793 },
794 view_dimension,
795 format: {
796 let f = map_storage_format_from_naga(format);
797 let original = map_storage_format_to_naga(f)
798 .ok_or(BindingError::BadStorageFormat(f))?;
799 debug_assert_eq!(format, original);
800 f
801 },
802 },
803 naga::ImageClass::External => BindingType::ExternalTexture,
804 }
805 }
806 ResourceType::AccelerationStructure { vertex_return } => {
807 BindingType::AccelerationStructure { vertex_return }
808 }
809 })
810 }
811}
812
813impl NumericType {
814 fn from_vertex_format(format: wgt::VertexFormat) -> Self {
815 use naga::{Scalar, VectorSize as Vs};
816 use wgt::VertexFormat as Vf;
817
818 let (dim, scalar) = match format {
819 Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32),
820 Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => {
821 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
822 }
823 Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32),
824 Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => {
825 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
826 }
827 Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32),
828 Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => {
829 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
830 }
831 Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32),
832 Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => {
833 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
834 }
835 Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => {
836 (NumericDimension::Scalar, Scalar::F32)
837 }
838 Vf::Unorm8x2
839 | Vf::Snorm8x2
840 | Vf::Unorm16x2
841 | Vf::Snorm16x2
842 | Vf::Float16x2
843 | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
844 Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
845 Vf::Unorm8x4
846 | Vf::Snorm8x4
847 | Vf::Unorm16x4
848 | Vf::Snorm16x4
849 | Vf::Float16x4
850 | Vf::Float32x4
851 | Vf::Unorm10_10_10_2
852 | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
853 Vf::Float64 => (NumericDimension::Scalar, Scalar::F64),
854 Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64),
855 Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64),
856 Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64),
857 };
858
859 NumericType {
860 dim,
861 scalar,
864 }
865 }
866
867 fn from_texture_format(format: wgt::TextureFormat) -> Self {
868 use naga::{Scalar, VectorSize as Vs};
869 use wgt::TextureFormat as Tf;
870
871 let (dim, scalar) = match format {
872 Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
873 (NumericDimension::Scalar, Scalar::F32)
874 }
875 Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32),
876 Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32),
877 Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
878 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
879 }
880 Tf::R64Uint => (NumericDimension::Scalar, Scalar::U64),
881 Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
882 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
883 }
884 Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
885 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
886 }
887 Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32),
888 Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
889 Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
890 Tf::Rgba8Unorm
891 | Tf::Rgba8UnormSrgb
892 | Tf::Rgba8Snorm
893 | Tf::Bgra8Unorm
894 | Tf::Bgra8UnormSrgb
895 | Tf::Rgb10a2Unorm
896 | Tf::Rgba16Float
897 | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
898 Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => {
899 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
900 }
901 Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
902 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
903 }
904 Tf::Rg11b10Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
905 Tf::Stencil8
906 | Tf::Depth16Unorm
907 | Tf::Depth32Float
908 | Tf::Depth32FloatStencil8
909 | Tf::Depth24Plus
910 | Tf::Depth24PlusStencil8 => {
911 panic!("Unexpected depth format")
912 }
913 Tf::NV12 => panic!("Unexpected nv12 format"),
914 Tf::P010 => panic!("Unexpected p010 format"),
915 Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
916 Tf::Bc1RgbaUnorm
917 | Tf::Bc1RgbaUnormSrgb
918 | Tf::Bc2RgbaUnorm
919 | Tf::Bc2RgbaUnormSrgb
920 | Tf::Bc3RgbaUnorm
921 | Tf::Bc3RgbaUnormSrgb
922 | Tf::Bc7RgbaUnorm
923 | Tf::Bc7RgbaUnormSrgb
924 | Tf::Etc2Rgb8A1Unorm
925 | Tf::Etc2Rgb8A1UnormSrgb
926 | Tf::Etc2Rgba8Unorm
927 | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
928 Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => {
929 (NumericDimension::Scalar, Scalar::F32)
930 }
931 Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => {
932 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
933 }
934 Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => {
935 (NumericDimension::Vector(Vs::Tri), Scalar::F32)
936 }
937 Tf::Astc {
938 block: _,
939 channel: _,
940 } => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
941 };
942
943 NumericType {
944 dim,
945 scalar,
948 }
949 }
950
951 fn is_subtype_of(&self, other: &NumericType) -> bool {
952 if self.scalar.width > other.scalar.width {
953 return false;
954 }
955 if self.scalar.kind != other.scalar.kind {
956 return false;
957 }
958 match (self.dim, other.dim) {
959 (NumericDimension::Scalar, NumericDimension::Scalar) => true,
960 (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
961 (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
962 (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
963 c0 == c1 && r0 == r1
964 }
965 _ => false,
966 }
967 }
968}
969
970pub fn check_texture_format(
972 format: wgt::TextureFormat,
973 output: &NumericType,
974) -> Result<(), NumericType> {
975 let nt = NumericType::from_texture_format(format);
976 if nt.is_subtype_of(output) {
977 Ok(())
978 } else {
979 Err(nt)
980 }
981}
982
983pub enum BindingLayoutSource<'a> {
984 Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
988 Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>),
992}
993
994impl<'a> BindingLayoutSource<'a> {
995 pub fn new_derived(limits: &wgt::Limits) -> Self {
996 let mut array = ArrayVec::new();
997 for _ in 0..limits.max_bind_groups {
998 array.push(Default::default());
999 }
1000 BindingLayoutSource::Derived(Box::new(array))
1001 }
1002}
1003
1004#[derive(Debug, Clone, Default)]
1005pub struct StageIo {
1006 pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>,
1007 pub task_payload_size: Option<u32>,
1009 pub primitive_index: Option<bool>,
1015}
1016
1017impl Interface {
1018 fn populate(
1019 list: &mut Vec<Varying>,
1020 binding: Option<&naga::Binding>,
1021 ty: naga::Handle<naga::Type>,
1022 arena: &naga::UniqueArena<naga::Type>,
1023 ) {
1024 let numeric_ty = match arena[ty].inner {
1025 naga::TypeInner::Scalar(scalar) => NumericType {
1026 dim: NumericDimension::Scalar,
1027 scalar,
1028 },
1029 naga::TypeInner::Vector { size, scalar } => NumericType {
1030 dim: NumericDimension::Vector(size),
1031 scalar,
1032 },
1033 naga::TypeInner::Matrix {
1034 columns,
1035 rows,
1036 scalar,
1037 } => NumericType {
1038 dim: NumericDimension::Matrix(columns, rows),
1039 scalar,
1040 },
1041 naga::TypeInner::Struct { ref members, .. } => {
1042 for member in members {
1043 Self::populate(list, member.binding.as_ref(), member.ty, arena);
1044 }
1045 return;
1046 }
1047 ref other => {
1048 log::debug!("Unexpected varying type: {other:?}");
1054 return;
1055 }
1056 };
1057
1058 let varying = match binding {
1059 Some(&naga::Binding::Location {
1060 location,
1061 interpolation,
1062 sampling,
1063 per_primitive,
1064 blend_src: _,
1065 }) => Varying::Local {
1066 location,
1067 iv: InterfaceVar {
1068 ty: numeric_ty,
1069 interpolation,
1070 sampling,
1071 per_primitive,
1072 },
1073 },
1074 Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in),
1075 None => {
1076 log::error!("Missing binding for a varying");
1077 return;
1078 }
1079 };
1080 list.push(varying);
1081 }
1082
1083 pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
1084 let mut resources = naga::Arena::new();
1085 let mut resource_mapping = FastHashMap::default();
1086 for (var_handle, var) in module.global_variables.iter() {
1087 let bind = match var.binding {
1088 Some(br) => br,
1089 _ => continue,
1090 };
1091 let naga_ty = &module.types[var.ty].inner;
1092
1093 let inner_ty = match *naga_ty {
1094 naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
1095 ref ty => ty,
1096 };
1097
1098 let ty = match *inner_ty {
1099 naga::TypeInner::Image {
1100 dim,
1101 arrayed,
1102 class,
1103 } => ResourceType::Texture {
1104 dim,
1105 arrayed,
1106 class,
1107 },
1108 naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1109 naga::TypeInner::AccelerationStructure { vertex_return } => {
1110 ResourceType::AccelerationStructure { vertex_return }
1111 }
1112 ref other => ResourceType::Buffer {
1113 size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(),
1114 },
1115 };
1116 let handle = resources.append(
1117 Resource {
1118 name: var.name.clone(),
1119 bind,
1120 ty,
1121 class: var.space,
1122 },
1123 Default::default(),
1124 );
1125 resource_mapping.insert(var_handle, handle);
1126 }
1127
1128 let mut entry_points = FastHashMap::default();
1129 entry_points.reserve(module.entry_points.len());
1130 for (index, entry_point) in module.entry_points.iter().enumerate() {
1131 let info = info.get_entry_point(index);
1132 let mut ep = EntryPoint::default();
1133 for arg in entry_point.function.arguments.iter() {
1134 Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1135 }
1136 if let Some(ref result) = entry_point.function.result {
1137 Self::populate(
1138 &mut ep.outputs,
1139 result.binding.as_ref(),
1140 result.ty,
1141 &module.types,
1142 );
1143 }
1144
1145 for (var_handle, var) in module.global_variables.iter() {
1146 let usage = info[var_handle];
1147 if !usage.is_empty() && var.binding.is_some() {
1148 ep.resources.push(resource_mapping[&var_handle]);
1149 }
1150 }
1151
1152 for key in info.sampling_set.iter() {
1153 ep.sampling_pairs
1154 .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1155 }
1156 ep.dual_source_blending = info.dual_source_blending;
1157 ep.workgroup_size = entry_point.workgroup_size;
1158
1159 if let Some(task_payload) = entry_point.task_payload {
1160 ep.task_payload_size = Some(
1161 module.types[module.global_variables[task_payload].ty]
1162 .inner
1163 .size(module.to_ctx()),
1164 );
1165 }
1166 if let Some(ref mesh_info) = entry_point.mesh_info {
1167 ep.mesh_info = Some(EntryPointMeshInfo {
1168 max_vertices: mesh_info.max_vertices,
1169 max_primitives: mesh_info.max_primitives,
1170 });
1171 Self::populate(
1172 &mut ep.outputs,
1173 None,
1174 mesh_info.vertex_output_type,
1175 &module.types,
1176 );
1177 Self::populate(
1178 &mut ep.outputs,
1179 None,
1180 mesh_info.primitive_output_type,
1181 &module.types,
1182 );
1183 }
1184
1185 entry_points.insert((entry_point.stage, entry_point.name.clone()), ep);
1186 }
1187
1188 Self {
1189 limits,
1190 resources,
1191 entry_points,
1192 }
1193 }
1194
1195 pub fn finalize_entry_point_name(
1196 &self,
1197 stage: naga::ShaderStage,
1198 entry_point_name: Option<&str>,
1199 ) -> Result<String, StageError> {
1200 entry_point_name
1201 .map(|ep| ep.to_string())
1202 .map(Ok)
1203 .unwrap_or_else(|| {
1204 let mut entry_points = self
1205 .entry_points
1206 .keys()
1207 .filter_map(|(ep_stage, name)| (ep_stage == &stage).then_some(name));
1208 let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1209 if entry_points.next().is_some() {
1210 return Err(StageError::MultipleEntryPointsFound);
1211 }
1212 Ok(first.clone())
1213 })
1214 }
1215
1216 pub fn check_stage(
1219 &self,
1220 layouts: &mut BindingLayoutSource<'_>,
1221 shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1222 entry_point_name: &str,
1223 shader_stage: ShaderStageForValidation,
1224 inputs: StageIo,
1225 ) -> Result<StageIo, StageError> {
1226 let pair = (shader_stage.to_naga(), entry_point_name.to_string());
1229 let entry_point = match self.entry_points.get(&pair) {
1230 Some(some) => some,
1231 None => return Err(StageError::MissingEntryPoint(pair.1)),
1232 };
1233 let (_, entry_point_name) = pair;
1234
1235 let stage_bit = shader_stage.to_wgt_bit();
1236
1237 for &handle in entry_point.resources.iter() {
1239 let res = &self.resources[handle];
1240 let result = 'err: {
1241 match layouts {
1242 BindingLayoutSource::Provided(layouts) => {
1243 if let ResourceType::Buffer { size } = res.ty {
1245 match shader_binding_sizes.entry(res.bind) {
1246 Entry::Occupied(e) => {
1247 *e.into_mut() = size.max(*e.get());
1248 }
1249 Entry::Vacant(e) => {
1250 e.insert(size);
1251 }
1252 }
1253 }
1254
1255 let Some(map) = layouts.get(res.bind.group as usize) else {
1256 break 'err Err(BindingError::Missing);
1257 };
1258
1259 let Some(entry) = map.get(res.bind.binding) else {
1260 break 'err Err(BindingError::Missing);
1261 };
1262
1263 if !entry.visibility.contains(stage_bit) {
1264 break 'err Err(BindingError::Invisible);
1265 }
1266
1267 res.check_binding_use(entry)
1268 }
1269 BindingLayoutSource::Derived(layouts) => {
1270 let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1271 break 'err Err(BindingError::Missing);
1272 };
1273
1274 let ty = match res.derive_binding_type(
1275 entry_point
1276 .sampling_pairs
1277 .iter()
1278 .any(|&(im, _samp)| im == handle),
1279 ) {
1280 Ok(ty) => ty,
1281 Err(error) => break 'err Err(error),
1282 };
1283
1284 match map.entry(res.bind.binding) {
1285 indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1286 break 'err Err(BindingError::InconsistentlyDerivedType)
1287 }
1288 indexmap::map::Entry::Occupied(e) => {
1289 e.into_mut().visibility |= stage_bit;
1290 }
1291 indexmap::map::Entry::Vacant(e) => {
1292 e.insert(BindGroupLayoutEntry {
1293 binding: res.bind.binding,
1294 ty,
1295 visibility: stage_bit,
1296 count: None,
1297 });
1298 }
1299 }
1300 Ok(())
1301 }
1302 }
1303 };
1304 if let Err(error) = result {
1305 return Err(StageError::Binding(res.bind, error));
1306 }
1307 }
1308
1309 if let BindingLayoutSource::Provided(layouts) = layouts {
1314 for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1315 let texture_bind = &self.resources[texture_handle].bind;
1316 let sampler_bind = &self.resources[sampler_handle].bind;
1317 let texture_layout = layouts[texture_bind.group as usize]
1318 .get(texture_bind.binding)
1319 .unwrap();
1320 let sampler_layout = layouts[sampler_bind.group as usize]
1321 .get(sampler_bind.binding)
1322 .unwrap();
1323 assert!(texture_layout.visibility.contains(stage_bit));
1324 assert!(sampler_layout.visibility.contains(stage_bit));
1325
1326 let sampler_filtering = matches!(
1327 sampler_layout.ty,
1328 BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1329 );
1330 let texture_sample_type = match texture_layout.ty {
1331 BindingType::Texture { sample_type, .. } => sample_type,
1332 BindingType::ExternalTexture => {
1333 wgt::TextureSampleType::Float { filterable: true }
1334 }
1335 _ => unreachable!(),
1336 };
1337
1338 let error = match (sampler_filtering, texture_sample_type) {
1339 (true, wgt::TextureSampleType::Float { filterable: false }) => {
1340 Some(FilteringError::Float)
1341 }
1342 (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1343 (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1344 _ => None,
1345 };
1346
1347 if let Some(error) = error {
1348 return Err(StageError::Filtering {
1349 texture: *texture_bind,
1350 sampler: *sampler_bind,
1351 error,
1352 });
1353 }
1354 }
1355 }
1356
1357 if shader_stage.to_naga().compute_like() {
1359 let (
1360 max_workgroup_size_limits,
1361 max_workgroup_size_total,
1362 per_dimension_limit,
1363 total_limit,
1364 ) = match shader_stage.to_naga() {
1365 naga::ShaderStage::Compute => (
1366 [
1367 self.limits.max_compute_workgroup_size_x,
1368 self.limits.max_compute_workgroup_size_y,
1369 self.limits.max_compute_workgroup_size_z,
1370 ],
1371 self.limits.max_compute_invocations_per_workgroup,
1372 "max_compute_workgroup_size_*",
1373 "max_compute_invocations_per_workgroup",
1374 ),
1375 naga::ShaderStage::Task => (
1376 [
1377 self.limits.max_task_invocations_per_dimension,
1378 self.limits.max_task_invocations_per_dimension,
1379 self.limits.max_task_invocations_per_dimension,
1380 ],
1381 self.limits.max_task_invocations_per_workgroup,
1382 "max_task_invocations_per_dimension",
1383 "max_task_invocations_per_workgroup",
1384 ),
1385 naga::ShaderStage::Mesh => (
1386 [
1387 self.limits.max_mesh_invocations_per_dimension,
1388 self.limits.max_mesh_invocations_per_dimension,
1389 self.limits.max_mesh_invocations_per_dimension,
1390 ],
1391 self.limits.max_mesh_invocations_per_workgroup,
1392 "max_mesh_invocations_per_dimension",
1393 "max_mesh_invocations_per_workgroup",
1394 ),
1395 _ => unreachable!(),
1396 };
1397 let total_invocations = entry_point.workgroup_size.iter().product::<u32>();
1398
1399 let workgroup_size_is_zero = entry_point.workgroup_size.contains(&0);
1400 let too_many_invocations = total_invocations > max_workgroup_size_total;
1401 let dimension_too_large = entry_point.workgroup_size[0] > max_workgroup_size_limits[0]
1402 || entry_point.workgroup_size[1] > max_workgroup_size_limits[1]
1403 || entry_point.workgroup_size[2] > max_workgroup_size_limits[2];
1404 if workgroup_size_is_zero || too_many_invocations || dimension_too_large {
1405 return Err(StageError::InvalidWorkgroupSize {
1406 current: entry_point.workgroup_size,
1407 current_total: total_invocations,
1408 limit: max_workgroup_size_limits,
1409 total: max_workgroup_size_total,
1410 per_dimension_limit,
1411 total_limit,
1412 });
1413 }
1414 }
1415
1416 let mut this_stage_primitive_index = false;
1417 let mut has_draw_id = false;
1418
1419 for input in entry_point.inputs.iter() {
1421 match *input {
1422 Varying::Local { location, ref iv } => {
1423 let result = inputs
1424 .varyings
1425 .get(&location)
1426 .ok_or(InputError::Missing)
1427 .and_then(|provided| {
1428 let (compatible, per_primitive_correct) = match shader_stage.to_naga() {
1429 naga::ShaderStage::Vertex => {
1432 let is_compatible =
1433 iv.ty.scalar.kind == provided.ty.scalar.kind;
1434 (is_compatible, !iv.per_primitive)
1436 }
1437 naga::ShaderStage::Fragment => {
1438 if iv.interpolation != provided.interpolation {
1439 return Err(InputError::InterpolationMismatch(
1440 provided.interpolation,
1441 ));
1442 }
1443 if iv.sampling != provided.sampling {
1444 return Err(InputError::SamplingMismatch(
1445 provided.sampling,
1446 ));
1447 }
1448 (
1449 iv.ty.is_subtype_of(&provided.ty),
1450 iv.per_primitive == provided.per_primitive,
1451 )
1452 }
1453 naga::ShaderStage::Compute
1455 | naga::ShaderStage::Task
1456 | naga::ShaderStage::Mesh => (false, false),
1457 };
1458 if !compatible {
1459 return Err(InputError::WrongType(provided.ty));
1460 } else if !per_primitive_correct {
1461 return Err(InputError::WrongPerPrimitive {
1462 pipeline_input: provided.per_primitive,
1463 shader: iv.per_primitive,
1464 });
1465 }
1466 Ok(())
1467 });
1468
1469 if let Err(error) = result {
1470 return Err(StageError::Input {
1471 location,
1472 var: iv.clone(),
1473 error,
1474 });
1475 }
1476 }
1477 Varying::BuiltIn(naga::BuiltIn::PrimitiveIndex) => {
1478 this_stage_primitive_index = true;
1479 }
1480 Varying::BuiltIn(naga::BuiltIn::DrawID) => {
1481 has_draw_id = true;
1482 }
1483 Varying::BuiltIn(_) => {}
1484 }
1485 }
1486
1487 match shader_stage {
1488 ShaderStageForValidation::Vertex {
1489 topology,
1490 compare_function,
1491 } => {
1492 let mut max_vertex_shader_output_variables =
1493 self.limits.max_inter_stage_shader_variables;
1494 let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1;
1495
1496 let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList {
1497 Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology)
1498 } else {
1499 None
1500 };
1501
1502 let deductions = point_list_deduction.into_iter();
1503
1504 for deduction in deductions.clone() {
1505 max_vertex_shader_output_variables = max_vertex_shader_output_variables
1508 .checked_sub(deduction.for_variables())
1509 .unwrap();
1510 max_vertex_shader_output_location = max_vertex_shader_output_location
1511 .checked_sub(deduction.for_location())
1512 .unwrap();
1513 }
1514
1515 let mut num_user_defined_outputs = 0;
1516
1517 for output in entry_point.outputs.iter() {
1518 match *output {
1519 Varying::Local { ref iv, location } => {
1520 if location > max_vertex_shader_output_location {
1521 return Err(StageError::VertexOutputLocationTooLarge {
1522 location,
1523 var: iv.clone(),
1524 limit: self.limits.max_inter_stage_shader_variables,
1525 deductions: deductions.collect(),
1526 });
1527 }
1528 num_user_defined_outputs += 1;
1529 }
1530 Varying::BuiltIn(_) => {}
1531 };
1532
1533 if let Some(
1534 cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1535 ) = compare_function
1536 {
1537 if let Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) =
1538 *output
1539 {
1540 log::warn!(
1541 concat!(
1542 "Vertex shader with entry point {} outputs a ",
1543 "@builtin(position) without the @invariant attribute and ",
1544 "is used in a pipeline with {cmp:?}. On some machines, ",
1545 "this can cause bad artifacting as {cmp:?} assumes the ",
1546 "values output from the vertex shader exactly match the ",
1547 "value in the depth buffer. The @invariant attribute on the ",
1548 "@builtin(position) vertex output ensures that the exact ",
1549 "same pixel depths are used every render."
1550 ),
1551 entry_point_name,
1552 cmp = cmp
1553 );
1554 }
1555 }
1556 }
1557
1558 if num_user_defined_outputs > max_vertex_shader_output_variables {
1559 return Err(StageError::TooManyUserDefinedVertexOutputs {
1560 num_found: num_user_defined_outputs,
1561 limit: self.limits.max_inter_stage_shader_variables,
1562 deductions: deductions.collect(),
1563 });
1564 }
1565 }
1566 ShaderStageForValidation::Fragment => {
1567 let mut max_fragment_shader_input_variables =
1568 self.limits.max_inter_stage_shader_variables;
1569
1570 let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1571 Varying::Local { .. } => None,
1572 Varying::BuiltIn(builtin) => {
1573 MaxFragmentShaderInputDeduction::from_inter_stage_builtin(*builtin).or_else(
1574 || {
1575 unreachable!(
1576 concat!(
1577 "unexpected built-in provided; ",
1578 "{:?} is not used for fragment stage input",
1579 ),
1580 builtin
1581 )
1582 },
1583 )
1584 }
1585 });
1586
1587 for deduction in deductions.clone() {
1588 max_fragment_shader_input_variables = max_fragment_shader_input_variables
1591 .checked_sub(deduction.for_variables())
1592 .unwrap();
1593 }
1594
1595 let mut num_user_defined_inputs = 0;
1596
1597 for output in entry_point.inputs.iter() {
1598 match *output {
1599 Varying::Local { ref iv, location } => {
1600 if location >= self.limits.max_inter_stage_shader_variables {
1601 return Err(StageError::FragmentInputLocationTooLarge {
1602 location,
1603 var: iv.clone(),
1604 limit: self.limits.max_inter_stage_shader_variables,
1605 deductions: deductions.collect(),
1606 });
1607 }
1608 num_user_defined_inputs += 1;
1609 }
1610 Varying::BuiltIn(_) => {}
1611 };
1612 }
1613
1614 if num_user_defined_inputs > max_fragment_shader_input_variables {
1615 return Err(StageError::TooManyUserDefinedFragmentInputs {
1616 num_found: num_user_defined_inputs,
1617 limit: self.limits.max_inter_stage_shader_variables,
1618 deductions: deductions.collect(),
1619 });
1620 }
1621
1622 for output in &entry_point.outputs {
1623 let &Varying::Local { location, ref iv } = output else {
1624 continue;
1625 };
1626 if location >= self.limits.max_color_attachments {
1627 return Err(StageError::ColorAttachmentLocationTooLarge {
1628 location,
1629 var: iv.clone(),
1630 limit: self.limits.max_color_attachments,
1631 });
1632 }
1633 }
1634 }
1635 _ => (),
1636 }
1637
1638 if let Some(ref mesh_info) = entry_point.mesh_info {
1639 if mesh_info.max_vertices > self.limits.max_mesh_output_vertices {
1640 return Err(StageError::TooManyMeshVertices {
1641 limit: self.limits.max_mesh_output_vertices,
1642 value: mesh_info.max_vertices,
1643 });
1644 }
1645 if mesh_info.max_primitives > self.limits.max_mesh_output_primitives {
1646 return Err(StageError::TooManyMeshPrimitives {
1647 limit: self.limits.max_mesh_output_primitives,
1648 value: mesh_info.max_primitives,
1649 });
1650 }
1651 }
1652 if let Some(task_payload_size) = entry_point.task_payload_size {
1653 if task_payload_size > self.limits.max_task_payload_size {
1654 return Err(StageError::TaskPayloadTooLarge {
1655 limit: self.limits.max_task_payload_size,
1656 value: task_payload_size,
1657 });
1658 }
1659 }
1660 if shader_stage.to_naga() == naga::ShaderStage::Mesh
1661 && entry_point.task_payload_size != inputs.task_payload_size
1662 {
1663 return Err(StageError::TaskPayloadMustMatch {
1664 input: inputs.task_payload_size,
1665 shader: entry_point.task_payload_size,
1666 });
1667 }
1668
1669 if shader_stage.to_naga() == naga::ShaderStage::Fragment
1671 && this_stage_primitive_index
1672 && inputs.primitive_index == Some(false)
1673 {
1674 return Err(StageError::InvalidPrimitiveIndex);
1675 } else if shader_stage.to_naga() == naga::ShaderStage::Fragment
1676 && !this_stage_primitive_index
1677 && inputs.primitive_index == Some(true)
1678 {
1679 return Err(StageError::MissingPrimitiveIndex);
1680 }
1681 if shader_stage.to_naga() == naga::ShaderStage::Mesh
1682 && inputs.task_payload_size.is_some()
1683 && has_draw_id
1684 {
1685 return Err(StageError::DrawIdError);
1686 }
1687
1688 let outputs = entry_point
1689 .outputs
1690 .iter()
1691 .filter_map(|output| match *output {
1692 Varying::Local { location, ref iv } => Some((location, iv.clone())),
1693 Varying::BuiltIn(_) => None,
1694 })
1695 .collect();
1696
1697 Ok(StageIo {
1698 task_payload_size: entry_point.task_payload_size,
1699 varyings: outputs,
1700 primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh {
1701 Some(this_stage_primitive_index)
1702 } else {
1703 None
1704 },
1705 })
1706 }
1707
1708 pub fn fragment_uses_dual_source_blending(
1709 &self,
1710 entry_point_name: &str,
1711 ) -> Result<bool, StageError> {
1712 let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string());
1713 self.entry_points
1714 .get(&pair)
1715 .ok_or(StageError::MissingEntryPoint(pair.1))
1716 .map(|ep| ep.dual_source_blending)
1717 }
1718}
1719
1720pub fn validate_color_attachment_bytes_per_sample(
1726 attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>,
1727 limit: u32,
1728) -> Result<(), crate::command::ColorAttachmentError> {
1729 let mut total_bytes_per_sample: u32 = 0;
1730 for format in attachment_formats {
1731 let byte_cost = format.target_pixel_byte_cost().unwrap();
1732 let alignment = format.target_component_alignment().unwrap();
1733
1734 total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
1735 total_bytes_per_sample += byte_cost;
1736 }
1737
1738 if total_bytes_per_sample > limit {
1739 return Err(
1740 crate::command::ColorAttachmentError::TooManyBytesPerSample {
1741 total: total_bytes_per_sample,
1742 limit,
1743 },
1744 );
1745 }
1746
1747 Ok(())
1748}
1749
1750pub enum ShaderStageForValidation {
1751 Vertex {
1752 topology: wgt::PrimitiveTopology,
1753 compare_function: Option<wgt::CompareFunction>,
1754 },
1755 Mesh,
1756 Fragment,
1757 Compute,
1758 Task,
1759}
1760
1761impl ShaderStageForValidation {
1762 pub fn to_naga(&self) -> naga::ShaderStage {
1763 match self {
1764 Self::Vertex { .. } => naga::ShaderStage::Vertex,
1765 Self::Mesh => naga::ShaderStage::Mesh,
1766 Self::Fragment => naga::ShaderStage::Fragment,
1767 Self::Compute => naga::ShaderStage::Compute,
1768 Self::Task => naga::ShaderStage::Task,
1769 }
1770 }
1771
1772 pub fn to_wgt_bit(&self) -> wgt::ShaderStages {
1773 match self {
1774 Self::Vertex { .. } => wgt::ShaderStages::VERTEX,
1775 Self::Mesh { .. } => wgt::ShaderStages::MESH,
1776 Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT,
1777 Self::Compute => wgt::ShaderStages::COMPUTE,
1778 Self::Task => wgt::ShaderStages::TASK,
1779 }
1780 }
1781}