1use alloc::string::String;
2
3use super::Capabilities;
4use crate::{arena::Handle, proc::Alignment};
5
6bitflags::bitflags! {
7 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
12 #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
13 #[repr(transparent)]
14 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
15 pub struct TypeFlags: u8 {
16 const DATA = 0x1;
28
29 const SIZED = 0x2;
43
44 const COPY = 0x4;
46
47 const IO_SHAREABLE = 0x8;
55
56 const HOST_SHAREABLE = 0x10;
58
59 const CREATION_RESOLVED = 0x20;
62
63 const ARGUMENT = 0x40;
65
66 const CONSTRUCTIBLE = 0x80;
74 }
75}
76
77#[derive(Clone, Copy, Debug, thiserror::Error)]
78#[cfg_attr(test, derive(PartialEq))]
79pub enum Disalignment {
80 #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
81 ArrayStride { stride: u32, alignment: Alignment },
82 #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
83 StructSpan { span: u32, alignment: Alignment },
84 #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
85 MemberOffset {
86 index: u32,
87 offset: u32,
88 alignment: Alignment,
89 },
90 #[error("The struct member[{index}] offset {offset} must be at least {expected}")]
91 MemberOffsetAfterStruct {
92 index: u32,
93 offset: u32,
94 expected: u32,
95 },
96 #[error("The struct member[{index}] is not statically sized")]
97 UnsizedMember { index: u32 },
98 #[error("The type is not host-shareable")]
99 NonHostShareable,
100}
101
102#[derive(Clone, Debug, thiserror::Error)]
103#[cfg_attr(test, derive(PartialEq))]
104pub enum TypeError {
105 #[error("Capability {0:?} is required")]
106 MissingCapability(Capabilities),
107 #[error("The {0:?} scalar width {1} is not supported for an atomic")]
108 InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
109 #[error("Invalid type for pointer target {0:?}")]
110 InvalidPointerBase(Handle<crate::Type>),
111 #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")]
112 InvalidPointerToUnsized {
113 base: Handle<crate::Type>,
114 space: crate::AddressSpace,
115 },
116 #[error("Expected data type, found {0:?}")]
117 InvalidData(Handle<crate::Type>),
118 #[error("Base type {0:?} for the array is invalid")]
119 InvalidArrayBaseType(Handle<crate::Type>),
120 #[error("Matrix elements must always be floating-point types")]
121 MatrixElementNotFloat,
122 #[error("The constant {0:?} is specialized, and cannot be used as an array size")]
123 UnsupportedSpecializedArrayLength(Handle<crate::Constant>),
124 #[error("{} of dimensionality {dim:?} and class {class:?} are not supported", if *.arrayed {"Arrayed images"} else {"Images"})]
125 UnsupportedImageType {
126 dim: crate::ImageDimension,
127 arrayed: bool,
128 class: crate::ImageClass,
129 },
130 #[error("Array stride {stride} does not match the expected {expected}")]
131 InvalidArrayStride { stride: u32, expected: u32 },
132 #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
133 InvalidDynamicArray(String, Handle<crate::Type>),
134 #[error("The base handle {0:?} has to be a struct")]
135 BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
136 #[error("Structure member[{index}] at {offset} overlaps the previous member")]
137 MemberOverlap { index: u32, offset: u32 },
138 #[error(
139 "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
140 )]
141 MemberOutOfBounds {
142 index: u32,
143 offset: u32,
144 size: u32,
145 span: u32,
146 },
147 #[error("Structure types must have at least one member")]
148 EmptyStruct,
149 #[error(transparent)]
150 WidthError(#[from] WidthError),
151 #[error(
152 "The base handle {0:?} has an override-expression that didn't get resolved to a constant"
153 )]
154 UnresolvedOverride(Handle<crate::Type>),
155}
156
157#[derive(Clone, Debug, thiserror::Error)]
158#[cfg_attr(test, derive(PartialEq))]
159pub enum WidthError {
160 #[error("The {0:?} scalar width {1} is not supported")]
161 Invalid(crate::ScalarKind, crate::Bytes),
162 #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")]
163 MissingCapability {
164 name: &'static str,
165 flag: &'static str,
166 },
167
168 #[error("Abstract types may only appear in constant expressions")]
169 Abstract,
170}
171
172#[derive(Clone, Debug, thiserror::Error)]
173#[cfg_attr(test, derive(PartialEq))]
174pub enum PushConstantError {
175 #[error("The scalar type {0:?} is not supported in push constants")]
176 InvalidScalar(crate::Scalar),
177}
178
179type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
181type PushConstantCompatibility = Result<(), PushConstantError>;
182
183fn check_member_layout(
184 accum: &mut LayoutCompatibility,
185 member: &crate::StructMember,
186 member_index: u32,
187 member_layout: LayoutCompatibility,
188 parent_handle: Handle<crate::Type>,
189) {
190 *accum = match (*accum, member_layout) {
191 (Ok(cur_alignment), Ok(alignment)) => {
192 if alignment.is_aligned(member.offset) {
193 Ok(cur_alignment.max(alignment))
194 } else {
195 Err((
196 parent_handle,
197 Disalignment::MemberOffset {
198 index: member_index,
199 offset: member.offset,
200 alignment,
201 },
202 ))
203 }
204 }
205 (Err(e), _) | (_, Err(e)) => Err(e),
206 };
207}
208
209const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
218 use crate::AddressSpace as As;
219 match space {
220 As::Function | As::Private => TypeFlags::ARGUMENT,
221 As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => {
222 TypeFlags::empty()
223 }
224 }
225}
226
227#[derive(Clone, Debug)]
228pub(super) struct TypeInfo {
229 pub flags: TypeFlags,
230 pub uniform_layout: LayoutCompatibility,
231 pub storage_layout: LayoutCompatibility,
232 pub push_constant_compatibility: PushConstantCompatibility,
233}
234
235impl TypeInfo {
236 const fn dummy() -> Self {
237 TypeInfo {
238 flags: TypeFlags::empty(),
239 uniform_layout: Ok(Alignment::ONE),
240 storage_layout: Ok(Alignment::ONE),
241 push_constant_compatibility: Ok(()),
242 }
243 }
244
245 const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
246 TypeInfo {
247 flags,
248 uniform_layout: Ok(alignment),
249 storage_layout: Ok(alignment),
250 push_constant_compatibility: Ok(()),
251 }
252 }
253}
254
255impl super::Validator {
256 const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
257 if self.capabilities.contains(capability) {
258 Ok(())
259 } else {
260 Err(TypeError::MissingCapability(capability))
261 }
262 }
263
264 pub(super) const fn check_width(
265 &self,
266 scalar: crate::Scalar,
267 ) -> Result<PushConstantCompatibility, WidthError> {
268 let mut push_constant_compatibility = Ok(());
269 let good = match scalar.kind {
270 crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH,
271 crate::ScalarKind::Float => match scalar.width {
272 8 => {
273 if !self.capabilities.contains(Capabilities::FLOAT64) {
274 return Err(WidthError::MissingCapability {
275 name: "f64",
276 flag: "FLOAT64",
277 });
278 }
279 true
280 }
281 2 => {
282 if !self.capabilities.contains(Capabilities::SHADER_FLOAT16) {
283 return Err(WidthError::MissingCapability {
284 name: "f16",
285 flag: "FLOAT16",
286 });
287 }
288
289 push_constant_compatibility = Err(PushConstantError::InvalidScalar(scalar));
290
291 true
292 }
293 _ => scalar.width == 4,
294 },
295 crate::ScalarKind::Sint => {
296 if scalar.width == 8 {
297 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
298 return Err(WidthError::MissingCapability {
299 name: "i64",
300 flag: "SHADER_INT64",
301 });
302 }
303 true
304 } else {
305 scalar.width == 4
306 }
307 }
308 crate::ScalarKind::Uint => {
309 if scalar.width == 8 {
310 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
311 return Err(WidthError::MissingCapability {
312 name: "u64",
313 flag: "SHADER_INT64",
314 });
315 }
316 true
317 } else {
318 scalar.width == 4
319 }
320 }
321 crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
322 return Err(WidthError::Abstract);
323 }
324 };
325 if good {
326 Ok(push_constant_compatibility)
327 } else {
328 Err(WidthError::Invalid(scalar.kind, scalar.width))
329 }
330 }
331
332 pub(super) fn reset_types(&mut self, size: usize) {
333 self.types.clear();
334 self.types.resize(size, TypeInfo::dummy());
335 self.layouter.clear();
336 }
337
338 pub(super) fn validate_type(
339 &self,
340 handle: Handle<crate::Type>,
341 gctx: crate::proc::GlobalCtx,
342 ) -> Result<TypeInfo, TypeError> {
343 use crate::TypeInner as Ti;
344 Ok(match gctx.types[handle].inner {
345 Ti::Scalar(scalar) => {
346 let push_constant_compatibility = self.check_width(scalar)?;
347 let shareable = if scalar.kind.is_numeric() {
348 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
349 } else {
350 TypeFlags::empty()
351 };
352 let mut type_info = TypeInfo::new(
353 TypeFlags::DATA
354 | TypeFlags::SIZED
355 | TypeFlags::COPY
356 | TypeFlags::ARGUMENT
357 | TypeFlags::CONSTRUCTIBLE
358 | TypeFlags::CREATION_RESOLVED
359 | shareable,
360 Alignment::from_width(scalar.width),
361 );
362 type_info.push_constant_compatibility = push_constant_compatibility;
363 type_info
364 }
365 Ti::Vector { size, scalar } => {
366 let push_constant_compatibility = self.check_width(scalar)?;
367 let shareable = if scalar.kind.is_numeric() {
368 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
369 } else {
370 TypeFlags::empty()
371 };
372 let mut type_info = TypeInfo::new(
373 TypeFlags::DATA
374 | TypeFlags::SIZED
375 | TypeFlags::COPY
376 | TypeFlags::ARGUMENT
377 | TypeFlags::CONSTRUCTIBLE
378 | TypeFlags::CREATION_RESOLVED
379 | shareable,
380 Alignment::from(size) * Alignment::from_width(scalar.width),
381 );
382 type_info.push_constant_compatibility = push_constant_compatibility;
383 type_info
384 }
385 Ti::Matrix {
386 columns: _,
387 rows,
388 scalar,
389 } => {
390 if scalar.kind != crate::ScalarKind::Float {
391 return Err(TypeError::MatrixElementNotFloat);
392 }
393 let push_constant_compatibility = self.check_width(scalar)?;
394 let mut type_info = TypeInfo::new(
395 TypeFlags::DATA
396 | TypeFlags::SIZED
397 | TypeFlags::COPY
398 | TypeFlags::HOST_SHAREABLE
399 | TypeFlags::ARGUMENT
400 | TypeFlags::CONSTRUCTIBLE
401 | TypeFlags::CREATION_RESOLVED,
402 Alignment::from(rows) * Alignment::from_width(scalar.width),
403 );
404 type_info.push_constant_compatibility = push_constant_compatibility;
405 type_info
406 }
407 Ti::Atomic(scalar) => {
408 match scalar {
409 crate::Scalar {
410 kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
411 width: _,
412 } => {
413 if scalar.width == 8
414 && !self.capabilities.intersects(
415 Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
416 | Capabilities::SHADER_INT64_ATOMIC_MIN_MAX,
417 )
418 {
419 return Err(TypeError::MissingCapability(
420 Capabilities::SHADER_INT64_ATOMIC_ALL_OPS,
421 ));
422 }
423 }
424 crate::Scalar::F32 => {
425 if !self
426 .capabilities
427 .contains(Capabilities::SHADER_FLOAT32_ATOMIC)
428 {
429 return Err(TypeError::MissingCapability(
430 Capabilities::SHADER_FLOAT32_ATOMIC,
431 ));
432 }
433 }
434 _ => return Err(TypeError::InvalidAtomicWidth(scalar.kind, scalar.width)),
435 };
436 TypeInfo::new(
437 TypeFlags::DATA
438 | TypeFlags::SIZED
439 | TypeFlags::HOST_SHAREABLE
440 | TypeFlags::CREATION_RESOLVED,
441 Alignment::from_width(scalar.width),
442 )
443 }
444 Ti::Pointer { base, space } => {
445 use crate::AddressSpace as As;
446
447 let base_info = &self.types[base.index()];
448 if !base_info.flags.contains(TypeFlags::DATA) {
449 return Err(TypeError::InvalidPointerBase(base));
450 }
451
452 if !base_info.flags.contains(TypeFlags::SIZED) {
464 match space {
465 As::Storage { .. } => {}
466 _ => {
467 return Err(TypeError::InvalidPointerToUnsized { base, space });
468 }
469 }
470 }
471
472 let argument_flag = ptr_space_argument_flag(space);
477
478 TypeInfo::new(
481 argument_flag
482 | TypeFlags::SIZED
483 | TypeFlags::COPY
484 | TypeFlags::CREATION_RESOLVED,
485 Alignment::ONE,
486 )
487 }
488 Ti::ValuePointer {
489 size: _,
490 scalar,
491 space,
492 } => {
493 let _ = self.check_width(scalar)?;
501
502 let argument_flag = ptr_space_argument_flag(space);
507
508 TypeInfo::new(
511 argument_flag
512 | TypeFlags::SIZED
513 | TypeFlags::COPY
514 | TypeFlags::CREATION_RESOLVED,
515 Alignment::ONE,
516 )
517 }
518 Ti::Array { base, size, stride } => {
519 let base_info = &self.types[base.index()];
520 if !base_info
521 .flags
522 .contains(TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::CREATION_RESOLVED)
523 {
524 return Err(TypeError::InvalidArrayBaseType(base));
525 }
526
527 let base_layout = self.layouter[base];
528 let general_alignment = base_layout.alignment;
529 let uniform_layout = match base_info.uniform_layout {
530 Ok(base_alignment) => {
531 let alignment = base_alignment
532 .max(general_alignment)
533 .max(Alignment::MIN_UNIFORM);
534 if alignment.is_aligned(stride) {
535 Ok(alignment)
536 } else {
537 Err((handle, Disalignment::ArrayStride { stride, alignment }))
538 }
539 }
540 Err(e) => Err(e),
541 };
542 let storage_layout = match base_info.storage_layout {
543 Ok(base_alignment) => {
544 let alignment = base_alignment.max(general_alignment);
545 if alignment.is_aligned(stride) {
546 Ok(alignment)
547 } else {
548 Err((handle, Disalignment::ArrayStride { stride, alignment }))
549 }
550 }
551 Err(e) => Err(e),
552 };
553
554 let type_info_mask = match size {
555 crate::ArraySize::Constant(_) => {
556 TypeFlags::DATA
557 | TypeFlags::SIZED
558 | TypeFlags::COPY
559 | TypeFlags::HOST_SHAREABLE
560 | TypeFlags::ARGUMENT
561 | TypeFlags::CONSTRUCTIBLE
562 | TypeFlags::CREATION_RESOLVED
563 }
564 crate::ArraySize::Pending(_) => {
565 TypeFlags::DATA
566 | TypeFlags::SIZED
567 | TypeFlags::COPY
568 | TypeFlags::HOST_SHAREABLE
569 | TypeFlags::ARGUMENT
570 }
571 crate::ArraySize::Dynamic => {
572 TypeFlags::DATA
576 | TypeFlags::COPY
577 | TypeFlags::HOST_SHAREABLE
578 | TypeFlags::CREATION_RESOLVED
579 }
580 };
581
582 TypeInfo {
583 flags: base_info.flags & type_info_mask,
584 uniform_layout,
585 storage_layout,
586 push_constant_compatibility: base_info.push_constant_compatibility.clone(),
587 }
588 }
589 Ti::Struct { ref members, span } => {
590 if members.is_empty() {
591 return Err(TypeError::EmptyStruct);
592 }
593
594 let mut ti = TypeInfo::new(
595 TypeFlags::DATA
596 | TypeFlags::SIZED
597 | TypeFlags::COPY
598 | TypeFlags::HOST_SHAREABLE
599 | TypeFlags::IO_SHAREABLE
600 | TypeFlags::ARGUMENT
601 | TypeFlags::CONSTRUCTIBLE
602 | TypeFlags::CREATION_RESOLVED,
603 Alignment::ONE,
604 );
605 ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
606
607 let mut min_offset = 0;
608 let mut prev_struct_data: Option<(u32, u32)> = None;
609
610 for (i, member) in members.iter().enumerate() {
611 let base_info = &self.types[member.ty.index()];
612 if !base_info
613 .flags
614 .contains(TypeFlags::DATA | TypeFlags::CREATION_RESOLVED)
615 {
616 return Err(TypeError::InvalidData(member.ty));
617 }
618 if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
619 if ti.uniform_layout.is_ok() {
620 ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
621 }
622 if ti.storage_layout.is_ok() {
623 ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
624 }
625 }
626 ti.flags &= base_info.flags;
627
628 if member.offset < min_offset {
629 if member.offset == 0 {
633 ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
634 } else {
635 return Err(TypeError::MemberOverlap {
636 index: i as u32,
637 offset: member.offset,
638 });
639 }
640 }
641
642 let base_size = gctx.types[member.ty].inner.size(gctx);
643 min_offset = member.offset + base_size;
644 if min_offset > span {
645 return Err(TypeError::MemberOutOfBounds {
646 index: i as u32,
647 offset: member.offset,
648 size: base_size,
649 span,
650 });
651 }
652
653 check_member_layout(
654 &mut ti.uniform_layout,
655 member,
656 i as u32,
657 base_info.uniform_layout,
658 handle,
659 );
660 check_member_layout(
661 &mut ti.storage_layout,
662 member,
663 i as u32,
664 base_info.storage_layout,
665 handle,
666 );
667 if base_info.push_constant_compatibility.is_err() {
668 ti.push_constant_compatibility =
669 base_info.push_constant_compatibility.clone();
670 }
671
672 if let Some((span, offset)) = prev_struct_data {
676 let diff = member.offset - offset;
677 let min = Alignment::MIN_UNIFORM.round_up(span);
678 if diff < min {
679 ti.uniform_layout = Err((
680 handle,
681 Disalignment::MemberOffsetAfterStruct {
682 index: i as u32,
683 offset: member.offset,
684 expected: offset + min,
685 },
686 ));
687 }
688 };
689
690 prev_struct_data = match gctx.types[member.ty].inner {
691 crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
692 _ => None,
693 };
694
695 if !base_info.flags.contains(TypeFlags::SIZED) {
697 let is_array = match gctx.types[member.ty].inner {
698 crate::TypeInner::Array { .. } => true,
699 _ => false,
700 };
701 if !is_array || i + 1 != members.len() {
702 let name = member.name.clone().unwrap_or_default();
703 return Err(TypeError::InvalidDynamicArray(name, member.ty));
704 }
705 if ti.uniform_layout.is_ok() {
706 ti.uniform_layout =
707 Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
708 }
709 }
710 }
711
712 let alignment = self.layouter[handle].alignment;
713 if !alignment.is_aligned(span) {
714 ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
715 ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
716 }
717
718 ti
719 }
720 Ti::Image {
721 dim,
722 arrayed,
723 class,
724 } => {
725 if arrayed && matches!(dim, crate::ImageDimension::D3) {
726 return Err(TypeError::UnsupportedImageType {
727 dim,
728 arrayed,
729 class,
730 });
731 }
732 if arrayed && matches!(dim, crate::ImageDimension::Cube) {
733 self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
734 }
735 TypeInfo::new(
736 TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
737 Alignment::ONE,
738 )
739 }
740 Ti::Sampler { .. } => TypeInfo::new(
741 TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
742 Alignment::ONE,
743 ),
744 Ti::AccelerationStructure { vertex_return } => {
745 self.require_type_capability(Capabilities::RAY_QUERY)?;
746 if vertex_return {
747 self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
748 }
749 TypeInfo::new(
750 TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
751 Alignment::ONE,
752 )
753 }
754 Ti::RayQuery { vertex_return } => {
755 self.require_type_capability(Capabilities::RAY_QUERY)?;
756 if vertex_return {
757 self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
758 }
759 TypeInfo::new(
760 TypeFlags::DATA
761 | TypeFlags::CONSTRUCTIBLE
762 | TypeFlags::SIZED
763 | TypeFlags::CREATION_RESOLVED,
764 Alignment::ONE,
765 )
766 }
767 Ti::BindingArray { base, size } => {
768 let type_info_mask = match size {
769 crate::ArraySize::Constant(_) => {
770 TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
771 }
772 crate::ArraySize::Pending(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
773 crate::ArraySize::Dynamic => {
774 TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
776 }
777 };
778 let base_info = &self.types[base.index()];
779
780 if base_info.flags.contains(TypeFlags::DATA) {
781 match gctx.types[base].inner {
783 crate::TypeInner::Struct { .. } => {}
784 _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
785 };
786 }
787
788 if !base_info.flags.contains(TypeFlags::CREATION_RESOLVED) {
789 return Err(TypeError::InvalidData(base));
790 }
791
792 TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
793 }
794 })
795 }
796}