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