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 is_subtype_of(self, other: NumericType) -> bool {
1107        if self.scalar.width > other.scalar.width {
1108            return false;
1109        }
1110        if self.scalar.kind != other.scalar.kind {
1111            return false;
1112        }
1113        match (self.dim, other.dim) {
1114            (NumericDimension::Scalar, NumericDimension::Scalar) => true,
1115            (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
1116            (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
1117            (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
1118                c0 == c1 && r0 == r1
1119            }
1120            _ => false,
1121        }
1122    }
1123}
1124
1125/// Return true if the fragment `format` is covered by the provided `output`.
1126pub fn check_color_attachment_compatibility(
1127    state: &wgt::ColorTargetState,
1128    output_ty: NumericType,
1129) -> Result<(), ColorStateError> {
1130    let pipeline_ty = NumericType::from_texture_format(state.format);
1131    if !pipeline_ty.is_subtype_of(output_ty) {
1132        return Err(ColorStateError::IncompatibleFormat {
1133            pipeline: pipeline_ty,
1134            shader: output_ty,
1135        });
1136    }
1137    if let Some(blend) = state.blend {
1138        for (factor, name) in [
1139            (blend.color.src_factor, "source"),
1140            (blend.color.dst_factor, "destination"),
1141        ] {
1142            if factor.uses_source_alpha()
1143                && output_ty.dim != NumericDimension::Vector(naga::VectorSize::Quad)
1144            {
1145                return Err(ColorStateError::InvalidAlphaBlend {
1146                    which: name,
1147                    factor,
1148                });
1149            }
1150        }
1151    }
1152
1153    Ok(())
1154}
1155
1156pub enum BindingLayoutSource {
1157    /// The binding layout is derived from the pipeline layout.
1158    ///
1159    /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces.
1160    Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
1161    /// The binding layout is provided by the user in BGLs.
1162    ///
1163    /// This will be validated against the shader's interfaces.
1164    Provided(Arc<crate::binding_model::PipelineLayout>),
1165}
1166
1167impl BindingLayoutSource {
1168    pub fn new_derived(limits: &wgt::Limits) -> Self {
1169        let mut array = ArrayVec::new();
1170        for _ in 0..limits.max_bind_groups {
1171            array.push(Default::default());
1172        }
1173        BindingLayoutSource::Derived(Box::new(array))
1174    }
1175}
1176
1177#[derive(Debug, Clone, Default)]
1178pub struct StageIo {
1179    pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>,
1180    /// This must match between mesh & task shaders
1181    pub task_payload_size: Option<u32>,
1182    /// Fragment shaders cannot input primitive index on mesh shaders that don't output it on DX12.
1183    /// Therefore, we track between shader stages if primitive index is written (or if vertex shader
1184    /// is used).
1185    ///
1186    /// This is Some if it was a mesh shader.
1187    pub primitive_index: Option<bool>,
1188    pub immediates: naga::valid::ImmediateUsage,
1189}
1190
1191impl Interface {
1192    /// Build some entry point's list of inputs or outputs.
1193    ///
1194    /// Given `ty` and `binding` that describe an entry point's argument or
1195    /// return value, figure out which builtins or locations are involved and
1196    /// add them to `list`, which is either [`EntryPoint::inputs`] or
1197    /// [`EntryPoint::outputs`].
1198    ///
1199    /// - If `ty` is a struct type, visit its members to find
1200    ///   individual bindings, and add them to `list`.
1201    ///
1202    /// - Otherwise, `binding` must be `Some(b)` where `b` describes a
1203    ///   binding's builtin or location, and `ty` gives its type. Add
1204    ///   this binding to `list`.
1205    fn populate(
1206        list: &mut Vec<Varying>,
1207        binding: Option<&naga::Binding>,
1208        ty: naga::Handle<naga::Type>,
1209        arena: &naga::UniqueArena<naga::Type>,
1210    ) {
1211        let numeric_ty = match arena[ty].inner {
1212            naga::TypeInner::Scalar(scalar) => NumericType {
1213                dim: NumericDimension::Scalar,
1214                scalar,
1215            },
1216            naga::TypeInner::Vector { size, scalar } => NumericType {
1217                dim: NumericDimension::Vector(size),
1218                scalar,
1219            },
1220            naga::TypeInner::Matrix {
1221                columns,
1222                rows,
1223                scalar,
1224            } => NumericType {
1225                dim: NumericDimension::Matrix(columns, rows),
1226                scalar,
1227            },
1228            naga::TypeInner::Struct { ref members, .. } => {
1229                for member in members {
1230                    Self::populate(list, member.binding.as_ref(), member.ty, arena);
1231                }
1232                return;
1233            }
1234            naga::TypeInner::Array { base, size, stride }
1235                if matches!(
1236                    binding,
1237                    Some(naga::Binding::BuiltIn(naga::BuiltIn::ClipDistances)),
1238                ) =>
1239            {
1240                // NOTE: We should already have validated these in `naga`.
1241                debug_assert_eq!(
1242                    &arena[base].inner,
1243                    &naga::TypeInner::Scalar(naga::Scalar::F32)
1244                );
1245                debug_assert_eq!(stride, 4);
1246
1247                let naga::ArraySize::Constant(array_size) = size else {
1248                    // NOTE: Based on the
1249                    // [spec](https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types):
1250                    //
1251                    // > The only valid use of a fixed-size array with an element count that is an
1252                    // > override-expression that is not a const-expression is as a memory view in
1253                    // > the workgroup address space.
1254                    unreachable!("non-constant array size for `clip_distances`")
1255                };
1256                let array_size = array_size.get();
1257
1258                list.push(Varying::BuiltIn(BuiltIn::ClipDistances { array_size }));
1259                return;
1260            }
1261            ref other => {
1262                log::error!("Unexpected varying type: {other:?}");
1263                return;
1264            }
1265        };
1266
1267        let varying = match binding {
1268            Some(&naga::Binding::Location {
1269                location,
1270                interpolation,
1271                sampling,
1272                per_primitive,
1273                blend_src: _,
1274            }) => Varying::UserDefined {
1275                location,
1276                iv: InterfaceVar {
1277                    ty: numeric_ty,
1278                    interpolation,
1279                    sampling,
1280                    per_primitive,
1281                },
1282            },
1283            Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(match built_in {
1284                naga::BuiltIn::Position { invariant } => BuiltIn::Position { invariant },
1285                naga::BuiltIn::ViewIndex => BuiltIn::ViewIndex,
1286                naga::BuiltIn::BaseInstance => BuiltIn::BaseInstance,
1287                naga::BuiltIn::BaseVertex => BuiltIn::BaseVertex,
1288                naga::BuiltIn::ClipDistances => unreachable!(),
1289                naga::BuiltIn::CullDistance => BuiltIn::CullDistance,
1290                naga::BuiltIn::InstanceIndex => BuiltIn::InstanceIndex,
1291                naga::BuiltIn::PointSize => BuiltIn::PointSize,
1292                naga::BuiltIn::VertexIndex => BuiltIn::VertexIndex,
1293                naga::BuiltIn::DrawIndex => BuiltIn::DrawIndex,
1294                naga::BuiltIn::FragDepth => BuiltIn::FragDepth,
1295                naga::BuiltIn::PointCoord => BuiltIn::PointCoord,
1296                naga::BuiltIn::FrontFacing => BuiltIn::FrontFacing,
1297                naga::BuiltIn::PrimitiveIndex => BuiltIn::PrimitiveIndex,
1298                naga::BuiltIn::Barycentric { perspective } => BuiltIn::Barycentric { perspective },
1299                naga::BuiltIn::SampleIndex => BuiltIn::SampleIndex,
1300                naga::BuiltIn::SampleMask => BuiltIn::SampleMask,
1301                naga::BuiltIn::GlobalInvocationId => BuiltIn::GlobalInvocationId,
1302                naga::BuiltIn::LocalInvocationId => BuiltIn::LocalInvocationId,
1303                naga::BuiltIn::LocalInvocationIndex => BuiltIn::LocalInvocationIndex,
1304                naga::BuiltIn::WorkGroupId => BuiltIn::WorkGroupId,
1305                naga::BuiltIn::WorkGroupSize => BuiltIn::WorkGroupSize,
1306                naga::BuiltIn::NumWorkGroups => BuiltIn::NumWorkGroups,
1307                naga::BuiltIn::NumSubgroups => BuiltIn::NumSubgroups,
1308                naga::BuiltIn::SubgroupId => BuiltIn::SubgroupId,
1309                naga::BuiltIn::SubgroupSize => BuiltIn::SubgroupSize,
1310                naga::BuiltIn::SubgroupInvocationId => BuiltIn::SubgroupInvocationId,
1311                naga::BuiltIn::MeshTaskSize => BuiltIn::MeshTaskSize,
1312                naga::BuiltIn::CullPrimitive => BuiltIn::CullPrimitive,
1313                naga::BuiltIn::PointIndex => BuiltIn::PointIndex,
1314                naga::BuiltIn::LineIndices => BuiltIn::LineIndices,
1315                naga::BuiltIn::TriangleIndices => BuiltIn::TriangleIndices,
1316                naga::BuiltIn::VertexCount => BuiltIn::VertexCount,
1317                naga::BuiltIn::Vertices => BuiltIn::Vertices,
1318                naga::BuiltIn::PrimitiveCount => BuiltIn::PrimitiveCount,
1319                naga::BuiltIn::Primitives => BuiltIn::Primitives,
1320                naga::BuiltIn::RayInvocationId => BuiltIn::RayInvocationId,
1321                naga::BuiltIn::NumRayInvocations => BuiltIn::NumRayInvocations,
1322                naga::BuiltIn::InstanceCustomData => BuiltIn::InstanceCustomData,
1323                naga::BuiltIn::GeometryIndex => BuiltIn::GeometryIndex,
1324                naga::BuiltIn::WorldRayOrigin => BuiltIn::WorldRayOrigin,
1325                naga::BuiltIn::WorldRayDirection => BuiltIn::WorldRayDirection,
1326                naga::BuiltIn::ObjectRayOrigin => BuiltIn::ObjectRayOrigin,
1327                naga::BuiltIn::ObjectRayDirection => BuiltIn::ObjectRayDirection,
1328                naga::BuiltIn::RayTmin => BuiltIn::RayTmin,
1329                naga::BuiltIn::RayTCurrentMax => BuiltIn::RayTCurrentMax,
1330                naga::BuiltIn::ObjectToWorld => BuiltIn::ObjectToWorld,
1331                naga::BuiltIn::WorldToObject => BuiltIn::WorldToObject,
1332                naga::BuiltIn::HitKind => BuiltIn::HitKind,
1333            }),
1334            None => {
1335                log::error!("Missing binding for a varying");
1336                return;
1337            }
1338        };
1339        list.push(varying);
1340    }
1341
1342    /// Construct an [`Interface`] value describing `module`.
1343    ///
1344    /// The `info` argument must be the results from validating `module`, and
1345    /// `limits` must be the limits for the device that we will use to create
1346    /// this shader module.
1347    pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
1348        let mut resources = naga::Arena::new();
1349        let mut resource_mapping = FastHashMap::default();
1350        for (var_handle, var) in module.global_variables.iter() {
1351            let bind = match var.binding {
1352                Some(br) => br,
1353                _ => continue,
1354            };
1355            let naga_ty = &module.types[var.ty].inner;
1356
1357            let inner_ty = match *naga_ty {
1358                naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
1359                ref ty => ty,
1360            };
1361
1362            let ty = match *inner_ty {
1363                naga::TypeInner::Image {
1364                    dim,
1365                    arrayed,
1366                    class,
1367                } => ResourceType::Texture {
1368                    dim,
1369                    arrayed,
1370                    class,
1371                },
1372                naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1373                naga::TypeInner::AccelerationStructure { vertex_return } => {
1374                    ResourceType::AccelerationStructure { vertex_return }
1375                }
1376                ref other => ResourceType::Buffer {
1377                    minimum_binding_size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64)
1378                        .unwrap(),
1379                },
1380            };
1381            let handle = resources.append(
1382                Resource {
1383                    name: var.name.clone(),
1384                    bind,
1385                    ty,
1386                    class: var.space,
1387                },
1388                Default::default(),
1389            );
1390            resource_mapping.insert(var_handle, handle);
1391        }
1392
1393        let mut entry_points = FastHashMap::default();
1394        entry_points.reserve(module.entry_points.len());
1395        for (index, entry_point) in module.entry_points.iter().enumerate() {
1396            let func_info = info.get_entry_point(index);
1397            let mut ep = EntryPoint::default();
1398            for arg in entry_point.function.arguments.iter() {
1399                Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1400            }
1401            if let Some(ref result) = entry_point.function.result {
1402                Self::populate(
1403                    &mut ep.outputs,
1404                    result.binding.as_ref(),
1405                    result.ty,
1406                    &module.types,
1407                );
1408            }
1409
1410            for (var_handle, var) in module.global_variables.iter() {
1411                let usage = func_info[var_handle];
1412                if !usage.is_empty() && var.binding.is_some() {
1413                    ep.resources.push(resource_mapping[&var_handle]);
1414                }
1415            }
1416
1417            for key in func_info.sampling_set.iter() {
1418                ep.sampling_pairs
1419                    .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1420            }
1421            ep.dual_source_blending = func_info.dual_source_blending;
1422            ep.workgroup_size = entry_point.workgroup_size;
1423
1424            // Find the used immediates. Naga should have validated that
1425            // at most one immediate is used by the entry point.
1426            let mut used_immediates = module
1427                .global_variables
1428                .iter()
1429                .filter(|&(_, var)| var.space == naga::AddressSpace::Immediate)
1430                .map(|(handle, _)| handle)
1431                .filter(|&handle| !func_info[handle].is_empty());
1432            ep.immediate_usage = used_immediates
1433                .next()
1434                .map(|handle| {
1435                    naga::valid::ImmediateUsage::from_type(
1436                        &module.types[module.global_variables[handle].ty].inner,
1437                        &module.types,
1438                        module.to_ctx(),
1439                    )
1440                })
1441                .unwrap_or_default();
1442            assert!(used_immediates.next().is_none());
1443
1444            if let Some(task_payload) = entry_point.task_payload {
1445                ep.task_payload_size = Some(
1446                    module.types[module.global_variables[task_payload].ty]
1447                        .inner
1448                        .size(module.to_ctx()),
1449                );
1450            }
1451            if let Some(ref mesh_info) = entry_point.mesh_info {
1452                ep.mesh_info = Some(EntryPointMeshInfo {
1453                    max_vertices: mesh_info.max_vertices,
1454                    max_primitives: mesh_info.max_primitives,
1455                    primitive_topology: match mesh_info.topology {
1456                        naga::MeshOutputTopology::Triangles => wgt::PrimitiveTopology::TriangleList,
1457                        naga::MeshOutputTopology::Lines => wgt::PrimitiveTopology::LineList,
1458                        naga::MeshOutputTopology::Points => wgt::PrimitiveTopology::PointList,
1459                    },
1460                });
1461                Self::populate(
1462                    &mut ep.outputs,
1463                    None,
1464                    mesh_info.vertex_output_type,
1465                    &module.types,
1466                );
1467                Self::populate(
1468                    &mut ep.outputs,
1469                    None,
1470                    mesh_info.primitive_output_type,
1471                    &module.types,
1472                );
1473            }
1474
1475            entry_points.insert(
1476                EntryPointKey(entry_point.stage, entry_point.name.clone()),
1477                ep,
1478            );
1479        }
1480
1481        Self {
1482            limits,
1483            resources,
1484            entry_points,
1485        }
1486    }
1487
1488    fn immediate_usage(
1489        &self,
1490        stage: naga::ShaderStage,
1491        entry_point_name: &str,
1492    ) -> naga::valid::ImmediateUsage {
1493        self.entry_points
1494            .get(&EntryPointKeyRef(stage, entry_point_name))
1495            .map(|ep| ep.immediate_usage)
1496            .unwrap_or_default()
1497    }
1498
1499    /// Select an entry point name, given an optional name and a shader stage.
1500    ///
1501    /// See [`ShaderModule::finalize_entry_point_name`] for details.
1502    ///
1503    /// [`ShaderModule::finalize_entry_point_name`]: crate::pipeline::ShaderModule::finalize_entry_point_name
1504    pub fn finalize_entry_point_name(
1505        &self,
1506        stage: naga::ShaderStage,
1507        entry_point_name: Option<&str>,
1508    ) -> Result<String, StageError> {
1509        entry_point_name
1510            .map(|ep| ep.to_string())
1511            .map(Ok)
1512            .unwrap_or_else(|| {
1513                let mut entry_points =
1514                    self.entry_points
1515                        .keys()
1516                        .filter_map(|EntryPointKey(ep_stage, name)| {
1517                            (ep_stage == &stage).then_some(name)
1518                        });
1519                let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1520                if entry_points.next().is_some() {
1521                    return Err(StageError::MultipleEntryPointsFound);
1522                }
1523                Ok(first.clone())
1524            })
1525    }
1526
1527    /// Analyze and validate an entry point for use as a given shader stage.
1528    ///
1529    /// Validate the entry point named `entry_point_name` for use in
1530    /// `shader_stage`:
1531    ///
1532    /// - Apply the WebGPU specification's [validating inter-stage interfaces]
1533    ///   algorithm.
1534    ///
1535    /// - Enforce workgroup size limits.
1536    ///
1537    /// - Check bind group layouts, and fill in derived bind group layouts.
1538    ///
1539    /// - Compute the minimum binding sizes, given the shader's resource
1540    ///   interface.
1541    ///
1542    /// - Check the compatibility between textures and samplers.
1543    ///
1544    /// Given `inputs`, describing this stage's inputs, return a [`StageIo`]
1545    /// describing its outputs.
1546    ///
1547    /// [validating inter-stage interfaces]: https://www.w3.org/TR/webgpu/#abstract-opdef-validating-inter-stage-interfaces
1548    pub fn check_stage(
1549        &self,
1550        layouts: &mut BindingLayoutSource,
1551        minimum_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1552        entry_point_name: &str,
1553        shader_stage: ShaderStageForValidation,
1554        inputs: StageIo,
1555        primitive_topology: Option<wgt::PrimitiveTopology>,
1556    ) -> Result<StageIo, StageError> {
1557        // Since a shader module can have multiple entry points with the same name,
1558        // we need to look for one with the right execution model.
1559        let pair = EntryPointKeyRef(shader_stage.to_naga(), entry_point_name);
1560        let entry_point = match self.entry_points.get(&pair) {
1561            Some(some) => some,
1562            None => return Err(StageError::MissingEntryPoint(pair.1.to_string())),
1563        };
1564        let EntryPointKeyRef(_, entry_point_name) = pair;
1565
1566        let stage_bit = shader_stage.to_wgt_bit();
1567
1568        // check resources visibility
1569        for &handle in entry_point.resources.iter() {
1570            let res = &self.resources[handle];
1571            let result = 'err: {
1572                match layouts {
1573                    BindingLayoutSource::Provided(pipeline_layout) => {
1574                        // update the required binding size for this buffer
1575                        if let ResourceType::Buffer {
1576                            minimum_binding_size,
1577                        } = res.ty
1578                        {
1579                            match minimum_binding_sizes.entry(res.bind) {
1580                                Entry::Occupied(e) => {
1581                                    *e.into_mut() = minimum_binding_size.max(*e.get());
1582                                }
1583                                Entry::Vacant(e) => {
1584                                    e.insert(minimum_binding_size);
1585                                }
1586                            }
1587                        }
1588
1589                        let Some(entry) =
1590                            pipeline_layout.get_bgl_entry(res.bind.group, res.bind.binding)
1591                        else {
1592                            break 'err Err(BindingError::Missing);
1593                        };
1594
1595                        if !entry.visibility.contains(stage_bit) {
1596                            break 'err Err(BindingError::Invisible);
1597                        }
1598
1599                        res.check_binding_use(entry)
1600                    }
1601                    BindingLayoutSource::Derived(layouts) => {
1602                        let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1603                            break 'err Err(BindingError::Missing);
1604                        };
1605
1606                        let ty = match res.derive_binding_type(
1607                            entry_point
1608                                .sampling_pairs
1609                                .iter()
1610                                .any(|&(im, _samp)| im == handle),
1611                        ) {
1612                            Ok(ty) => ty,
1613                            Err(error) => break 'err Err(error),
1614                        };
1615
1616                        match map.entry(res.bind.binding) {
1617                            indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1618                                break 'err Err(BindingError::InconsistentlyDerivedType)
1619                            }
1620                            indexmap::map::Entry::Occupied(e) => {
1621                                e.into_mut().visibility |= stage_bit;
1622                            }
1623                            indexmap::map::Entry::Vacant(e) => {
1624                                e.insert(BindGroupLayoutEntry {
1625                                    binding: res.bind.binding,
1626                                    ty,
1627                                    visibility: stage_bit,
1628                                    count: None,
1629                                });
1630                            }
1631                        }
1632                        Ok(())
1633                    }
1634                }
1635            };
1636            if let Err(error) = result {
1637                return Err(StageError::Binding(res.bind, error));
1638            }
1639        }
1640
1641        // Check the compatibility between textures and samplers
1642        //
1643        // We only need to do this if the binding layout is provided by the user, as derived
1644        // layouts will inherently be correctly tagged.
1645        if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
1646            for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1647                let texture_bind = &self.resources[texture_handle].bind;
1648                let sampler_bind = &self.resources[sampler_handle].bind;
1649                let texture_layout = pipeline_layout
1650                    .get_bgl_entry(texture_bind.group, texture_bind.binding)
1651                    .unwrap();
1652                let sampler_layout = pipeline_layout
1653                    .get_bgl_entry(sampler_bind.group, sampler_bind.binding)
1654                    .unwrap();
1655                assert!(texture_layout.visibility.contains(stage_bit));
1656                assert!(sampler_layout.visibility.contains(stage_bit));
1657
1658                let sampler_filtering = matches!(
1659                    sampler_layout.ty,
1660                    BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1661                );
1662                let texture_sample_type = match texture_layout.ty {
1663                    BindingType::Texture { sample_type, .. } => sample_type,
1664                    BindingType::ExternalTexture => {
1665                        wgt::TextureSampleType::Float { filterable: true }
1666                    }
1667                    _ => unreachable!(),
1668                };
1669
1670                let error = match (sampler_filtering, texture_sample_type) {
1671                    (true, wgt::TextureSampleType::Float { filterable: false }) => {
1672                        Some(FilteringError::Float)
1673                    }
1674                    (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1675                    (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1676                    _ => None,
1677                };
1678
1679                if let Some(error) = error {
1680                    return Err(StageError::Filtering {
1681                        texture: *texture_bind,
1682                        sampler: *sampler_bind,
1683                        error,
1684                    });
1685                }
1686            }
1687        }
1688
1689        // check workgroup size limits
1690        if shader_stage.to_naga().compute_like() {
1691            let workgroup_size_check = match shader_stage.to_naga() {
1692                naga::ShaderStage::Compute => WorkgroupSizeCheck {
1693                    dimensions: &entry_point.workgroup_size,
1694                    per_dimension_limits: &[
1695                        self.limits.max_compute_workgroup_size_x,
1696                        self.limits.max_compute_workgroup_size_y,
1697                        self.limits.max_compute_workgroup_size_z,
1698                    ],
1699                    per_dimension_limits_desc: "max_compute_workgroup_size_*",
1700
1701                    total_limit: self.limits.max_compute_invocations_per_workgroup,
1702                    total_limit_desc: "max_compute_invocations_per_workgroup",
1703                },
1704                naga::ShaderStage::Task => WorkgroupSizeCheck {
1705                    dimensions: &entry_point.workgroup_size,
1706                    per_dimension_limits: &[self.limits.max_task_invocations_per_dimension; 3],
1707                    per_dimension_limits_desc: "max_task_invocations_per_dimension",
1708
1709                    total_limit: self.limits.max_task_invocations_per_workgroup,
1710                    total_limit_desc: "max_task_invocations_per_workgroup",
1711                },
1712                naga::ShaderStage::Mesh => WorkgroupSizeCheck {
1713                    dimensions: &entry_point.workgroup_size,
1714                    per_dimension_limits: &[self.limits.max_mesh_invocations_per_dimension; 3],
1715                    per_dimension_limits_desc: "max_mesh_invocations_per_dimension",
1716
1717                    total_limit: self.limits.max_mesh_invocations_per_workgroup,
1718                    total_limit_desc: "max_mesh_invocations_per_workgroup",
1719                },
1720                _ => unreachable!(),
1721            };
1722            let total = workgroup_size_check.check_and_compute_total_invocations()?;
1723            if total == 0 {
1724                return Err(StageError::InvalidWorkgroupSize(
1725                    InvalidWorkgroupSizeError::Zero {
1726                        dimensions: entry_point.workgroup_size,
1727                    },
1728                ));
1729            }
1730        }
1731
1732        let mut this_stage_primitive_index = false;
1733        let mut has_draw_id = false;
1734        let mut has_per_vertex = false;
1735
1736        // check inputs compatibility
1737        for input in entry_point.inputs.iter() {
1738            match *input {
1739                Varying::UserDefined { location, ref iv } => {
1740                    let result = inputs
1741                        .varyings
1742                        .get(&location)
1743                        .ok_or(InputError::Missing)
1744                        .and_then(|provided| {
1745                            let (compatible, per_primitive_correct) = match shader_stage.to_naga() {
1746                                // For vertex attributes, there are defaults filled out
1747                                // by the driver if data is not provided.
1748                                naga::ShaderStage::Vertex => {
1749                                    let is_compatible =
1750                                        iv.ty.scalar.kind == provided.ty.scalar.kind;
1751                                    // vertex inputs don't count towards inter-stage
1752                                    (is_compatible, !iv.per_primitive)
1753                                }
1754                                naga::ShaderStage::Fragment => {
1755                                    if iv.interpolation != provided.interpolation {
1756                                        return Err(InputError::InterpolationMismatch(
1757                                            provided.interpolation,
1758                                        ));
1759                                    }
1760                                    if iv.sampling != provided.sampling {
1761                                        return Err(InputError::SamplingMismatch(
1762                                            provided.sampling,
1763                                        ));
1764                                    }
1765                                    (
1766                                        iv.ty.is_subtype_of(provided.ty),
1767                                        iv.per_primitive == provided.per_primitive,
1768                                    )
1769                                }
1770                                // These can't have varying inputs
1771                                naga::ShaderStage::Compute
1772                                | naga::ShaderStage::Task
1773                                | naga::ShaderStage::Mesh => (false, false),
1774                                naga::ShaderStage::RayGeneration
1775                                | naga::ShaderStage::AnyHit
1776                                | naga::ShaderStage::ClosestHit
1777                                | naga::ShaderStage::Miss => {
1778                                    unreachable!()
1779                                }
1780                            };
1781                            if !compatible {
1782                                return Err(InputError::WrongType(provided.ty));
1783                            } else if !per_primitive_correct {
1784                                return Err(InputError::WrongPerPrimitive {
1785                                    pipeline_input: provided.per_primitive,
1786                                    shader: iv.per_primitive,
1787                                });
1788                            }
1789                            Ok(())
1790                        });
1791
1792                    if let Err(error) = result {
1793                        return Err(StageError::Input {
1794                            location,
1795                            var: iv.clone(),
1796                            error,
1797                        });
1798                    }
1799                    has_per_vertex |= iv.interpolation == Some(naga::Interpolation::PerVertex);
1800                }
1801                Varying::BuiltIn(BuiltIn::PrimitiveIndex) => {
1802                    this_stage_primitive_index = true;
1803                }
1804                Varying::BuiltIn(BuiltIn::DrawIndex) => {
1805                    has_draw_id = true;
1806                }
1807                Varying::BuiltIn(_) => {}
1808            }
1809        }
1810
1811        match shader_stage {
1812            ShaderStageForValidation::Vertex {
1813                topology,
1814                compare_function,
1815            } => {
1816                let mut max_vertex_shader_output_variables =
1817                    self.limits.max_inter_stage_shader_variables;
1818                let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1;
1819
1820                let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList {
1821                    Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology)
1822                } else {
1823                    None
1824                };
1825
1826                let clip_distance_deductions = entry_point.outputs.iter().filter_map(|output| {
1827                    if let &Varying::BuiltIn(BuiltIn::ClipDistances { array_size }) = output {
1828                        Some(MaxVertexShaderOutputDeduction::ClipDistances { array_size })
1829                    } else {
1830                        None
1831                    }
1832                });
1833                debug_assert!(
1834                    clip_distance_deductions.clone().count() <= 1,
1835                    "multiple `clip_distances` outputs found"
1836                );
1837
1838                let deductions = point_list_deduction
1839                    .into_iter()
1840                    .chain(clip_distance_deductions);
1841
1842                for deduction in deductions.clone() {
1843                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1844                    // ever exceed the minimum variables available.
1845                    max_vertex_shader_output_variables = max_vertex_shader_output_variables
1846                        .checked_sub(deduction.for_variables())
1847                        .unwrap();
1848                    max_vertex_shader_output_location = max_vertex_shader_output_location
1849                        .checked_sub(deduction.for_location())
1850                        .unwrap();
1851                }
1852
1853                let mut num_user_defined_outputs = 0;
1854
1855                for output in entry_point.outputs.iter() {
1856                    match *output {
1857                        Varying::UserDefined { ref iv, location } => {
1858                            if location > max_vertex_shader_output_location {
1859                                return Err(StageError::VertexOutputLocationTooLarge {
1860                                    location,
1861                                    var: iv.clone(),
1862                                    limit: self.limits.max_inter_stage_shader_variables,
1863                                    deductions: deductions.collect(),
1864                                });
1865                            }
1866                            num_user_defined_outputs += 1;
1867                        }
1868                        Varying::BuiltIn(_) => {}
1869                    };
1870
1871                    if let Some(
1872                        cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1873                    ) = compare_function
1874                    {
1875                        if let Varying::BuiltIn(BuiltIn::Position { invariant: false }) = *output {
1876                            log::warn!(
1877                                concat!(
1878                                    "Vertex shader with entry point {} outputs a ",
1879                                    "@builtin(position) without the @invariant attribute and ",
1880                                    "is used in a pipeline with {cmp:?}. On some machines, ",
1881                                    "this can cause bad artifacting as {cmp:?} assumes the ",
1882                                    "values output from the vertex shader exactly match the ",
1883                                    "value in the depth buffer. The @invariant attribute on the ",
1884                                    "@builtin(position) vertex output ensures that the exact ",
1885                                    "same pixel depths are used every render."
1886                                ),
1887                                entry_point_name,
1888                                cmp = cmp
1889                            );
1890                        }
1891                    }
1892                }
1893
1894                if num_user_defined_outputs > max_vertex_shader_output_variables {
1895                    return Err(StageError::TooManyUserDefinedVertexOutputs {
1896                        num_found: num_user_defined_outputs,
1897                        limit: self.limits.max_inter_stage_shader_variables,
1898                        deductions: deductions.collect(),
1899                    });
1900                }
1901            }
1902            ShaderStageForValidation::Fragment {
1903                dual_source_blending,
1904                has_depth_attachment,
1905            } => {
1906                let mut max_fragment_shader_input_variables =
1907                    self.limits.max_inter_stage_shader_variables;
1908
1909                let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1910                    Varying::UserDefined { .. } => None,
1911                    Varying::BuiltIn(builtin) => {
1912                        MaxFragmentShaderInputDeduction::from_inter_stage_builtin(builtin.to_naga())
1913                            .or_else(|| {
1914                                unreachable!(
1915                                    concat!(
1916                                        "unexpected built-in provided; ",
1917                                        "{:?} is not used for fragment stage input",
1918                                    ),
1919                                    builtin
1920                                )
1921                            })
1922                    }
1923                });
1924
1925                for deduction in deductions.clone() {
1926                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1927                    // ever exceed the minimum variables available.
1928                    max_fragment_shader_input_variables = max_fragment_shader_input_variables
1929                        .checked_sub(deduction.for_variables())
1930                        .unwrap();
1931                }
1932
1933                let mut num_user_defined_inputs = 0;
1934
1935                for output in entry_point.inputs.iter() {
1936                    match *output {
1937                        Varying::UserDefined { ref iv, location } => {
1938                            if location >= self.limits.max_inter_stage_shader_variables {
1939                                return Err(StageError::FragmentInputLocationTooLarge {
1940                                    location,
1941                                    var: iv.clone(),
1942                                    limit: self.limits.max_inter_stage_shader_variables,
1943                                    deductions: deductions.collect(),
1944                                });
1945                            }
1946                            num_user_defined_inputs += 1;
1947                        }
1948                        Varying::BuiltIn(_) => {}
1949                    };
1950                }
1951
1952                if num_user_defined_inputs > max_fragment_shader_input_variables {
1953                    return Err(StageError::TooManyUserDefinedFragmentInputs {
1954                        num_found: num_user_defined_inputs,
1955                        limit: self.limits.max_inter_stage_shader_variables,
1956                        deductions: deductions.collect(),
1957                    });
1958                }
1959
1960                for output in &entry_point.outputs {
1961                    let &Varying::UserDefined { location, ref iv } = output else {
1962                        continue;
1963                    };
1964                    if location >= self.limits.max_color_attachments {
1965                        return Err(StageError::ColorAttachmentLocationTooLarge {
1966                            location,
1967                            var: iv.clone(),
1968                            limit: self.limits.max_color_attachments,
1969                        });
1970                    }
1971                }
1972
1973                // If the pipeline uses dual-source blending, then the shader
1974                // must configure appropriate I/O, but it is not an error to
1975                // use a shader that defines the I/O in a pipeline that only
1976                // uses one blend source.
1977                if dual_source_blending && !entry_point.dual_source_blending {
1978                    return Err(StageError::InvalidDualSourceBlending);
1979                }
1980
1981                if entry_point
1982                    .outputs
1983                    .contains(&Varying::BuiltIn(BuiltIn::FragDepth))
1984                    && !has_depth_attachment
1985                {
1986                    return Err(StageError::MissingFragDepthAttachment);
1987                }
1988            }
1989            ShaderStageForValidation::Mesh => {
1990                for output in &entry_point.outputs {
1991                    if matches!(output, Varying::BuiltIn(BuiltIn::PrimitiveIndex)) {
1992                        this_stage_primitive_index = true;
1993                    }
1994                }
1995            }
1996            _ => (),
1997        }
1998
1999        if let Some(ref mesh_info) = entry_point.mesh_info {
2000            if mesh_info.max_vertices > self.limits.max_mesh_output_vertices {
2001                return Err(StageError::TooManyMeshVertices {
2002                    limit: self.limits.max_mesh_output_vertices,
2003                    value: mesh_info.max_vertices,
2004                });
2005            }
2006            if mesh_info.max_primitives > self.limits.max_mesh_output_primitives {
2007                return Err(StageError::TooManyMeshPrimitives {
2008                    limit: self.limits.max_mesh_output_primitives,
2009                    value: mesh_info.max_primitives,
2010                });
2011            }
2012            if primitive_topology != Some(mesh_info.primitive_topology) {
2013                return Err(StageError::MeshTopologyMismatch);
2014            }
2015        }
2016        if let Some(task_payload_size) = entry_point.task_payload_size {
2017            if task_payload_size > self.limits.max_task_payload_size {
2018                return Err(StageError::TaskPayloadTooLarge {
2019                    limit: self.limits.max_task_payload_size,
2020                    value: task_payload_size,
2021                });
2022            }
2023        }
2024        if shader_stage.to_naga() == naga::ShaderStage::Mesh
2025            && entry_point.task_payload_size != inputs.task_payload_size
2026        {
2027            return Err(StageError::TaskPayloadMustMatch {
2028                input: inputs.task_payload_size,
2029                shader: entry_point.task_payload_size,
2030            });
2031        }
2032
2033        // Fragment shader primitive index is treated like a varying
2034        if shader_stage.to_naga() == naga::ShaderStage::Fragment
2035            && this_stage_primitive_index
2036            && inputs.primitive_index == Some(false)
2037        {
2038            return Err(StageError::InvalidPrimitiveIndex);
2039        } else if shader_stage.to_naga() == naga::ShaderStage::Fragment
2040            && !this_stage_primitive_index
2041            && inputs.primitive_index == Some(true)
2042        {
2043            return Err(StageError::MissingPrimitiveIndex);
2044        }
2045        if shader_stage.to_naga() == naga::ShaderStage::Mesh
2046            && inputs.task_payload_size.is_some()
2047            && has_draw_id
2048        {
2049            return Err(StageError::DrawIdError);
2050        }
2051
2052        if primitive_topology.is_none_or(|e| !e.is_triangles()) && has_per_vertex {
2053            return Err(StageError::PerVertexNotTriangles);
2054        }
2055
2056        let outputs = entry_point
2057            .outputs
2058            .iter()
2059            .filter_map(|output| match *output {
2060                Varying::UserDefined { location, ref iv } => Some((location, iv.clone())),
2061                Varying::BuiltIn(_) => None,
2062            })
2063            .collect();
2064
2065        let immediate_usage = self
2066            .immediate_usage(shader_stage.to_naga(), entry_point_name)
2067            .merge(&inputs.immediates);
2068
2069        // Check pipeline layout immediate size
2070        if let BindingLayoutSource::Provided(pipeline_layout) = layouts {
2071            if pipeline_layout.immediate_size < immediate_usage.size() {
2072                return Err(StageError::LayoutImmediateSize {
2073                    layout: pipeline_layout.immediate_size,
2074                    required: immediate_usage.size(),
2075                });
2076            }
2077        }
2078
2079        Ok(StageIo {
2080            task_payload_size: entry_point.task_payload_size,
2081            varyings: outputs,
2082            primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh {
2083                Some(this_stage_primitive_index)
2084            } else {
2085                None
2086            },
2087            immediates: immediate_usage,
2088        })
2089    }
2090}
2091
2092pub fn check_color_attachment_count(
2093    num_attachments: usize,
2094    limit: u32,
2095) -> Result<(), ColorAttachmentError> {
2096    let limit = usize::try_from(limit).unwrap();
2097    if num_attachments > limit {
2098        return Err(ColorAttachmentError::TooMany {
2099            given: num_attachments,
2100            limit,
2101        });
2102    }
2103
2104    Ok(())
2105}
2106
2107/// Validate a list of color attachment formats against `maxColorAttachmentBytesPerSample`.
2108///
2109/// The color attachments can be from a render pass descriptor or a pipeline descriptor.
2110///
2111/// Implements <https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample>.
2112pub fn validate_color_attachment_bytes_per_sample(
2113    attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>,
2114    limit: u32,
2115) -> Result<(), ColorAttachmentError> {
2116    let mut total_bytes_per_sample: u32 = 0;
2117    for format in attachment_formats {
2118        let byte_cost = format.target_pixel_byte_cost().unwrap();
2119        let alignment = format.target_component_alignment().unwrap();
2120
2121        total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
2122        total_bytes_per_sample += byte_cost;
2123    }
2124
2125    if total_bytes_per_sample > limit {
2126        return Err(ColorAttachmentError::TooManyBytesPerSample {
2127            total: total_bytes_per_sample,
2128            limit,
2129        });
2130    }
2131
2132    Ok(())
2133}
2134
2135#[derive(Clone, Debug, Error)]
2136pub enum InvalidWorkgroupSizeError {
2137    #[error(
2138        "Workgroup size {dimensions:?} ({total} total invocations) must be less or equal to \
2139        the per-dimension limit `Limits::{per_dimension_limits_desc}` of {per_dimension_limits:?} \
2140        and the total invocation limit `Limits::{total_limit_desc}` of {total_limit}"
2141    )]
2142    LimitExceeded {
2143        dimensions: [u32; 3],
2144        per_dimension_limits: [u32; 3],
2145        per_dimension_limits_desc: &'static str,
2146        total: u32,
2147        total_limit: u32,
2148        total_limit_desc: &'static str,
2149    },
2150    #[error("Workgroup sizes {dimensions:?} must be positive")]
2151    Zero { dimensions: [u32; 3] },
2152}
2153
2154/// A helper type for avoiding argument order mistakes when calling
2155/// [`Self::check_and_compute_total_invocations`].
2156#[derive(Clone, Debug)]
2157pub(crate) struct WorkgroupSizeCheck<'a> {
2158    pub dimensions: &'a [u32; 3],
2159    pub per_dimension_limits: &'a [u32; 3],
2160    pub per_dimension_limits_desc: &'static str,
2161    pub total_limit: u32,
2162    pub total_limit_desc: &'static str,
2163}
2164
2165impl WorkgroupSizeCheck<'_> {
2166    /// Check X/Y/Z workgroup sizes against per-dimension and overall limits.
2167    ///
2168    /// This function does not check that the sizes are non-zero. In a dispatch, it is legal for
2169    /// the size to be zero. In shader or pipeline creation, it is an error for the size to be
2170    /// zero, and the caller must check that.
2171    pub(crate) fn check_and_compute_total_invocations(
2172        self,
2173    ) -> Result<u32, InvalidWorkgroupSizeError> {
2174        let Self {
2175            dimensions,
2176            per_dimension_limits,
2177            per_dimension_limits_desc,
2178            total_limit,
2179            total_limit_desc,
2180        } = self;
2181
2182        let total = dimensions
2183            .iter()
2184            .fold(1u32, |total, &dim| total.saturating_mul(dim));
2185
2186        let invalid_total_invocations = total > total_limit;
2187
2188        let dimension_too_large = dimensions
2189            .iter()
2190            .zip(per_dimension_limits.iter())
2191            .any(|(dim, limit)| dim > limit);
2192
2193        if invalid_total_invocations || dimension_too_large {
2194            Err(InvalidWorkgroupSizeError::LimitExceeded {
2195                dimensions: *dimensions,
2196                per_dimension_limits: *per_dimension_limits,
2197                per_dimension_limits_desc,
2198                total,
2199                total_limit,
2200                total_limit_desc,
2201            })
2202        } else {
2203            Ok(total)
2204        }
2205    }
2206}
2207
2208pub enum ShaderStageForValidation {
2209    Vertex {
2210        topology: wgt::PrimitiveTopology,
2211        compare_function: Option<wgt::CompareFunction>,
2212    },
2213    Mesh,
2214    Fragment {
2215        dual_source_blending: bool,
2216        has_depth_attachment: bool,
2217    },
2218    Compute,
2219    Task,
2220}
2221
2222impl ShaderStageForValidation {
2223    pub fn to_naga(&self) -> naga::ShaderStage {
2224        match self {
2225            Self::Vertex { .. } => naga::ShaderStage::Vertex,
2226            Self::Mesh => naga::ShaderStage::Mesh,
2227            Self::Fragment { .. } => naga::ShaderStage::Fragment,
2228            Self::Compute => naga::ShaderStage::Compute,
2229            Self::Task => naga::ShaderStage::Task,
2230        }
2231    }
2232
2233    pub fn to_wgt_bit(&self) -> wgt::ShaderStages {
2234        match self {
2235            Self::Vertex { .. } => wgt::ShaderStages::VERTEX,
2236            Self::Mesh => wgt::ShaderStages::MESH,
2237            Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT,
2238            Self::Compute => wgt::ShaderStages::COMPUTE,
2239            Self::Task => wgt::ShaderStages::TASK,
2240        }
2241    }
2242}