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