Skip to main content

naga/valid/
interface.rs

1use alloc::vec::Vec;
2
3use bit_set::BitSet;
4
5use super::{
6    analyzer::{FunctionInfo, GlobalUse},
7    Capabilities, Disalignment, FunctionError, ImmediateError, ModuleInfo,
8};
9use crate::arena::{Handle, UniqueArena};
10use crate::span::{AddSpan as _, MapErrWithSpan as _, SpanProvider as _, WithSpan};
11
12const MAX_WORKGROUP_SIZE: u32 = 0x4000;
13
14#[derive(Clone, Debug, thiserror::Error)]
15#[cfg_attr(test, derive(PartialEq))]
16pub enum GlobalVariableError {
17    #[error("Usage isn't compatible with address space {0:?}")]
18    InvalidUsage(crate::AddressSpace),
19    #[error("Type isn't compatible with address space {0:?}")]
20    InvalidType(crate::AddressSpace),
21    #[error("Type {0:?} isn't compatible with binding arrays")]
22    InvalidBindingArray(Handle<crate::Type>),
23    #[error("Type flags {seen:?} do not meet the required {required:?}")]
24    MissingTypeFlags {
25        required: super::TypeFlags,
26        seen: super::TypeFlags,
27    },
28    #[error("Capability {0:?} is not supported")]
29    UnsupportedCapability(Capabilities),
30    #[error("Binding decoration is missing or not applicable")]
31    InvalidBinding,
32    #[error("Alignment requirements for address space {0:?} are not met by {1:?}")]
33    Alignment(
34        crate::AddressSpace,
35        Handle<crate::Type>,
36        #[source] Disalignment,
37    ),
38    #[error("Initializer must be an override-expression")]
39    InitializerExprType,
40    #[error("Initializer doesn't match the variable type")]
41    InitializerType,
42    #[error("Initializer can't be used with address space {0:?}")]
43    InitializerNotAllowed(crate::AddressSpace),
44    #[error("Storage address space doesn't support write-only access")]
45    StorageAddressSpaceWriteOnlyNotSupported,
46    #[error("Type is not valid for use as a immediate data")]
47    InvalidImmediateType(#[source] ImmediateError),
48    #[error("Task payload must not be zero-sized")]
49    ZeroSizedTaskPayload,
50    #[error("Memory decorations (`@coherent`, `@volatile`) are only valid for variables in the `storage` address space")]
51    InvalidMemoryDecorationsAddressSpace,
52    #[error("`@coherent` requires the MEMORY_DECORATION_COHERENT capability")]
53    CoherentNotSupported,
54    #[error("`@volatile` requires the MEMORY_DECORATION_VOLATILE capability")]
55    VolatileNotSupported,
56}
57
58#[derive(Clone, Debug, thiserror::Error)]
59#[cfg_attr(test, derive(PartialEq))]
60pub enum VaryingError {
61    #[error("The type {0:?} does not match the varying")]
62    InvalidType(Handle<crate::Type>),
63    #[error(
64        "The type {0:?} cannot be used for user-defined entry point inputs or outputs. \
65        Only numeric scalars and vectors are allowed."
66    )]
67    NotIOShareableType(Handle<crate::Type>),
68    #[error("Interpolation {0:?} is only valid for stage {1:?}")]
69    InvalidInterpolationInStage(crate::Interpolation, crate::ShaderStage),
70    #[error("Cannot combine {interpolation:?} interpolation with the {sampling:?} sample type")]
71    InvalidInterpolationSamplingCombination {
72        interpolation: crate::Interpolation,
73        sampling: crate::Sampling,
74    },
75    #[error("`@interpolate(flat)` must be explicitly specified for integer I/O")]
76    InvalidInterpolationForInteger,
77    #[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")]
78    MissingInterpolation,
79    #[error("Built-in {0:?} is not available at this stage")]
80    InvalidBuiltInStage(crate::BuiltIn),
81    #[error("Built-in type for {0:?} is invalid. Found {1:?}")]
82    InvalidBuiltInType(crate::BuiltIn, crate::TypeInner),
83    #[error("Entry point arguments and return values must all have bindings")]
84    MissingBinding,
85    #[error("Struct member {0} is missing a binding")]
86    MemberMissingBinding(u32),
87    #[error("Multiple bindings at location {location} are present")]
88    BindingCollision { location: u32 },
89    #[error("Multiple bindings use the same `blend_src` {blend_src}")]
90    BindingCollisionBlendSrc { blend_src: u32 },
91    #[error("Built-in {0:?} is present more than once")]
92    DuplicateBuiltIn(crate::BuiltIn),
93    #[error("Capability {0:?} is not supported")]
94    UnsupportedCapability(Capabilities),
95    #[error("The attribute {0:?} is only valid as an output for stage {1:?}")]
96    InvalidInputAttributeInStage(&'static str, crate::ShaderStage),
97    #[error("The attribute {0:?} is not valid for stage {1:?}")]
98    InvalidAttributeInStage(&'static str, crate::ShaderStage),
99    #[error("`@blend_src` can only be used at location 0, indices 0 and 1. Found `@location({location}) @blend_src({blend_src})`.")]
100    InvalidBlendSrcIndex { location: u32, blend_src: u32 },
101    #[error(
102        "`@blend_src` structure must specify two sources. \
103        Found `@blend_src({present_blend_src})` but not `@blend_src({absent_blend_src})`.",
104        absent_blend_src = if *present_blend_src == 0 { 1 } else { 0 },
105    )]
106    IncompleteBlendSrcUsage { present_blend_src: u32 },
107    #[error("Structure using `@blend_src` may not specify `@location` on any other members. Found a binding at `@location({location})`.")]
108    InvalidBlendSrcWithOtherBindings { location: u32 },
109    #[error("Both `@blend_src` structure members must have the same type. `blend_src(0)` has type {blend_src_0_type:?} and `blend_src(1)` has type {blend_src_1_type:?}.")]
110    BlendSrcOutputTypeMismatch {
111        blend_src_0_type: Handle<crate::Type>,
112        blend_src_1_type: Handle<crate::Type>,
113    },
114    #[error("`@blend_src` can only be used on struct members, not directly on entry point I/O")]
115    BlendSrcNotOnStructMember,
116    #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")]
117    InvalidMultiDimensionalSubgroupBuiltIn,
118    #[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")]
119    InvalidPerPrimitive,
120    #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")]
121    MissingPerPrimitive,
122    #[error("Per vertex fragment inputs must be an array of length 3.")]
123    PerVertexNotArrayOfThree,
124    #[error("Per vertex can only have Center sampling or no sampling modifier")]
125    InvalidPerVertexSampling,
126}
127
128#[derive(Clone, Debug, thiserror::Error)]
129#[cfg_attr(test, derive(PartialEq))]
130pub enum EntryPointError {
131    #[error("Multiple conflicting entry points")]
132    Conflict,
133    #[error("Vertex shaders must return a `@builtin(position)` output value")]
134    MissingVertexOutputPosition,
135    #[error("Early depth test is not applicable")]
136    UnexpectedEarlyDepthTest,
137    #[error("Workgroup size is not applicable")]
138    UnexpectedWorkgroupSize,
139    #[error("Workgroup size is out of range")]
140    OutOfRangeWorkgroupSize,
141    #[error("Uses operations forbidden at this stage")]
142    ForbiddenStageOperations,
143    #[error("Global variable {0:?} is used incorrectly as {1:?}")]
144    InvalidGlobalUsage(Handle<crate::GlobalVariable>, GlobalUse),
145    #[error("More than 1 immediate data variable is used")]
146    MoreThanOneImmediateUsed,
147    #[error("Bindings for {0:?} conflict with other resource")]
148    BindingCollision(Handle<crate::GlobalVariable>),
149    #[error("Argument {0} varying error")]
150    Argument(u32, #[source] VaryingError),
151    #[error(transparent)]
152    Result(#[from] VaryingError),
153    #[error(transparent)]
154    Function(#[from] FunctionError),
155    #[error("Capability {0:?} is not supported")]
156    UnsupportedCapability(Capabilities),
157
158    #[error("mesh shader entry point missing mesh shader attributes")]
159    ExpectedMeshShaderAttributes,
160    #[error("Non mesh shader entry point cannot have mesh shader attributes")]
161    UnexpectedMeshShaderAttributes,
162    #[error("Non mesh/task shader entry point cannot have task payload attribute")]
163    UnexpectedTaskPayload,
164    #[error("Task payload must be declared with `var<task_payload>`")]
165    TaskPayloadWrongAddressSpace,
166    #[error("For a task payload to be used, it must be declared with @payload")]
167    WrongTaskPayloadUsed,
168    #[error("Task shader entry point must return @builtin(mesh_task_size) vec3<u32>")]
169    WrongTaskShaderEntryResult,
170    #[error("Task shaders must declare a task payload output")]
171    ExpectedTaskPayload,
172    #[error(
173        "Mesh shader output variable must be a struct with fields that are all allowed builtins"
174    )]
175    BadMeshOutputVariableType,
176    #[error("Mesh shader output variable fields must have types that are in accordance with the mesh shader spec")]
177    BadMeshOutputVariableField,
178    #[error("Mesh shader entry point cannot have a return type")]
179    UnexpectedMeshShaderEntryResult,
180    #[error(
181        "Mesh output type must be a user-defined struct with fields in alignment with the mesh shader spec"
182    )]
183    InvalidMeshOutputType,
184    #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")]
185    InvalidMeshPrimitiveOutputType,
186    #[error("Mesh output global variable must live in the workgroup address space")]
187    WrongMeshOutputAddressSpace,
188    #[error("Task payload must be at least 4 bytes, but is {0} bytes")]
189    TaskPayloadTooSmall(u32),
190    #[error("Only the `ray_generation`, `closest_hit`, and `any_hit` shader stages can access a global variable in the `ray_payload` address space")]
191    RayPayloadInInvalidStage(crate::ShaderStage),
192    #[error("Only the `closest_hit`, `any_hit`, and `miss` shader stages can access a global variable in the `incoming_ray_payload` address space")]
193    IncomingRayPayloadInInvalidStage(crate::ShaderStage),
194    #[error("Compute shader entry point cannot have a return type")]
195    UnexpectedComputeShaderEntryResult,
196}
197
198fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
199    let mut storage_usage = GlobalUse::QUERY;
200    if access.contains(crate::StorageAccess::LOAD) {
201        storage_usage |= GlobalUse::READ;
202    }
203    if access.contains(crate::StorageAccess::STORE) {
204        storage_usage |= GlobalUse::WRITE;
205    }
206    if access.contains(crate::StorageAccess::ATOMIC) {
207        storage_usage |= GlobalUse::ATOMIC;
208    }
209    storage_usage
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213enum MeshOutputType {
214    None,
215    VertexOutput,
216    PrimitiveOutput,
217}
218
219struct VaryingContext<'a> {
220    stage: crate::ShaderStage,
221    output: bool,
222    types: &'a UniqueArena<crate::Type>,
223    type_info: &'a Vec<super::r#type::TypeInfo>,
224    location_mask: &'a mut BitSet,
225    dual_source_blending: Option<&'a mut bool>,
226    built_ins: &'a mut crate::FastHashSet<crate::BuiltIn>,
227    capabilities: Capabilities,
228    flags: super::ValidationFlags,
229    mesh_output_type: MeshOutputType,
230    has_task_payload: bool,
231}
232
233impl VaryingContext<'_> {
234    fn validate_impl(
235        &mut self,
236        ep: &crate::EntryPoint,
237        ty: Handle<crate::Type>,
238        binding: &crate::Binding,
239    ) -> Result<(), VaryingError> {
240        use crate::{BuiltIn as Bi, ShaderStage as St, TypeInner as Ti, VectorSize as Vs};
241
242        let ty_inner = &self.types[ty].inner;
243        match *binding {
244            crate::Binding::BuiltIn(built_in) => {
245                // Ignore the `invariant` field for the sake of duplicate checks,
246                // but use the original in error messages.
247                let canonical = match built_in {
248                    crate::BuiltIn::Position { .. } => {
249                        crate::BuiltIn::Position { invariant: false }
250                    }
251                    crate::BuiltIn::Barycentric { .. } => {
252                        crate::BuiltIn::Barycentric { perspective: false }
253                    }
254                    x => x,
255                };
256
257                if self.built_ins.contains(&canonical) {
258                    return Err(VaryingError::DuplicateBuiltIn(built_in));
259                }
260                self.built_ins.insert(canonical);
261
262                let required = match built_in {
263                    Bi::ClipDistances => Capabilities::CLIP_DISTANCES,
264                    Bi::CullDistance => Capabilities::CULL_DISTANCE,
265                    // Primitive index is allowed w/o any other extensions in any- and closest-hit shaders
266                    Bi::PrimitiveIndex if !matches!(ep.stage, St::AnyHit | St::ClosestHit) => {
267                        Capabilities::PRIMITIVE_INDEX
268                    }
269                    Bi::Barycentric { .. } => Capabilities::SHADER_BARYCENTRICS,
270                    Bi::ViewIndex => Capabilities::MULTIVIEW,
271                    Bi::SampleIndex => Capabilities::MULTISAMPLED_SHADING,
272                    Bi::NumSubgroups
273                    | Bi::SubgroupId
274                    | Bi::SubgroupSize
275                    | Bi::SubgroupInvocationId => Capabilities::SUBGROUP,
276                    Bi::DrawIndex => Capabilities::DRAW_INDEX,
277                    _ => Capabilities::empty(),
278                };
279                if !self.capabilities.contains(required) {
280                    return Err(VaryingError::UnsupportedCapability(required));
281                }
282
283                if matches!(
284                    built_in,
285                    crate::BuiltIn::SubgroupId | crate::BuiltIn::SubgroupInvocationId
286                ) && ep.workgroup_size[1..].iter().any(|&s| s > 1)
287                {
288                    return Err(VaryingError::InvalidMultiDimensionalSubgroupBuiltIn);
289                }
290
291                let (visible, type_good) = match built_in {
292                    Bi::BaseInstance | Bi::BaseVertex | Bi::VertexIndex => (
293                        self.stage == St::Vertex && !self.output,
294                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
295                    ),
296                    Bi::InstanceIndex => (
297                        matches!(self.stage, St::Vertex | St::AnyHit | St::ClosestHit)
298                            && !self.output,
299                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
300                    ),
301                    Bi::DrawIndex => (
302                        // Always allowed in task/vertex stage. Allowed in mesh stage if there is no task stage in the pipeline.
303                        (self.stage == St::Vertex
304                            || self.stage == St::Task
305                            || (self.stage == St::Mesh && !self.has_task_payload))
306                            && !self.output,
307                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
308                    ),
309                    Bi::ClipDistances | Bi::CullDistance => (
310                        (self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
311                        match *ty_inner {
312                            Ti::Array { base, size, .. } => {
313                                self.types[base].inner == Ti::Scalar(crate::Scalar::F32)
314                                    && match size {
315                                        crate::ArraySize::Constant(non_zero) => non_zero.get() <= 8,
316                                        _ => false,
317                                    }
318                            }
319                            _ => false,
320                        },
321                    ),
322                    Bi::PointSize => (
323                        (self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
324                        *ty_inner == Ti::Scalar(crate::Scalar::F32),
325                    ),
326                    Bi::PointCoord => (
327                        self.stage == St::Fragment && !self.output,
328                        *ty_inner
329                            == Ti::Vector {
330                                size: Vs::Bi,
331                                scalar: crate::Scalar::F32,
332                            },
333                    ),
334                    Bi::Position { .. } => (
335                        match self.stage {
336                            St::Vertex | St::Mesh => self.output,
337                            St::Fragment => !self.output,
338                            St::Compute | St::Task => false,
339                            St::RayGeneration | St::AnyHit | St::ClosestHit | St::Miss => false,
340                        },
341                        *ty_inner
342                            == Ti::Vector {
343                                size: Vs::Quad,
344                                scalar: crate::Scalar::F32,
345                            },
346                    ),
347                    Bi::ViewIndex => (
348                        match self.stage {
349                            St::Vertex | St::Fragment | St::Task | St::Mesh => !self.output,
350                            St::Compute
351                            | St::RayGeneration
352                            | St::AnyHit
353                            | St::ClosestHit
354                            | St::Miss => false,
355                        },
356                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
357                    ),
358                    Bi::FragDepth => (
359                        self.stage == St::Fragment && self.output,
360                        *ty_inner == Ti::Scalar(crate::Scalar::F32),
361                    ),
362                    Bi::FrontFacing => (
363                        self.stage == St::Fragment && !self.output,
364                        *ty_inner == Ti::Scalar(crate::Scalar::BOOL),
365                    ),
366                    Bi::PrimitiveIndex => (
367                        (matches!(self.stage, St::Fragment | St::AnyHit | St::ClosestHit)
368                            && !self.output)
369                            || (self.stage == St::Mesh
370                                && self.output
371                                && self.mesh_output_type == MeshOutputType::PrimitiveOutput),
372                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
373                    ),
374                    Bi::Barycentric { .. } => (
375                        self.stage == St::Fragment && !self.output,
376                        *ty_inner
377                            == Ti::Vector {
378                                size: Vs::Tri,
379                                scalar: crate::Scalar::F32,
380                            },
381                    ),
382                    Bi::SampleIndex => (
383                        self.stage == St::Fragment && !self.output,
384                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
385                    ),
386                    Bi::SampleMask => (
387                        self.stage == St::Fragment,
388                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
389                    ),
390                    Bi::LocalInvocationIndex => (
391                        self.stage.compute_like() && !self.output,
392                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
393                    ),
394                    Bi::GlobalInvocationId
395                    | Bi::LocalInvocationId
396                    | Bi::WorkGroupId
397                    | Bi::WorkGroupSize
398                    | Bi::NumWorkGroups => (
399                        self.stage.compute_like() && !self.output,
400                        *ty_inner
401                            == Ti::Vector {
402                                size: Vs::Tri,
403                                scalar: crate::Scalar::U32,
404                            },
405                    ),
406                    Bi::NumSubgroups | Bi::SubgroupId => (
407                        self.stage.compute_like() && !self.output,
408                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
409                    ),
410                    Bi::SubgroupSize | Bi::SubgroupInvocationId => (
411                        match self.stage {
412                            St::Compute
413                            | St::Fragment
414                            | St::Task
415                            | St::Mesh
416                            | St::RayGeneration
417                            | St::AnyHit
418                            | St::ClosestHit
419                            | St::Miss => !self.output,
420                            St::Vertex => false,
421                        },
422                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
423                    ),
424                    Bi::CullPrimitive => (
425                        self.mesh_output_type == MeshOutputType::PrimitiveOutput,
426                        *ty_inner == Ti::Scalar(crate::Scalar::BOOL),
427                    ),
428                    Bi::PointIndex => (
429                        self.mesh_output_type == MeshOutputType::PrimitiveOutput,
430                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
431                    ),
432                    Bi::LineIndices => (
433                        self.mesh_output_type == MeshOutputType::PrimitiveOutput,
434                        *ty_inner
435                            == Ti::Vector {
436                                size: Vs::Bi,
437                                scalar: crate::Scalar::U32,
438                            },
439                    ),
440                    Bi::TriangleIndices => (
441                        self.mesh_output_type == MeshOutputType::PrimitiveOutput,
442                        *ty_inner
443                            == Ti::Vector {
444                                size: Vs::Tri,
445                                scalar: crate::Scalar::U32,
446                            },
447                    ),
448                    Bi::MeshTaskSize => (
449                        self.stage == St::Task && self.output,
450                        *ty_inner
451                            == Ti::Vector {
452                                size: Vs::Tri,
453                                scalar: crate::Scalar::U32,
454                            },
455                    ),
456                    Bi::RayInvocationId => (
457                        match self.stage {
458                            St::Vertex | St::Fragment | St::Compute | St::Mesh | St::Task => false,
459                            St::RayGeneration | St::AnyHit | St::ClosestHit | St::Miss => true,
460                        },
461                        *ty_inner
462                            == Ti::Vector {
463                                size: Vs::Tri,
464                                scalar: crate::Scalar::U32,
465                            },
466                    ),
467                    Bi::NumRayInvocations => (
468                        match self.stage {
469                            St::Vertex | St::Fragment | St::Compute | St::Mesh | St::Task => false,
470                            St::RayGeneration | St::AnyHit | St::ClosestHit | St::Miss => true,
471                        },
472                        *ty_inner
473                            == Ti::Vector {
474                                size: Vs::Tri,
475                                scalar: crate::Scalar::U32,
476                            },
477                    ),
478                    Bi::InstanceCustomData => (
479                        match self.stage {
480                            St::RayGeneration
481                            | St::Miss
482                            | St::Vertex
483                            | St::Fragment
484                            | St::Compute
485                            | St::Mesh
486                            | St::Task => false,
487                            St::AnyHit | St::ClosestHit => true,
488                        },
489                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
490                    ),
491                    Bi::GeometryIndex => (
492                        match self.stage {
493                            St::RayGeneration
494                            | St::Miss
495                            | St::Vertex
496                            | St::Fragment
497                            | St::Compute
498                            | St::Mesh
499                            | St::Task => false,
500                            St::AnyHit | St::ClosestHit => true,
501                        },
502                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
503                    ),
504                    Bi::WorldRayOrigin => (
505                        match self.stage {
506                            St::RayGeneration
507                            | St::Vertex
508                            | St::Fragment
509                            | St::Compute
510                            | St::Mesh
511                            | St::Task => false,
512                            St::AnyHit | St::ClosestHit | St::Miss => true,
513                        },
514                        *ty_inner
515                            == Ti::Vector {
516                                size: Vs::Tri,
517                                scalar: crate::Scalar::F32,
518                            },
519                    ),
520                    Bi::WorldRayDirection => (
521                        match self.stage {
522                            St::RayGeneration
523                            | St::Vertex
524                            | St::Fragment
525                            | St::Compute
526                            | St::Mesh
527                            | St::Task => false,
528                            St::AnyHit | St::ClosestHit | St::Miss => true,
529                        },
530                        *ty_inner
531                            == Ti::Vector {
532                                size: Vs::Tri,
533                                scalar: crate::Scalar::F32,
534                            },
535                    ),
536                    Bi::ObjectRayOrigin => (
537                        match self.stage {
538                            St::RayGeneration
539                            | St::Miss
540                            | St::Vertex
541                            | St::Fragment
542                            | St::Compute
543                            | St::Mesh
544                            | St::Task => false,
545                            St::AnyHit | St::ClosestHit => true,
546                        },
547                        *ty_inner
548                            == Ti::Vector {
549                                size: Vs::Tri,
550                                scalar: crate::Scalar::F32,
551                            },
552                    ),
553                    Bi::ObjectRayDirection => (
554                        match self.stage {
555                            St::RayGeneration
556                            | St::Miss
557                            | St::Vertex
558                            | St::Fragment
559                            | St::Compute
560                            | St::Mesh
561                            | St::Task => false,
562                            St::AnyHit | St::ClosestHit => true,
563                        },
564                        *ty_inner
565                            == Ti::Vector {
566                                size: Vs::Tri,
567                                scalar: crate::Scalar::F32,
568                            },
569                    ),
570                    Bi::RayTmin => (
571                        match self.stage {
572                            St::RayGeneration
573                            | St::Vertex
574                            | St::Fragment
575                            | St::Compute
576                            | St::Mesh
577                            | St::Task => false,
578                            St::AnyHit | St::ClosestHit | St::Miss => true,
579                        },
580                        *ty_inner == Ti::Scalar(crate::Scalar::F32),
581                    ),
582                    Bi::RayTCurrentMax => (
583                        match self.stage {
584                            St::RayGeneration
585                            | St::Vertex
586                            | St::Fragment
587                            | St::Compute
588                            | St::Mesh
589                            | St::Task => false,
590                            St::AnyHit | St::ClosestHit | St::Miss => true,
591                        },
592                        *ty_inner == Ti::Scalar(crate::Scalar::F32),
593                    ),
594                    Bi::ObjectToWorld => (
595                        match self.stage {
596                            St::RayGeneration
597                            | St::Miss
598                            | St::Vertex
599                            | St::Fragment
600                            | St::Compute
601                            | St::Mesh
602                            | St::Task => false,
603                            St::AnyHit | St::ClosestHit => true,
604                        },
605                        *ty_inner
606                            == Ti::Matrix {
607                                columns: crate::VectorSize::Quad,
608                                rows: crate::VectorSize::Tri,
609                                scalar: crate::Scalar::F32,
610                            },
611                    ),
612                    Bi::WorldToObject => (
613                        match self.stage {
614                            St::RayGeneration
615                            | St::Miss
616                            | St::Vertex
617                            | St::Fragment
618                            | St::Compute
619                            | St::Mesh
620                            | St::Task => false,
621                            St::AnyHit | St::ClosestHit => true,
622                        },
623                        *ty_inner
624                            == Ti::Matrix {
625                                columns: crate::VectorSize::Quad,
626                                rows: crate::VectorSize::Tri,
627                                scalar: crate::Scalar::F32,
628                            },
629                    ),
630                    Bi::HitKind => (
631                        match self.stage {
632                            St::RayGeneration
633                            | St::Miss
634                            | St::Vertex
635                            | St::Fragment
636                            | St::Compute
637                            | St::Mesh
638                            | St::Task => false,
639                            St::AnyHit | St::ClosestHit => true,
640                        },
641                        *ty_inner == Ti::Scalar(crate::Scalar::U32),
642                    ),
643                    Bi::HitBarycentrics => (
644                        match self.stage {
645                            St::RayGeneration
646                            | St::Miss
647                            | St::Vertex
648                            | St::Fragment
649                            | St::Compute
650                            | St::Mesh
651                            | St::Task => false,
652                            St::AnyHit | St::ClosestHit => !self.output,
653                        },
654                        *ty_inner
655                            == Ti::Vector {
656                                size: Vs::Bi,
657                                scalar: crate::Scalar::F32,
658                            },
659                    ),
660                    // Validated elsewhere, shouldn't be here
661                    Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives => {
662                        (false, true)
663                    }
664                };
665                match built_in {
666                    Bi::CullPrimitive
667                    | Bi::PointIndex
668                    | Bi::LineIndices
669                    | Bi::TriangleIndices
670                    | Bi::MeshTaskSize
671                    | Bi::VertexCount
672                    | Bi::PrimitiveCount
673                    | Bi::Vertices
674                    | Bi::Primitives
675                        if !self.capabilities.contains(Capabilities::MESH_SHADER) =>
676                    {
677                        return Err(VaryingError::UnsupportedCapability(
678                            Capabilities::MESH_SHADER,
679                        ));
680                    }
681                    _ => (),
682                }
683
684                if !visible {
685                    return Err(VaryingError::InvalidBuiltInStage(built_in));
686                }
687                if !type_good {
688                    return Err(VaryingError::InvalidBuiltInType(built_in, ty_inner.clone()));
689                }
690            }
691            crate::Binding::Location {
692                location,
693                interpolation,
694                sampling,
695                blend_src,
696                per_primitive,
697            } => {
698                if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) {
699                    return Err(VaryingError::UnsupportedCapability(
700                        Capabilities::MESH_SHADER,
701                    ));
702                }
703                if interpolation == Some(crate::Interpolation::PerVertex) {
704                    if self.stage != crate::ShaderStage::Fragment {
705                        return Err(VaryingError::InvalidInterpolationInStage(
706                            crate::Interpolation::PerVertex,
707                            crate::ShaderStage::Fragment,
708                        ));
709                    }
710                    if !self.capabilities.contains(Capabilities::PER_VERTEX) {
711                        return Err(VaryingError::UnsupportedCapability(
712                            Capabilities::PER_VERTEX,
713                        ));
714                    }
715                    if sampling.is_some_and(|e| e != crate::Sampling::Center) {
716                        return Err(VaryingError::InvalidPerVertexSampling);
717                    }
718                }
719                // If this is per-vertex, we change the type we validate to the inner type, otherwise we leave it be.
720                // This lets all validation be done on the inner type once we've ensured the per-vertex is array<T, 3>
721                let (ty, ty_inner) = if interpolation == Some(crate::Interpolation::PerVertex) {
722                    let three = crate::ArraySize::Constant(core::num::NonZeroU32::new(3).unwrap());
723                    match ty_inner {
724                        &Ti::Array { base, size, .. } if size == three => {
725                            (base, &self.types[base].inner)
726                        }
727                        _ => return Err(VaryingError::PerVertexNotArrayOfThree),
728                    }
729                } else {
730                    (ty, ty_inner)
731                };
732
733                // Only IO-shareable types may be stored in locations.
734                if !self.type_info[ty.index()]
735                    .flags
736                    .contains(super::TypeFlags::IO_SHAREABLE)
737                {
738                    return Err(VaryingError::NotIOShareableType(ty));
739                }
740
741                // Check whether `per_primitive` is appropriate for this stage and direction.
742                if self.mesh_output_type == MeshOutputType::PrimitiveOutput {
743                    // All mesh shader `Location` outputs must be `per_primitive`.
744                    if !per_primitive {
745                        return Err(VaryingError::MissingPerPrimitive);
746                    }
747                } else if self.stage == crate::ShaderStage::Fragment && !self.output {
748                    // Fragment stage inputs may be `per_primitive`. We'll only
749                    // know if these are correct when the whole mesh pipeline is
750                    // created and we're paired with a specific mesh or vertex
751                    // shader.
752                } else if per_primitive {
753                    // All other `Location` bindings must not be `per_primitive`.
754                    return Err(VaryingError::InvalidPerPrimitive);
755                }
756
757                if blend_src.is_some() {
758                    return Err(VaryingError::BlendSrcNotOnStructMember);
759                } else if !self.location_mask.insert(location as usize)
760                    && self.flags.contains(super::ValidationFlags::BINDINGS)
761                {
762                    return Err(VaryingError::BindingCollision { location });
763                }
764
765                if let Some(interpolation) = interpolation {
766                    let invalid_sampling = match (interpolation, sampling) {
767                        (_, None)
768                        | (
769                            crate::Interpolation::Perspective | crate::Interpolation::Linear,
770                            Some(
771                                crate::Sampling::Center
772                                | crate::Sampling::Centroid
773                                | crate::Sampling::Sample,
774                            ),
775                        )
776                        | (
777                            crate::Interpolation::Flat,
778                            Some(crate::Sampling::First | crate::Sampling::Either),
779                        ) => None,
780                        (_, Some(invalid_sampling)) => Some(invalid_sampling),
781                    };
782                    if let Some(sampling) = invalid_sampling {
783                        return Err(VaryingError::InvalidInterpolationSamplingCombination {
784                            interpolation,
785                            sampling,
786                        });
787                    }
788                }
789
790                let needs_interpolation = match self.stage {
791                    crate::ShaderStage::Vertex => self.output,
792                    crate::ShaderStage::Fragment => !self.output && !per_primitive,
793                    crate::ShaderStage::Compute
794                    | crate::ShaderStage::Task
795                    | crate::ShaderStage::RayGeneration
796                    | crate::ShaderStage::AnyHit
797                    | crate::ShaderStage::ClosestHit
798                    | crate::ShaderStage::Miss => false,
799                    crate::ShaderStage::Mesh => self.output,
800                };
801
802                // It doesn't make sense to specify a sampling when `interpolation` is `Flat`, but
803                // SPIR-V and GLSL both explicitly tolerate such combinations of decorators /
804                // qualifiers, so we won't complain about that here.
805                let _ = sampling;
806
807                let mut required = match sampling {
808                    Some(crate::Sampling::Sample) => Capabilities::MULTISAMPLED_SHADING,
809                    _ => Capabilities::empty(),
810                };
811                if interpolation == Some(crate::Interpolation::Linear) {
812                    required |= Capabilities::LINEAR_INTERPOLATION;
813                }
814                if !self.capabilities.contains(required) {
815                    return Err(VaryingError::UnsupportedCapability(
816                        required - self.capabilities,
817                    ));
818                }
819
820                if interpolation != Some(crate::Interpolation::PerVertex) {
821                    match ty_inner.scalar_kind() {
822                        Some(crate::ScalarKind::Float) => {
823                            // Default interpolation is applied in the front end.
824                            if needs_interpolation && interpolation.is_none() {
825                                return Err(VaryingError::MissingInterpolation);
826                            }
827                        }
828                        Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => {
829                            // Integers do not have a default interpolation; `flat` must be
830                            // specified explicitly.
831                            if needs_interpolation
832                                && interpolation != Some(crate::Interpolation::Flat)
833                            {
834                                return Err(VaryingError::InvalidInterpolationForInteger);
835                            }
836                        }
837                        Some(_) | None => return Err(VaryingError::InvalidType(ty)),
838                    }
839                }
840            }
841        }
842
843        Ok(())
844    }
845
846    fn validate(
847        &mut self,
848        ep: &crate::EntryPoint,
849        ty: Handle<crate::Type>,
850        binding: Option<&crate::Binding>,
851    ) -> Result<(), WithSpan<VaryingError>> {
852        let span_context = self.types.get_span_context(ty);
853        match binding {
854            Some(binding) => self
855                .validate_impl(ep, ty, binding)
856                .map_err(|e| e.with_span_context(span_context)),
857            None => {
858                let crate::TypeInner::Struct { ref members, .. } = self.types[ty].inner else {
859                    if self.flags.contains(super::ValidationFlags::BINDINGS) {
860                        return Err(VaryingError::MissingBinding.with_span());
861                    } else {
862                        return Ok(());
863                    }
864                };
865
866                if self.type_info[ty.index()]
867                    .flags
868                    .contains(super::TypeFlags::IO_SHAREABLE)
869                {
870                    // `@blend_src` is the only case where `IO_SHAREABLE` is set on a struct (as
871                    // opposed to members of a struct). The struct definition is validated during
872                    // type validation.
873                    if self.stage != crate::ShaderStage::Fragment {
874                        return Err(
875                            VaryingError::InvalidAttributeInStage("blend_src", self.stage)
876                                .with_span(),
877                        );
878                    }
879                    if !self.output {
880                        return Err(VaryingError::InvalidInputAttributeInStage(
881                            "blend_src",
882                            self.stage,
883                        )
884                        .with_span());
885                    }
886                    // Dual blend sources must always be at location 0.
887                    if !self.location_mask.insert(0)
888                        && self.flags.contains(super::ValidationFlags::BINDINGS)
889                    {
890                        return Err(VaryingError::BindingCollision { location: 0 }.with_span());
891                    }
892
893                    **self
894                        .dual_source_blending
895                        .as_mut()
896                        .expect("unexpected dual source blending") = true;
897                } else {
898                    for (index, member) in members.iter().enumerate() {
899                        let span_context = self.types.get_span_context(ty);
900                        match member.binding {
901                            None => {
902                                if self.flags.contains(super::ValidationFlags::BINDINGS) {
903                                    return Err(VaryingError::MemberMissingBinding(index as u32)
904                                        .with_span_context(span_context));
905                                }
906                            }
907                            Some(ref binding) => self
908                                .validate_impl(ep, member.ty, binding)
909                                .map_err(|e| e.with_span_context(span_context))?,
910                        }
911                    }
912                }
913                Ok(())
914            }
915        }
916    }
917}
918
919impl super::Validator {
920    pub(super) fn validate_global_var(
921        &self,
922        var: &crate::GlobalVariable,
923        gctx: crate::proc::GlobalCtx,
924        mod_info: &ModuleInfo,
925        global_expr_kind: &crate::proc::ExpressionKindTracker,
926    ) -> Result<(), GlobalVariableError> {
927        use super::TypeFlags;
928
929        log::debug!("var {var:?}");
930        let inner_ty = match gctx.types[var.ty].inner {
931            // A binding array is (mostly) supposed to behave the same as a
932            // series of individually bound resources, so we can (mostly)
933            // validate a `binding_array<T>` as if it were just a plain `T`.
934            crate::TypeInner::BindingArray { base, .. } => match var.space {
935                crate::AddressSpace::Storage { .. } => {
936                    if !self
937                        .capabilities
938                        .contains(Capabilities::STORAGE_BUFFER_BINDING_ARRAY)
939                    {
940                        return Err(GlobalVariableError::UnsupportedCapability(
941                            Capabilities::STORAGE_BUFFER_BINDING_ARRAY,
942                        ));
943                    }
944                    base
945                }
946                crate::AddressSpace::Uniform => {
947                    if !self
948                        .capabilities
949                        .contains(Capabilities::BUFFER_BINDING_ARRAY)
950                    {
951                        return Err(GlobalVariableError::UnsupportedCapability(
952                            Capabilities::BUFFER_BINDING_ARRAY,
953                        ));
954                    }
955                    base
956                }
957                crate::AddressSpace::Handle => {
958                    match gctx.types[base].inner {
959                        crate::TypeInner::Image { class, .. } => match class {
960                            crate::ImageClass::Storage { .. } => {
961                                if !self
962                                    .capabilities
963                                    .contains(Capabilities::STORAGE_TEXTURE_BINDING_ARRAY)
964                                {
965                                    return Err(GlobalVariableError::UnsupportedCapability(
966                                        Capabilities::STORAGE_TEXTURE_BINDING_ARRAY,
967                                    ));
968                                }
969                            }
970                            crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => {
971                                if !self
972                                    .capabilities
973                                    .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY)
974                                {
975                                    return Err(GlobalVariableError::UnsupportedCapability(
976                                        Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY,
977                                    ));
978                                }
979                            }
980                            crate::ImageClass::External => {
981                                // This should have been rejected in `validate_type`.
982                                unreachable!("binding arrays of external images are not supported");
983                            }
984                        },
985                        crate::TypeInner::Sampler { .. }
986                            if !self
987                                .capabilities
988                                .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY) =>
989                        {
990                            return Err(GlobalVariableError::UnsupportedCapability(
991                                Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY,
992                            ));
993                        }
994                        crate::TypeInner::AccelerationStructure { .. }
995                            if !self
996                                .capabilities
997                                .contains(Capabilities::ACCELERATION_STRUCTURE_BINDING_ARRAY) =>
998                        {
999                            return Err(GlobalVariableError::UnsupportedCapability(
1000                                Capabilities::ACCELERATION_STRUCTURE_BINDING_ARRAY,
1001                            ));
1002                        }
1003                        crate::TypeInner::RayQuery { .. } => {
1004                            // This should have been rejected in `validate_type`.
1005                            unreachable!("binding arrays of ray queries are not supported");
1006                        }
1007                        _ => {
1008                            // Fall through to the regular validation, which will reject `base`
1009                            // as invalid in `AddressSpace::Handle`.
1010                        }
1011                    }
1012                    base
1013                }
1014                _ => return Err(GlobalVariableError::InvalidUsage(var.space)),
1015            },
1016            _ => var.ty,
1017        };
1018        let type_info = &self.types[inner_ty.index()];
1019
1020        let (required_type_flags, is_resource) = match var.space {
1021            crate::AddressSpace::Function => {
1022                return Err(GlobalVariableError::InvalidUsage(var.space))
1023            }
1024            crate::AddressSpace::Storage { access } => {
1025                if let Err((ty_handle, disalignment)) = type_info.storage_layout {
1026                    if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
1027                        return Err(GlobalVariableError::Alignment(
1028                            var.space,
1029                            ty_handle,
1030                            disalignment,
1031                        ));
1032                    }
1033                }
1034                if access == crate::StorageAccess::STORE {
1035                    return Err(GlobalVariableError::StorageAddressSpaceWriteOnlyNotSupported);
1036                }
1037                (
1038                    TypeFlags::DATA | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED,
1039                    true,
1040                )
1041            }
1042            crate::AddressSpace::Uniform => {
1043                if let Err((ty_handle, disalignment)) = type_info.uniform_layout {
1044                    if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
1045                        return Err(GlobalVariableError::Alignment(
1046                            var.space,
1047                            ty_handle,
1048                            disalignment,
1049                        ));
1050                    }
1051                }
1052                (
1053                    TypeFlags::DATA
1054                        | TypeFlags::COPY
1055                        | TypeFlags::SIZED
1056                        | TypeFlags::HOST_SHAREABLE
1057                        | TypeFlags::CREATION_RESOLVED,
1058                    true,
1059                )
1060            }
1061            crate::AddressSpace::Handle => {
1062                match gctx.types[inner_ty].inner {
1063                    crate::TypeInner::Image { class, .. } => match class {
1064                        crate::ImageClass::Storage {
1065                            format:
1066                                crate::StorageFormat::R16Unorm
1067                                | crate::StorageFormat::R16Snorm
1068                                | crate::StorageFormat::Rg16Unorm
1069                                | crate::StorageFormat::Rg16Snorm
1070                                | crate::StorageFormat::Rgba16Unorm
1071                                | crate::StorageFormat::Rgba16Snorm,
1072                            ..
1073                        } if !self
1074                            .capabilities
1075                            .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) =>
1076                        {
1077                            return Err(GlobalVariableError::UnsupportedCapability(
1078                                Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS,
1079                            ));
1080                        }
1081                        _ => {}
1082                    },
1083                    crate::TypeInner::Sampler { .. }
1084                    | crate::TypeInner::AccelerationStructure { .. }
1085                    | crate::TypeInner::RayQuery { .. } => {}
1086                    _ => {
1087                        return Err(GlobalVariableError::InvalidType(var.space));
1088                    }
1089                }
1090
1091                (TypeFlags::empty(), true)
1092            }
1093            crate::AddressSpace::Private => (
1094                TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED,
1095                false,
1096            ),
1097            crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false),
1098            crate::AddressSpace::TaskPayload => {
1099                if !self.capabilities.contains(Capabilities::MESH_SHADER) {
1100                    return Err(GlobalVariableError::UnsupportedCapability(
1101                        Capabilities::MESH_SHADER,
1102                    ));
1103                }
1104                (TypeFlags::DATA | TypeFlags::SIZED, false)
1105            }
1106            crate::AddressSpace::Immediate => {
1107                if !self.capabilities.contains(Capabilities::IMMEDIATES) {
1108                    return Err(GlobalVariableError::UnsupportedCapability(
1109                        Capabilities::IMMEDIATES,
1110                    ));
1111                }
1112                if let Err(ref err) = type_info.immediates_compatibility {
1113                    return Err(GlobalVariableError::InvalidImmediateType(err.clone()));
1114                }
1115                (
1116                    TypeFlags::DATA
1117                        | TypeFlags::COPY
1118                        | TypeFlags::HOST_SHAREABLE
1119                        | TypeFlags::SIZED,
1120                    false,
1121                )
1122            }
1123            crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => {
1124                if !self
1125                    .capabilities
1126                    .contains(Capabilities::RAY_TRACING_PIPELINE)
1127                {
1128                    return Err(GlobalVariableError::UnsupportedCapability(
1129                        Capabilities::RAY_TRACING_PIPELINE,
1130                    ));
1131                }
1132                (TypeFlags::DATA | TypeFlags::SIZED, false)
1133            }
1134        };
1135
1136        if !type_info.flags.contains(required_type_flags) {
1137            return Err(GlobalVariableError::MissingTypeFlags {
1138                seen: type_info.flags,
1139                required: required_type_flags,
1140            });
1141        }
1142
1143        if is_resource != var.binding.is_some() {
1144            if self.flags.contains(super::ValidationFlags::BINDINGS) {
1145                return Err(GlobalVariableError::InvalidBinding);
1146            }
1147        }
1148
1149        if var.space == crate::AddressSpace::TaskPayload {
1150            let ty = &gctx.types[var.ty].inner;
1151            // HLSL doesn't allow zero sized payloads.
1152            if ty.try_size(gctx) == Some(0) {
1153                return Err(GlobalVariableError::ZeroSizedTaskPayload);
1154            }
1155        }
1156
1157        if !var.memory_decorations.is_empty()
1158            && !matches!(var.space, crate::AddressSpace::Storage { .. })
1159        {
1160            return Err(GlobalVariableError::InvalidMemoryDecorationsAddressSpace);
1161        }
1162        if var
1163            .memory_decorations
1164            .contains(crate::MemoryDecorations::COHERENT)
1165            && !self
1166                .capabilities
1167                .contains(Capabilities::MEMORY_DECORATION_COHERENT)
1168        {
1169            return Err(GlobalVariableError::CoherentNotSupported);
1170        }
1171        if var
1172            .memory_decorations
1173            .contains(crate::MemoryDecorations::VOLATILE)
1174            && !self
1175                .capabilities
1176                .contains(Capabilities::MEMORY_DECORATION_VOLATILE)
1177        {
1178            return Err(GlobalVariableError::VolatileNotSupported);
1179        }
1180
1181        if let Some(init) = var.init {
1182            match var.space {
1183                crate::AddressSpace::Private | crate::AddressSpace::Function => {}
1184                _ => {
1185                    return Err(GlobalVariableError::InitializerNotAllowed(var.space));
1186                }
1187            }
1188
1189            if !global_expr_kind.is_const_or_override(init) {
1190                return Err(GlobalVariableError::InitializerExprType);
1191            }
1192
1193            if !gctx.compare_types(
1194                &crate::proc::TypeResolution::Handle(var.ty),
1195                &mod_info[init],
1196            ) {
1197                return Err(GlobalVariableError::InitializerType);
1198            }
1199        }
1200
1201        Ok(())
1202    }
1203
1204    /// Validate the mesh shader output type `ty`, used as `mesh_output_type`.
1205    fn validate_mesh_output_type(
1206        &mut self,
1207        ep: &crate::EntryPoint,
1208        module: &crate::Module,
1209        ty: Handle<crate::Type>,
1210        mesh_output_type: MeshOutputType,
1211    ) -> Result<(), WithSpan<EntryPointError>> {
1212        if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) {
1213            return Err(EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types));
1214        }
1215        let mut result_built_ins = crate::FastHashSet::default();
1216        let mut ctx = VaryingContext {
1217            stage: ep.stage,
1218            output: true,
1219            types: &module.types,
1220            type_info: &self.types,
1221            location_mask: &mut self.location_mask,
1222            dual_source_blending: None,
1223            built_ins: &mut result_built_ins,
1224            capabilities: self.capabilities,
1225            flags: self.flags,
1226            mesh_output_type,
1227            has_task_payload: ep.task_payload.is_some(),
1228        };
1229        ctx.validate(ep, ty, None)
1230            .map_err_inner(|e| EntryPointError::Result(e).with_span())?;
1231        if mesh_output_type == MeshOutputType::PrimitiveOutput {
1232            let mut num_indices_builtins = 0;
1233            if result_built_ins.contains(&crate::BuiltIn::PointIndex) {
1234                num_indices_builtins += 1;
1235            }
1236            if result_built_ins.contains(&crate::BuiltIn::LineIndices) {
1237                num_indices_builtins += 1;
1238            }
1239            if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) {
1240                num_indices_builtins += 1;
1241            }
1242            if num_indices_builtins != 1 {
1243                return Err(EntryPointError::InvalidMeshPrimitiveOutputType
1244                    .with_span_handle(ty, &module.types));
1245            }
1246        } else if mesh_output_type == MeshOutputType::VertexOutput
1247            && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false })
1248        {
1249            return Err(
1250                EntryPointError::MissingVertexOutputPosition.with_span_handle(ty, &module.types)
1251            );
1252        }
1253
1254        Ok(())
1255    }
1256
1257    pub(super) fn validate_entry_point(
1258        &mut self,
1259        ep: &crate::EntryPoint,
1260        module: &crate::Module,
1261        mod_info: &ModuleInfo,
1262    ) -> Result<FunctionInfo, WithSpan<EntryPointError>> {
1263        match ep.stage {
1264            crate::ShaderStage::Task | crate::ShaderStage::Mesh
1265                if !self.capabilities.contains(Capabilities::MESH_SHADER) =>
1266            {
1267                return Err(
1268                    EntryPointError::UnsupportedCapability(Capabilities::MESH_SHADER).with_span(),
1269                );
1270            }
1271            crate::ShaderStage::RayGeneration
1272            | crate::ShaderStage::AnyHit
1273            | crate::ShaderStage::ClosestHit
1274            | crate::ShaderStage::Miss
1275                if !self
1276                    .capabilities
1277                    .contains(Capabilities::RAY_TRACING_PIPELINE) =>
1278            {
1279                return Err(EntryPointError::UnsupportedCapability(
1280                    Capabilities::RAY_TRACING_PIPELINE,
1281                )
1282                .with_span());
1283            }
1284            _ => {}
1285        }
1286        if ep.early_depth_test.is_some() {
1287            let required = Capabilities::EARLY_DEPTH_TEST;
1288            if !self.capabilities.contains(required) {
1289                return Err(
1290                    EntryPointError::Result(VaryingError::UnsupportedCapability(required))
1291                        .with_span(),
1292                );
1293            }
1294
1295            if ep.stage != crate::ShaderStage::Fragment {
1296                return Err(EntryPointError::UnexpectedEarlyDepthTest.with_span());
1297            }
1298        }
1299
1300        if ep.stage.compute_like() {
1301            if ep
1302                .workgroup_size
1303                .iter()
1304                .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE)
1305            {
1306                return Err(EntryPointError::OutOfRangeWorkgroupSize.with_span());
1307            }
1308        } else if ep.workgroup_size != [0; 3] {
1309            return Err(EntryPointError::UnexpectedWorkgroupSize.with_span());
1310        }
1311
1312        match (ep.stage, &ep.mesh_info) {
1313            (crate::ShaderStage::Mesh, &None) => {
1314                return Err(EntryPointError::ExpectedMeshShaderAttributes.with_span());
1315            }
1316            (crate::ShaderStage::Mesh, &Some(..)) => {}
1317            (_, &Some(_)) => {
1318                return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span());
1319            }
1320            (_, _) => {}
1321        }
1322
1323        let mut info = self
1324            .validate_function(&ep.function, module, mod_info, true)
1325            .map_err(WithSpan::into_other)?;
1326
1327        // Validate the task shader payload.
1328        match ep.stage {
1329            // Task shaders must produce a payload.
1330            crate::ShaderStage::Task => {
1331                let Some(handle) = ep.task_payload else {
1332                    return Err(EntryPointError::ExpectedTaskPayload.with_span());
1333                };
1334                if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
1335                    return Err(EntryPointError::TaskPayloadWrongAddressSpace
1336                        .with_span_handle(handle, &module.global_variables));
1337                }
1338                info.insert_global_use(GlobalUse::READ | GlobalUse::WRITE, handle);
1339            }
1340
1341            // Mesh shaders may accept a payload.
1342            crate::ShaderStage::Mesh => {
1343                if let Some(handle) = ep.task_payload {
1344                    if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
1345                        return Err(EntryPointError::TaskPayloadWrongAddressSpace
1346                            .with_span_handle(handle, &module.global_variables));
1347                    }
1348                    info.insert_global_use(GlobalUse::READ, handle);
1349                }
1350                if let Some(ref mesh_info) = ep.mesh_info {
1351                    info.insert_global_use(GlobalUse::READ, mesh_info.output_variable);
1352                }
1353            }
1354
1355            // Other stages must not have a payload.
1356            _ => {
1357                if let Some(handle) = ep.task_payload {
1358                    return Err(EntryPointError::UnexpectedTaskPayload
1359                        .with_span_handle(handle, &module.global_variables));
1360                }
1361            }
1362        }
1363
1364        {
1365            use super::ShaderStages;
1366
1367            let stage_bit = match ep.stage {
1368                crate::ShaderStage::Vertex => ShaderStages::VERTEX,
1369                crate::ShaderStage::Fragment => ShaderStages::FRAGMENT,
1370                crate::ShaderStage::Compute => ShaderStages::COMPUTE,
1371                crate::ShaderStage::Mesh => ShaderStages::MESH,
1372                crate::ShaderStage::Task => ShaderStages::TASK,
1373                crate::ShaderStage::RayGeneration => ShaderStages::RAY_GENERATION,
1374                crate::ShaderStage::AnyHit => ShaderStages::ANY_HIT,
1375                crate::ShaderStage::ClosestHit => ShaderStages::CLOSEST_HIT,
1376                crate::ShaderStage::Miss => ShaderStages::MISS,
1377            };
1378
1379            if !info.available_stages.contains(stage_bit) {
1380                return Err(EntryPointError::ForbiddenStageOperations.with_span());
1381            }
1382        }
1383
1384        self.location_mask.make_empty();
1385        let mut argument_built_ins = crate::FastHashSet::default();
1386        // TODO: add span info to function arguments
1387        for (index, fa) in ep.function.arguments.iter().enumerate() {
1388            let mut ctx = VaryingContext {
1389                stage: ep.stage,
1390                output: false,
1391                types: &module.types,
1392                type_info: &self.types,
1393                location_mask: &mut self.location_mask,
1394                dual_source_blending: Some(&mut info.dual_source_blending),
1395                built_ins: &mut argument_built_ins,
1396                capabilities: self.capabilities,
1397                flags: self.flags,
1398                mesh_output_type: MeshOutputType::None,
1399                has_task_payload: ep.task_payload.is_some(),
1400            };
1401            ctx.validate(ep, fa.ty, fa.binding.as_ref())
1402                .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?;
1403            match ep.stage {
1404                nt::ShaderStage::Compute | nt::ShaderStage::Mesh | nt::ShaderStage::Task => {
1405                    let reject_location_binding = |binding| {
1406                        if let Some(&crate::ir::Binding::Location { .. }) = binding {
1407                            return Err(EntryPointError::Argument(
1408                                index as u32,
1409                                VaryingError::InvalidAttributeInStage("location", ep.stage),
1410                            )
1411                            .with_span());
1412                        }
1413                        Ok(())
1414                    };
1415                    reject_location_binding(fa.binding.as_ref())?;
1416
1417                    if let &crate::TypeInner::Struct { ref members, .. } =
1418                        &module.types[fa.ty].inner
1419                    {
1420                        members
1421                            .iter()
1422                            .map(|m| m.binding.as_ref())
1423                            .try_for_each(reject_location_binding)?;
1424                    }
1425                }
1426                nt::ShaderStage::Vertex
1427                | nt::ShaderStage::Fragment
1428                | nt::ShaderStage::RayGeneration
1429                | nt::ShaderStage::Miss
1430                | nt::ShaderStage::AnyHit
1431                | nt::ShaderStage::ClosestHit => {}
1432            }
1433        }
1434
1435        self.location_mask.make_empty();
1436        if let Some(ref fr) = ep.function.result {
1437            let mut result_built_ins = crate::FastHashSet::default();
1438            let mut ctx = VaryingContext {
1439                stage: ep.stage,
1440                output: true,
1441                types: &module.types,
1442                type_info: &self.types,
1443                location_mask: &mut self.location_mask,
1444                dual_source_blending: Some(&mut info.dual_source_blending),
1445                built_ins: &mut result_built_ins,
1446                capabilities: self.capabilities,
1447                flags: self.flags,
1448                mesh_output_type: MeshOutputType::None,
1449                has_task_payload: ep.task_payload.is_some(),
1450            };
1451            ctx.validate(ep, fr.ty, fr.binding.as_ref())
1452                .map_err_inner(|e| EntryPointError::Result(e).with_span())?;
1453            match ep.stage {
1454                nt::ShaderStage::Vertex => {
1455                    if !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) {
1456                        return Err(EntryPointError::MissingVertexOutputPosition.with_span());
1457                    }
1458                }
1459                nt::ShaderStage::Mesh => {
1460                    return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span())
1461                }
1462                nt::ShaderStage::Task => {
1463                    let ok = module.types[fr.ty].inner
1464                        == crate::TypeInner::Vector {
1465                            size: crate::VectorSize::Tri,
1466                            scalar: crate::Scalar::U32,
1467                        };
1468                    if !ok {
1469                        return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
1470                    }
1471                }
1472                nt::ShaderStage::Compute => {
1473                    return Err(EntryPointError::UnexpectedComputeShaderEntryResult.with_span())
1474                }
1475                nt::ShaderStage::Fragment
1476                | nt::ShaderStage::RayGeneration
1477                | nt::ShaderStage::Miss
1478                | nt::ShaderStage::AnyHit
1479                | nt::ShaderStage::ClosestHit => {}
1480            }
1481        } else {
1482            match ep.stage {
1483                nt::ShaderStage::Vertex => {
1484                    return Err(EntryPointError::MissingVertexOutputPosition.with_span())
1485                }
1486                nt::ShaderStage::Task => {
1487                    return Err(EntryPointError::WrongTaskShaderEntryResult.with_span())
1488                }
1489                nt::ShaderStage::Mesh
1490                | nt::ShaderStage::Fragment
1491                | nt::ShaderStage::Compute
1492                | nt::ShaderStage::RayGeneration
1493                | nt::ShaderStage::Miss
1494                | nt::ShaderStage::AnyHit
1495                | nt::ShaderStage::ClosestHit => {}
1496            }
1497        }
1498
1499        {
1500            let mut used_immediates = module
1501                .global_variables
1502                .iter()
1503                .filter(|&(_, var)| var.space == crate::AddressSpace::Immediate)
1504                .map(|(handle, _)| handle)
1505                .filter(|&handle| !info[handle].is_empty());
1506            // Check if there is more than one immediate data, and error if so.
1507            // Use a loop for when returning multiple errors is supported.
1508            if let Some(handle) = used_immediates.nth(1) {
1509                return Err(EntryPointError::MoreThanOneImmediateUsed
1510                    .with_span_handle(handle, &module.global_variables));
1511            }
1512        }
1513
1514        self.ep_resource_bindings.clear();
1515        for (var_handle, var) in module.global_variables.iter() {
1516            let usage = info[var_handle];
1517            if usage.is_empty() {
1518                continue;
1519            }
1520
1521            if var.space == crate::AddressSpace::TaskPayload {
1522                if ep.task_payload != Some(var_handle) {
1523                    return Err(EntryPointError::WrongTaskPayloadUsed
1524                        .with_span_handle(var_handle, &module.global_variables));
1525                }
1526                let size = module.types[var.ty].inner.size(module.to_ctx());
1527                if size < 4 {
1528                    return Err(EntryPointError::TaskPayloadTooSmall(size)
1529                        .with_span_handle(var_handle, &module.global_variables));
1530                }
1531            }
1532
1533            let allowed_usage = match var.space {
1534                crate::AddressSpace::Function => unreachable!(),
1535                crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY,
1536                crate::AddressSpace::Storage { access } => storage_usage(access),
1537                crate::AddressSpace::Handle => match module.types[var.ty].inner {
1538                    crate::TypeInner::BindingArray { base, .. } => match module.types[base].inner {
1539                        crate::TypeInner::Image {
1540                            class: crate::ImageClass::Storage { access, .. },
1541                            ..
1542                        } => storage_usage(access),
1543                        _ => GlobalUse::READ | GlobalUse::QUERY,
1544                    },
1545                    crate::TypeInner::Image {
1546                        class: crate::ImageClass::Storage { access, .. },
1547                        ..
1548                    } => storage_usage(access),
1549                    _ => GlobalUse::READ | GlobalUse::QUERY,
1550                },
1551                crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {
1552                    GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY
1553                }
1554                crate::AddressSpace::TaskPayload => {
1555                    GlobalUse::READ
1556                        | GlobalUse::QUERY
1557                        | if ep.stage == crate::ShaderStage::Task {
1558                            GlobalUse::WRITE
1559                        } else {
1560                            GlobalUse::empty()
1561                        }
1562                }
1563                crate::AddressSpace::Immediate => GlobalUse::READ,
1564                crate::AddressSpace::RayPayload => {
1565                    if !matches!(
1566                        ep.stage,
1567                        crate::ShaderStage::RayGeneration
1568                            | crate::ShaderStage::ClosestHit
1569                            | crate::ShaderStage::Miss
1570                    ) {
1571                        return Err(EntryPointError::RayPayloadInInvalidStage(ep.stage)
1572                            .with_span_handle(var_handle, &module.global_variables));
1573                    }
1574                    GlobalUse::READ | GlobalUse::QUERY | GlobalUse::WRITE
1575                }
1576                crate::AddressSpace::IncomingRayPayload => {
1577                    if !matches!(
1578                        ep.stage,
1579                        crate::ShaderStage::AnyHit
1580                            | crate::ShaderStage::ClosestHit
1581                            | crate::ShaderStage::Miss
1582                    ) {
1583                        return Err(EntryPointError::IncomingRayPayloadInInvalidStage(ep.stage)
1584                            .with_span_handle(var_handle, &module.global_variables));
1585                    }
1586                    GlobalUse::READ | GlobalUse::QUERY | GlobalUse::WRITE
1587                }
1588            };
1589            if !allowed_usage.contains(usage) {
1590                log::warn!("\tUsage error for: {var:?}");
1591                log::warn!("\tAllowed usage: {allowed_usage:?}, requested: {usage:?}");
1592                return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage)
1593                    .with_span_handle(var_handle, &module.global_variables));
1594            }
1595
1596            if let Some(ref bind) = var.binding {
1597                if !self.ep_resource_bindings.insert(*bind) {
1598                    if self.flags.contains(super::ValidationFlags::BINDINGS) {
1599                        return Err(EntryPointError::BindingCollision(var_handle)
1600                            .with_span_handle(var_handle, &module.global_variables));
1601                    }
1602                }
1603            }
1604        }
1605
1606        // If this is a `Mesh` entry point, check its vertex and primitive output types.
1607        // We verified previously that only mesh shaders can have `mesh_info`.
1608        if let &Some(ref mesh_info) = &ep.mesh_info {
1609            if module.global_variables[mesh_info.output_variable].space
1610                != crate::AddressSpace::WorkGroup
1611            {
1612                return Err(EntryPointError::WrongMeshOutputAddressSpace.with_span());
1613            }
1614
1615            let mut implied = module.analyze_mesh_shader_info(mesh_info.output_variable);
1616            if let Some(e) = implied.2 {
1617                return Err(e);
1618            }
1619
1620            if let Some(e) = mesh_info.max_vertices_override {
1621                if let crate::Expression::Override(o) = module.global_expressions[e] {
1622                    if implied.1[0] != Some(o) {
1623                        return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1624                    }
1625                }
1626            }
1627            if let Some(e) = mesh_info.max_primitives_override {
1628                if let crate::Expression::Override(o) = module.global_expressions[e] {
1629                    if implied.1[1] != Some(o) {
1630                        return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1631                    }
1632                }
1633            }
1634
1635            implied.0.max_vertices_override = mesh_info.max_vertices_override;
1636            implied.0.max_primitives_override = mesh_info.max_primitives_override;
1637            if implied.0 != *mesh_info {
1638                return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1639            }
1640            if mesh_info.topology == crate::MeshOutputTopology::Points
1641                && !self
1642                    .capabilities
1643                    .contains(Capabilities::MESH_SHADER_POINT_TOPOLOGY)
1644            {
1645                return Err(EntryPointError::UnsupportedCapability(
1646                    Capabilities::MESH_SHADER_POINT_TOPOLOGY,
1647                )
1648                .with_span());
1649            }
1650
1651            self.validate_mesh_output_type(
1652                ep,
1653                module,
1654                mesh_info.vertex_output_type,
1655                MeshOutputType::VertexOutput,
1656            )?;
1657            self.validate_mesh_output_type(
1658                ep,
1659                module,
1660                mesh_info.primitive_output_type,
1661                MeshOutputType::PrimitiveOutput,
1662            )?;
1663        }
1664
1665        Ok(info)
1666    }
1667}