Skip to main content

wgpu_core/
validation.rs

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