1use alloc::{
2 boxed::Box,
3 string::{String, ToString as _},
4 sync::Arc,
5 vec::Vec,
6};
7use core::fmt;
8
9use arrayvec::ArrayVec;
10use hashbrown::{hash_map::Entry, HashSet};
11use shader_io_deductions::{display_deductions_as_optional_list, MaxVertexShaderOutputDeduction};
12use thiserror::Error;
13use wgt::{
14 error::{ErrorType, WebGpuError},
15 BindGroupLayoutEntry, BindingType,
16};
17
18use crate::{
19 command::ColorAttachmentError, device::bgl, pipeline::ColorStateError,
20 resource::InvalidResourceError,
21 validation::shader_io_deductions::MaxFragmentShaderInputDeduction, FastHashMap, FastHashSet,
22};
23
24pub mod shader_io_deductions;
25
26#[derive(Debug)]
27enum ResourceType {
28 Buffer {
29 minimum_binding_size: wgt::BufferSize,
30 },
31 Texture {
32 dim: naga::ImageDimension,
33 arrayed: bool,
34 class: naga::ImageClass,
35 },
36 Sampler {
37 comparison: bool,
38 },
39 AccelerationStructure {
40 vertex_return: bool,
41 },
42}
43
44#[derive(Clone, Debug)]
45pub enum BindingTypeName {
46 Buffer,
47 Texture,
48 Sampler,
49 AccelerationStructure,
50 ExternalTexture,
51}
52
53impl From<&ResourceType> for BindingTypeName {
54 fn from(ty: &ResourceType) -> BindingTypeName {
55 match ty {
56 ResourceType::Buffer { .. } => BindingTypeName::Buffer,
57 ResourceType::Texture {
58 class: naga::ImageClass::External,
59 ..
60 } => BindingTypeName::ExternalTexture,
61 ResourceType::Texture { .. } => BindingTypeName::Texture,
62 ResourceType::Sampler { .. } => BindingTypeName::Sampler,
63 ResourceType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
64 }
65 }
66}
67
68impl From<&BindingType> for BindingTypeName {
69 fn from(ty: &BindingType) -> BindingTypeName {
70 match ty {
71 BindingType::Buffer { .. } => BindingTypeName::Buffer,
72 BindingType::Texture { .. } => BindingTypeName::Texture,
73 BindingType::StorageTexture { .. } => BindingTypeName::Texture,
74 BindingType::Sampler { .. } => BindingTypeName::Sampler,
75 BindingType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
76 BindingType::ExternalTexture => BindingTypeName::ExternalTexture,
77 }
78 }
79}
80
81#[derive(Debug)]
82struct Resource {
83 #[allow(unused)]
84 name: Option<String>,
85 bind: naga::ResourceBinding,
86 ty: ResourceType,
87 class: naga::AddressSpace,
88}
89
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91enum NumericDimension {
92 Scalar,
93 Vector(naga::VectorSize),
94 Matrix(naga::VectorSize, naga::VectorSize),
95}
96
97impl fmt::Display for NumericDimension {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 match *self {
100 Self::Scalar => write!(f, ""),
101 Self::Vector(size) => write!(f, "x{}", size as u8),
102 Self::Matrix(columns, rows) => write!(f, "x{}{}", columns as u8, rows as u8),
103 }
104 }
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub struct NumericType {
109 dim: NumericDimension,
110 scalar: naga::Scalar,
111}
112
113impl fmt::Display for NumericType {
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 write!(
116 f,
117 "{:?}{}{}",
118 self.scalar.kind,
119 self.scalar.width * 8,
120 self.dim
121 )
122 }
123}
124
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct InterfaceVar {
127 pub ty: NumericType,
128 interpolation: Option<naga::Interpolation>,
129 sampling: Option<naga::Sampling>,
130 per_primitive: bool,
131}
132
133impl InterfaceVar {
134 pub fn vertex_attribute(format: wgt::VertexFormat) -> Self {
135 InterfaceVar {
136 ty: NumericType::from_vertex_format(format),
137 interpolation: None,
138 sampling: None,
139 per_primitive: false,
140 }
141 }
142}
143
144impl fmt::Display for InterfaceVar {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 write!(
147 f,
148 "{} interpolated as {:?} with sampling {:?}",
149 self.ty, self.interpolation, self.sampling
150 )
151 }
152}
153
154#[derive(Debug, Eq, PartialEq)]
161enum Varying {
162 UserDefined { location: u32, iv: InterfaceVar },
168
169 BuiltIn(BuiltIn),
175}
176
177#[derive(Clone, Debug, Eq, PartialEq)]
178enum BuiltIn {
179 Position { invariant: bool },
180 ViewIndex,
181 BaseInstance,
182 BaseVertex,
183 ClipDistances { array_size: u32 },
184 CullDistance,
185 InstanceIndex,
186 PointSize,
187 VertexIndex,
188 DrawIndex,
189 FragDepth,
190 PointCoord,
191 FrontFacing,
192 PrimitiveIndex,
193 Barycentric { perspective: bool },
194 SampleIndex,
195 SampleMask,
196 GlobalInvocationId,
197 LocalInvocationId,
198 LocalInvocationIndex,
199 WorkGroupId,
200 WorkGroupSize,
201 NumWorkGroups,
202 NumSubgroups,
203 SubgroupId,
204 SubgroupSize,
205 SubgroupInvocationId,
206 MeshTaskSize,
207 CullPrimitive,
208 PointIndex,
209 LineIndices,
210 TriangleIndices,
211 VertexCount,
212 Vertices,
213 PrimitiveCount,
214 Primitives,
215 RayInvocationId,
216 NumRayInvocations,
217 InstanceCustomData,
218 GeometryIndex,
219 WorldRayOrigin,
220 WorldRayDirection,
221 ObjectRayOrigin,
222 ObjectRayDirection,
223 RayTmin,
224 RayTCurrentMax,
225 ObjectToWorld,
226 WorldToObject,
227 HitKind,
228}
229
230impl BuiltIn {
231 pub fn to_naga(&self) -> naga::BuiltIn {
232 match self {
233 &Self::Position { invariant } => naga::BuiltIn::Position { invariant },
234 Self::ViewIndex => naga::BuiltIn::ViewIndex,
235 Self::BaseInstance => naga::BuiltIn::BaseInstance,
236 Self::BaseVertex => naga::BuiltIn::BaseVertex,
237 Self::ClipDistances { .. } => naga::BuiltIn::ClipDistances,
238 Self::CullDistance => naga::BuiltIn::CullDistance,
239 Self::InstanceIndex => naga::BuiltIn::InstanceIndex,
240 Self::PointSize => naga::BuiltIn::PointSize,
241 Self::VertexIndex => naga::BuiltIn::VertexIndex,
242 Self::DrawIndex => naga::BuiltIn::DrawIndex,
243 Self::FragDepth => naga::BuiltIn::FragDepth,
244 Self::PointCoord => naga::BuiltIn::PointCoord,
245 Self::FrontFacing => naga::BuiltIn::FrontFacing,
246 Self::PrimitiveIndex => naga::BuiltIn::PrimitiveIndex,
247 Self::Barycentric { perspective } => naga::BuiltIn::Barycentric {
248 perspective: *perspective,
249 },
250 Self::SampleIndex => naga::BuiltIn::SampleIndex,
251 Self::SampleMask => naga::BuiltIn::SampleMask,
252 Self::GlobalInvocationId => naga::BuiltIn::GlobalInvocationId,
253 Self::LocalInvocationId => naga::BuiltIn::LocalInvocationId,
254 Self::LocalInvocationIndex => naga::BuiltIn::LocalInvocationIndex,
255 Self::WorkGroupId => naga::BuiltIn::WorkGroupId,
256 Self::WorkGroupSize => naga::BuiltIn::WorkGroupSize,
257 Self::NumWorkGroups => naga::BuiltIn::NumWorkGroups,
258 Self::NumSubgroups => naga::BuiltIn::NumSubgroups,
259 Self::SubgroupId => naga::BuiltIn::SubgroupId,
260 Self::SubgroupSize => naga::BuiltIn::SubgroupSize,
261 Self::SubgroupInvocationId => naga::BuiltIn::SubgroupInvocationId,
262 Self::MeshTaskSize => naga::BuiltIn::MeshTaskSize,
263 Self::CullPrimitive => naga::BuiltIn::CullPrimitive,
264 Self::PointIndex => naga::BuiltIn::PointIndex,
265 Self::LineIndices => naga::BuiltIn::LineIndices,
266 Self::TriangleIndices => naga::BuiltIn::TriangleIndices,
267 Self::VertexCount => naga::BuiltIn::VertexCount,
268 Self::Vertices => naga::BuiltIn::Vertices,
269 Self::PrimitiveCount => naga::BuiltIn::PrimitiveCount,
270 Self::Primitives => naga::BuiltIn::Primitives,
271 Self::RayInvocationId => naga::BuiltIn::RayInvocationId,
272 Self::NumRayInvocations => naga::BuiltIn::NumRayInvocations,
273 Self::InstanceCustomData => naga::BuiltIn::InstanceCustomData,
274 Self::GeometryIndex => naga::BuiltIn::GeometryIndex,
275 Self::WorldRayOrigin => naga::BuiltIn::WorldRayOrigin,
276 Self::WorldRayDirection => naga::BuiltIn::WorldRayDirection,
277 Self::ObjectRayOrigin => naga::BuiltIn::ObjectRayOrigin,
278 Self::ObjectRayDirection => naga::BuiltIn::ObjectRayDirection,
279 Self::RayTmin => naga::BuiltIn::RayTmin,
280 Self::RayTCurrentMax => naga::BuiltIn::RayTCurrentMax,
281 Self::ObjectToWorld => naga::BuiltIn::ObjectToWorld,
282 Self::WorldToObject => naga::BuiltIn::WorldToObject,
283 Self::HitKind => naga::BuiltIn::HitKind,
284 }
285 }
286}
287
288#[derive(Debug)]
289struct EntryPointMeshInfo {
290 max_vertices: u32,
291 max_primitives: u32,
292 primitive_topology: wgt::PrimitiveTopology,
293}
294
295#[derive(Debug, Default)]
299struct EntryPoint {
300 inputs: Vec<Varying>,
306
307 outputs: Vec<Varying>,
315
316 resources: Vec<naga::Handle<Resource>>,
326
327 sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>,
334
335 workgroup_size: [u32; 3],
342
343 dual_source_blending: bool,
345
346 task_payload_size: Option<u32>,
349
350 mesh_info: Option<EntryPointMeshInfo>,
352
353 immediate_usage: naga::valid::ImmediateUsage,
355}
356
357#[derive(Debug, Hash, PartialEq, Eq)]
358struct EntryPointKey(naga::ShaderStage, String);
359
360#[derive(Debug, Hash, PartialEq, Eq)]
361struct EntryPointKeyRef<'a>(naga::ShaderStage, &'a str);
362
363impl hashbrown::Equivalent<EntryPointKey> for EntryPointKeyRef<'_> {
364 fn equivalent(&self, key: &EntryPointKey) -> bool {
365 self.0 == key.0 && self.1 == key.1
366 }
367}
368
369#[derive(Debug)]
373pub struct Interface {
374 limits: wgt::Limits,
380
381 resources: naga::Arena<Resource>,
392
393 entry_points: FastHashMap<EntryPointKey, EntryPoint>,
399}
400
401#[derive(Debug)]
402pub struct PassthroughInterface {
403 pub entry_point_names: HashSet<String>,
404}
405
406#[expect(clippy::large_enum_variant)]
410#[derive(Debug)]
411pub enum ShaderMetaData {
412 Interface(Interface),
413 Passthrough(PassthroughInterface),
414}
415impl ShaderMetaData {
416 pub fn interface(&self) -> Option<&Interface> {
417 match self {
418 Self::Interface(i) => Some(i),
419 Self::Passthrough(_) => None,
420 }
421 }
422}
423
424#[derive(Clone, Debug, Error)]
425#[non_exhaustive]
426pub enum BindingError {
427 #[error("Binding is missing from the pipeline layout")]
428 Missing,
429 #[error("Visibility flags don't include the shader stage")]
430 Invisible,
431 #[error(
432 "Type on the shader side ({shader:?}) does not match the pipeline binding ({binding:?})"
433 )]
434 WrongType {
435 binding: BindingTypeName,
436 shader: BindingTypeName,
437 },
438 #[error("Storage class {binding:?} doesn't match the shader {shader:?}")]
439 WrongAddressSpace {
440 binding: naga::AddressSpace,
441 shader: naga::AddressSpace,
442 },
443 #[error("Address space {space:?} is not a valid Buffer address space")]
444 WrongBufferAddressSpace { space: naga::AddressSpace },
445 #[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}")]
446 WrongBufferSize {
447 buffer_size: wgt::BufferSize,
448 min_binding_size: wgt::BufferSize,
449 },
450 #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")]
451 WrongTextureViewDimension {
452 dim: naga::ImageDimension,
453 is_array: bool,
454 binding: BindingType,
455 },
456 #[error("Texture class {binding:?} doesn't match the shader {shader:?}")]
457 WrongTextureClass {
458 binding: naga::ImageClass,
459 shader: naga::ImageClass,
460 },
461 #[error("Comparison flag doesn't match the shader")]
462 WrongSamplerComparison,
463 #[error("Derived bind group layout type is not consistent between stages")]
464 InconsistentlyDerivedType,
465 #[error("Texture format {0:?} is not supported for storage use")]
466 BadStorageFormat(wgt::TextureFormat),
467}
468
469impl WebGpuError for BindingError {
470 fn webgpu_error_type(&self) -> ErrorType {
471 ErrorType::Validation
472 }
473}
474
475#[derive(Clone, Debug, Error)]
476#[non_exhaustive]
477pub enum FilteringError {
478 #[error("Integer textures can't be sampled with a filtering sampler")]
479 Integer,
480 #[error("Non-filterable float textures can't be sampled with a filtering sampler")]
481 Float,
482}
483
484impl WebGpuError for FilteringError {
485 fn webgpu_error_type(&self) -> ErrorType {
486 ErrorType::Validation
487 }
488}
489
490#[derive(Clone, Debug, Error)]
491#[non_exhaustive]
492pub enum InputError {
493 #[error("Input is not provided by the earlier stage in the pipeline")]
494 Missing,
495 #[error("Input type is not compatible with the provided {0}")]
496 WrongType(NumericType),
497 #[error("Input interpolation doesn't match provided {0:?}")]
498 InterpolationMismatch(Option<naga::Interpolation>),
499 #[error("Input sampling doesn't match provided {0:?}")]
500 SamplingMismatch(Option<naga::Sampling>),
501 #[error("Pipeline input has per_primitive={pipeline_input}, but shader expects per_primitive={shader}")]
502 WrongPerPrimitive { pipeline_input: bool, shader: bool },
503}
504
505impl WebGpuError for InputError {
506 fn webgpu_error_type(&self) -> ErrorType {
507 ErrorType::Validation
508 }
509}
510
511#[derive(Clone, Debug, Error)]
513#[non_exhaustive]
514pub enum StageError {
515 #[error(transparent)]
516 InvalidWorkgroupSize(#[from] InvalidWorkgroupSizeError),
517 #[error("Unable to find entry point '{0}'")]
518 MissingEntryPoint(String),
519 #[error("Shader global {0:?} is not available in the pipeline layout")]
520 Binding(naga::ResourceBinding, #[source] BindingError),
521 #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")]
522 Filtering {
523 texture: naga::ResourceBinding,
524 sampler: naga::ResourceBinding,
525 #[source]
526 error: FilteringError,
527 },
528 #[error("Location[{location}] {var} is not provided by the previous stage outputs")]
529 Input {
530 location: wgt::ShaderLocation,
531 var: InterfaceVar,
532 #[source]
533 error: InputError,
534 },
535 #[error(
536 "Unable to select an entry point: no entry point was found in the provided shader module"
537 )]
538 NoEntryPointFound,
539 #[error(
540 "Unable to select an entry point: \
541 multiple entry points were found in the provided shader module, \
542 but no entry point was specified"
543 )]
544 MultipleEntryPointsFound,
545 #[error(transparent)]
546 InvalidResource(#[from] InvalidResourceError),
547 #[error(
548 "vertex shader output location Location[{location}] ({var}) exceeds the \
549 `max_inter_stage_shader_variables` limit ({}, 0-based){}",
550 limit - 1,
552 display_deductions_as_optional_list(deductions, |d| d.for_location())
553 )]
554 VertexOutputLocationTooLarge {
555 location: u32,
556 var: InterfaceVar,
557 limit: u32,
558 deductions: Vec<MaxVertexShaderOutputDeduction>,
559 },
560 #[error(
561 "found {num_found} user-defined vertex shader output variables, which exceeds the \
562 `max_inter_stage_shader_variables` limit ({limit}){}",
563 display_deductions_as_optional_list(deductions, |d| d.for_variables())
564 )]
565 TooManyUserDefinedVertexOutputs {
566 num_found: u32,
567 limit: u32,
568 deductions: Vec<MaxVertexShaderOutputDeduction>,
569 },
570 #[error(
571 "fragment shader input location Location[{location}] ({var}) exceeds the \
572 `max_inter_stage_shader_variables` limit ({}, 0-based){}",
573 limit - 1,
575 display_deductions_as_optional_list(deductions, |d| d.for_variables())
579 )]
580 FragmentInputLocationTooLarge {
581 location: u32,
582 var: InterfaceVar,
583 limit: u32,
584 deductions: Vec<MaxFragmentShaderInputDeduction>,
585 },
586 #[error(
587 "found {num_found} user-defined fragment shader input variables, which exceeds the \
588 `max_inter_stage_shader_variables` limit ({limit}){}",
589 display_deductions_as_optional_list(deductions, |d| d.for_variables())
590 )]
591 TooManyUserDefinedFragmentInputs {
592 num_found: u32,
593 limit: u32,
594 deductions: Vec<MaxFragmentShaderInputDeduction>,
595 },
596 #[error(
597 "Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
598 )]
599 ColorAttachmentLocationTooLarge {
600 location: u32,
601 var: InterfaceVar,
602 limit: u32,
603 },
604 #[error("Mesh shaders are limited to {limit} output vertices by `Limits::max_mesh_output_vertices`, but the shader has a maximum number of {value}")]
605 TooManyMeshVertices { limit: u32, value: u32 },
606 #[error("Mesh shaders are limited to {limit} output primitives by `Limits::max_mesh_output_primitives`, but the shader has a maximum number of {value}")]
607 TooManyMeshPrimitives { limit: u32, value: u32 },
608 #[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}")]
609 TaskPayloadTooLarge { limit: u32, value: u32 },
610 #[error("Mesh shader's task payload has size ({shader:?}), which doesn't match the payload declared in the task stage ({input:?})")]
611 TaskPayloadMustMatch {
612 input: Option<u32>,
613 shader: Option<u32>,
614 },
615 #[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.")]
616 InvalidPrimitiveIndex,
617 #[error("If a mesh shader writes to primitive index, it must be read by the fragment shader.")]
618 MissingPrimitiveIndex,
619 #[error("DrawId cannot be used in a mesh shader in a pipeline with a task shader")]
620 DrawIdError,
621 #[error("Pipeline uses dual-source blending, but the shader does not support it")]
622 InvalidDualSourceBlending,
623 #[error("Fragment shader writes depth, but pipeline does not have a depth attachment")]
624 MissingFragDepthAttachment,
625 #[error("Per vertex fragment inputs can only be used in triangle primitive pipelines")]
626 PerVertexNotTriangles,
627 #[error("Mesh shader pipelines must have primitive topology of TriangleList, LineList or PointList, and this must match with what the mesh shader declares.")]
628 MeshTopologyMismatch,
629 #[error("Pipeline layout immediate size ({layout}) must be >= the required immediate size ({required}) of the shader entry point")]
630 LayoutImmediateSize { layout: u32, required: u32 },
631}
632
633impl WebGpuError for StageError {
634 fn webgpu_error_type(&self) -> ErrorType {
635 match self {
636 Self::Binding(_, e) => e.webgpu_error_type(),
637 Self::InvalidResource(e) => e.webgpu_error_type(),
638 Self::Filtering {
639 texture: _,
640 sampler: _,
641 error,
642 } => error.webgpu_error_type(),
643 Self::Input {
644 location: _,
645 var: _,
646 error,
647 } => error.webgpu_error_type(),
648 Self::InvalidWorkgroupSize { .. }
649 | Self::MissingEntryPoint(..)
650 | Self::NoEntryPointFound
651 | Self::MultipleEntryPointsFound
652 | Self::VertexOutputLocationTooLarge { .. }
653 | Self::TooManyUserDefinedVertexOutputs { .. }
654 | Self::FragmentInputLocationTooLarge { .. }
655 | Self::TooManyUserDefinedFragmentInputs { .. }
656 | Self::ColorAttachmentLocationTooLarge { .. }
657 | Self::TooManyMeshVertices { .. }
658 | Self::TooManyMeshPrimitives { .. }
659 | Self::TaskPayloadTooLarge { .. }
660 | Self::TaskPayloadMustMatch { .. }
661 | Self::InvalidPrimitiveIndex
662 | Self::MissingPrimitiveIndex
663 | Self::DrawIdError
664 | Self::InvalidDualSourceBlending
665 | Self::MissingFragDepthAttachment
666 | Self::PerVertexNotTriangles
667 | Self::MeshTopologyMismatch
668 | Self::LayoutImmediateSize { .. } => ErrorType::Validation,
669 }
670 }
671}
672
673pub use wgpu_naga_bridge::map_storage_format_from_naga;
674pub use wgpu_naga_bridge::map_storage_format_to_naga;
675
676impl Resource {
677 fn check_binding_use(&self, entry: &BindGroupLayoutEntry) -> Result<(), BindingError> {
678 match self.ty {
679 ResourceType::Buffer {
680 minimum_binding_size,
681 } => {
682 let min_size = match entry.ty {
683 BindingType::Buffer {
684 ty,
685 has_dynamic_offset: _,
686 min_binding_size,
687 } => {
688 let class = match ty {
689 wgt::BufferBindingType::Uniform => naga::AddressSpace::Uniform,
690 wgt::BufferBindingType::Storage { read_only } => {
691 let mut naga_access = naga::StorageAccess::LOAD;
692 naga_access.set(naga::StorageAccess::STORE, !read_only);
693 naga::AddressSpace::Storage {
694 access: naga_access,
695 }
696 }
697 };
698 if self.class != class {
699 return Err(BindingError::WrongAddressSpace {
700 binding: class,
701 shader: self.class,
702 });
703 }
704 min_binding_size
705 }
706 _ => {
707 return Err(BindingError::WrongType {
708 binding: (&entry.ty).into(),
709 shader: (&self.ty).into(),
710 })
711 }
712 };
713 match min_size {
714 Some(non_zero) if non_zero < minimum_binding_size => {
715 return Err(BindingError::WrongBufferSize {
716 buffer_size: minimum_binding_size,
717 min_binding_size: non_zero,
718 })
719 }
720 _ => (),
721 }
722 }
723 ResourceType::Sampler { comparison } => match entry.ty {
724 BindingType::Sampler(ty) => {
725 if (ty == wgt::SamplerBindingType::Comparison) != comparison {
726 return Err(BindingError::WrongSamplerComparison);
727 }
728 }
729 _ => {
730 return Err(BindingError::WrongType {
731 binding: (&entry.ty).into(),
732 shader: (&self.ty).into(),
733 })
734 }
735 },
736 ResourceType::Texture {
737 dim,
738 arrayed,
739 class: shader_class,
740 } => {
741 let view_dimension = match entry.ty {
742 BindingType::Texture { view_dimension, .. }
743 | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
744 BindingType::ExternalTexture => wgt::TextureViewDimension::D2,
745 _ => {
746 return Err(BindingError::WrongTextureViewDimension {
747 dim,
748 is_array: false,
749 binding: entry.ty,
750 })
751 }
752 };
753 if arrayed {
754 match (dim, view_dimension) {
755 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
756 (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
757 _ => {
758 return Err(BindingError::WrongTextureViewDimension {
759 dim,
760 is_array: true,
761 binding: entry.ty,
762 })
763 }
764 }
765 } else {
766 match (dim, view_dimension) {
767 (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
768 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
769 (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
770 (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
771 _ => {
772 return Err(BindingError::WrongTextureViewDimension {
773 dim,
774 is_array: false,
775 binding: entry.ty,
776 })
777 }
778 }
779 }
780 match entry.ty {
781 BindingType::Texture {
782 sample_type,
783 view_dimension: _,
784 multisampled: multi,
785 } => {
786 let binding_class = match sample_type {
787 wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
788 kind: naga::ScalarKind::Float,
789 multi,
790 },
791 wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
792 kind: naga::ScalarKind::Sint,
793 multi,
794 },
795 wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
796 kind: naga::ScalarKind::Uint,
797 multi,
798 },
799 wgt::TextureSampleType::Depth => naga::ImageClass::Depth { multi },
800 };
801 if shader_class == binding_class {
802 Ok(())
803 } else {
804 Err(binding_class)
805 }
806 }
807 BindingType::StorageTexture {
808 access: wgt_binding_access,
809 format: wgt_binding_format,
810 view_dimension: _,
811 } => {
812 const LOAD_STORE: naga::StorageAccess =
813 naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
814 let binding_format = map_storage_format_to_naga(wgt_binding_format)
815 .ok_or(BindingError::BadStorageFormat(wgt_binding_format))?;
816 let binding_access = match wgt_binding_access {
817 wgt::StorageTextureAccess::ReadOnly => naga::StorageAccess::LOAD,
818 wgt::StorageTextureAccess::WriteOnly => naga::StorageAccess::STORE,
819 wgt::StorageTextureAccess::ReadWrite => LOAD_STORE,
820 wgt::StorageTextureAccess::Atomic => {
821 naga::StorageAccess::ATOMIC | LOAD_STORE
822 }
823 };
824 match shader_class {
825 naga::ImageClass::Storage {
828 format: shader_format,
829 access: shader_access,
830 } if shader_format == binding_format
831 && (shader_access == binding_access
832 || shader_access == naga::StorageAccess::STORE
833 && binding_access == LOAD_STORE) =>
834 {
835 Ok(())
836 }
837 _ => Err(naga::ImageClass::Storage {
838 format: binding_format,
839 access: binding_access,
840 }),
841 }
842 }
843 BindingType::ExternalTexture => {
844 let binding_class = naga::ImageClass::External;
845 if shader_class == binding_class {
846 Ok(())
847 } else {
848 Err(binding_class)
849 }
850 }
851 _ => {
852 return Err(BindingError::WrongType {
853 binding: (&entry.ty).into(),
854 shader: (&self.ty).into(),
855 })
856 }
857 }
858 .map_err(|binding_class| BindingError::WrongTextureClass {
859 binding: binding_class,
860 shader: shader_class,
861 })?;
862 }
863 ResourceType::AccelerationStructure { vertex_return } => match entry.ty {
864 BindingType::AccelerationStructure {
865 vertex_return: entry_vertex_return,
866 } if vertex_return == entry_vertex_return => (),
867 _ => {
868 return Err(BindingError::WrongType {
869 binding: (&entry.ty).into(),
870 shader: (&self.ty).into(),
871 })
872 }
873 },
874 };
875
876 Ok(())
877 }
878
879 fn derive_binding_type(
880 &self,
881 is_reffed_by_sampler_in_entrypoint: bool,
882 ) -> Result<BindingType, BindingError> {
883 Ok(match self.ty {
884 ResourceType::Buffer {
885 minimum_binding_size,
886 } => BindingType::Buffer {
887 ty: match self.class {
888 naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform,
889 naga::AddressSpace::Storage { access } => wgt::BufferBindingType::Storage {
890 read_only: access == naga::StorageAccess::LOAD,
891 },
892 _ => return Err(BindingError::WrongBufferAddressSpace { space: self.class }),
893 },
894 has_dynamic_offset: false,
895 min_binding_size: Some(minimum_binding_size),
896 },
897 ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison {
898 wgt::SamplerBindingType::Comparison
899 } else {
900 wgt::SamplerBindingType::Filtering
901 }),
902 ResourceType::Texture {
903 dim,
904 arrayed,
905 class,
906 } => {
907 let view_dimension = match dim {
908 naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
909 naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
910 naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
911 naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
912 naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
913 naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
914 };
915 match class {
916 naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
917 sample_type: match kind {
918 naga::ScalarKind::Float => wgt::TextureSampleType::Float {
919 filterable: is_reffed_by_sampler_in_entrypoint,
920 },
921 naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
922 naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
923 naga::ScalarKind::AbstractInt
924 | naga::ScalarKind::AbstractFloat
925 | naga::ScalarKind::Bool => unreachable!(),
926 },
927 view_dimension,
928 multisampled: multi,
929 },
930 naga::ImageClass::Depth { multi } => BindingType::Texture {
931 sample_type: wgt::TextureSampleType::Depth,
932 view_dimension,
933 multisampled: multi,
934 },
935 naga::ImageClass::Storage { format, access } => BindingType::StorageTexture {
936 access: {
937 const LOAD_STORE: naga::StorageAccess =
938 naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
939 match access {
940 naga::StorageAccess::LOAD => wgt::StorageTextureAccess::ReadOnly,
941 naga::StorageAccess::STORE => wgt::StorageTextureAccess::WriteOnly,
942 LOAD_STORE => wgt::StorageTextureAccess::ReadWrite,
943 _ if access.contains(naga::StorageAccess::ATOMIC) => {
944 wgt::StorageTextureAccess::Atomic
945 }
946 _ => unreachable!(),
947 }
948 },
949 view_dimension,
950 format: {
951 let f = map_storage_format_from_naga(format);
952 let original = map_storage_format_to_naga(f)
953 .ok_or(BindingError::BadStorageFormat(f))?;
954 debug_assert_eq!(format, original);
955 f
956 },
957 },
958 naga::ImageClass::External => BindingType::ExternalTexture,
959 }
960 }
961 ResourceType::AccelerationStructure { vertex_return } => {
962 BindingType::AccelerationStructure { vertex_return }
963 }
964 })
965 }
966}
967
968impl NumericType {
969 fn from_vertex_format(format: wgt::VertexFormat) -> Self {
970 use naga::{Scalar, VectorSize as Vs};
971 use wgt::VertexFormat as Vf;
972
973 let (dim, scalar) = match format {
974 Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32),
975 Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => {
976 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
977 }
978 Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32),
979 Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => {
980 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
981 }
982 Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32),
983 Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => {
984 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
985 }
986 Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32),
987 Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => {
988 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
989 }
990 Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => {
991 (NumericDimension::Scalar, Scalar::F32)
992 }
993 Vf::Unorm8x2
994 | Vf::Snorm8x2
995 | Vf::Unorm16x2
996 | Vf::Snorm16x2
997 | Vf::Float16x2
998 | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
999 Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
1000 Vf::Unorm8x4
1001 | Vf::Snorm8x4
1002 | Vf::Unorm16x4
1003 | Vf::Snorm16x4
1004 | Vf::Float16x4
1005 | Vf::Float32x4
1006 | Vf::Unorm10_10_10_2
1007 | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
1008 Vf::Float64 => (NumericDimension::Scalar, Scalar::F64),
1009 Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64),
1010 Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64),
1011 Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64),
1012 };
1013
1014 NumericType {
1015 dim,
1016 scalar,
1019 }
1020 }
1021
1022 fn from_texture_format(format: wgt::TextureFormat) -> Self {
1023 use naga::{Scalar, VectorSize as Vs};
1024 use wgt::TextureFormat as Tf;
1025
1026 let (dim, scalar) = match format {
1027 Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
1028 (NumericDimension::Scalar, Scalar::F32)
1029 }
1030 Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32),
1031 Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32),
1032 Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
1033 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
1034 }
1035 Tf::R64Uint => (NumericDimension::Scalar, Scalar::U64),
1036 Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
1037 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
1038 }
1039 Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
1040 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
1041 }
1042 Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32),
1043 Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
1044 Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
1045 Tf::Rgba8Unorm
1046 | Tf::Rgba8UnormSrgb
1047 | Tf::Rgba8Snorm
1048 | Tf::Bgra8Unorm
1049 | Tf::Bgra8UnormSrgb
1050 | Tf::Rgb10a2Unorm
1051 | Tf::Rgba16Float
1052 | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
1053 Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => {
1054 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
1055 }
1056 Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
1057 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
1058 }
1059 Tf::Rg11b10Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
1060 Tf::Stencil8
1061 | Tf::Depth16Unorm
1062 | Tf::Depth32Float
1063 | Tf::Depth32FloatStencil8
1064 | Tf::Depth24Plus
1065 | Tf::Depth24PlusStencil8 => {
1066 panic!("Unexpected depth format")
1067 }
1068 Tf::NV12 => panic!("Unexpected nv12 format"),
1069 Tf::P010 => panic!("Unexpected p010 format"),
1070 Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
1071 Tf::Bc1RgbaUnorm
1072 | Tf::Bc1RgbaUnormSrgb
1073 | Tf::Bc2RgbaUnorm
1074 | Tf::Bc2RgbaUnormSrgb
1075 | Tf::Bc3RgbaUnorm
1076 | Tf::Bc3RgbaUnormSrgb
1077 | Tf::Bc7RgbaUnorm
1078 | Tf::Bc7RgbaUnormSrgb
1079 | Tf::Etc2Rgb8A1Unorm
1080 | Tf::Etc2Rgb8A1UnormSrgb
1081 | Tf::Etc2Rgba8Unorm
1082 | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
1083 Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => {
1084 (NumericDimension::Scalar, Scalar::F32)
1085 }
1086 Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => {
1087 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
1088 }
1089 Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => {
1090 (NumericDimension::Vector(Vs::Tri), Scalar::F32)
1091 }
1092 Tf::Astc {
1093 block: _,
1094 channel: _,
1095 } => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
1096 };
1097
1098 NumericType {
1099 dim,
1100 scalar,
1103 }
1104 }
1105
1106 fn is_subtype_of(self, other: NumericType) -> bool {
1107 if self.scalar.width > other.scalar.width {
1108 return false;
1109 }
1110 if self.scalar.kind != other.scalar.kind {
1111 return false;
1112 }
1113 match (self.dim, other.dim) {
1114 (NumericDimension::Scalar, NumericDimension::Scalar) => true,
1115 (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
1116 (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
1117 (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
1118 c0 == c1 && r0 == r1
1119 }
1120 _ => false,
1121 }
1122 }
1123}
1124
1125pub fn check_color_attachment_compatibility(
1127 state: &wgt::ColorTargetState,
1128 output_ty: NumericType,
1129) -> Result<(), ColorStateError> {
1130 let pipeline_ty = NumericType::from_texture_format(state.format);
1131 if !pipeline_ty.is_subtype_of(output_ty) {
1132 return Err(ColorStateError::IncompatibleFormat {
1133 pipeline: pipeline_ty,
1134 shader: output_ty,
1135 });
1136 }
1137 if let Some(blend) = state.blend {
1138 for (factor, name) in [
1139 (blend.color.src_factor, "source"),
1140 (blend.color.dst_factor, "destination"),
1141 ] {
1142 if factor.uses_source_alpha()
1143 && output_ty.dim != NumericDimension::Vector(naga::VectorSize::Quad)
1144 {
1145 return Err(ColorStateError::InvalidAlphaBlend {
1146 which: name,
1147 factor,
1148 });
1149 }
1150 }
1151 }
1152
1153 Ok(())
1154}
1155
1156pub enum BindingLayoutSource {
1157 Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
1161 Provided(Arc<crate::binding_model::PipelineLayout>),
1165}
1166
1167impl BindingLayoutSource {
1168 pub fn new_derived(limits: &wgt::Limits) -> Self {
1169 let mut array = ArrayVec::new();
1170 for _ in 0..limits.max_bind_groups {
1171 array.push(Default::default());
1172 }
1173 BindingLayoutSource::Derived(Box::new(array))
1174 }
1175}
1176
1177#[derive(Debug, Clone, Default)]
1178pub struct StageIo {
1179 pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>,
1180 pub task_payload_size: Option<u32>,
1182 pub primitive_index: Option<bool>,
1188 pub immediates: naga::valid::ImmediateUsage,
1189}
1190
1191impl Interface {
1192 fn populate(
1206 list: &mut Vec<Varying>,
1207 binding: Option<&naga::Binding>,
1208 ty: naga::Handle<naga::Type>,
1209 arena: &naga::UniqueArena<naga::Type>,
1210 ) {
1211 let numeric_ty = match arena[ty].inner {
1212 naga::TypeInner::Scalar(scalar) => NumericType {
1213 dim: NumericDimension::Scalar,
1214 scalar,
1215 },
1216 naga::TypeInner::Vector { size, scalar } => NumericType {
1217 dim: NumericDimension::Vector(size),
1218 scalar,
1219 },
1220 naga::TypeInner::Matrix {
1221 columns,
1222 rows,
1223 scalar,
1224 } => NumericType {
1225 dim: NumericDimension::Matrix(columns, rows),
1226 scalar,
1227 },
1228 naga::TypeInner::Struct { ref members, .. } => {
1229 for member in members {
1230 Self::populate(list, member.binding.as_ref(), member.ty, arena);
1231 }
1232 return;
1233 }
1234 naga::TypeInner::Array { base, size, stride }
1235 if matches!(
1236 binding,
1237 Some(naga::Binding::BuiltIn(naga::BuiltIn::ClipDistances)),
1238 ) =>
1239 {
1240 debug_assert_eq!(
1242 &arena[base].inner,
1243 &naga::TypeInner::Scalar(naga::Scalar::F32)
1244 );
1245 debug_assert_eq!(stride, 4);
1246
1247 let naga::ArraySize::Constant(array_size) = size else {
1248 unreachable!("non-constant array size for `clip_distances`")
1255 };
1256 let array_size = array_size.get();
1257
1258 list.push(Varying::BuiltIn(BuiltIn::ClipDistances { array_size }));
1259 return;
1260 }
1261 ref other => {
1262 log::error!("Unexpected varying type: {other:?}");
1263 return;
1264 }
1265 };
1266
1267 let varying = match binding {
1268 Some(&naga::Binding::Location {
1269 location,
1270 interpolation,
1271 sampling,
1272 per_primitive,
1273 blend_src: _,
1274 }) => Varying::UserDefined {
1275 location,
1276 iv: InterfaceVar {
1277 ty: numeric_ty,
1278 interpolation,
1279 sampling,
1280 per_primitive,
1281 },
1282 },
1283 Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(match built_in {
1284 naga::BuiltIn::Position { invariant } => BuiltIn::Position { invariant },
1285 naga::BuiltIn::ViewIndex => BuiltIn::ViewIndex,
1286 naga::BuiltIn::BaseInstance => BuiltIn::BaseInstance,
1287 naga::BuiltIn::BaseVertex => BuiltIn::BaseVertex,
1288 naga::BuiltIn::ClipDistances => unreachable!(),
1289 naga::BuiltIn::CullDistance => BuiltIn::CullDistance,
1290 naga::BuiltIn::InstanceIndex => BuiltIn::InstanceIndex,
1291 naga::BuiltIn::PointSize => BuiltIn::PointSize,
1292 naga::BuiltIn::VertexIndex => BuiltIn::VertexIndex,
1293 naga::BuiltIn::DrawIndex => BuiltIn::DrawIndex,
1294 naga::BuiltIn::FragDepth => BuiltIn::FragDepth,
1295 naga::BuiltIn::PointCoord => BuiltIn::PointCoord,
1296 naga::BuiltIn::FrontFacing => BuiltIn::FrontFacing,
1297 naga::BuiltIn::PrimitiveIndex => BuiltIn::PrimitiveIndex,
1298 naga::BuiltIn::Barycentric { perspective } => BuiltIn::Barycentric { perspective },
1299 naga::BuiltIn::SampleIndex => BuiltIn::SampleIndex,
1300 naga::BuiltIn::SampleMask => BuiltIn::SampleMask,
1301 naga::BuiltIn::GlobalInvocationId => BuiltIn::GlobalInvocationId,
1302 naga::BuiltIn::LocalInvocationId => BuiltIn::LocalInvocationId,
1303 naga::BuiltIn::LocalInvocationIndex => BuiltIn::LocalInvocationIndex,
1304 naga::BuiltIn::WorkGroupId => BuiltIn::WorkGroupId,
1305 naga::BuiltIn::WorkGroupSize => BuiltIn::WorkGroupSize,
1306 naga::BuiltIn::NumWorkGroups => BuiltIn::NumWorkGroups,
1307 naga::BuiltIn::NumSubgroups => BuiltIn::NumSubgroups,
1308 naga::BuiltIn::SubgroupId => BuiltIn::SubgroupId,
1309 naga::BuiltIn::SubgroupSize => BuiltIn::SubgroupSize,
1310 naga::BuiltIn::SubgroupInvocationId => BuiltIn::SubgroupInvocationId,
1311 naga::BuiltIn::MeshTaskSize => BuiltIn::MeshTaskSize,
1312 naga::BuiltIn::CullPrimitive => BuiltIn::CullPrimitive,
1313 naga::BuiltIn::PointIndex => BuiltIn::PointIndex,
1314 naga::BuiltIn::LineIndices => BuiltIn::LineIndices,
1315 naga::BuiltIn::TriangleIndices => BuiltIn::TriangleIndices,
1316 naga::BuiltIn::VertexCount => BuiltIn::VertexCount,
1317 naga::BuiltIn::Vertices => BuiltIn::Vertices,
1318 naga::BuiltIn::PrimitiveCount => BuiltIn::PrimitiveCount,
1319 naga::BuiltIn::Primitives => BuiltIn::Primitives,
1320 naga::BuiltIn::RayInvocationId => BuiltIn::RayInvocationId,
1321 naga::BuiltIn::NumRayInvocations => BuiltIn::NumRayInvocations,
1322 naga::BuiltIn::InstanceCustomData => BuiltIn::InstanceCustomData,
1323 naga::BuiltIn::GeometryIndex => BuiltIn::GeometryIndex,
1324 naga::BuiltIn::WorldRayOrigin => BuiltIn::WorldRayOrigin,
1325 naga::BuiltIn::WorldRayDirection => BuiltIn::WorldRayDirection,
1326 naga::BuiltIn::ObjectRayOrigin => BuiltIn::ObjectRayOrigin,
1327 naga::BuiltIn::ObjectRayDirection => BuiltIn::ObjectRayDirection,
1328 naga::BuiltIn::RayTmin => BuiltIn::RayTmin,
1329 naga::BuiltIn::RayTCurrentMax => BuiltIn::RayTCurrentMax,
1330 naga::BuiltIn::ObjectToWorld => BuiltIn::ObjectToWorld,
1331 naga::BuiltIn::WorldToObject => BuiltIn::WorldToObject,
1332 naga::BuiltIn::HitKind => BuiltIn::HitKind,
1333 }),
1334 None => {
1335 log::error!("Missing binding for a varying");
1336 return;
1337 }
1338 };
1339 list.push(varying);
1340 }
1341
1342 pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
1348 let mut resources = naga::Arena::new();
1349 let mut resource_mapping = FastHashMap::default();
1350 for (var_handle, var) in module.global_variables.iter() {
1351 let bind = match var.binding {
1352 Some(br) => br,
1353 _ => continue,
1354 };
1355 let naga_ty = &module.types[var.ty].inner;
1356
1357 let inner_ty = match *naga_ty {
1358 naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
1359 ref ty => ty,
1360 };
1361
1362 let ty = match *inner_ty {
1363 naga::TypeInner::Image {
1364 dim,
1365 arrayed,
1366 class,
1367 } => ResourceType::Texture {
1368 dim,
1369 arrayed,
1370 class,
1371 },
1372 naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1373 naga::TypeInner::AccelerationStructure { vertex_return } => {
1374 ResourceType::AccelerationStructure { vertex_return }
1375 }
1376 ref other => ResourceType::Buffer {
1377 minimum_binding_size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64)
1378 .unwrap(),
1379 },
1380 };
1381 let handle = resources.append(
1382 Resource {
1383 name: var.name.clone(),
1384 bind,
1385 ty,
1386 class: var.space,
1387 },
1388 Default::default(),
1389 );
1390 resource_mapping.insert(var_handle, handle);
1391 }
1392
1393 let mut entry_points = FastHashMap::default();
1394 entry_points.reserve(module.entry_points.len());
1395 for (index, entry_point) in module.entry_points.iter().enumerate() {
1396 let func_info = info.get_entry_point(index);
1397 let mut ep = EntryPoint::default();
1398 for arg in entry_point.function.arguments.iter() {
1399 Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1400 }
1401 if let Some(ref result) = entry_point.function.result {
1402 Self::populate(
1403 &mut ep.outputs,
1404 result.binding.as_ref(),
1405 result.ty,
1406 &module.types,
1407 );
1408 }
1409
1410 for (var_handle, var) in module.global_variables.iter() {
1411 let usage = func_info[var_handle];
1412 if !usage.is_empty() && var.binding.is_some() {
1413 ep.resources.push(resource_mapping[&var_handle]);
1414 }
1415 }
1416
1417 for key in func_info.sampling_set.iter() {
1418 ep.sampling_pairs
1419 .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1420 }
1421 ep.dual_source_blending = func_info.dual_source_blending;
1422 ep.workgroup_size = entry_point.workgroup_size;
1423
1424 let mut used_immediates = module
1427 .global_variables
1428 .iter()
1429 .filter(|&(_, var)| var.space == naga::AddressSpace::Immediate)
1430 .map(|(handle, _)| handle)
1431 .filter(|&handle| !func_info[handle].is_empty());
1432 ep.immediate_usage = used_immediates
1433 .next()
1434 .map(|handle| {
1435 naga::valid::ImmediateUsage::from_type(
1436 &module.types[module.global_variables[handle].ty].inner,
1437 &module.types,
1438 module.to_ctx(),
1439 )
1440 })
1441 .unwrap_or_default();
1442 assert!(used_immediates.next().is_none());
1443
1444 if let Some(task_payload) = entry_point.task_payload {
1445 ep.task_payload_size = Some(
1446 module.types[module.global_variables[task_payload].ty]
1447 .inner
1448 .size(module.to_ctx()),
1449 );
1450 }
1451 if let Some(ref mesh_info) = entry_point.mesh_info {
1452 ep.mesh_info = Some(EntryPointMeshInfo {
1453 max_vertices: mesh_info.max_vertices,
1454 max_primitives: mesh_info.max_primitives,
1455 primitive_topology: match mesh_info.topology {
1456 naga::MeshOutputTopology::Triangles => wgt::PrimitiveTopology::TriangleList,
1457 naga::MeshOutputTopology::Lines => wgt::PrimitiveTopology::LineList,
1458 naga::MeshOutputTopology::Points => wgt::PrimitiveTopology::PointList,
1459 },
1460 });
1461 Self::populate(
1462 &mut ep.outputs,
1463 None,
1464 mesh_info.vertex_output_type,
1465 &module.types,
1466 );
1467 Self::populate(
1468 &mut ep.outputs,
1469 None,
1470 mesh_info.primitive_output_type,
1471 &module.types,
1472 );
1473 }
1474
1475 entry_points.insert(
1476 EntryPointKey(entry_point.stage, entry_point.name.clone()),
1477 ep,
1478 );
1479 }
1480
1481 Self {
1482 limits,
1483 resources,
1484 entry_points,
1485 }
1486 }
1487
1488 fn immediate_usage(
1489 &self,
1490 stage: naga::ShaderStage,
1491 entry_point_name: &str,
1492 ) -> naga::valid::ImmediateUsage {
1493 self.entry_points
1494 .get(&EntryPointKeyRef(stage, entry_point_name))
1495 .map(|ep| ep.immediate_usage)
1496 .unwrap_or_default()
1497 }
1498
1499 pub fn finalize_entry_point_name(
1505 &self,
1506 stage: naga::ShaderStage,
1507 entry_point_name: Option<&str>,
1508 ) -> Result<String, StageError> {
1509 entry_point_name
1510 .map(|ep| ep.to_string())
1511 .map(Ok)
1512 .unwrap_or_else(|| {
1513 let mut entry_points =
1514 self.entry_points
1515 .keys()
1516 .filter_map(|EntryPointKey(ep_stage, name)| {
1517 (ep_stage == &stage).then_some(name)
1518 });
1519 let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1520 if entry_points.next().is_some() {
1521 return Err(StageError::MultipleEntryPointsFound);
1522 }
1523 Ok(first.clone())
1524 })
1525 }
1526
1527 pub fn check_stage(
1549 &self,
1550 layouts: &mut BindingLayoutSource,
1551 minimum_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1552 entry_point_name: &str,
1553 shader_stage: ShaderStageForValidation,
1554 inputs: StageIo,
1555 primitive_topology: Option<wgt::PrimitiveTopology>,
1556 ) -> Result<StageIo, StageError> {
1557 let pair = EntryPointKeyRef(shader_stage.to_naga(), entry_point_name);
1560 let entry_point = match self.entry_points.get(&pair) {
1561 Some(some) => some,
1562 None => return Err(StageError::MissingEntryPoint(pair.1.to_string())),
1563 };
1564 let EntryPointKeyRef(_, entry_point_name) = pair;
1565
1566 let stage_bit = shader_stage.to_wgt_bit();
1567
1568 for &handle in entry_point.resources.iter() {
1570 let res = &self.resources[handle];
1571 let result = 'err: {
1572 match layouts {
1573 BindingLayoutSource::Provided(pipeline_layout) => {
1574 if let ResourceType::Buffer {
1576 minimum_binding_size,
1577 } = res.ty
1578 {
1579 match minimum_binding_sizes.entry(res.bind) {
1580 Entry::Occupied(e) => {
1581 *e.into_mut() = minimum_binding_size.max(*e.get());
1582 }
1583 Entry::Vacant(e) => {
1584 e.insert(minimum_binding_size);
1585 }
1586 }
1587 }
1588
1589 let Some(entry) =
1590 pipeline_layout.get_bgl_entry(res.bind.group, res.bind.binding)
1591 else {
1592 break 'err Err(BindingError::Missing);
1593 };
1594
1595 if !entry.visibility.contains(stage_bit) {
1596 break 'err Err(BindingError::Invisible);
1597 }
1598
1599 res.check_binding_use(entry)
1600 }
1601 BindingLayoutSource::Derived(layouts) => {
1602 let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1603 break 'err Err(BindingError::Missing);
1604 };
1605
1606 let ty = match res.derive_binding_type(
1607 entry_point
1608 .sampling_pairs
1609 .iter()
1610 .any(|&(im, _samp)| im == handle),
1611 ) {
1612 Ok(ty) => ty,
1613 Err(error) => break 'err Err(error),
1614 };
1615
1616 match map.entry(res.bind.binding) {
1617 indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1618 break 'err Err(BindingError::InconsistentlyDerivedType)
1619 }
1620 indexmap::map::Entry::Occupied(e) => {
1621 e.into_mut().visibility |= stage_bit;
1622 }
1623 indexmap::map::Entry::Vacant(e) => {
1624 e.insert(BindGroupLayoutEntry {
1625 binding: res.bind.binding,
1626 ty,
1627 visibility: stage_bit,
1628 count: None,
1629 });
1630 }
1631 }
1632 Ok(())
1633 }
1634 }
1635 };
1636 if let Err(error) = result {
1637 return Err(StageError::Binding(res.bind, error));
1638 }
1639 }
1640
1641 if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
1646 for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1647 let texture_bind = &self.resources[texture_handle].bind;
1648 let sampler_bind = &self.resources[sampler_handle].bind;
1649 let texture_layout = pipeline_layout
1650 .get_bgl_entry(texture_bind.group, texture_bind.binding)
1651 .unwrap();
1652 let sampler_layout = pipeline_layout
1653 .get_bgl_entry(sampler_bind.group, sampler_bind.binding)
1654 .unwrap();
1655 assert!(texture_layout.visibility.contains(stage_bit));
1656 assert!(sampler_layout.visibility.contains(stage_bit));
1657
1658 let sampler_filtering = matches!(
1659 sampler_layout.ty,
1660 BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1661 );
1662 let texture_sample_type = match texture_layout.ty {
1663 BindingType::Texture { sample_type, .. } => sample_type,
1664 BindingType::ExternalTexture => {
1665 wgt::TextureSampleType::Float { filterable: true }
1666 }
1667 _ => unreachable!(),
1668 };
1669
1670 let error = match (sampler_filtering, texture_sample_type) {
1671 (true, wgt::TextureSampleType::Float { filterable: false }) => {
1672 Some(FilteringError::Float)
1673 }
1674 (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1675 (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1676 _ => None,
1677 };
1678
1679 if let Some(error) = error {
1680 return Err(StageError::Filtering {
1681 texture: *texture_bind,
1682 sampler: *sampler_bind,
1683 error,
1684 });
1685 }
1686 }
1687 }
1688
1689 if shader_stage.to_naga().compute_like() {
1691 let workgroup_size_check = match shader_stage.to_naga() {
1692 naga::ShaderStage::Compute => WorkgroupSizeCheck {
1693 dimensions: &entry_point.workgroup_size,
1694 per_dimension_limits: &[
1695 self.limits.max_compute_workgroup_size_x,
1696 self.limits.max_compute_workgroup_size_y,
1697 self.limits.max_compute_workgroup_size_z,
1698 ],
1699 per_dimension_limits_desc: "max_compute_workgroup_size_*",
1700
1701 total_limit: self.limits.max_compute_invocations_per_workgroup,
1702 total_limit_desc: "max_compute_invocations_per_workgroup",
1703 },
1704 naga::ShaderStage::Task => WorkgroupSizeCheck {
1705 dimensions: &entry_point.workgroup_size,
1706 per_dimension_limits: &[self.limits.max_task_invocations_per_dimension; 3],
1707 per_dimension_limits_desc: "max_task_invocations_per_dimension",
1708
1709 total_limit: self.limits.max_task_invocations_per_workgroup,
1710 total_limit_desc: "max_task_invocations_per_workgroup",
1711 },
1712 naga::ShaderStage::Mesh => WorkgroupSizeCheck {
1713 dimensions: &entry_point.workgroup_size,
1714 per_dimension_limits: &[self.limits.max_mesh_invocations_per_dimension; 3],
1715 per_dimension_limits_desc: "max_mesh_invocations_per_dimension",
1716
1717 total_limit: self.limits.max_mesh_invocations_per_workgroup,
1718 total_limit_desc: "max_mesh_invocations_per_workgroup",
1719 },
1720 _ => unreachable!(),
1721 };
1722 let total = workgroup_size_check.check_and_compute_total_invocations()?;
1723 if total == 0 {
1724 return Err(StageError::InvalidWorkgroupSize(
1725 InvalidWorkgroupSizeError::Zero {
1726 dimensions: entry_point.workgroup_size,
1727 },
1728 ));
1729 }
1730 }
1731
1732 let mut this_stage_primitive_index = false;
1733 let mut has_draw_id = false;
1734 let mut has_per_vertex = false;
1735
1736 for input in entry_point.inputs.iter() {
1738 match *input {
1739 Varying::UserDefined { location, ref iv } => {
1740 let result = inputs
1741 .varyings
1742 .get(&location)
1743 .ok_or(InputError::Missing)
1744 .and_then(|provided| {
1745 let (compatible, per_primitive_correct) = match shader_stage.to_naga() {
1746 naga::ShaderStage::Vertex => {
1749 let is_compatible =
1750 iv.ty.scalar.kind == provided.ty.scalar.kind;
1751 (is_compatible, !iv.per_primitive)
1753 }
1754 naga::ShaderStage::Fragment => {
1755 if iv.interpolation != provided.interpolation {
1756 return Err(InputError::InterpolationMismatch(
1757 provided.interpolation,
1758 ));
1759 }
1760 if iv.sampling != provided.sampling {
1761 return Err(InputError::SamplingMismatch(
1762 provided.sampling,
1763 ));
1764 }
1765 (
1766 iv.ty.is_subtype_of(provided.ty),
1767 iv.per_primitive == provided.per_primitive,
1768 )
1769 }
1770 naga::ShaderStage::Compute
1772 | naga::ShaderStage::Task
1773 | naga::ShaderStage::Mesh => (false, false),
1774 naga::ShaderStage::RayGeneration
1775 | naga::ShaderStage::AnyHit
1776 | naga::ShaderStage::ClosestHit
1777 | naga::ShaderStage::Miss => {
1778 unreachable!()
1779 }
1780 };
1781 if !compatible {
1782 return Err(InputError::WrongType(provided.ty));
1783 } else if !per_primitive_correct {
1784 return Err(InputError::WrongPerPrimitive {
1785 pipeline_input: provided.per_primitive,
1786 shader: iv.per_primitive,
1787 });
1788 }
1789 Ok(())
1790 });
1791
1792 if let Err(error) = result {
1793 return Err(StageError::Input {
1794 location,
1795 var: iv.clone(),
1796 error,
1797 });
1798 }
1799 has_per_vertex |= iv.interpolation == Some(naga::Interpolation::PerVertex);
1800 }
1801 Varying::BuiltIn(BuiltIn::PrimitiveIndex) => {
1802 this_stage_primitive_index = true;
1803 }
1804 Varying::BuiltIn(BuiltIn::DrawIndex) => {
1805 has_draw_id = true;
1806 }
1807 Varying::BuiltIn(_) => {}
1808 }
1809 }
1810
1811 match shader_stage {
1812 ShaderStageForValidation::Vertex {
1813 topology,
1814 compare_function,
1815 } => {
1816 let mut max_vertex_shader_output_variables =
1817 self.limits.max_inter_stage_shader_variables;
1818 let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1;
1819
1820 let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList {
1821 Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology)
1822 } else {
1823 None
1824 };
1825
1826 let clip_distance_deductions = entry_point.outputs.iter().filter_map(|output| {
1827 if let &Varying::BuiltIn(BuiltIn::ClipDistances { array_size }) = output {
1828 Some(MaxVertexShaderOutputDeduction::ClipDistances { array_size })
1829 } else {
1830 None
1831 }
1832 });
1833 debug_assert!(
1834 clip_distance_deductions.clone().count() <= 1,
1835 "multiple `clip_distances` outputs found"
1836 );
1837
1838 let deductions = point_list_deduction
1839 .into_iter()
1840 .chain(clip_distance_deductions);
1841
1842 for deduction in deductions.clone() {
1843 max_vertex_shader_output_variables = max_vertex_shader_output_variables
1846 .checked_sub(deduction.for_variables())
1847 .unwrap();
1848 max_vertex_shader_output_location = max_vertex_shader_output_location
1849 .checked_sub(deduction.for_location())
1850 .unwrap();
1851 }
1852
1853 let mut num_user_defined_outputs = 0;
1854
1855 for output in entry_point.outputs.iter() {
1856 match *output {
1857 Varying::UserDefined { ref iv, location } => {
1858 if location > max_vertex_shader_output_location {
1859 return Err(StageError::VertexOutputLocationTooLarge {
1860 location,
1861 var: iv.clone(),
1862 limit: self.limits.max_inter_stage_shader_variables,
1863 deductions: deductions.collect(),
1864 });
1865 }
1866 num_user_defined_outputs += 1;
1867 }
1868 Varying::BuiltIn(_) => {}
1869 };
1870
1871 if let Some(
1872 cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1873 ) = compare_function
1874 {
1875 if let Varying::BuiltIn(BuiltIn::Position { invariant: false }) = *output {
1876 log::warn!(
1877 concat!(
1878 "Vertex shader with entry point {} outputs a ",
1879 "@builtin(position) without the @invariant attribute and ",
1880 "is used in a pipeline with {cmp:?}. On some machines, ",
1881 "this can cause bad artifacting as {cmp:?} assumes the ",
1882 "values output from the vertex shader exactly match the ",
1883 "value in the depth buffer. The @invariant attribute on the ",
1884 "@builtin(position) vertex output ensures that the exact ",
1885 "same pixel depths are used every render."
1886 ),
1887 entry_point_name,
1888 cmp = cmp
1889 );
1890 }
1891 }
1892 }
1893
1894 if num_user_defined_outputs > max_vertex_shader_output_variables {
1895 return Err(StageError::TooManyUserDefinedVertexOutputs {
1896 num_found: num_user_defined_outputs,
1897 limit: self.limits.max_inter_stage_shader_variables,
1898 deductions: deductions.collect(),
1899 });
1900 }
1901 }
1902 ShaderStageForValidation::Fragment {
1903 dual_source_blending,
1904 has_depth_attachment,
1905 } => {
1906 let mut max_fragment_shader_input_variables =
1907 self.limits.max_inter_stage_shader_variables;
1908
1909 let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1910 Varying::UserDefined { .. } => None,
1911 Varying::BuiltIn(builtin) => {
1912 MaxFragmentShaderInputDeduction::from_inter_stage_builtin(builtin.to_naga())
1913 .or_else(|| {
1914 unreachable!(
1915 concat!(
1916 "unexpected built-in provided; ",
1917 "{:?} is not used for fragment stage input",
1918 ),
1919 builtin
1920 )
1921 })
1922 }
1923 });
1924
1925 for deduction in deductions.clone() {
1926 max_fragment_shader_input_variables = max_fragment_shader_input_variables
1929 .checked_sub(deduction.for_variables())
1930 .unwrap();
1931 }
1932
1933 let mut num_user_defined_inputs = 0;
1934
1935 for output in entry_point.inputs.iter() {
1936 match *output {
1937 Varying::UserDefined { ref iv, location } => {
1938 if location >= self.limits.max_inter_stage_shader_variables {
1939 return Err(StageError::FragmentInputLocationTooLarge {
1940 location,
1941 var: iv.clone(),
1942 limit: self.limits.max_inter_stage_shader_variables,
1943 deductions: deductions.collect(),
1944 });
1945 }
1946 num_user_defined_inputs += 1;
1947 }
1948 Varying::BuiltIn(_) => {}
1949 };
1950 }
1951
1952 if num_user_defined_inputs > max_fragment_shader_input_variables {
1953 return Err(StageError::TooManyUserDefinedFragmentInputs {
1954 num_found: num_user_defined_inputs,
1955 limit: self.limits.max_inter_stage_shader_variables,
1956 deductions: deductions.collect(),
1957 });
1958 }
1959
1960 for output in &entry_point.outputs {
1961 let &Varying::UserDefined { location, ref iv } = output else {
1962 continue;
1963 };
1964 if location >= self.limits.max_color_attachments {
1965 return Err(StageError::ColorAttachmentLocationTooLarge {
1966 location,
1967 var: iv.clone(),
1968 limit: self.limits.max_color_attachments,
1969 });
1970 }
1971 }
1972
1973 if dual_source_blending && !entry_point.dual_source_blending {
1978 return Err(StageError::InvalidDualSourceBlending);
1979 }
1980
1981 if entry_point
1982 .outputs
1983 .contains(&Varying::BuiltIn(BuiltIn::FragDepth))
1984 && !has_depth_attachment
1985 {
1986 return Err(StageError::MissingFragDepthAttachment);
1987 }
1988 }
1989 ShaderStageForValidation::Mesh => {
1990 for output in &entry_point.outputs {
1991 if matches!(output, Varying::BuiltIn(BuiltIn::PrimitiveIndex)) {
1992 this_stage_primitive_index = true;
1993 }
1994 }
1995 }
1996 _ => (),
1997 }
1998
1999 if let Some(ref mesh_info) = entry_point.mesh_info {
2000 if mesh_info.max_vertices > self.limits.max_mesh_output_vertices {
2001 return Err(StageError::TooManyMeshVertices {
2002 limit: self.limits.max_mesh_output_vertices,
2003 value: mesh_info.max_vertices,
2004 });
2005 }
2006 if mesh_info.max_primitives > self.limits.max_mesh_output_primitives {
2007 return Err(StageError::TooManyMeshPrimitives {
2008 limit: self.limits.max_mesh_output_primitives,
2009 value: mesh_info.max_primitives,
2010 });
2011 }
2012 if primitive_topology != Some(mesh_info.primitive_topology) {
2013 return Err(StageError::MeshTopologyMismatch);
2014 }
2015 }
2016 if let Some(task_payload_size) = entry_point.task_payload_size {
2017 if task_payload_size > self.limits.max_task_payload_size {
2018 return Err(StageError::TaskPayloadTooLarge {
2019 limit: self.limits.max_task_payload_size,
2020 value: task_payload_size,
2021 });
2022 }
2023 }
2024 if shader_stage.to_naga() == naga::ShaderStage::Mesh
2025 && entry_point.task_payload_size != inputs.task_payload_size
2026 {
2027 return Err(StageError::TaskPayloadMustMatch {
2028 input: inputs.task_payload_size,
2029 shader: entry_point.task_payload_size,
2030 });
2031 }
2032
2033 if shader_stage.to_naga() == naga::ShaderStage::Fragment
2035 && this_stage_primitive_index
2036 && inputs.primitive_index == Some(false)
2037 {
2038 return Err(StageError::InvalidPrimitiveIndex);
2039 } else if shader_stage.to_naga() == naga::ShaderStage::Fragment
2040 && !this_stage_primitive_index
2041 && inputs.primitive_index == Some(true)
2042 {
2043 return Err(StageError::MissingPrimitiveIndex);
2044 }
2045 if shader_stage.to_naga() == naga::ShaderStage::Mesh
2046 && inputs.task_payload_size.is_some()
2047 && has_draw_id
2048 {
2049 return Err(StageError::DrawIdError);
2050 }
2051
2052 if primitive_topology.is_none_or(|e| !e.is_triangles()) && has_per_vertex {
2053 return Err(StageError::PerVertexNotTriangles);
2054 }
2055
2056 let outputs = entry_point
2057 .outputs
2058 .iter()
2059 .filter_map(|output| match *output {
2060 Varying::UserDefined { location, ref iv } => Some((location, iv.clone())),
2061 Varying::BuiltIn(_) => None,
2062 })
2063 .collect();
2064
2065 let immediate_usage = self
2066 .immediate_usage(shader_stage.to_naga(), entry_point_name)
2067 .merge(&inputs.immediates);
2068
2069 if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
2071 if pipeline_layout.immediate_size < immediate_usage.size() {
2072 return Err(StageError::LayoutImmediateSize {
2073 layout: pipeline_layout.immediate_size,
2074 required: immediate_usage.size(),
2075 });
2076 }
2077 }
2078
2079 Ok(StageIo {
2080 task_payload_size: entry_point.task_payload_size,
2081 varyings: outputs,
2082 primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh {
2083 Some(this_stage_primitive_index)
2084 } else {
2085 None
2086 },
2087 immediates: immediate_usage,
2088 })
2089 }
2090}
2091
2092pub fn check_color_attachment_count(
2093 num_attachments: usize,
2094 limit: u32,
2095) -> Result<(), ColorAttachmentError> {
2096 let limit = usize::try_from(limit).unwrap();
2097 if num_attachments > limit {
2098 return Err(ColorAttachmentError::TooMany {
2099 given: num_attachments,
2100 limit,
2101 });
2102 }
2103
2104 Ok(())
2105}
2106
2107pub fn validate_color_attachment_bytes_per_sample(
2113 attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>,
2114 limit: u32,
2115) -> Result<(), ColorAttachmentError> {
2116 let mut total_bytes_per_sample: u32 = 0;
2117 for format in attachment_formats {
2118 let byte_cost = format.target_pixel_byte_cost().unwrap();
2119 let alignment = format.target_component_alignment().unwrap();
2120
2121 total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
2122 total_bytes_per_sample += byte_cost;
2123 }
2124
2125 if total_bytes_per_sample > limit {
2126 return Err(ColorAttachmentError::TooManyBytesPerSample {
2127 total: total_bytes_per_sample,
2128 limit,
2129 });
2130 }
2131
2132 Ok(())
2133}
2134
2135#[derive(Clone, Debug, Error)]
2136pub enum InvalidWorkgroupSizeError {
2137 #[error(
2138 "Workgroup size {dimensions:?} ({total} total invocations) must be less or equal to \
2139 the per-dimension limit `Limits::{per_dimension_limits_desc}` of {per_dimension_limits:?} \
2140 and the total invocation limit `Limits::{total_limit_desc}` of {total_limit}"
2141 )]
2142 LimitExceeded {
2143 dimensions: [u32; 3],
2144 per_dimension_limits: [u32; 3],
2145 per_dimension_limits_desc: &'static str,
2146 total: u32,
2147 total_limit: u32,
2148 total_limit_desc: &'static str,
2149 },
2150 #[error("Workgroup sizes {dimensions:?} must be positive")]
2151 Zero { dimensions: [u32; 3] },
2152}
2153
2154#[derive(Clone, Debug)]
2157pub(crate) struct WorkgroupSizeCheck<'a> {
2158 pub dimensions: &'a [u32; 3],
2159 pub per_dimension_limits: &'a [u32; 3],
2160 pub per_dimension_limits_desc: &'static str,
2161 pub total_limit: u32,
2162 pub total_limit_desc: &'static str,
2163}
2164
2165impl WorkgroupSizeCheck<'_> {
2166 pub(crate) fn check_and_compute_total_invocations(
2172 self,
2173 ) -> Result<u32, InvalidWorkgroupSizeError> {
2174 let Self {
2175 dimensions,
2176 per_dimension_limits,
2177 per_dimension_limits_desc,
2178 total_limit,
2179 total_limit_desc,
2180 } = self;
2181
2182 let total = dimensions
2183 .iter()
2184 .fold(1u32, |total, &dim| total.saturating_mul(dim));
2185
2186 let invalid_total_invocations = total > total_limit;
2187
2188 let dimension_too_large = dimensions
2189 .iter()
2190 .zip(per_dimension_limits.iter())
2191 .any(|(dim, limit)| dim > limit);
2192
2193 if invalid_total_invocations || dimension_too_large {
2194 Err(InvalidWorkgroupSizeError::LimitExceeded {
2195 dimensions: *dimensions,
2196 per_dimension_limits: *per_dimension_limits,
2197 per_dimension_limits_desc,
2198 total,
2199 total_limit,
2200 total_limit_desc,
2201 })
2202 } else {
2203 Ok(total)
2204 }
2205 }
2206}
2207
2208pub enum ShaderStageForValidation {
2209 Vertex {
2210 topology: wgt::PrimitiveTopology,
2211 compare_function: Option<wgt::CompareFunction>,
2212 },
2213 Mesh,
2214 Fragment {
2215 dual_source_blending: bool,
2216 has_depth_attachment: bool,
2217 },
2218 Compute,
2219 Task,
2220}
2221
2222impl ShaderStageForValidation {
2223 pub fn to_naga(&self) -> naga::ShaderStage {
2224 match self {
2225 Self::Vertex { .. } => naga::ShaderStage::Vertex,
2226 Self::Mesh => naga::ShaderStage::Mesh,
2227 Self::Fragment { .. } => naga::ShaderStage::Fragment,
2228 Self::Compute => naga::ShaderStage::Compute,
2229 Self::Task => naga::ShaderStage::Task,
2230 }
2231 }
2232
2233 pub fn to_wgt_bit(&self) -> wgt::ShaderStages {
2234 match self {
2235 Self::Vertex { .. } => wgt::ShaderStages::VERTEX,
2236 Self::Mesh => wgt::ShaderStages::MESH,
2237 Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT,
2238 Self::Compute => wgt::ShaderStages::COMPUTE,
2239 Self::Task => wgt::ShaderStages::TASK,
2240 }
2241 }
2242}