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