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 compatible_with_shader_output(self, shader: NumericType) -> bool {
1107 if self.scalar.kind != shader.scalar.kind {
1108 return false;
1109 }
1110 match (self.dim, shader.dim) {
1111 (NumericDimension::Scalar, NumericDimension::Scalar) => true,
1112 (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
1113 (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
1114 (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
1115 c0 == c1 && r0 == r1
1116 }
1117 _ => false,
1118 }
1119 }
1120}
1121
1122pub fn check_color_attachment_compatibility(
1124 state: &wgt::ColorTargetState,
1125 output_ty: NumericType,
1126) -> Result<(), ColorStateError> {
1127 let pipeline_ty = NumericType::from_texture_format(state.format);
1128 if !pipeline_ty.compatible_with_shader_output(output_ty) {
1129 return Err(ColorStateError::IncompatibleFormat {
1130 pipeline: pipeline_ty,
1131 shader: output_ty,
1132 });
1133 }
1134 if let Some(blend) = state.blend {
1135 for (factor, name) in [
1136 (blend.color.src_factor, "source"),
1137 (blend.color.dst_factor, "destination"),
1138 ] {
1139 if factor.uses_source_alpha()
1140 && output_ty.dim != NumericDimension::Vector(naga::VectorSize::Quad)
1141 {
1142 return Err(ColorStateError::InvalidAlphaBlend {
1143 which: name,
1144 factor,
1145 });
1146 }
1147 }
1148 }
1149
1150 Ok(())
1151}
1152
1153pub enum BindingLayoutSource {
1154 Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
1158 Provided(Arc<crate::binding_model::PipelineLayout>),
1162}
1163
1164impl BindingLayoutSource {
1165 pub fn new_derived(limits: &wgt::Limits) -> Self {
1166 let mut array = ArrayVec::new();
1167 for _ in 0..limits.max_bind_groups {
1168 array.push(Default::default());
1169 }
1170 BindingLayoutSource::Derived(Box::new(array))
1171 }
1172}
1173
1174#[derive(Debug, Clone, Default)]
1175pub struct StageIo {
1176 pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>,
1177 pub task_payload_size: Option<u32>,
1179 pub primitive_index: Option<bool>,
1185 pub immediates: naga::valid::ImmediateUsage,
1186}
1187
1188impl Interface {
1189 fn populate(
1203 list: &mut Vec<Varying>,
1204 binding: Option<&naga::Binding>,
1205 ty: naga::Handle<naga::Type>,
1206 arena: &naga::UniqueArena<naga::Type>,
1207 ) {
1208 let numeric_ty = match arena[ty].inner {
1209 naga::TypeInner::Scalar(scalar) => NumericType {
1210 dim: NumericDimension::Scalar,
1211 scalar,
1212 },
1213 naga::TypeInner::Vector { size, scalar } => NumericType {
1214 dim: NumericDimension::Vector(size),
1215 scalar,
1216 },
1217 naga::TypeInner::Matrix {
1218 columns,
1219 rows,
1220 scalar,
1221 } => NumericType {
1222 dim: NumericDimension::Matrix(columns, rows),
1223 scalar,
1224 },
1225 naga::TypeInner::Struct { ref members, .. } => {
1226 for member in members {
1227 Self::populate(list, member.binding.as_ref(), member.ty, arena);
1228 }
1229 return;
1230 }
1231 naga::TypeInner::Array { base, size, stride }
1232 if matches!(
1233 binding,
1234 Some(naga::Binding::BuiltIn(naga::BuiltIn::ClipDistances)),
1235 ) =>
1236 {
1237 debug_assert_eq!(
1239 &arena[base].inner,
1240 &naga::TypeInner::Scalar(naga::Scalar::F32)
1241 );
1242 debug_assert_eq!(stride, 4);
1243
1244 let naga::ArraySize::Constant(array_size) = size else {
1245 unreachable!("non-constant array size for `clip_distances`")
1252 };
1253 let array_size = array_size.get();
1254
1255 list.push(Varying::BuiltIn(BuiltIn::ClipDistances { array_size }));
1256 return;
1257 }
1258 ref other => {
1259 log::error!("Unexpected varying type: {other:?}");
1260 return;
1261 }
1262 };
1263
1264 let varying = match binding {
1265 Some(&naga::Binding::Location {
1266 location,
1267 interpolation,
1268 sampling,
1269 per_primitive,
1270 blend_src: _,
1271 }) => Varying::UserDefined {
1272 location,
1273 iv: InterfaceVar {
1274 ty: numeric_ty,
1275 interpolation,
1276 sampling,
1277 per_primitive,
1278 },
1279 },
1280 Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(match built_in {
1281 naga::BuiltIn::Position { invariant } => BuiltIn::Position { invariant },
1282 naga::BuiltIn::ViewIndex => BuiltIn::ViewIndex,
1283 naga::BuiltIn::BaseInstance => BuiltIn::BaseInstance,
1284 naga::BuiltIn::BaseVertex => BuiltIn::BaseVertex,
1285 naga::BuiltIn::ClipDistances => unreachable!(),
1286 naga::BuiltIn::CullDistance => BuiltIn::CullDistance,
1287 naga::BuiltIn::InstanceIndex => BuiltIn::InstanceIndex,
1288 naga::BuiltIn::PointSize => BuiltIn::PointSize,
1289 naga::BuiltIn::VertexIndex => BuiltIn::VertexIndex,
1290 naga::BuiltIn::DrawIndex => BuiltIn::DrawIndex,
1291 naga::BuiltIn::FragDepth => BuiltIn::FragDepth,
1292 naga::BuiltIn::PointCoord => BuiltIn::PointCoord,
1293 naga::BuiltIn::FrontFacing => BuiltIn::FrontFacing,
1294 naga::BuiltIn::PrimitiveIndex => BuiltIn::PrimitiveIndex,
1295 naga::BuiltIn::Barycentric { perspective } => BuiltIn::Barycentric { perspective },
1296 naga::BuiltIn::SampleIndex => BuiltIn::SampleIndex,
1297 naga::BuiltIn::SampleMask => BuiltIn::SampleMask,
1298 naga::BuiltIn::GlobalInvocationId => BuiltIn::GlobalInvocationId,
1299 naga::BuiltIn::LocalInvocationId => BuiltIn::LocalInvocationId,
1300 naga::BuiltIn::LocalInvocationIndex => BuiltIn::LocalInvocationIndex,
1301 naga::BuiltIn::WorkGroupId => BuiltIn::WorkGroupId,
1302 naga::BuiltIn::WorkGroupSize => BuiltIn::WorkGroupSize,
1303 naga::BuiltIn::NumWorkGroups => BuiltIn::NumWorkGroups,
1304 naga::BuiltIn::NumSubgroups => BuiltIn::NumSubgroups,
1305 naga::BuiltIn::SubgroupId => BuiltIn::SubgroupId,
1306 naga::BuiltIn::SubgroupSize => BuiltIn::SubgroupSize,
1307 naga::BuiltIn::SubgroupInvocationId => BuiltIn::SubgroupInvocationId,
1308 naga::BuiltIn::MeshTaskSize => BuiltIn::MeshTaskSize,
1309 naga::BuiltIn::CullPrimitive => BuiltIn::CullPrimitive,
1310 naga::BuiltIn::PointIndex => BuiltIn::PointIndex,
1311 naga::BuiltIn::LineIndices => BuiltIn::LineIndices,
1312 naga::BuiltIn::TriangleIndices => BuiltIn::TriangleIndices,
1313 naga::BuiltIn::VertexCount => BuiltIn::VertexCount,
1314 naga::BuiltIn::Vertices => BuiltIn::Vertices,
1315 naga::BuiltIn::PrimitiveCount => BuiltIn::PrimitiveCount,
1316 naga::BuiltIn::Primitives => BuiltIn::Primitives,
1317 naga::BuiltIn::RayInvocationId => BuiltIn::RayInvocationId,
1318 naga::BuiltIn::NumRayInvocations => BuiltIn::NumRayInvocations,
1319 naga::BuiltIn::InstanceCustomData => BuiltIn::InstanceCustomData,
1320 naga::BuiltIn::GeometryIndex => BuiltIn::GeometryIndex,
1321 naga::BuiltIn::WorldRayOrigin => BuiltIn::WorldRayOrigin,
1322 naga::BuiltIn::WorldRayDirection => BuiltIn::WorldRayDirection,
1323 naga::BuiltIn::ObjectRayOrigin => BuiltIn::ObjectRayOrigin,
1324 naga::BuiltIn::ObjectRayDirection => BuiltIn::ObjectRayDirection,
1325 naga::BuiltIn::RayTmin => BuiltIn::RayTmin,
1326 naga::BuiltIn::RayTCurrentMax => BuiltIn::RayTCurrentMax,
1327 naga::BuiltIn::ObjectToWorld => BuiltIn::ObjectToWorld,
1328 naga::BuiltIn::WorldToObject => BuiltIn::WorldToObject,
1329 naga::BuiltIn::HitKind => BuiltIn::HitKind,
1330 }),
1331 None => {
1332 log::error!("Missing binding for a varying");
1333 return;
1334 }
1335 };
1336 list.push(varying);
1337 }
1338
1339 pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
1345 let mut resources = naga::Arena::new();
1346 let mut resource_mapping = FastHashMap::default();
1347 for (var_handle, var) in module.global_variables.iter() {
1348 let bind = match var.binding {
1349 Some(br) => br,
1350 _ => continue,
1351 };
1352 let naga_ty = &module.types[var.ty].inner;
1353
1354 let inner_ty = match *naga_ty {
1355 naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
1356 ref ty => ty,
1357 };
1358
1359 let ty = match *inner_ty {
1360 naga::TypeInner::Image {
1361 dim,
1362 arrayed,
1363 class,
1364 } => ResourceType::Texture {
1365 dim,
1366 arrayed,
1367 class,
1368 },
1369 naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1370 naga::TypeInner::AccelerationStructure { vertex_return } => {
1371 ResourceType::AccelerationStructure { vertex_return }
1372 }
1373 ref other => ResourceType::Buffer {
1374 minimum_binding_size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64)
1375 .unwrap(),
1376 },
1377 };
1378 let handle = resources.append(
1379 Resource {
1380 name: var.name.clone(),
1381 bind,
1382 ty,
1383 class: var.space,
1384 },
1385 Default::default(),
1386 );
1387 resource_mapping.insert(var_handle, handle);
1388 }
1389
1390 let mut entry_points = FastHashMap::default();
1391 entry_points.reserve(module.entry_points.len());
1392 for (index, entry_point) in module.entry_points.iter().enumerate() {
1393 let func_info = info.get_entry_point(index);
1394 let mut ep = EntryPoint::default();
1395 for arg in entry_point.function.arguments.iter() {
1396 Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1397 }
1398 if let Some(ref result) = entry_point.function.result {
1399 Self::populate(
1400 &mut ep.outputs,
1401 result.binding.as_ref(),
1402 result.ty,
1403 &module.types,
1404 );
1405 }
1406
1407 for (var_handle, var) in module.global_variables.iter() {
1408 let usage = func_info[var_handle];
1409 if !usage.is_empty() && var.binding.is_some() {
1410 ep.resources.push(resource_mapping[&var_handle]);
1411 }
1412 }
1413
1414 for key in func_info.sampling_set.iter() {
1415 ep.sampling_pairs
1416 .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1417 }
1418 ep.dual_source_blending = func_info.dual_source_blending;
1419 ep.workgroup_size = entry_point.workgroup_size;
1420
1421 let mut used_immediates = module
1424 .global_variables
1425 .iter()
1426 .filter(|&(_, var)| var.space == naga::AddressSpace::Immediate)
1427 .map(|(handle, _)| handle)
1428 .filter(|&handle| !func_info[handle].is_empty());
1429 ep.immediate_usage = used_immediates
1430 .next()
1431 .map(|handle| {
1432 naga::valid::ImmediateUsage::from_type(
1433 &module.types[module.global_variables[handle].ty].inner,
1434 &module.types,
1435 module.to_ctx(),
1436 )
1437 })
1438 .unwrap_or_default();
1439 assert!(used_immediates.next().is_none());
1440
1441 if let Some(task_payload) = entry_point.task_payload {
1442 ep.task_payload_size = Some(
1443 module.types[module.global_variables[task_payload].ty]
1444 .inner
1445 .size(module.to_ctx()),
1446 );
1447 }
1448 if let Some(ref mesh_info) = entry_point.mesh_info {
1449 ep.mesh_info = Some(EntryPointMeshInfo {
1450 max_vertices: mesh_info.max_vertices,
1451 max_primitives: mesh_info.max_primitives,
1452 primitive_topology: match mesh_info.topology {
1453 naga::MeshOutputTopology::Triangles => wgt::PrimitiveTopology::TriangleList,
1454 naga::MeshOutputTopology::Lines => wgt::PrimitiveTopology::LineList,
1455 naga::MeshOutputTopology::Points => wgt::PrimitiveTopology::PointList,
1456 },
1457 });
1458 Self::populate(
1459 &mut ep.outputs,
1460 None,
1461 mesh_info.vertex_output_type,
1462 &module.types,
1463 );
1464 Self::populate(
1465 &mut ep.outputs,
1466 None,
1467 mesh_info.primitive_output_type,
1468 &module.types,
1469 );
1470 }
1471
1472 entry_points.insert(
1473 EntryPointKey(entry_point.stage, entry_point.name.clone()),
1474 ep,
1475 );
1476 }
1477
1478 Self {
1479 limits,
1480 resources,
1481 entry_points,
1482 }
1483 }
1484
1485 fn immediate_usage(
1486 &self,
1487 stage: naga::ShaderStage,
1488 entry_point_name: &str,
1489 ) -> naga::valid::ImmediateUsage {
1490 self.entry_points
1491 .get(&EntryPointKeyRef(stage, entry_point_name))
1492 .map(|ep| ep.immediate_usage)
1493 .unwrap_or_default()
1494 }
1495
1496 pub fn finalize_entry_point_name(
1502 &self,
1503 stage: naga::ShaderStage,
1504 entry_point_name: Option<&str>,
1505 ) -> Result<String, StageError> {
1506 entry_point_name
1507 .map(|ep| ep.to_string())
1508 .map(Ok)
1509 .unwrap_or_else(|| {
1510 let mut entry_points =
1511 self.entry_points
1512 .keys()
1513 .filter_map(|EntryPointKey(ep_stage, name)| {
1514 (ep_stage == &stage).then_some(name)
1515 });
1516 let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1517 if entry_points.next().is_some() {
1518 return Err(StageError::MultipleEntryPointsFound);
1519 }
1520 Ok(first.clone())
1521 })
1522 }
1523
1524 pub fn check_stage(
1546 &self,
1547 layouts: &mut BindingLayoutSource,
1548 minimum_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1549 entry_point_name: &str,
1550 shader_stage: ShaderStageForValidation,
1551 inputs: StageIo,
1552 primitive_topology: Option<wgt::PrimitiveTopology>,
1553 ) -> Result<StageIo, StageError> {
1554 let pair = EntryPointKeyRef(shader_stage.to_naga(), entry_point_name);
1557 let entry_point = match self.entry_points.get(&pair) {
1558 Some(some) => some,
1559 None => return Err(StageError::MissingEntryPoint(pair.1.to_string())),
1560 };
1561 let EntryPointKeyRef(_, entry_point_name) = pair;
1562
1563 let stage_bit = shader_stage.to_wgt_bit();
1564
1565 for &handle in entry_point.resources.iter() {
1567 let res = &self.resources[handle];
1568 let result = 'err: {
1569 match layouts {
1570 BindingLayoutSource::Provided(pipeline_layout) => {
1571 if let ResourceType::Buffer {
1573 minimum_binding_size,
1574 } = res.ty
1575 {
1576 match minimum_binding_sizes.entry(res.bind) {
1577 Entry::Occupied(e) => {
1578 *e.into_mut() = minimum_binding_size.max(*e.get());
1579 }
1580 Entry::Vacant(e) => {
1581 e.insert(minimum_binding_size);
1582 }
1583 }
1584 }
1585
1586 let Some(entry) =
1587 pipeline_layout.get_bgl_entry(res.bind.group, res.bind.binding)
1588 else {
1589 break 'err Err(BindingError::Missing);
1590 };
1591
1592 if !entry.visibility.contains(stage_bit) {
1593 break 'err Err(BindingError::Invisible);
1594 }
1595
1596 res.check_binding_use(entry)
1597 }
1598 BindingLayoutSource::Derived(layouts) => {
1599 let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1600 break 'err Err(BindingError::Missing);
1601 };
1602
1603 let ty = match res.derive_binding_type(
1604 entry_point
1605 .sampling_pairs
1606 .iter()
1607 .any(|&(im, _samp)| im == handle),
1608 ) {
1609 Ok(ty) => ty,
1610 Err(error) => break 'err Err(error),
1611 };
1612
1613 match map.entry(res.bind.binding) {
1614 indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1615 break 'err Err(BindingError::InconsistentlyDerivedType)
1616 }
1617 indexmap::map::Entry::Occupied(e) => {
1618 e.into_mut().visibility |= stage_bit;
1619 }
1620 indexmap::map::Entry::Vacant(e) => {
1621 e.insert(BindGroupLayoutEntry {
1622 binding: res.bind.binding,
1623 ty,
1624 visibility: stage_bit,
1625 count: None,
1626 });
1627 }
1628 }
1629 Ok(())
1630 }
1631 }
1632 };
1633 if let Err(error) = result {
1634 return Err(StageError::Binding(res.bind, error));
1635 }
1636 }
1637
1638 if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
1643 for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1644 let texture_bind = &self.resources[texture_handle].bind;
1645 let sampler_bind = &self.resources[sampler_handle].bind;
1646 let texture_layout = pipeline_layout
1647 .get_bgl_entry(texture_bind.group, texture_bind.binding)
1648 .unwrap();
1649 let sampler_layout = pipeline_layout
1650 .get_bgl_entry(sampler_bind.group, sampler_bind.binding)
1651 .unwrap();
1652 assert!(texture_layout.visibility.contains(stage_bit));
1653 assert!(sampler_layout.visibility.contains(stage_bit));
1654
1655 let sampler_filtering = matches!(
1656 sampler_layout.ty,
1657 BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1658 );
1659 let texture_sample_type = match texture_layout.ty {
1660 BindingType::Texture { sample_type, .. } => sample_type,
1661 BindingType::ExternalTexture => {
1662 wgt::TextureSampleType::Float { filterable: true }
1663 }
1664 _ => unreachable!(),
1665 };
1666
1667 let error = match (sampler_filtering, texture_sample_type) {
1668 (true, wgt::TextureSampleType::Float { filterable: false }) => {
1669 Some(FilteringError::Float)
1670 }
1671 (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1672 (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1673 _ => None,
1674 };
1675
1676 if let Some(error) = error {
1677 return Err(StageError::Filtering {
1678 texture: *texture_bind,
1679 sampler: *sampler_bind,
1680 error,
1681 });
1682 }
1683 }
1684 }
1685
1686 if shader_stage.to_naga().compute_like() {
1688 let workgroup_size_check = match shader_stage.to_naga() {
1689 naga::ShaderStage::Compute => WorkgroupSizeCheck {
1690 dimensions: &entry_point.workgroup_size,
1691 per_dimension_limits: &[
1692 self.limits.max_compute_workgroup_size_x,
1693 self.limits.max_compute_workgroup_size_y,
1694 self.limits.max_compute_workgroup_size_z,
1695 ],
1696 per_dimension_limits_desc: "max_compute_workgroup_size_*",
1697
1698 total_limit: self.limits.max_compute_invocations_per_workgroup,
1699 total_limit_desc: "max_compute_invocations_per_workgroup",
1700 },
1701 naga::ShaderStage::Task => WorkgroupSizeCheck {
1702 dimensions: &entry_point.workgroup_size,
1703 per_dimension_limits: &[self.limits.max_task_invocations_per_dimension; 3],
1704 per_dimension_limits_desc: "max_task_invocations_per_dimension",
1705
1706 total_limit: self.limits.max_task_invocations_per_workgroup,
1707 total_limit_desc: "max_task_invocations_per_workgroup",
1708 },
1709 naga::ShaderStage::Mesh => WorkgroupSizeCheck {
1710 dimensions: &entry_point.workgroup_size,
1711 per_dimension_limits: &[self.limits.max_mesh_invocations_per_dimension; 3],
1712 per_dimension_limits_desc: "max_mesh_invocations_per_dimension",
1713
1714 total_limit: self.limits.max_mesh_invocations_per_workgroup,
1715 total_limit_desc: "max_mesh_invocations_per_workgroup",
1716 },
1717 _ => unreachable!(),
1718 };
1719 let total = workgroup_size_check.check_and_compute_total_invocations()?;
1720 if total == 0 {
1721 return Err(StageError::InvalidWorkgroupSize(
1722 InvalidWorkgroupSizeError::Zero {
1723 dimensions: entry_point.workgroup_size,
1724 },
1725 ));
1726 }
1727 }
1728
1729 let mut this_stage_primitive_index = false;
1730 let mut has_draw_id = false;
1731 let mut has_per_vertex = false;
1732
1733 for input in entry_point.inputs.iter() {
1735 match *input {
1736 Varying::UserDefined { location, ref iv } => {
1737 let result = inputs
1738 .varyings
1739 .get(&location)
1740 .ok_or(InputError::Missing)
1741 .and_then(|provided| {
1742 let (compatible, per_primitive_correct) = match shader_stage.to_naga() {
1743 naga::ShaderStage::Vertex => {
1746 let is_compatible =
1747 iv.ty.scalar.kind == provided.ty.scalar.kind;
1748 (is_compatible, !iv.per_primitive)
1750 }
1751 naga::ShaderStage::Fragment => {
1752 if iv.interpolation != provided.interpolation {
1753 return Err(InputError::InterpolationMismatch(
1754 provided.interpolation,
1755 ));
1756 }
1757 if iv.sampling != provided.sampling {
1758 return Err(InputError::SamplingMismatch(
1759 provided.sampling,
1760 ));
1761 }
1762 (
1763 iv.ty == provided.ty,
1764 iv.per_primitive == provided.per_primitive,
1765 )
1766 }
1767 naga::ShaderStage::Compute
1769 | naga::ShaderStage::Task
1770 | naga::ShaderStage::Mesh => (false, false),
1771 naga::ShaderStage::RayGeneration
1772 | naga::ShaderStage::AnyHit
1773 | naga::ShaderStage::ClosestHit
1774 | naga::ShaderStage::Miss => {
1775 unreachable!()
1776 }
1777 };
1778 if !compatible {
1779 return Err(InputError::WrongType(provided.ty));
1780 } else if !per_primitive_correct {
1781 return Err(InputError::WrongPerPrimitive {
1782 pipeline_input: provided.per_primitive,
1783 shader: iv.per_primitive,
1784 });
1785 }
1786 Ok(())
1787 });
1788
1789 if let Err(error) = result {
1790 return Err(StageError::Input {
1791 location,
1792 var: iv.clone(),
1793 error,
1794 });
1795 }
1796 has_per_vertex |= iv.interpolation == Some(naga::Interpolation::PerVertex);
1797 }
1798 Varying::BuiltIn(BuiltIn::PrimitiveIndex) => {
1799 this_stage_primitive_index = true;
1800 }
1801 Varying::BuiltIn(BuiltIn::DrawIndex) => {
1802 has_draw_id = true;
1803 }
1804 Varying::BuiltIn(_) => {}
1805 }
1806 }
1807
1808 match shader_stage {
1809 ShaderStageForValidation::Vertex {
1810 topology,
1811 compare_function,
1812 } => {
1813 let mut max_vertex_shader_output_variables =
1814 self.limits.max_inter_stage_shader_variables;
1815 let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1;
1816
1817 let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList {
1818 Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology)
1819 } else {
1820 None
1821 };
1822
1823 let clip_distance_deductions = entry_point.outputs.iter().filter_map(|output| {
1824 if let &Varying::BuiltIn(BuiltIn::ClipDistances { array_size }) = output {
1825 Some(MaxVertexShaderOutputDeduction::ClipDistances { array_size })
1826 } else {
1827 None
1828 }
1829 });
1830 debug_assert!(
1831 clip_distance_deductions.clone().count() <= 1,
1832 "multiple `clip_distances` outputs found"
1833 );
1834
1835 let deductions = point_list_deduction
1836 .into_iter()
1837 .chain(clip_distance_deductions);
1838
1839 for deduction in deductions.clone() {
1840 max_vertex_shader_output_variables = max_vertex_shader_output_variables
1843 .checked_sub(deduction.for_variables())
1844 .unwrap();
1845 max_vertex_shader_output_location = max_vertex_shader_output_location
1846 .checked_sub(deduction.for_location())
1847 .unwrap();
1848 }
1849
1850 let mut num_user_defined_outputs = 0;
1851
1852 for output in entry_point.outputs.iter() {
1853 match *output {
1854 Varying::UserDefined { ref iv, location } => {
1855 if location > max_vertex_shader_output_location {
1856 return Err(StageError::VertexOutputLocationTooLarge {
1857 location,
1858 var: iv.clone(),
1859 limit: self.limits.max_inter_stage_shader_variables,
1860 deductions: deductions.collect(),
1861 });
1862 }
1863 num_user_defined_outputs += 1;
1864 }
1865 Varying::BuiltIn(_) => {}
1866 };
1867
1868 if let Some(
1869 cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1870 ) = compare_function
1871 {
1872 if let Varying::BuiltIn(BuiltIn::Position { invariant: false }) = *output {
1873 log::warn!(
1874 concat!(
1875 "Vertex shader with entry point {} outputs a ",
1876 "@builtin(position) without the @invariant attribute and ",
1877 "is used in a pipeline with {cmp:?}. On some machines, ",
1878 "this can cause bad artifacting as {cmp:?} assumes the ",
1879 "values output from the vertex shader exactly match the ",
1880 "value in the depth buffer. The @invariant attribute on the ",
1881 "@builtin(position) vertex output ensures that the exact ",
1882 "same pixel depths are used every render."
1883 ),
1884 entry_point_name,
1885 cmp = cmp
1886 );
1887 }
1888 }
1889 }
1890
1891 if num_user_defined_outputs > max_vertex_shader_output_variables {
1892 return Err(StageError::TooManyUserDefinedVertexOutputs {
1893 num_found: num_user_defined_outputs,
1894 limit: self.limits.max_inter_stage_shader_variables,
1895 deductions: deductions.collect(),
1896 });
1897 }
1898 }
1899 ShaderStageForValidation::Fragment {
1900 dual_source_blending,
1901 has_depth_attachment,
1902 } => {
1903 let mut max_fragment_shader_input_variables =
1904 self.limits.max_inter_stage_shader_variables;
1905
1906 let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1907 Varying::UserDefined { .. } => None,
1908 Varying::BuiltIn(builtin) => {
1909 MaxFragmentShaderInputDeduction::from_inter_stage_builtin(builtin.to_naga())
1910 .or_else(|| {
1911 unreachable!(
1912 concat!(
1913 "unexpected built-in provided; ",
1914 "{:?} is not used for fragment stage input",
1915 ),
1916 builtin
1917 )
1918 })
1919 }
1920 });
1921
1922 for deduction in deductions.clone() {
1923 max_fragment_shader_input_variables = max_fragment_shader_input_variables
1926 .checked_sub(deduction.for_variables())
1927 .unwrap();
1928 }
1929
1930 let mut num_user_defined_inputs = 0;
1931
1932 for output in entry_point.inputs.iter() {
1933 match *output {
1934 Varying::UserDefined { ref iv, location } => {
1935 if location >= self.limits.max_inter_stage_shader_variables {
1936 return Err(StageError::FragmentInputLocationTooLarge {
1937 location,
1938 var: iv.clone(),
1939 limit: self.limits.max_inter_stage_shader_variables,
1940 deductions: deductions.collect(),
1941 });
1942 }
1943 num_user_defined_inputs += 1;
1944 }
1945 Varying::BuiltIn(_) => {}
1946 };
1947 }
1948
1949 if num_user_defined_inputs > max_fragment_shader_input_variables {
1950 return Err(StageError::TooManyUserDefinedFragmentInputs {
1951 num_found: num_user_defined_inputs,
1952 limit: self.limits.max_inter_stage_shader_variables,
1953 deductions: deductions.collect(),
1954 });
1955 }
1956
1957 for output in &entry_point.outputs {
1958 let &Varying::UserDefined { location, ref iv } = output else {
1959 continue;
1960 };
1961 if location >= self.limits.max_color_attachments {
1962 return Err(StageError::ColorAttachmentLocationTooLarge {
1963 location,
1964 var: iv.clone(),
1965 limit: self.limits.max_color_attachments,
1966 });
1967 }
1968 }
1969
1970 if dual_source_blending && !entry_point.dual_source_blending {
1975 return Err(StageError::InvalidDualSourceBlending);
1976 }
1977
1978 if entry_point
1979 .outputs
1980 .contains(&Varying::BuiltIn(BuiltIn::FragDepth))
1981 && !has_depth_attachment
1982 {
1983 return Err(StageError::MissingFragDepthAttachment);
1984 }
1985 }
1986 ShaderStageForValidation::Mesh => {
1987 for output in &entry_point.outputs {
1988 if matches!(output, Varying::BuiltIn(BuiltIn::PrimitiveIndex)) {
1989 this_stage_primitive_index = true;
1990 }
1991 }
1992 }
1993 _ => (),
1994 }
1995
1996 if let Some(ref mesh_info) = entry_point.mesh_info {
1997 if mesh_info.max_vertices > self.limits.max_mesh_output_vertices {
1998 return Err(StageError::TooManyMeshVertices {
1999 limit: self.limits.max_mesh_output_vertices,
2000 value: mesh_info.max_vertices,
2001 });
2002 }
2003 if mesh_info.max_primitives > self.limits.max_mesh_output_primitives {
2004 return Err(StageError::TooManyMeshPrimitives {
2005 limit: self.limits.max_mesh_output_primitives,
2006 value: mesh_info.max_primitives,
2007 });
2008 }
2009 if primitive_topology != Some(mesh_info.primitive_topology) {
2010 return Err(StageError::MeshTopologyMismatch);
2011 }
2012 }
2013 if let Some(task_payload_size) = entry_point.task_payload_size {
2014 if task_payload_size > self.limits.max_task_payload_size {
2015 return Err(StageError::TaskPayloadTooLarge {
2016 limit: self.limits.max_task_payload_size,
2017 value: task_payload_size,
2018 });
2019 }
2020 }
2021 if shader_stage.to_naga() == naga::ShaderStage::Mesh
2022 && entry_point.task_payload_size != inputs.task_payload_size
2023 {
2024 return Err(StageError::TaskPayloadMustMatch {
2025 input: inputs.task_payload_size,
2026 shader: entry_point.task_payload_size,
2027 });
2028 }
2029
2030 if shader_stage.to_naga() == naga::ShaderStage::Fragment
2032 && this_stage_primitive_index
2033 && inputs.primitive_index == Some(false)
2034 {
2035 return Err(StageError::InvalidPrimitiveIndex);
2036 } else if shader_stage.to_naga() == naga::ShaderStage::Fragment
2037 && !this_stage_primitive_index
2038 && inputs.primitive_index == Some(true)
2039 {
2040 return Err(StageError::MissingPrimitiveIndex);
2041 }
2042 if shader_stage.to_naga() == naga::ShaderStage::Mesh
2043 && inputs.task_payload_size.is_some()
2044 && has_draw_id
2045 {
2046 return Err(StageError::DrawIdError);
2047 }
2048
2049 if primitive_topology.is_none_or(|e| !e.is_triangles()) && has_per_vertex {
2050 return Err(StageError::PerVertexNotTriangles);
2051 }
2052
2053 let outputs = entry_point
2054 .outputs
2055 .iter()
2056 .filter_map(|output| match *output {
2057 Varying::UserDefined { location, ref iv } => Some((location, iv.clone())),
2058 Varying::BuiltIn(_) => None,
2059 })
2060 .collect();
2061
2062 let immediate_usage = self
2063 .immediate_usage(shader_stage.to_naga(), entry_point_name)
2064 .merge(&inputs.immediates);
2065
2066 if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
2068 if pipeline_layout.immediate_size < immediate_usage.size() {
2069 return Err(StageError::LayoutImmediateSize {
2070 layout: pipeline_layout.immediate_size,
2071 required: immediate_usage.size(),
2072 });
2073 }
2074 }
2075
2076 Ok(StageIo {
2077 task_payload_size: entry_point.task_payload_size,
2078 varyings: outputs,
2079 primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh {
2080 Some(this_stage_primitive_index)
2081 } else {
2082 None
2083 },
2084 immediates: immediate_usage,
2085 })
2086 }
2087}
2088
2089pub fn check_color_attachment_count(
2090 num_attachments: usize,
2091 limit: u32,
2092) -> Result<(), ColorAttachmentError> {
2093 let limit = usize::try_from(limit).unwrap();
2094 if num_attachments > limit {
2095 return Err(ColorAttachmentError::TooMany {
2096 given: num_attachments,
2097 limit,
2098 });
2099 }
2100
2101 Ok(())
2102}
2103
2104pub fn validate_color_attachment_bytes_per_sample(
2110 attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>,
2111 limit: u32,
2112) -> Result<(), ColorAttachmentError> {
2113 let mut total_bytes_per_sample: u32 = 0;
2114 for format in attachment_formats {
2115 let byte_cost = format.target_pixel_byte_cost().unwrap();
2116 let alignment = format.target_component_alignment().unwrap();
2117
2118 total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
2119 total_bytes_per_sample += byte_cost;
2120 }
2121
2122 if total_bytes_per_sample > limit {
2123 return Err(ColorAttachmentError::TooManyBytesPerSample {
2124 total: total_bytes_per_sample,
2125 limit,
2126 });
2127 }
2128
2129 Ok(())
2130}
2131
2132#[derive(Clone, Debug, Error)]
2133pub enum InvalidWorkgroupSizeError {
2134 #[error(
2135 "Workgroup size {dimensions:?} ({total} total invocations) must be less or equal to \
2136 the per-dimension limit `Limits::{per_dimension_limits_desc}` of {per_dimension_limits:?} \
2137 and the total invocation limit `Limits::{total_limit_desc}` of {total_limit}"
2138 )]
2139 LimitExceeded {
2140 dimensions: [u32; 3],
2141 per_dimension_limits: [u32; 3],
2142 per_dimension_limits_desc: &'static str,
2143 total: u32,
2144 total_limit: u32,
2145 total_limit_desc: &'static str,
2146 },
2147 #[error("Workgroup sizes {dimensions:?} must be positive")]
2148 Zero { dimensions: [u32; 3] },
2149}
2150
2151#[derive(Clone, Debug)]
2154pub(crate) struct WorkgroupSizeCheck<'a> {
2155 pub dimensions: &'a [u32; 3],
2156 pub per_dimension_limits: &'a [u32; 3],
2157 pub per_dimension_limits_desc: &'static str,
2158 pub total_limit: u32,
2159 pub total_limit_desc: &'static str,
2160}
2161
2162impl WorkgroupSizeCheck<'_> {
2163 pub(crate) fn check_and_compute_total_invocations(
2169 self,
2170 ) -> Result<u32, InvalidWorkgroupSizeError> {
2171 let Self {
2172 dimensions,
2173 per_dimension_limits,
2174 per_dimension_limits_desc,
2175 total_limit,
2176 total_limit_desc,
2177 } = self;
2178
2179 let total = dimensions
2180 .iter()
2181 .fold(1u32, |total, &dim| total.saturating_mul(dim));
2182
2183 let invalid_total_invocations = total > total_limit;
2184
2185 let dimension_too_large = dimensions
2186 .iter()
2187 .zip(per_dimension_limits.iter())
2188 .any(|(dim, limit)| dim > limit);
2189
2190 if invalid_total_invocations || dimension_too_large {
2191 Err(InvalidWorkgroupSizeError::LimitExceeded {
2192 dimensions: *dimensions,
2193 per_dimension_limits: *per_dimension_limits,
2194 per_dimension_limits_desc,
2195 total,
2196 total_limit,
2197 total_limit_desc,
2198 })
2199 } else {
2200 Ok(total)
2201 }
2202 }
2203}
2204
2205pub enum ShaderStageForValidation {
2206 Vertex {
2207 topology: wgt::PrimitiveTopology,
2208 compare_function: Option<wgt::CompareFunction>,
2209 },
2210 Mesh,
2211 Fragment {
2212 dual_source_blending: bool,
2213 has_depth_attachment: bool,
2214 },
2215 Compute,
2216 Task,
2217}
2218
2219impl ShaderStageForValidation {
2220 pub fn to_naga(&self) -> naga::ShaderStage {
2221 match self {
2222 Self::Vertex { .. } => naga::ShaderStage::Vertex,
2223 Self::Mesh => naga::ShaderStage::Mesh,
2224 Self::Fragment { .. } => naga::ShaderStage::Fragment,
2225 Self::Compute => naga::ShaderStage::Compute,
2226 Self::Task => naga::ShaderStage::Task,
2227 }
2228 }
2229
2230 pub fn to_wgt_bit(&self) -> wgt::ShaderStages {
2231 match self {
2232 Self::Vertex { .. } => wgt::ShaderStages::VERTEX,
2233 Self::Mesh => wgt::ShaderStages::MESH,
2234 Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT,
2235 Self::Compute => wgt::ShaderStages::COMPUTE,
2236 Self::Task => wgt::ShaderStages::TASK,
2237 }
2238 }
2239}