1use alloc::vec::Vec;
2
3use bit_set::BitSet;
4
5use super::{
6 analyzer::{FunctionInfo, GlobalUse},
7 Capabilities, Disalignment, FunctionError, ModuleInfo, PushConstantError,
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 flags {seen:?} do not meet the required {required:?}")]
22 MissingTypeFlags {
23 required: super::TypeFlags,
24 seen: super::TypeFlags,
25 },
26 #[error("Capability {0:?} is not supported")]
27 UnsupportedCapability(Capabilities),
28 #[error("Binding decoration is missing or not applicable")]
29 InvalidBinding,
30 #[error("Alignment requirements for address space {0:?} are not met by {1:?}")]
31 Alignment(
32 crate::AddressSpace,
33 Handle<crate::Type>,
34 #[source] Disalignment,
35 ),
36 #[error("Initializer must be an override-expression")]
37 InitializerExprType,
38 #[error("Initializer doesn't match the variable type")]
39 InitializerType,
40 #[error("Initializer can't be used with address space {0:?}")]
41 InitializerNotAllowed(crate::AddressSpace),
42 #[error("Storage address space doesn't support write-only access")]
43 StorageAddressSpaceWriteOnlyNotSupported,
44 #[error("Type is not valid for use as a push constant")]
45 InvalidPushConstantType(#[source] PushConstantError),
46}
47
48#[derive(Clone, Debug, thiserror::Error)]
49#[cfg_attr(test, derive(PartialEq))]
50pub enum VaryingError {
51 #[error("The type {0:?} does not match the varying")]
52 InvalidType(Handle<crate::Type>),
53 #[error("The type {0:?} cannot be used for user-defined entry point inputs or outputs")]
54 NotIOShareableType(Handle<crate::Type>),
55 #[error("Interpolation is not valid")]
56 InvalidInterpolation,
57 #[error("Cannot combine {interpolation:?} interpolation with the {sampling:?} sample type")]
58 InvalidInterpolationSamplingCombination {
59 interpolation: crate::Interpolation,
60 sampling: crate::Sampling,
61 },
62 #[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")]
63 MissingInterpolation,
64 #[error("Built-in {0:?} is not available at this stage")]
65 InvalidBuiltInStage(crate::BuiltIn),
66 #[error("Built-in type for {0:?} is invalid")]
67 InvalidBuiltInType(crate::BuiltIn),
68 #[error("Entry point arguments and return values must all have bindings")]
69 MissingBinding,
70 #[error("Struct member {0} is missing a binding")]
71 MemberMissingBinding(u32),
72 #[error("Multiple bindings at location {location} are present")]
73 BindingCollision { location: u32 },
74 #[error("Multiple bindings use the same `blend_src` {blend_src}")]
75 BindingCollisionBlendSrc { blend_src: u32 },
76 #[error("Built-in {0:?} is present more than once")]
77 DuplicateBuiltIn(crate::BuiltIn),
78 #[error("Capability {0:?} is not supported")]
79 UnsupportedCapability(Capabilities),
80 #[error("The attribute {0:?} is only valid as an output for stage {1:?}")]
81 InvalidInputAttributeInStage(&'static str, crate::ShaderStage),
82 #[error("The attribute {0:?} is not valid for stage {1:?}")]
83 InvalidAttributeInStage(&'static str, crate::ShaderStage),
84 #[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}.")]
85 InvalidBlendSrcIndex { location: u32, blend_src: u32 },
86 #[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)`.")]
87 IncompleteBlendSrcUsage,
88 #[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:?}.")]
89 BlendSrcOutputTypeMismatch {
90 blend_src_0_type: Handle<crate::Type>,
91 blend_src_1_type: Handle<crate::Type>,
92 },
93 #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")]
94 InvalidMultiDimensionalSubgroupBuiltIn,
95}
96
97#[derive(Clone, Debug, thiserror::Error)]
98#[cfg_attr(test, derive(PartialEq))]
99pub enum EntryPointError {
100 #[error("Multiple conflicting entry points")]
101 Conflict,
102 #[error("Vertex shaders must return a `@builtin(position)` output value")]
103 MissingVertexOutputPosition,
104 #[error("Early depth test is not applicable")]
105 UnexpectedEarlyDepthTest,
106 #[error("Workgroup size is not applicable")]
107 UnexpectedWorkgroupSize,
108 #[error("Workgroup size is out of range")]
109 OutOfRangeWorkgroupSize,
110 #[error("Uses operations forbidden at this stage")]
111 ForbiddenStageOperations,
112 #[error("Global variable {0:?} is used incorrectly as {1:?}")]
113 InvalidGlobalUsage(Handle<crate::GlobalVariable>, GlobalUse),
114 #[error("More than 1 push constant variable is used")]
115 MoreThanOnePushConstantUsed,
116 #[error("Bindings for {0:?} conflict with other resource")]
117 BindingCollision(Handle<crate::GlobalVariable>),
118 #[error("Argument {0} varying error")]
119 Argument(u32, #[source] VaryingError),
120 #[error(transparent)]
121 Result(#[from] VaryingError),
122 #[error("Location {location} interpolation of an integer has to be flat")]
123 InvalidIntegerInterpolation { location: u32 },
124 #[error(transparent)]
125 Function(#[from] FunctionError),
126}
127
128fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
129 let mut storage_usage = GlobalUse::QUERY;
130 if access.contains(crate::StorageAccess::LOAD) {
131 storage_usage |= GlobalUse::READ;
132 }
133 if access.contains(crate::StorageAccess::STORE) {
134 storage_usage |= GlobalUse::WRITE;
135 }
136 if access.contains(crate::StorageAccess::ATOMIC) {
137 storage_usage |= GlobalUse::ATOMIC;
138 }
139 storage_usage
140}
141
142struct VaryingContext<'a> {
143 stage: crate::ShaderStage,
144 output: bool,
145 types: &'a UniqueArena<crate::Type>,
146 type_info: &'a Vec<super::r#type::TypeInfo>,
147 location_mask: &'a mut BitSet,
148 blend_src_mask: &'a mut BitSet,
149 built_ins: &'a mut crate::FastHashSet<crate::BuiltIn>,
150 capabilities: Capabilities,
151 flags: super::ValidationFlags,
152}
153
154impl VaryingContext<'_> {
155 fn validate_impl(
156 &mut self,
157 ep: &crate::EntryPoint,
158 ty: Handle<crate::Type>,
159 binding: &crate::Binding,
160 ) -> Result<(), VaryingError> {
161 use crate::{BuiltIn as Bi, ShaderStage as St, TypeInner as Ti, VectorSize as Vs};
162
163 let ty_inner = &self.types[ty].inner;
164 match *binding {
165 crate::Binding::BuiltIn(built_in) => {
166 let canonical = if let crate::BuiltIn::Position { .. } = built_in {
169 crate::BuiltIn::Position { invariant: false }
170 } else {
171 built_in
172 };
173
174 if self.built_ins.contains(&canonical) {
175 return Err(VaryingError::DuplicateBuiltIn(built_in));
176 }
177 self.built_ins.insert(canonical);
178
179 let required = match built_in {
180 Bi::ClipDistance => Capabilities::CLIP_DISTANCE,
181 Bi::CullDistance => Capabilities::CULL_DISTANCE,
182 Bi::PrimitiveIndex => Capabilities::PRIMITIVE_INDEX,
183 Bi::ViewIndex => Capabilities::MULTIVIEW,
184 Bi::SampleIndex => Capabilities::MULTISAMPLED_SHADING,
185 Bi::NumSubgroups
186 | Bi::SubgroupId
187 | Bi::SubgroupSize
188 | Bi::SubgroupInvocationId => Capabilities::SUBGROUP,
189 _ => Capabilities::empty(),
190 };
191 if !self.capabilities.contains(required) {
192 return Err(VaryingError::UnsupportedCapability(required));
193 }
194
195 if matches!(
196 built_in,
197 crate::BuiltIn::SubgroupId | crate::BuiltIn::SubgroupInvocationId
198 ) && ep.workgroup_size[1..].iter().any(|&s| s > 1)
199 {
200 return Err(VaryingError::InvalidMultiDimensionalSubgroupBuiltIn);
201 }
202
203 let (visible, type_good) = match built_in {
204 Bi::BaseInstance
205 | Bi::BaseVertex
206 | Bi::InstanceIndex
207 | Bi::VertexIndex
208 | Bi::DrawID => (
209 self.stage == St::Vertex && !self.output,
210 *ty_inner == Ti::Scalar(crate::Scalar::U32),
211 ),
212 Bi::ClipDistance | Bi::CullDistance => (
213 self.stage == St::Vertex && self.output,
214 match *ty_inner {
215 Ti::Array { base, size, .. } => {
216 self.types[base].inner == Ti::Scalar(crate::Scalar::F32)
217 && match size {
218 crate::ArraySize::Constant(non_zero) => non_zero.get() <= 8,
219 _ => false,
220 }
221 }
222 _ => false,
223 },
224 ),
225 Bi::PointSize => (
226 self.stage == St::Vertex && self.output,
227 *ty_inner == Ti::Scalar(crate::Scalar::F32),
228 ),
229 Bi::PointCoord => (
230 self.stage == St::Fragment && !self.output,
231 *ty_inner
232 == Ti::Vector {
233 size: Vs::Bi,
234 scalar: crate::Scalar::F32,
235 },
236 ),
237 Bi::Position { .. } => (
238 match self.stage {
239 St::Vertex => self.output,
240 St::Fragment => !self.output,
241 St::Compute => false,
242 St::Task | St::Mesh => unreachable!(),
243 },
244 *ty_inner
245 == Ti::Vector {
246 size: Vs::Quad,
247 scalar: crate::Scalar::F32,
248 },
249 ),
250 Bi::ViewIndex => (
251 match self.stage {
252 St::Vertex | St::Fragment => !self.output,
253 St::Compute => false,
254 St::Task | St::Mesh => unreachable!(),
255 },
256 *ty_inner == Ti::Scalar(crate::Scalar::I32),
257 ),
258 Bi::FragDepth => (
259 self.stage == St::Fragment && self.output,
260 *ty_inner == Ti::Scalar(crate::Scalar::F32),
261 ),
262 Bi::FrontFacing => (
263 self.stage == St::Fragment && !self.output,
264 *ty_inner == Ti::Scalar(crate::Scalar::BOOL),
265 ),
266 Bi::PrimitiveIndex => (
267 self.stage == St::Fragment && !self.output,
268 *ty_inner == Ti::Scalar(crate::Scalar::U32),
269 ),
270 Bi::SampleIndex => (
271 self.stage == St::Fragment && !self.output,
272 *ty_inner == Ti::Scalar(crate::Scalar::U32),
273 ),
274 Bi::SampleMask => (
275 self.stage == St::Fragment,
276 *ty_inner == Ti::Scalar(crate::Scalar::U32),
277 ),
278 Bi::LocalInvocationIndex => (
279 self.stage == St::Compute && !self.output,
280 *ty_inner == Ti::Scalar(crate::Scalar::U32),
281 ),
282 Bi::GlobalInvocationId
283 | Bi::LocalInvocationId
284 | Bi::WorkGroupId
285 | Bi::WorkGroupSize
286 | Bi::NumWorkGroups => (
287 self.stage == St::Compute && !self.output,
288 *ty_inner
289 == Ti::Vector {
290 size: Vs::Tri,
291 scalar: crate::Scalar::U32,
292 },
293 ),
294 Bi::NumSubgroups | Bi::SubgroupId => (
295 self.stage == St::Compute && !self.output,
296 *ty_inner == Ti::Scalar(crate::Scalar::U32),
297 ),
298 Bi::SubgroupSize | Bi::SubgroupInvocationId => (
299 match self.stage {
300 St::Compute | St::Fragment => !self.output,
301 St::Vertex => false,
302 St::Task | St::Mesh => unreachable!(),
303 },
304 *ty_inner == Ti::Scalar(crate::Scalar::U32),
305 ),
306 };
307
308 if !visible {
309 return Err(VaryingError::InvalidBuiltInStage(built_in));
310 }
311 if !type_good {
312 log::warn!("Wrong builtin type: {ty_inner:?}");
313 return Err(VaryingError::InvalidBuiltInType(built_in));
314 }
315 }
316 crate::Binding::Location {
317 location,
318 interpolation,
319 sampling,
320 blend_src,
321 } => {
322 if !self.type_info[ty.index()]
324 .flags
325 .contains(super::TypeFlags::IO_SHAREABLE)
326 {
327 return Err(VaryingError::NotIOShareableType(ty));
328 }
329
330 if let Some(blend_src) = blend_src {
331 if !self
334 .capabilities
335 .contains(Capabilities::DUAL_SOURCE_BLENDING)
336 {
337 return Err(VaryingError::UnsupportedCapability(
338 Capabilities::DUAL_SOURCE_BLENDING,
339 ));
340 }
341 if self.stage != crate::ShaderStage::Fragment {
342 return Err(VaryingError::InvalidAttributeInStage(
343 "blend_src",
344 self.stage,
345 ));
346 }
347 if !self.output {
348 return Err(VaryingError::InvalidInputAttributeInStage(
349 "blend_src",
350 self.stage,
351 ));
352 }
353 if (blend_src != 0 && blend_src != 1) || location != 0 {
354 return Err(VaryingError::InvalidBlendSrcIndex {
355 location,
356 blend_src,
357 });
358 }
359 if !self.blend_src_mask.insert(blend_src as usize) {
360 return Err(VaryingError::BindingCollisionBlendSrc { blend_src });
361 }
362 } else if !self.location_mask.insert(location as usize)
363 && self.flags.contains(super::ValidationFlags::BINDINGS)
364 {
365 return Err(VaryingError::BindingCollision { location });
366 }
367
368 if let Some(interpolation) = interpolation {
369 let invalid_sampling = match (interpolation, sampling) {
370 (_, None)
371 | (
372 crate::Interpolation::Perspective | crate::Interpolation::Linear,
373 Some(
374 crate::Sampling::Center
375 | crate::Sampling::Centroid
376 | crate::Sampling::Sample,
377 ),
378 )
379 | (
380 crate::Interpolation::Flat,
381 Some(crate::Sampling::First | crate::Sampling::Either),
382 ) => None,
383 (_, Some(invalid_sampling)) => Some(invalid_sampling),
384 };
385 if let Some(sampling) = invalid_sampling {
386 return Err(VaryingError::InvalidInterpolationSamplingCombination {
387 interpolation,
388 sampling,
389 });
390 }
391 }
392
393 let needs_interpolation = match self.stage {
394 crate::ShaderStage::Vertex => self.output,
395 crate::ShaderStage::Fragment => !self.output,
396 crate::ShaderStage::Compute => false,
397 crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
398 };
399
400 let _ = sampling;
404
405 let required = match sampling {
406 Some(crate::Sampling::Sample) => Capabilities::MULTISAMPLED_SHADING,
407 _ => Capabilities::empty(),
408 };
409 if !self.capabilities.contains(required) {
410 return Err(VaryingError::UnsupportedCapability(required));
411 }
412
413 match ty_inner.scalar_kind() {
414 Some(crate::ScalarKind::Float) => {
415 if needs_interpolation && interpolation.is_none() {
416 return Err(VaryingError::MissingInterpolation);
417 }
418 }
419 Some(_) => {
420 if needs_interpolation && interpolation != Some(crate::Interpolation::Flat)
421 {
422 return Err(VaryingError::InvalidInterpolation);
423 }
424 }
425 None => return Err(VaryingError::InvalidType(ty)),
426 }
427 }
428 }
429
430 Ok(())
431 }
432
433 fn validate(
434 &mut self,
435 ep: &crate::EntryPoint,
436 ty: Handle<crate::Type>,
437 binding: Option<&crate::Binding>,
438 ) -> Result<(), WithSpan<VaryingError>> {
439 let span_context = self.types.get_span_context(ty);
440 match binding {
441 Some(binding) => self
442 .validate_impl(ep, ty, binding)
443 .map_err(|e| e.with_span_context(span_context)),
444 None => {
445 match self.types[ty].inner {
446 crate::TypeInner::Struct { ref members, .. } => {
447 for (index, member) in members.iter().enumerate() {
448 let span_context = self.types.get_span_context(ty);
449 match member.binding {
450 None => {
451 if self.flags.contains(super::ValidationFlags::BINDINGS) {
452 return Err(VaryingError::MemberMissingBinding(
453 index as u32,
454 )
455 .with_span_context(span_context));
456 }
457 }
458 Some(ref binding) => self
459 .validate_impl(ep, member.ty, binding)
460 .map_err(|e| e.with_span_context(span_context))?,
461 }
462 }
463
464 if !self.blend_src_mask.is_empty() {
465 let span_context = self.types.get_span_context(ty);
466
467 if members.len() != 2 || self.blend_src_mask.len() != 2 {
469 return Err(VaryingError::IncompleteBlendSrcUsage
470 .with_span_context(span_context));
471 }
472 if members[0].ty != members[1].ty {
474 return Err(VaryingError::BlendSrcOutputTypeMismatch {
475 blend_src_0_type: members[0].ty,
476 blend_src_1_type: members[1].ty,
477 }
478 .with_span_context(span_context));
479 }
480 }
481 }
482 _ => {
483 if self.flags.contains(super::ValidationFlags::BINDINGS) {
484 return Err(VaryingError::MissingBinding.with_span());
485 }
486 }
487 }
488 Ok(())
489 }
490 }
491 }
492}
493
494impl super::Validator {
495 pub(super) fn validate_global_var(
496 &self,
497 var: &crate::GlobalVariable,
498 gctx: crate::proc::GlobalCtx,
499 mod_info: &ModuleInfo,
500 global_expr_kind: &crate::proc::ExpressionKindTracker,
501 ) -> Result<(), GlobalVariableError> {
502 use super::TypeFlags;
503
504 log::debug!("var {var:?}");
505 let inner_ty = match gctx.types[var.ty].inner {
506 crate::TypeInner::BindingArray { base, .. } => match var.space {
510 crate::AddressSpace::Storage { .. }
511 | crate::AddressSpace::Uniform
512 | crate::AddressSpace::Handle => base,
513 _ => return Err(GlobalVariableError::InvalidUsage(var.space)),
514 },
515 _ => var.ty,
516 };
517 let type_info = &self.types[inner_ty.index()];
518
519 let (required_type_flags, is_resource) = match var.space {
520 crate::AddressSpace::Function => {
521 return Err(GlobalVariableError::InvalidUsage(var.space))
522 }
523 crate::AddressSpace::Storage { access } => {
524 if let Err((ty_handle, disalignment)) = type_info.storage_layout {
525 if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
526 return Err(GlobalVariableError::Alignment(
527 var.space,
528 ty_handle,
529 disalignment,
530 ));
531 }
532 }
533 if access == crate::StorageAccess::STORE {
534 return Err(GlobalVariableError::StorageAddressSpaceWriteOnlyNotSupported);
535 }
536 (
537 TypeFlags::DATA | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED,
538 true,
539 )
540 }
541 crate::AddressSpace::Uniform => {
542 if let Err((ty_handle, disalignment)) = type_info.uniform_layout {
543 if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) {
544 return Err(GlobalVariableError::Alignment(
545 var.space,
546 ty_handle,
547 disalignment,
548 ));
549 }
550 }
551 (
552 TypeFlags::DATA
553 | TypeFlags::COPY
554 | TypeFlags::SIZED
555 | TypeFlags::HOST_SHAREABLE
556 | TypeFlags::CREATION_RESOLVED,
557 true,
558 )
559 }
560 crate::AddressSpace::Handle => {
561 match gctx.types[inner_ty].inner {
562 crate::TypeInner::Image { class, .. } => match class {
563 crate::ImageClass::Storage {
564 format:
565 crate::StorageFormat::R16Unorm
566 | crate::StorageFormat::R16Snorm
567 | crate::StorageFormat::Rg16Unorm
568 | crate::StorageFormat::Rg16Snorm
569 | crate::StorageFormat::Rgba16Unorm
570 | crate::StorageFormat::Rgba16Snorm,
571 ..
572 } => {
573 if !self
574 .capabilities
575 .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS)
576 {
577 return Err(GlobalVariableError::UnsupportedCapability(
578 Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS,
579 ));
580 }
581 }
582 _ => {}
583 },
584 crate::TypeInner::Sampler { .. }
585 | crate::TypeInner::AccelerationStructure { .. }
586 | crate::TypeInner::RayQuery { .. } => {}
587 _ => {
588 return Err(GlobalVariableError::InvalidType(var.space));
589 }
590 }
591
592 (TypeFlags::empty(), true)
593 }
594 crate::AddressSpace::Private => (
595 TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED,
596 false,
597 ),
598 crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false),
599 crate::AddressSpace::PushConstant => {
600 if !self.capabilities.contains(Capabilities::PUSH_CONSTANT) {
601 return Err(GlobalVariableError::UnsupportedCapability(
602 Capabilities::PUSH_CONSTANT,
603 ));
604 }
605 if let Err(ref err) = type_info.push_constant_compatibility {
606 return Err(GlobalVariableError::InvalidPushConstantType(err.clone()));
607 }
608 (
609 TypeFlags::DATA
610 | TypeFlags::COPY
611 | TypeFlags::HOST_SHAREABLE
612 | TypeFlags::SIZED,
613 false,
614 )
615 }
616 };
617
618 if !type_info.flags.contains(required_type_flags) {
619 return Err(GlobalVariableError::MissingTypeFlags {
620 seen: type_info.flags,
621 required: required_type_flags,
622 });
623 }
624
625 if is_resource != var.binding.is_some() {
626 if self.flags.contains(super::ValidationFlags::BINDINGS) {
627 return Err(GlobalVariableError::InvalidBinding);
628 }
629 }
630
631 if let Some(init) = var.init {
632 match var.space {
633 crate::AddressSpace::Private | crate::AddressSpace::Function => {}
634 _ => {
635 return Err(GlobalVariableError::InitializerNotAllowed(var.space));
636 }
637 }
638
639 if !global_expr_kind.is_const_or_override(init) {
640 return Err(GlobalVariableError::InitializerExprType);
641 }
642
643 if !gctx.compare_types(
644 &crate::proc::TypeResolution::Handle(var.ty),
645 &mod_info[init],
646 ) {
647 return Err(GlobalVariableError::InitializerType);
648 }
649 }
650
651 Ok(())
652 }
653
654 pub(super) fn validate_entry_point(
655 &mut self,
656 ep: &crate::EntryPoint,
657 module: &crate::Module,
658 mod_info: &ModuleInfo,
659 ) -> Result<FunctionInfo, WithSpan<EntryPointError>> {
660 if ep.early_depth_test.is_some() {
661 let required = Capabilities::EARLY_DEPTH_TEST;
662 if !self.capabilities.contains(required) {
663 return Err(
664 EntryPointError::Result(VaryingError::UnsupportedCapability(required))
665 .with_span(),
666 );
667 }
668
669 if ep.stage != crate::ShaderStage::Fragment {
670 return Err(EntryPointError::UnexpectedEarlyDepthTest.with_span());
671 }
672 }
673
674 if ep.stage == crate::ShaderStage::Compute {
675 if ep
676 .workgroup_size
677 .iter()
678 .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE)
679 {
680 return Err(EntryPointError::OutOfRangeWorkgroupSize.with_span());
681 }
682 } else if ep.workgroup_size != [0; 3] {
683 return Err(EntryPointError::UnexpectedWorkgroupSize.with_span());
684 }
685
686 let mut info = self
687 .validate_function(&ep.function, module, mod_info, true)
688 .map_err(WithSpan::into_other)?;
689
690 {
691 use super::ShaderStages;
692
693 let stage_bit = match ep.stage {
694 crate::ShaderStage::Vertex => ShaderStages::VERTEX,
695 crate::ShaderStage::Fragment => ShaderStages::FRAGMENT,
696 crate::ShaderStage::Compute => ShaderStages::COMPUTE,
697 crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(),
698 };
699
700 if !info.available_stages.contains(stage_bit) {
701 return Err(EntryPointError::ForbiddenStageOperations.with_span());
702 }
703 }
704
705 self.location_mask.clear();
706 let mut argument_built_ins = crate::FastHashSet::default();
707 for (index, fa) in ep.function.arguments.iter().enumerate() {
709 let mut ctx = VaryingContext {
710 stage: ep.stage,
711 output: false,
712 types: &module.types,
713 type_info: &self.types,
714 location_mask: &mut self.location_mask,
715 blend_src_mask: &mut self.blend_src_mask,
716 built_ins: &mut argument_built_ins,
717 capabilities: self.capabilities,
718 flags: self.flags,
719 };
720 ctx.validate(ep, fa.ty, fa.binding.as_ref())
721 .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?;
722 }
723
724 self.location_mask.clear();
725 if let Some(ref fr) = ep.function.result {
726 let mut result_built_ins = crate::FastHashSet::default();
727 let mut ctx = VaryingContext {
728 stage: ep.stage,
729 output: true,
730 types: &module.types,
731 type_info: &self.types,
732 location_mask: &mut self.location_mask,
733 blend_src_mask: &mut self.blend_src_mask,
734 built_ins: &mut result_built_ins,
735 capabilities: self.capabilities,
736 flags: self.flags,
737 };
738 ctx.validate(ep, fr.ty, fr.binding.as_ref())
739 .map_err_inner(|e| EntryPointError::Result(e).with_span())?;
740 if ep.stage == crate::ShaderStage::Vertex
741 && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false })
742 {
743 return Err(EntryPointError::MissingVertexOutputPosition.with_span());
744 }
745 if !self.blend_src_mask.is_empty() {
746 info.dual_source_blending = true;
747 }
748 } else if ep.stage == crate::ShaderStage::Vertex {
749 return Err(EntryPointError::MissingVertexOutputPosition.with_span());
750 }
751
752 {
753 let mut used_push_constants = module
754 .global_variables
755 .iter()
756 .filter(|&(_, var)| var.space == crate::AddressSpace::PushConstant)
757 .map(|(handle, _)| handle)
758 .filter(|&handle| !info[handle].is_empty());
759 if let Some(handle) = used_push_constants.nth(1) {
762 return Err(EntryPointError::MoreThanOnePushConstantUsed
763 .with_span_handle(handle, &module.global_variables));
764 }
765 }
766
767 self.ep_resource_bindings.clear();
768 for (var_handle, var) in module.global_variables.iter() {
769 let usage = info[var_handle];
770 if usage.is_empty() {
771 continue;
772 }
773
774 let allowed_usage = match var.space {
775 crate::AddressSpace::Function => unreachable!(),
776 crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY,
777 crate::AddressSpace::Storage { access } => storage_usage(access),
778 crate::AddressSpace::Handle => match module.types[var.ty].inner {
779 crate::TypeInner::BindingArray { base, .. } => match module.types[base].inner {
780 crate::TypeInner::Image {
781 class: crate::ImageClass::Storage { access, .. },
782 ..
783 } => storage_usage(access),
784 _ => GlobalUse::READ | GlobalUse::QUERY,
785 },
786 crate::TypeInner::Image {
787 class: crate::ImageClass::Storage { access, .. },
788 ..
789 } => storage_usage(access),
790 _ => GlobalUse::READ | GlobalUse::QUERY,
791 },
792 crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {
793 GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY
794 }
795 crate::AddressSpace::PushConstant => GlobalUse::READ,
796 };
797 if !allowed_usage.contains(usage) {
798 log::warn!("\tUsage error for: {var:?}");
799 log::warn!("\tAllowed usage: {allowed_usage:?}, requested: {usage:?}");
800 return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage)
801 .with_span_handle(var_handle, &module.global_variables));
802 }
803
804 if let Some(ref bind) = var.binding {
805 if !self.ep_resource_bindings.insert(*bind) {
806 if self.flags.contains(super::ValidationFlags::BINDINGS) {
807 return Err(EntryPointError::BindingCollision(var_handle)
808 .with_span_handle(var_handle, &module.global_variables));
809 }
810 }
811 }
812 }
813
814 Ok(info)
815 }
816}