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