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