wgpu_core/
validation.rs

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// Most shaders will use a standard interface which is very large.
324// Passthrough shaders have a much smaller interface. No reason to
325// box the standard interface though.
326#[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/// Errors produced when validating a programmable stage of a pipeline.
429#[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        // NOTE: Remember: the limit is 0-based for indices.
468        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        // NOTE: Remember: the limit is 0-based for indices.
491        limit - 1,
492        // NOTE: WebGPU spec. validation for fragment inputs is expressed in terms of variables
493        // (unlike vertex outputs), so we use `MaxFragmentShaderInputDeduction::for_variables` here
494        // (and not a non-existent `for_locations`).
495        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                            // Formats must match exactly. A write-only shader (but not a
741                            // read-only shader) is compatible with a read-write binding.
742                            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            //Note: Shader always sees data as int, uint, or float.
930            // It doesn't know if the original is normalized in a tighter form.
931            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            //Note: Shader always sees data as int, uint, or float.
1014            // It doesn't know if the original is normalized in a tighter form.
1015            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
1038/// Return true if the fragment `format` is covered by the provided `output`.
1039pub 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    /// The binding layout is derived from the pipeline layout.
1053    ///
1054    /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces.
1055    Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
1056    /// The binding layout is provided by the user in BGLs.
1057    ///
1058    /// This will be validated against the shader's interfaces.
1059    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    /// This must match between mesh & task shaders
1076    pub task_payload_size: Option<u32>,
1077    /// Fragment shaders cannot input primitive index on mesh shaders that don't output it on DX12.
1078    /// Therefore, we track between shader stages if primitive index is written (or if vertex shader
1079    /// is used).
1080    ///
1081    /// This is Some if it was a mesh shader.
1082    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                // NOTE: We should already have validated these in `naga`.
1123                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                    // NOTE: Based on the
1131                    // [spec](https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types):
1132                    //
1133                    // > The only valid use of a fixed-size array with an element count that is an
1134                    // > override-expression that is not a const-expression is as a memory view in
1135                    // > the workgroup address space.
1136                    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                //Note: technically this should be at least `log::error`, but
1145                // the reality is - every shader coming from `glslc` outputs an array
1146                // of clip distances and hits this path :(
1147                // So we lower it to `log::debug` to be less annoying as
1148                // there's nothing the user can do about it.
1149                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            // Find the used immediates. Naga should have validated that
1306            // at most one immediate is used by the entry point.
1307            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    /// Among other things, this implements some validation logic defined by the WebGPU spec. at
1404    /// <https://www.w3.org/TR/webgpu/#abstract-opdef-validating-inter-stage-interfaces>.
1405    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        // Since a shader module can have multiple entry points with the same name,
1415        // we need to look for one with the right execution model.
1416        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        // check resources visibility
1426        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                        // update the required binding size for this buffer
1432                        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        // Check the compatibility between textures and samplers
1496        //
1497        // We only need to do this if the binding layout is provided by the user, as derived
1498        // layouts will inherently be correctly tagged.
1499        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        // check workgroup size limits
1544        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        // check inputs compatibility
1591        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                                // For vertex attributes, there are defaults filled out
1601                                // by the driver if data is not provided.
1602                                naga::ShaderStage::Vertex => {
1603                                    let is_compatible =
1604                                        iv.ty.scalar.kind == provided.ty.scalar.kind;
1605                                    // vertex inputs don't count towards inter-stage
1606                                    (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                                // These can't have varying inputs
1625                                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                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1698                    // ever exceed the minimum variables available.
1699                    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                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1781                    // ever exceed the minimum variables available.
1782                    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 the pipeline uses dual-source blending, then the shader
1828                // must configure appropriate I/O, but it is not an error to
1829                // use a shader that defines the I/O in a pipeline that only
1830                // uses one blend source.
1831                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        // Fragment shader primitive index is treated like a varying
1888        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        // Check pipeline layout immediate size
1924        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
1961/// Validate a list of color attachment formats against `maxColorAttachmentBytesPerSample`.
1962///
1963/// The color attachments can be from a render pass descriptor or a pipeline descriptor.
1964///
1965/// Implements <https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample>.
1966pub 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/// A helper type for avoiding argument order mistakes when calling
2009/// [`Self::check_and_compute_total_invocations`].
2010#[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    /// Check X/Y/Z workgroup sizes against per-dimension and overall limits.
2021    ///
2022    /// This function does not check that the sizes are non-zero. In a dispatch, it is legal for
2023    /// the size to be zero. In shader or pipeline creation, it is an error for the size to be
2024    /// zero, and the caller must check that.
2025    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}