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