Skip to main content

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