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}
51
52#[derive(Clone, Debug, thiserror::Error)]
53#[cfg_attr(test, derive(PartialEq))]
54pub enum VaryingError {
55 #[error("The type {0:?} does not match the varying")]
56 InvalidType(Handle<crate::Type>),
57 #[error("The type {0:?} cannot be used for user-defined entry point inputs or outputs")]
58 NotIOShareableType(Handle<crate::Type>),
59 #[error("Interpolation is not valid")]
60 InvalidInterpolation,
61 #[error("Interpolation {0:?} is only valid for stage {1:?}")]
62 InvalidInterpolationInStage(crate::Interpolation, crate::ShaderStage),
63 #[error("Cannot combine {interpolation:?} interpolation with the {sampling:?} sample type")]
64 InvalidInterpolationSamplingCombination {
65 interpolation: crate::Interpolation,
66 sampling: crate::Sampling,
67 },
68 #[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")]
69 MissingInterpolation,
70 #[error("Built-in {0:?} is not available at this stage")]
71 InvalidBuiltInStage(crate::BuiltIn),
72 #[error("Built-in type for {0:?} is invalid. Found {1:?}")]
73 InvalidBuiltInType(crate::BuiltIn, crate::TypeInner),
74 #[error("Entry point arguments and return values must all have bindings")]
75 MissingBinding,
76 #[error("Struct member {0} is missing a binding")]
77 MemberMissingBinding(u32),
78 #[error("Multiple bindings at location {location} are present")]
79 BindingCollision { location: u32 },
80 #[error("Multiple bindings use the same `blend_src` {blend_src}")]
81 BindingCollisionBlendSrc { blend_src: u32 },
82 #[error("Built-in {0:?} is present more than once")]
83 DuplicateBuiltIn(crate::BuiltIn),
84 #[error("Capability {0:?} is not supported")]
85 UnsupportedCapability(Capabilities),
86 #[error("The attribute {0:?} is only valid as an output for stage {1:?}")]
87 InvalidInputAttributeInStage(&'static str, crate::ShaderStage),
88 #[error("The attribute {0:?} is not valid for stage {1:?}")]
89 InvalidAttributeInStage(&'static str, crate::ShaderStage),
90 #[error("The `blend_src` attribute can only be used on location 0, only indices 0 and 1 are valid. Location was {location}, index was {blend_src}.")]
91 InvalidBlendSrcIndex { location: u32, blend_src: u32 },
92 #[error("If `blend_src` is used, there must be exactly two outputs both with location 0, one with `blend_src(0)` and the other with `blend_src(1)`.")]
93 IncompleteBlendSrcUsage,
94 #[error("If `blend_src` is used, both outputs must have the same type. `blend_src(0)` has type {blend_src_0_type:?} and `blend_src(1)` has type {blend_src_1_type:?}.")]
95 BlendSrcOutputTypeMismatch {
96 blend_src_0_type: Handle<crate::Type>,
97 blend_src_1_type: Handle<crate::Type>,
98 },
99 #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")]
100 InvalidMultiDimensionalSubgroupBuiltIn,
101 #[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")]
102 InvalidPerPrimitive,
103 #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")]
104 MissingPerPrimitive,
105 #[error("Per vertex fragment inputs must be an array of length 3.")]
106 PerVertexNotArrayOfThree,
107}
108
109#[derive(Clone, Debug, thiserror::Error)]
110#[cfg_attr(test, derive(PartialEq))]
111pub enum EntryPointError {
112 #[error("Multiple conflicting entry points")]
113 Conflict,
114 #[error("Vertex shaders must return a `@builtin(position)` output value")]
115 MissingVertexOutputPosition,
116 #[error("Early depth test is not applicable")]
117 UnexpectedEarlyDepthTest,
118 #[error("Workgroup size is not applicable")]
119 UnexpectedWorkgroupSize,
120 #[error("Workgroup size is out of range")]
121 OutOfRangeWorkgroupSize,
122 #[error("Uses operations forbidden at this stage")]
123 ForbiddenStageOperations,
124 #[error("Global variable {0:?} is used incorrectly as {1:?}")]
125 InvalidGlobalUsage(Handle<crate::GlobalVariable>, GlobalUse),
126 #[error("More than 1 immediate data variable is used")]
127 MoreThanOneImmediateUsed,
128 #[error("Bindings for {0:?} conflict with other resource")]
129 BindingCollision(Handle<crate::GlobalVariable>),
130 #[error("Argument {0} varying error")]
131 Argument(u32, #[source] VaryingError),
132 #[error(transparent)]
133 Result(#[from] VaryingError),
134 #[error("Location {location} interpolation of an integer has to be flat")]
135 InvalidIntegerInterpolation { location: u32 },
136 #[error(transparent)]
137 Function(#[from] FunctionError),
138 #[error("Capability {0:?} is not supported")]
139 UnsupportedCapability(Capabilities),
140
141 #[error("mesh shader entry point missing mesh shader attributes")]
142 ExpectedMeshShaderAttributes,
143 #[error("Non mesh shader entry point cannot have mesh shader attributes")]
144 UnexpectedMeshShaderAttributes,
145 #[error("Non mesh/task shader entry point cannot have task payload attribute")]
146 UnexpectedTaskPayload,
147 #[error("Task payload must be declared with `var<task_payload>`")]
148 TaskPayloadWrongAddressSpace,
149 #[error("For a task payload to be used, it must be declared with @payload")]
150 WrongTaskPayloadUsed,
151 #[error("Task shader entry point must return @builtin(mesh_task_size) vec3<u32>")]
152 WrongTaskShaderEntryResult,
153 #[error("Task shaders must declare a task payload output")]
154 ExpectedTaskPayload,
155 #[error(
156 "Mesh shader output variable must be a struct with fields that are all allowed builtins"
157 )]
158 BadMeshOutputVariableType,
159 #[error("Mesh shader output variable fields must have types that are in accordance with the mesh shader spec")]
160 BadMeshOutputVariableField,
161 #[error("Mesh shader entry point cannot have a return type")]
162 UnexpectedMeshShaderEntryResult,
163 #[error(
164 "Mesh output type must be a user-defined struct with fields in alignment with the mesh shader spec"
165 )]
166 InvalidMeshOutputType,
167 #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")]
168 InvalidMeshPrimitiveOutputType,
169 #[error("Mesh output global variable must live in the workgroup address space")]
170 WrongMeshOutputAddressSpace,
171 #[error("Task payload must be at least 4 bytes, but is {0} bytes")]
172 TaskPayloadTooSmall(u32),
173}
174
175fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
176 let mut storage_usage = GlobalUse::QUERY;
177 if access.contains(crate::StorageAccess::LOAD) {
178 storage_usage |= GlobalUse::READ;
179 }
180 if access.contains(crate::StorageAccess::STORE) {
181 storage_usage |= GlobalUse::WRITE;
182 }
183 if access.contains(crate::StorageAccess::ATOMIC) {
184 storage_usage |= GlobalUse::ATOMIC;
185 }
186 storage_usage
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190enum MeshOutputType {
191 None,
192 VertexOutput,
193 PrimitiveOutput,
194}
195
196struct VaryingContext<'a> {
197 stage: crate::ShaderStage,
198 output: bool,
199 types: &'a UniqueArena<crate::Type>,
200 type_info: &'a Vec<super::r#type::TypeInfo>,
201 location_mask: &'a mut BitSet,
202 blend_src_mask: &'a mut BitSet,
203 built_ins: &'a mut crate::FastHashSet<crate::BuiltIn>,
204 capabilities: Capabilities,
205 flags: super::ValidationFlags,
206 mesh_output_type: MeshOutputType,
207 has_task_payload: bool,
208}
209
210impl VaryingContext<'_> {
211 fn validate_impl(
212 &mut self,
213 ep: &crate::EntryPoint,
214 ty: Handle<crate::Type>,
215 binding: &crate::Binding,
216 ) -> Result<(), VaryingError> {
217 use crate::{BuiltIn as Bi, ShaderStage as St, TypeInner as Ti, VectorSize as Vs};
218
219 let ty_inner = &self.types[ty].inner;
220 match *binding {
221 crate::Binding::BuiltIn(built_in) => {
222 let canonical = match built_in {
225 crate::BuiltIn::Position { .. } => {
226 crate::BuiltIn::Position { invariant: false }
227 }
228 crate::BuiltIn::Barycentric { .. } => {
229 crate::BuiltIn::Barycentric { perspective: false }
230 }
231 x => x,
232 };
233
234 if self.built_ins.contains(&canonical) {
235 return Err(VaryingError::DuplicateBuiltIn(built_in));
236 }
237 self.built_ins.insert(canonical);
238
239 let required = match built_in {
240 Bi::ClipDistance => Capabilities::CLIP_DISTANCE,
241 Bi::CullDistance => Capabilities::CULL_DISTANCE,
242 Bi::PrimitiveIndex => Capabilities::PRIMITIVE_INDEX,
243 Bi::Barycentric { .. } => Capabilities::SHADER_BARYCENTRICS,
244 Bi::ViewIndex => Capabilities::MULTIVIEW,
245 Bi::SampleIndex => Capabilities::MULTISAMPLED_SHADING,
246 Bi::NumSubgroups
247 | Bi::SubgroupId
248 | Bi::SubgroupSize
249 | Bi::SubgroupInvocationId => Capabilities::SUBGROUP,
250 _ => Capabilities::empty(),
251 };
252 if !self.capabilities.contains(required) {
253 return Err(VaryingError::UnsupportedCapability(required));
254 }
255
256 if matches!(
257 built_in,
258 crate::BuiltIn::SubgroupId | crate::BuiltIn::SubgroupInvocationId
259 ) && ep.workgroup_size[1..].iter().any(|&s| s > 1)
260 {
261 return Err(VaryingError::InvalidMultiDimensionalSubgroupBuiltIn);
262 }
263
264 let (visible, type_good) = match built_in {
265 Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => (
266 self.stage == St::Vertex && !self.output,
267 *ty_inner == Ti::Scalar(crate::Scalar::U32),
268 ),
269 Bi::DrawID => (
270 (self.stage == St::Vertex
272 || self.stage == St::Task
273 || (self.stage == St::Mesh && !self.has_task_payload))
274 && !self.output,
275 *ty_inner == Ti::Scalar(crate::Scalar::U32),
276 ),
277 Bi::ClipDistance | Bi::CullDistance => (
278 (self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
279 match *ty_inner {
280 Ti::Array { base, size, .. } => {
281 self.types[base].inner == Ti::Scalar(crate::Scalar::F32)
282 && match size {
283 crate::ArraySize::Constant(non_zero) => non_zero.get() <= 8,
284 _ => false,
285 }
286 }
287 _ => false,
288 },
289 ),
290 Bi::PointSize => (
291 (self.stage == St::Vertex || self.stage == St::Mesh) && self.output,
292 *ty_inner == Ti::Scalar(crate::Scalar::F32),
293 ),
294 Bi::PointCoord => (
295 self.stage == St::Fragment && !self.output,
296 *ty_inner
297 == Ti::Vector {
298 size: Vs::Bi,
299 scalar: crate::Scalar::F32,
300 },
301 ),
302 Bi::Position { .. } => (
303 match self.stage {
304 St::Vertex | St::Mesh => self.output,
305 St::Fragment => !self.output,
306 St::Compute | St::Task => false,
307 },
308 *ty_inner
309 == Ti::Vector {
310 size: Vs::Quad,
311 scalar: crate::Scalar::F32,
312 },
313 ),
314 Bi::ViewIndex => (
315 match self.stage {
316 St::Vertex | St::Fragment | St::Task | St::Mesh => !self.output,
317 St::Compute => false,
318 },
319 *ty_inner == Ti::Scalar(crate::Scalar::U32),
320 ),
321 Bi::FragDepth => (
322 self.stage == St::Fragment && self.output,
323 *ty_inner == Ti::Scalar(crate::Scalar::F32),
324 ),
325 Bi::FrontFacing => (
326 self.stage == St::Fragment && !self.output,
327 *ty_inner == Ti::Scalar(crate::Scalar::BOOL),
328 ),
329 Bi::PrimitiveIndex => (
330 (self.stage == St::Fragment && !self.output)
331 || (self.stage == St::Mesh
332 && self.output
333 && self.mesh_output_type == MeshOutputType::PrimitiveOutput),
334 *ty_inner == Ti::Scalar(crate::Scalar::U32),
335 ),
336 Bi::Barycentric { .. } => (
337 self.stage == St::Fragment && !self.output,
338 *ty_inner
339 == Ti::Vector {
340 size: Vs::Tri,
341 scalar: crate::Scalar::F32,
342 },
343 ),
344 Bi::SampleIndex => (
345 self.stage == St::Fragment && !self.output,
346 *ty_inner == Ti::Scalar(crate::Scalar::U32),
347 ),
348 Bi::SampleMask => (
349 self.stage == St::Fragment,
350 *ty_inner == Ti::Scalar(crate::Scalar::U32),
351 ),
352 Bi::LocalInvocationIndex => (
353 self.stage.compute_like() && !self.output,
354 *ty_inner == Ti::Scalar(crate::Scalar::U32),
355 ),
356 Bi::GlobalInvocationId
357 | Bi::LocalInvocationId
358 | Bi::WorkGroupId
359 | Bi::WorkGroupSize
360 | Bi::NumWorkGroups => (
361 self.stage.compute_like() && !self.output,
362 *ty_inner
363 == Ti::Vector {
364 size: Vs::Tri,
365 scalar: crate::Scalar::U32,
366 },
367 ),
368 Bi::NumSubgroups | Bi::SubgroupId => (
369 self.stage.compute_like() && !self.output,
370 *ty_inner == Ti::Scalar(crate::Scalar::U32),
371 ),
372 Bi::SubgroupSize | Bi::SubgroupInvocationId => (
373 match self.stage {
374 St::Compute | St::Fragment | St::Task | St::Mesh => !self.output,
375 St::Vertex => false,
376 },
377 *ty_inner == Ti::Scalar(crate::Scalar::U32),
378 ),
379 Bi::CullPrimitive => (
380 self.mesh_output_type == MeshOutputType::PrimitiveOutput,
381 *ty_inner == Ti::Scalar(crate::Scalar::BOOL),
382 ),
383 Bi::PointIndex => (
384 self.mesh_output_type == MeshOutputType::PrimitiveOutput,
385 *ty_inner == Ti::Scalar(crate::Scalar::U32),
386 ),
387 Bi::LineIndices => (
388 self.mesh_output_type == MeshOutputType::PrimitiveOutput,
389 *ty_inner
390 == Ti::Vector {
391 size: Vs::Bi,
392 scalar: crate::Scalar::U32,
393 },
394 ),
395 Bi::TriangleIndices => (
396 self.mesh_output_type == MeshOutputType::PrimitiveOutput,
397 *ty_inner
398 == Ti::Vector {
399 size: Vs::Tri,
400 scalar: crate::Scalar::U32,
401 },
402 ),
403 Bi::MeshTaskSize => (
404 self.stage == St::Task && self.output,
405 *ty_inner
406 == Ti::Vector {
407 size: Vs::Tri,
408 scalar: crate::Scalar::U32,
409 },
410 ),
411 Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives => {
413 (false, true)
414 }
415 };
416 match built_in {
417 Bi::CullPrimitive
418 | Bi::PointIndex
419 | Bi::LineIndices
420 | Bi::TriangleIndices
421 | Bi::MeshTaskSize
422 | Bi::VertexCount
423 | Bi::PrimitiveCount
424 | Bi::Vertices
425 | Bi::Primitives => {
426 if !self.capabilities.contains(Capabilities::MESH_SHADER) {
427 return Err(VaryingError::UnsupportedCapability(
428 Capabilities::MESH_SHADER,
429 ));
430 }
431 }
432 _ => (),
433 }
434
435 if !visible {
436 return Err(VaryingError::InvalidBuiltInStage(built_in));
437 }
438 if !type_good {
439 return Err(VaryingError::InvalidBuiltInType(built_in, ty_inner.clone()));
440 }
441 }
442 crate::Binding::Location {
443 location,
444 interpolation,
445 sampling,
446 blend_src,
447 per_primitive,
448 } => {
449 if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) {
450 return Err(VaryingError::UnsupportedCapability(
451 Capabilities::MESH_SHADER,
452 ));
453 }
454 if interpolation == Some(crate::Interpolation::PerVertex) {
455 if self.stage != crate::ShaderStage::Fragment {
456 return Err(VaryingError::InvalidInterpolationInStage(
457 crate::Interpolation::PerVertex,
458 crate::ShaderStage::Fragment,
459 ));
460 }
461 if !self.capabilities.contains(Capabilities::PER_VERTEX) {
462 return Err(VaryingError::UnsupportedCapability(
463 Capabilities::PER_VERTEX,
464 ));
465 }
466 }
467 let (ty, ty_inner) = if interpolation == Some(crate::Interpolation::PerVertex) {
470 let three = crate::ArraySize::Constant(core::num::NonZeroU32::new(3).unwrap());
471 match ty_inner {
472 &Ti::Array { base, size, .. } if size == three => {
473 (base, &self.types[base].inner)
474 }
475 _ => return Err(VaryingError::PerVertexNotArrayOfThree),
476 }
477 } else {
478 (ty, ty_inner)
479 };
480
481 if !self.type_info[ty.index()]
483 .flags
484 .contains(super::TypeFlags::IO_SHAREABLE)
485 {
486 return Err(VaryingError::NotIOShareableType(ty));
487 }
488
489 if self.mesh_output_type == MeshOutputType::PrimitiveOutput {
491 if !per_primitive {
493 return Err(VaryingError::MissingPerPrimitive);
494 }
495 } else if self.stage == crate::ShaderStage::Fragment && !self.output {
496 } else if per_primitive {
501 return Err(VaryingError::InvalidPerPrimitive);
503 }
504
505 if let Some(blend_src) = blend_src {
506 if !self
509 .capabilities
510 .contains(Capabilities::DUAL_SOURCE_BLENDING)
511 {
512 return Err(VaryingError::UnsupportedCapability(
513 Capabilities::DUAL_SOURCE_BLENDING,
514 ));
515 }
516 if self.stage != crate::ShaderStage::Fragment {
517 return Err(VaryingError::InvalidAttributeInStage(
518 "blend_src",
519 self.stage,
520 ));
521 }
522 if !self.output {
523 return Err(VaryingError::InvalidInputAttributeInStage(
524 "blend_src",
525 self.stage,
526 ));
527 }
528 if (blend_src != 0 && blend_src != 1) || location != 0 {
529 return Err(VaryingError::InvalidBlendSrcIndex {
530 location,
531 blend_src,
532 });
533 }
534 if !self.blend_src_mask.insert(blend_src as usize) {
535 return Err(VaryingError::BindingCollisionBlendSrc { blend_src });
536 }
537 } else if !self.location_mask.insert(location as usize)
538 && self.flags.contains(super::ValidationFlags::BINDINGS)
539 {
540 return Err(VaryingError::BindingCollision { location });
541 }
542
543 if let Some(interpolation) = interpolation {
544 let invalid_sampling = match (interpolation, sampling) {
545 (_, None)
546 | (
547 crate::Interpolation::Perspective | crate::Interpolation::Linear,
548 Some(
549 crate::Sampling::Center
550 | crate::Sampling::Centroid
551 | crate::Sampling::Sample,
552 ),
553 )
554 | (
555 crate::Interpolation::Flat,
556 Some(crate::Sampling::First | crate::Sampling::Either),
557 ) => None,
558 (_, Some(invalid_sampling)) => Some(invalid_sampling),
559 };
560 if let Some(sampling) = invalid_sampling {
561 return Err(VaryingError::InvalidInterpolationSamplingCombination {
562 interpolation,
563 sampling,
564 });
565 }
566 }
567
568 let needs_interpolation = match self.stage {
569 crate::ShaderStage::Vertex => self.output,
570 crate::ShaderStage::Fragment => !self.output && !per_primitive,
571 crate::ShaderStage::Compute | crate::ShaderStage::Task => false,
572 crate::ShaderStage::Mesh => self.output,
573 };
574
575 let _ = sampling;
579
580 let required = match sampling {
581 Some(crate::Sampling::Sample) => Capabilities::MULTISAMPLED_SHADING,
582 _ => Capabilities::empty(),
583 };
584 if !self.capabilities.contains(required) {
585 return Err(VaryingError::UnsupportedCapability(required));
586 }
587
588 if interpolation != Some(crate::Interpolation::PerVertex) {
589 match ty_inner.scalar_kind() {
590 Some(crate::ScalarKind::Float) => {
591 if needs_interpolation && interpolation.is_none() {
592 return Err(VaryingError::MissingInterpolation);
593 }
594 }
595 Some(_) => {
596 if needs_interpolation
597 && interpolation != Some(crate::Interpolation::Flat)
598 {
599 return Err(VaryingError::InvalidInterpolation);
600 }
601 }
602 None => return Err(VaryingError::InvalidType(ty)),
603 }
604 }
605 }
606 }
607
608 Ok(())
609 }
610
611 fn validate(
612 &mut self,
613 ep: &crate::EntryPoint,
614 ty: Handle<crate::Type>,
615 binding: Option<&crate::Binding>,
616 ) -> Result<(), WithSpan<VaryingError>> {
617 let span_context = self.types.get_span_context(ty);
618 match binding {
619 Some(binding) => self
620 .validate_impl(ep, ty, binding)
621 .map_err(|e| e.with_span_context(span_context)),
622 None => {
623 let crate::TypeInner::Struct { ref members, .. } = self.types[ty].inner else {
624 if self.flags.contains(super::ValidationFlags::BINDINGS) {
625 return Err(VaryingError::MissingBinding.with_span());
626 } else {
627 return Ok(());
628 }
629 };
630
631 for (index, member) in members.iter().enumerate() {
632 let span_context = self.types.get_span_context(ty);
633 match member.binding {
634 None => {
635 if self.flags.contains(super::ValidationFlags::BINDINGS) {
636 return Err(VaryingError::MemberMissingBinding(index as u32)
637 .with_span_context(span_context));
638 }
639 }
640 Some(ref binding) => self
641 .validate_impl(ep, member.ty, binding)
642 .map_err(|e| e.with_span_context(span_context))?,
643 }
644 }
645
646 if !self.blend_src_mask.is_empty() {
647 let span_context = self.types.get_span_context(ty);
648
649 if members.len() != 2 || self.blend_src_mask.len() != 2 {
651 return Err(
652 VaryingError::IncompleteBlendSrcUsage.with_span_context(span_context)
653 );
654 }
655 if members[0].ty != members[1].ty {
657 return Err(VaryingError::BlendSrcOutputTypeMismatch {
658 blend_src_0_type: members[0].ty,
659 blend_src_1_type: members[1].ty,
660 }
661 .with_span_context(span_context));
662 }
663 }
664 Ok(())
665 }
666 }
667 }
668}
669
670impl super::Validator {
671 pub(super) fn validate_global_var(
672 &self,
673 var: &crate::GlobalVariable,
674 gctx: crate::proc::GlobalCtx,
675 mod_info: &ModuleInfo,
676 global_expr_kind: &crate::proc::ExpressionKindTracker,
677 ) -> Result<(), GlobalVariableError> {
678 use super::TypeFlags;
679
680 log::debug!("var {var:?}");
681 let inner_ty = match gctx.types[var.ty].inner {
682 crate::TypeInner::BindingArray { base, .. } => match var.space {
686 crate::AddressSpace::Storage { .. } => {
687 if !self
688 .capabilities
689 .contains(Capabilities::STORAGE_BUFFER_BINDING_ARRAY)
690 {
691 return Err(GlobalVariableError::UnsupportedCapability(
692 Capabilities::STORAGE_BUFFER_BINDING_ARRAY,
693 ));
694 }
695 base
696 }
697 crate::AddressSpace::Uniform => {
698 if !self
699 .capabilities
700 .contains(Capabilities::BUFFER_BINDING_ARRAY)
701 {
702 return Err(GlobalVariableError::UnsupportedCapability(
703 Capabilities::BUFFER_BINDING_ARRAY,
704 ));
705 }
706 base
707 }
708 crate::AddressSpace::Handle => {
709 match gctx.types[base].inner {
710 crate::TypeInner::Image { class, .. } => match class {
711 crate::ImageClass::Storage { .. } => {
712 if !self
713 .capabilities
714 .contains(Capabilities::STORAGE_TEXTURE_BINDING_ARRAY)
715 {
716 return Err(GlobalVariableError::UnsupportedCapability(
717 Capabilities::STORAGE_TEXTURE_BINDING_ARRAY,
718 ));
719 }
720 }
721 crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => {
722 if !self
723 .capabilities
724 .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY)
725 {
726 return Err(GlobalVariableError::UnsupportedCapability(
727 Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY,
728 ));
729 }
730 }
731 crate::ImageClass::External => {
732 unreachable!("binding arrays of external images are not supported");
734 }
735 },
736 crate::TypeInner::Sampler { .. } => {
737 if !self
738 .capabilities
739 .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY)
740 {
741 return Err(GlobalVariableError::UnsupportedCapability(
742 Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY,
743 ));
744 }
745 }
746 crate::TypeInner::AccelerationStructure { .. } => {
747 return Err(GlobalVariableError::InvalidBindingArray(base));
748 }
749 crate::TypeInner::RayQuery { .. } => {
750 unreachable!("binding arrays of ray queries are not supported");
752 }
753 _ => {
754 }
757 }
758 base
759 }
760 _ => return Err(GlobalVariableError::InvalidUsage(var.space)),
761 },
762 _ => var.ty,
763 };
764 let type_info = &self.types[inner_ty.index()];
765
766 let (required_type_flags, is_resource) = match var.space {
767 crate::AddressSpace::Function => {
768 return Err(GlobalVariableError::InvalidUsage(var.space))
769 }
770 crate::AddressSpace::Storage { access } => {
771 if let Err((ty_handle, disalignment)) = type_info.storage_layout {
772 if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
773 return Err(GlobalVariableError::Alignment(
774 var.space,
775 ty_handle,
776 disalignment,
777 ));
778 }
779 }
780 if access == crate::StorageAccess::STORE {
781 return Err(GlobalVariableError::StorageAddressSpaceWriteOnlyNotSupported);
782 }
783 (
784 TypeFlags::DATA | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED,
785 true,
786 )
787 }
788 crate::AddressSpace::Uniform => {
789 if let Err((ty_handle, disalignment)) = type_info.uniform_layout {
790 if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
791 return Err(GlobalVariableError::Alignment(
792 var.space,
793 ty_handle,
794 disalignment,
795 ));
796 }
797 }
798 (
799 TypeFlags::DATA
800 | TypeFlags::COPY
801 | TypeFlags::SIZED
802 | TypeFlags::HOST_SHAREABLE
803 | TypeFlags::CREATION_RESOLVED,
804 true,
805 )
806 }
807 crate::AddressSpace::Handle => {
808 match gctx.types[inner_ty].inner {
809 crate::TypeInner::Image { class, .. } => match class {
810 crate::ImageClass::Storage {
811 format:
812 crate::StorageFormat::R16Unorm
813 | crate::StorageFormat::R16Snorm
814 | crate::StorageFormat::Rg16Unorm
815 | crate::StorageFormat::Rg16Snorm
816 | crate::StorageFormat::Rgba16Unorm
817 | crate::StorageFormat::Rgba16Snorm,
818 ..
819 } => {
820 if !self
821 .capabilities
822 .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS)
823 {
824 return Err(GlobalVariableError::UnsupportedCapability(
825 Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS,
826 ));
827 }
828 }
829 _ => {}
830 },
831 crate::TypeInner::Sampler { .. }
832 | crate::TypeInner::AccelerationStructure { .. }
833 | crate::TypeInner::RayQuery { .. } => {}
834 _ => {
835 return Err(GlobalVariableError::InvalidType(var.space));
836 }
837 }
838
839 (TypeFlags::empty(), true)
840 }
841 crate::AddressSpace::Private => (
842 TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED,
843 false,
844 ),
845 crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false),
846 crate::AddressSpace::TaskPayload => {
847 if !self.capabilities.contains(Capabilities::MESH_SHADER) {
848 return Err(GlobalVariableError::UnsupportedCapability(
849 Capabilities::MESH_SHADER,
850 ));
851 }
852 (TypeFlags::DATA | TypeFlags::SIZED, false)
853 }
854 crate::AddressSpace::Immediate => {
855 if !self.capabilities.contains(Capabilities::IMMEDIATES) {
856 return Err(GlobalVariableError::UnsupportedCapability(
857 Capabilities::IMMEDIATES,
858 ));
859 }
860 if let Err(ref err) = type_info.immediates_compatibility {
861 return Err(GlobalVariableError::InvalidImmediateType(err.clone()));
862 }
863 (
864 TypeFlags::DATA
865 | TypeFlags::COPY
866 | TypeFlags::HOST_SHAREABLE
867 | TypeFlags::SIZED,
868 false,
869 )
870 }
871 };
872
873 if !type_info.flags.contains(required_type_flags) {
874 return Err(GlobalVariableError::MissingTypeFlags {
875 seen: type_info.flags,
876 required: required_type_flags,
877 });
878 }
879
880 if is_resource != var.binding.is_some() {
881 if self.flags.contains(super::ValidationFlags::BINDINGS) {
882 return Err(GlobalVariableError::InvalidBinding);
883 }
884 }
885
886 if var.space == crate::AddressSpace::TaskPayload {
887 let ty = &gctx.types[var.ty].inner;
888 if ty.try_size(gctx) == Some(0) {
890 return Err(GlobalVariableError::ZeroSizedTaskPayload);
891 }
892 }
893
894 if let Some(init) = var.init {
895 match var.space {
896 crate::AddressSpace::Private | crate::AddressSpace::Function => {}
897 _ => {
898 return Err(GlobalVariableError::InitializerNotAllowed(var.space));
899 }
900 }
901
902 if !global_expr_kind.is_const_or_override(init) {
903 return Err(GlobalVariableError::InitializerExprType);
904 }
905
906 if !gctx.compare_types(
907 &crate::proc::TypeResolution::Handle(var.ty),
908 &mod_info[init],
909 ) {
910 return Err(GlobalVariableError::InitializerType);
911 }
912 }
913
914 Ok(())
915 }
916
917 fn validate_mesh_output_type(
919 &mut self,
920 ep: &crate::EntryPoint,
921 module: &crate::Module,
922 ty: Handle<crate::Type>,
923 mesh_output_type: MeshOutputType,
924 ) -> Result<(), WithSpan<EntryPointError>> {
925 if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) {
926 return Err(EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types));
927 }
928 let mut result_built_ins = crate::FastHashSet::default();
929 let mut ctx = VaryingContext {
930 stage: ep.stage,
931 output: true,
932 types: &module.types,
933 type_info: &self.types,
934 location_mask: &mut self.location_mask,
935 blend_src_mask: &mut self.blend_src_mask,
936 built_ins: &mut result_built_ins,
937 capabilities: self.capabilities,
938 flags: self.flags,
939 mesh_output_type,
940 has_task_payload: ep.task_payload.is_some(),
941 };
942 ctx.validate(ep, ty, None)
943 .map_err_inner(|e| EntryPointError::Result(e).with_span())?;
944 if mesh_output_type == MeshOutputType::PrimitiveOutput {
945 let mut num_indices_builtins = 0;
946 if result_built_ins.contains(&crate::BuiltIn::PointIndex) {
947 num_indices_builtins += 1;
948 }
949 if result_built_ins.contains(&crate::BuiltIn::LineIndices) {
950 num_indices_builtins += 1;
951 }
952 if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) {
953 num_indices_builtins += 1;
954 }
955 if num_indices_builtins != 1 {
956 return Err(EntryPointError::InvalidMeshPrimitiveOutputType
957 .with_span_handle(ty, &module.types));
958 }
959 } else if mesh_output_type == MeshOutputType::VertexOutput
960 && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false })
961 {
962 return Err(
963 EntryPointError::MissingVertexOutputPosition.with_span_handle(ty, &module.types)
964 );
965 }
966
967 Ok(())
968 }
969
970 pub(super) fn validate_entry_point(
971 &mut self,
972 ep: &crate::EntryPoint,
973 module: &crate::Module,
974 mod_info: &ModuleInfo,
975 ) -> Result<FunctionInfo, WithSpan<EntryPointError>> {
976 if matches!(
977 ep.stage,
978 crate::ShaderStage::Task | crate::ShaderStage::Mesh
979 ) && !self.capabilities.contains(Capabilities::MESH_SHADER)
980 {
981 return Err(
982 EntryPointError::UnsupportedCapability(Capabilities::MESH_SHADER).with_span(),
983 );
984 }
985 if ep.early_depth_test.is_some() {
986 let required = Capabilities::EARLY_DEPTH_TEST;
987 if !self.capabilities.contains(required) {
988 return Err(
989 EntryPointError::Result(VaryingError::UnsupportedCapability(required))
990 .with_span(),
991 );
992 }
993
994 if ep.stage != crate::ShaderStage::Fragment {
995 return Err(EntryPointError::UnexpectedEarlyDepthTest.with_span());
996 }
997 }
998
999 if ep.stage.compute_like() {
1000 if ep
1001 .workgroup_size
1002 .iter()
1003 .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE)
1004 {
1005 return Err(EntryPointError::OutOfRangeWorkgroupSize.with_span());
1006 }
1007 } else if ep.workgroup_size != [0; 3] {
1008 return Err(EntryPointError::UnexpectedWorkgroupSize.with_span());
1009 }
1010
1011 match (ep.stage, &ep.mesh_info) {
1012 (crate::ShaderStage::Mesh, &None) => {
1013 return Err(EntryPointError::ExpectedMeshShaderAttributes.with_span());
1014 }
1015 (crate::ShaderStage::Mesh, &Some(..)) => {}
1016 (_, &Some(_)) => {
1017 return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span());
1018 }
1019 (_, _) => {}
1020 }
1021
1022 let mut info = self
1023 .validate_function(&ep.function, module, mod_info, true)
1024 .map_err(WithSpan::into_other)?;
1025
1026 match ep.stage {
1028 crate::ShaderStage::Task => {
1030 let Some(handle) = ep.task_payload else {
1031 return Err(EntryPointError::ExpectedTaskPayload.with_span());
1032 };
1033 if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
1034 return Err(EntryPointError::TaskPayloadWrongAddressSpace
1035 .with_span_handle(handle, &module.global_variables));
1036 }
1037 info.insert_global_use(GlobalUse::READ | GlobalUse::WRITE, handle);
1038 }
1039
1040 crate::ShaderStage::Mesh => {
1042 if let Some(handle) = ep.task_payload {
1043 if module.global_variables[handle].space != crate::AddressSpace::TaskPayload {
1044 return Err(EntryPointError::TaskPayloadWrongAddressSpace
1045 .with_span_handle(handle, &module.global_variables));
1046 }
1047 info.insert_global_use(GlobalUse::READ, handle);
1048 }
1049 if let Some(ref mesh_info) = ep.mesh_info {
1050 info.insert_global_use(GlobalUse::READ, mesh_info.output_variable);
1051 }
1052 }
1053
1054 _ => {
1056 if let Some(handle) = ep.task_payload {
1057 return Err(EntryPointError::UnexpectedTaskPayload
1058 .with_span_handle(handle, &module.global_variables));
1059 }
1060 }
1061 }
1062
1063 {
1064 use super::ShaderStages;
1065
1066 let stage_bit = match ep.stage {
1067 crate::ShaderStage::Vertex => ShaderStages::VERTEX,
1068 crate::ShaderStage::Fragment => ShaderStages::FRAGMENT,
1069 crate::ShaderStage::Compute => ShaderStages::COMPUTE,
1070 crate::ShaderStage::Mesh => ShaderStages::MESH,
1071 crate::ShaderStage::Task => ShaderStages::TASK,
1072 };
1073
1074 if !info.available_stages.contains(stage_bit) {
1075 return Err(EntryPointError::ForbiddenStageOperations.with_span());
1076 }
1077 }
1078
1079 self.location_mask.clear();
1080 let mut argument_built_ins = crate::FastHashSet::default();
1081 for (index, fa) in ep.function.arguments.iter().enumerate() {
1083 let mut ctx = VaryingContext {
1084 stage: ep.stage,
1085 output: false,
1086 types: &module.types,
1087 type_info: &self.types,
1088 location_mask: &mut self.location_mask,
1089 blend_src_mask: &mut self.blend_src_mask,
1090 built_ins: &mut argument_built_ins,
1091 capabilities: self.capabilities,
1092 flags: self.flags,
1093 mesh_output_type: MeshOutputType::None,
1094 has_task_payload: ep.task_payload.is_some(),
1095 };
1096 ctx.validate(ep, fa.ty, fa.binding.as_ref())
1097 .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?;
1098 }
1099
1100 self.location_mask.clear();
1101 if let Some(ref fr) = ep.function.result {
1102 let mut result_built_ins = crate::FastHashSet::default();
1103 let mut ctx = VaryingContext {
1104 stage: ep.stage,
1105 output: true,
1106 types: &module.types,
1107 type_info: &self.types,
1108 location_mask: &mut self.location_mask,
1109 blend_src_mask: &mut self.blend_src_mask,
1110 built_ins: &mut result_built_ins,
1111 capabilities: self.capabilities,
1112 flags: self.flags,
1113 mesh_output_type: MeshOutputType::None,
1114 has_task_payload: ep.task_payload.is_some(),
1115 };
1116 ctx.validate(ep, fr.ty, fr.binding.as_ref())
1117 .map_err_inner(|e| EntryPointError::Result(e).with_span())?;
1118 if ep.stage == crate::ShaderStage::Vertex
1119 && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false })
1120 {
1121 return Err(EntryPointError::MissingVertexOutputPosition.with_span());
1122 }
1123 if ep.stage == crate::ShaderStage::Mesh {
1124 return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span());
1125 }
1126 if ep.stage == crate::ShaderStage::Task {
1128 let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize)
1129 && result_built_ins.len() == 1
1130 && self.location_mask.is_empty();
1131 if !ok {
1132 return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
1133 }
1134 }
1135 if !self.blend_src_mask.is_empty() {
1136 info.dual_source_blending = true;
1137 }
1138 } else if ep.stage == crate::ShaderStage::Vertex {
1139 return Err(EntryPointError::MissingVertexOutputPosition.with_span());
1140 } else if ep.stage == crate::ShaderStage::Task {
1141 return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
1142 }
1143
1144 {
1145 let mut used_immediates = module
1146 .global_variables
1147 .iter()
1148 .filter(|&(_, var)| var.space == crate::AddressSpace::Immediate)
1149 .map(|(handle, _)| handle)
1150 .filter(|&handle| !info[handle].is_empty());
1151 if let Some(handle) = used_immediates.nth(1) {
1154 return Err(EntryPointError::MoreThanOneImmediateUsed
1155 .with_span_handle(handle, &module.global_variables));
1156 }
1157 }
1158
1159 self.ep_resource_bindings.clear();
1160 for (var_handle, var) in module.global_variables.iter() {
1161 let usage = info[var_handle];
1162 if usage.is_empty() {
1163 continue;
1164 }
1165
1166 if var.space == crate::AddressSpace::TaskPayload {
1167 if ep.task_payload != Some(var_handle) {
1168 return Err(EntryPointError::WrongTaskPayloadUsed
1169 .with_span_handle(var_handle, &module.global_variables));
1170 }
1171 let size = module.types[var.ty].inner.size(module.to_ctx());
1172 if size < 4 {
1173 return Err(EntryPointError::TaskPayloadTooSmall(size)
1174 .with_span_handle(var_handle, &module.global_variables));
1175 }
1176 }
1177
1178 let allowed_usage = match var.space {
1179 crate::AddressSpace::Function => unreachable!(),
1180 crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY,
1181 crate::AddressSpace::Storage { access } => storage_usage(access),
1182 crate::AddressSpace::Handle => match module.types[var.ty].inner {
1183 crate::TypeInner::BindingArray { base, .. } => match module.types[base].inner {
1184 crate::TypeInner::Image {
1185 class: crate::ImageClass::Storage { access, .. },
1186 ..
1187 } => storage_usage(access),
1188 _ => GlobalUse::READ | GlobalUse::QUERY,
1189 },
1190 crate::TypeInner::Image {
1191 class: crate::ImageClass::Storage { access, .. },
1192 ..
1193 } => storage_usage(access),
1194 _ => GlobalUse::READ | GlobalUse::QUERY,
1195 },
1196 crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {
1197 GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY
1198 }
1199 crate::AddressSpace::TaskPayload => {
1200 GlobalUse::READ
1201 | GlobalUse::QUERY
1202 | if ep.stage == crate::ShaderStage::Task {
1203 GlobalUse::WRITE
1204 } else {
1205 GlobalUse::empty()
1206 }
1207 }
1208 crate::AddressSpace::Immediate => GlobalUse::READ,
1209 };
1210 if !allowed_usage.contains(usage) {
1211 log::warn!("\tUsage error for: {var:?}");
1212 log::warn!("\tAllowed usage: {allowed_usage:?}, requested: {usage:?}");
1213 return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage)
1214 .with_span_handle(var_handle, &module.global_variables));
1215 }
1216
1217 if let Some(ref bind) = var.binding {
1218 if !self.ep_resource_bindings.insert(*bind) {
1219 if self.flags.contains(super::ValidationFlags::BINDINGS) {
1220 return Err(EntryPointError::BindingCollision(var_handle)
1221 .with_span_handle(var_handle, &module.global_variables));
1222 }
1223 }
1224 }
1225 }
1226
1227 if let &Some(ref mesh_info) = &ep.mesh_info {
1230 if module.global_variables[mesh_info.output_variable].space
1231 != crate::AddressSpace::WorkGroup
1232 {
1233 return Err(EntryPointError::WrongMeshOutputAddressSpace.with_span());
1234 }
1235
1236 let mut implied = module.analyze_mesh_shader_info(mesh_info.output_variable);
1237 if let Some(e) = implied.2 {
1238 return Err(e);
1239 }
1240
1241 if let Some(e) = mesh_info.max_vertices_override {
1242 if let crate::Expression::Override(o) = module.global_expressions[e] {
1243 if implied.1[0] != Some(o) {
1244 return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1245 }
1246 }
1247 }
1248 if let Some(e) = mesh_info.max_primitives_override {
1249 if let crate::Expression::Override(o) = module.global_expressions[e] {
1250 if implied.1[1] != Some(o) {
1251 return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1252 }
1253 }
1254 }
1255
1256 implied.0.max_vertices_override = mesh_info.max_vertices_override;
1257 implied.0.max_primitives_override = mesh_info.max_primitives_override;
1258 if implied.0 != *mesh_info {
1259 return Err(EntryPointError::BadMeshOutputVariableType.with_span());
1260 }
1261 if mesh_info.topology == crate::MeshOutputTopology::Points
1262 && !self
1263 .capabilities
1264 .contains(Capabilities::MESH_SHADER_POINT_TOPOLOGY)
1265 {
1266 return Err(EntryPointError::UnsupportedCapability(
1267 Capabilities::MESH_SHADER_POINT_TOPOLOGY,
1268 )
1269 .with_span());
1270 }
1271
1272 self.validate_mesh_output_type(
1273 ep,
1274 module,
1275 mesh_info.vertex_output_type,
1276 MeshOutputType::VertexOutput,
1277 )?;
1278 self.validate_mesh_output_type(
1279 ep,
1280 module,
1281 mesh_info.primitive_output_type,
1282 MeshOutputType::PrimitiveOutput,
1283 )?;
1284 }
1285
1286 Ok(info)
1287 }
1288}