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;
22
23 const SIZED = 0x2;
37
38 const COPY = 0x4;
40
41 const IO_SHAREABLE = 0x8;
47
48 const HOST_SHAREABLE = 0x10;
50
51 const ARGUMENT = 0x40;
53
54 const CONSTRUCTIBLE = 0x80;
62 }
63}
64
65#[derive(Clone, Copy, Debug, thiserror::Error)]
66#[cfg_attr(test, derive(PartialEq))]
67pub enum Disalignment {
68 #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
69 ArrayStride { stride: u32, alignment: Alignment },
70 #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
71 StructSpan { span: u32, alignment: Alignment },
72 #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
73 MemberOffset {
74 index: u32,
75 offset: u32,
76 alignment: Alignment,
77 },
78 #[error("The struct member[{index}] offset {offset} must be at least {expected}")]
79 MemberOffsetAfterStruct {
80 index: u32,
81 offset: u32,
82 expected: u32,
83 },
84 #[error("The struct member[{index}] is not statically sized")]
85 UnsizedMember { index: u32 },
86 #[error("The type is not host-shareable")]
87 NonHostShareable,
88}
89
90#[derive(Clone, Debug, thiserror::Error)]
91#[cfg_attr(test, derive(PartialEq))]
92pub enum TypeError {
93 #[error("Capability {0:?} is required")]
94 MissingCapability(Capabilities),
95 #[error("The {0:?} scalar width {1} is not supported for an atomic")]
96 InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
97 #[error("Invalid type for pointer target {0:?}")]
98 InvalidPointerBase(Handle<crate::Type>),
99 #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")]
100 InvalidPointerToUnsized {
101 base: Handle<crate::Type>,
102 space: crate::AddressSpace,
103 },
104 #[error("Expected data type, found {0:?}")]
105 InvalidData(Handle<crate::Type>),
106 #[error("Base type {0:?} for the array is invalid")]
107 InvalidArrayBaseType(Handle<crate::Type>),
108 #[error("Matrix elements must always be floating-point types")]
109 MatrixElementNotFloat,
110 #[error("The constant {0:?} is specialized, and cannot be used as an array size")]
111 UnsupportedSpecializedArrayLength(Handle<crate::Constant>),
112 #[error("{} of dimensionality {dim:?} and class {class:?} are not supported", if *.arrayed {"Arrayed images"} else {"Images"})]
113 UnsupportedImageType {
114 dim: crate::ImageDimension,
115 arrayed: bool,
116 class: crate::ImageClass,
117 },
118 #[error("Array stride {stride} does not match the expected {expected}")]
119 InvalidArrayStride { stride: u32, expected: u32 },
120 #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
121 InvalidDynamicArray(String, Handle<crate::Type>),
122 #[error("The base handle {0:?} has to be a struct")]
123 BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
124 #[error("Structure member[{index}] at {offset} overlaps the previous member")]
125 MemberOverlap { index: u32, offset: u32 },
126 #[error(
127 "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
128 )]
129 MemberOutOfBounds {
130 index: u32,
131 offset: u32,
132 size: u32,
133 span: u32,
134 },
135 #[error("Structure types must have at least one member")]
136 EmptyStruct,
137 #[error(transparent)]
138 WidthError(#[from] WidthError),
139}
140
141#[derive(Clone, Debug, thiserror::Error)]
142#[cfg_attr(test, derive(PartialEq))]
143pub enum WidthError {
144 #[error("The {0:?} scalar width {1} is not supported")]
145 Invalid(crate::ScalarKind, crate::Bytes),
146 #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")]
147 MissingCapability {
148 name: &'static str,
149 flag: &'static str,
150 },
151
152 #[error("Abstract types may only appear in constant expressions")]
153 Abstract,
154}
155
156type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
158
159fn check_member_layout(
160 accum: &mut LayoutCompatibility,
161 member: &crate::StructMember,
162 member_index: u32,
163 member_layout: LayoutCompatibility,
164 parent_handle: Handle<crate::Type>,
165) {
166 *accum = match (*accum, member_layout) {
167 (Ok(cur_alignment), Ok(alignment)) => {
168 if alignment.is_aligned(member.offset) {
169 Ok(cur_alignment.max(alignment))
170 } else {
171 Err((
172 parent_handle,
173 Disalignment::MemberOffset {
174 index: member_index,
175 offset: member.offset,
176 alignment,
177 },
178 ))
179 }
180 }
181 (Err(e), _) | (_, Err(e)) => Err(e),
182 };
183}
184
185const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
194 use crate::AddressSpace as As;
195 match space {
196 As::Function | As::Private => TypeFlags::ARGUMENT,
197 As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => {
198 TypeFlags::empty()
199 }
200 }
201}
202
203#[derive(Clone, Debug)]
204pub(super) struct TypeInfo {
205 pub flags: TypeFlags,
206 pub uniform_layout: LayoutCompatibility,
207 pub storage_layout: LayoutCompatibility,
208}
209
210impl TypeInfo {
211 const fn dummy() -> Self {
212 TypeInfo {
213 flags: TypeFlags::empty(),
214 uniform_layout: Ok(Alignment::ONE),
215 storage_layout: Ok(Alignment::ONE),
216 }
217 }
218
219 const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
220 TypeInfo {
221 flags,
222 uniform_layout: Ok(alignment),
223 storage_layout: Ok(alignment),
224 }
225 }
226}
227
228impl super::Validator {
229 const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
230 if self.capabilities.contains(capability) {
231 Ok(())
232 } else {
233 Err(TypeError::MissingCapability(capability))
234 }
235 }
236
237 pub(super) const fn check_width(&self, scalar: crate::Scalar) -> Result<(), WidthError> {
238 let good = match scalar.kind {
239 crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH,
240 crate::ScalarKind::Float => {
241 if scalar.width == 8 {
242 if !self.capabilities.contains(Capabilities::FLOAT64) {
243 return Err(WidthError::MissingCapability {
244 name: "f64",
245 flag: "FLOAT64",
246 });
247 }
248 true
249 } else {
250 scalar.width == 4
251 }
252 }
253 crate::ScalarKind::Sint => {
254 if scalar.width == 8 {
255 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
256 return Err(WidthError::MissingCapability {
257 name: "i64",
258 flag: "SHADER_INT64",
259 });
260 }
261 true
262 } else {
263 scalar.width == 4
264 }
265 }
266 crate::ScalarKind::Uint => {
267 if scalar.width == 8 {
268 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
269 return Err(WidthError::MissingCapability {
270 name: "u64",
271 flag: "SHADER_INT64",
272 });
273 }
274 true
275 } else {
276 scalar.width == 4
277 }
278 }
279 crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
280 return Err(WidthError::Abstract);
281 }
282 };
283 if good {
284 Ok(())
285 } else {
286 Err(WidthError::Invalid(scalar.kind, scalar.width))
287 }
288 }
289
290 pub(super) fn reset_types(&mut self, size: usize) {
291 self.types.clear();
292 self.types.resize(size, TypeInfo::dummy());
293 self.layouter.clear();
294 }
295
296 pub(super) fn validate_type(
297 &self,
298 handle: Handle<crate::Type>,
299 gctx: crate::proc::GlobalCtx,
300 ) -> Result<TypeInfo, TypeError> {
301 use crate::TypeInner as Ti;
302 Ok(match gctx.types[handle].inner {
303 Ti::Scalar(scalar) => {
304 self.check_width(scalar)?;
305 let shareable = if scalar.kind.is_numeric() {
306 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
307 } else {
308 TypeFlags::empty()
309 };
310 TypeInfo::new(
311 TypeFlags::DATA
312 | TypeFlags::SIZED
313 | TypeFlags::COPY
314 | TypeFlags::ARGUMENT
315 | TypeFlags::CONSTRUCTIBLE
316 | shareable,
317 Alignment::from_width(scalar.width),
318 )
319 }
320 Ti::Vector { size, scalar } => {
321 self.check_width(scalar)?;
322 let shareable = if scalar.kind.is_numeric() {
323 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
324 } else {
325 TypeFlags::empty()
326 };
327 TypeInfo::new(
328 TypeFlags::DATA
329 | TypeFlags::SIZED
330 | TypeFlags::COPY
331 | TypeFlags::ARGUMENT
332 | TypeFlags::CONSTRUCTIBLE
333 | shareable,
334 Alignment::from(size) * Alignment::from_width(scalar.width),
335 )
336 }
337 Ti::Matrix {
338 columns: _,
339 rows,
340 scalar,
341 } => {
342 if scalar.kind != crate::ScalarKind::Float {
343 return Err(TypeError::MatrixElementNotFloat);
344 }
345 self.check_width(scalar)?;
346 TypeInfo::new(
347 TypeFlags::DATA
348 | TypeFlags::SIZED
349 | TypeFlags::COPY
350 | TypeFlags::HOST_SHAREABLE
351 | TypeFlags::ARGUMENT
352 | TypeFlags::CONSTRUCTIBLE,
353 Alignment::from(rows) * Alignment::from_width(scalar.width),
354 )
355 }
356 Ti::Atomic(crate::Scalar { kind, width }) => {
357 let good = match kind {
358 crate::ScalarKind::Bool
359 | crate::ScalarKind::Float
360 | crate::ScalarKind::AbstractInt
361 | crate::ScalarKind::AbstractFloat => false,
362 crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4,
363 };
364 if !good {
365 return Err(TypeError::InvalidAtomicWidth(kind, width));
366 }
367 TypeInfo::new(
368 TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
369 Alignment::from_width(width),
370 )
371 }
372 Ti::Pointer { base, space } => {
373 use crate::AddressSpace as As;
374
375 let base_info = &self.types[base.index()];
376 if !base_info.flags.contains(TypeFlags::DATA) {
377 return Err(TypeError::InvalidPointerBase(base));
378 }
379
380 if !base_info.flags.contains(TypeFlags::SIZED) {
392 match space {
393 As::Storage { .. } => {}
394 _ => {
395 return Err(TypeError::InvalidPointerToUnsized { base, space });
396 }
397 }
398 }
399
400 let argument_flag = ptr_space_argument_flag(space);
405
406 TypeInfo::new(
409 argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
410 Alignment::ONE,
411 )
412 }
413 Ti::ValuePointer {
414 size: _,
415 scalar,
416 space,
417 } => {
418 self.check_width(scalar)?;
426
427 let argument_flag = ptr_space_argument_flag(space);
432
433 TypeInfo::new(
436 argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
437 Alignment::ONE,
438 )
439 }
440 Ti::Array { base, size, stride } => {
441 let base_info = &self.types[base.index()];
442 if !base_info.flags.contains(TypeFlags::DATA | TypeFlags::SIZED) {
443 return Err(TypeError::InvalidArrayBaseType(base));
444 }
445
446 let base_layout = self.layouter[base];
447 let general_alignment = base_layout.alignment;
448 let uniform_layout = match base_info.uniform_layout {
449 Ok(base_alignment) => {
450 let alignment = base_alignment
451 .max(general_alignment)
452 .max(Alignment::MIN_UNIFORM);
453 if alignment.is_aligned(stride) {
454 Ok(alignment)
455 } else {
456 Err((handle, Disalignment::ArrayStride { stride, alignment }))
457 }
458 }
459 Err(e) => Err(e),
460 };
461 let storage_layout = match base_info.storage_layout {
462 Ok(base_alignment) => {
463 let alignment = base_alignment.max(general_alignment);
464 if alignment.is_aligned(stride) {
465 Ok(alignment)
466 } else {
467 Err((handle, Disalignment::ArrayStride { stride, alignment }))
468 }
469 }
470 Err(e) => Err(e),
471 };
472
473 let type_info_mask = match size {
474 crate::ArraySize::Constant(_) => {
475 TypeFlags::DATA
476 | TypeFlags::SIZED
477 | TypeFlags::COPY
478 | TypeFlags::HOST_SHAREABLE
479 | TypeFlags::ARGUMENT
480 | TypeFlags::CONSTRUCTIBLE
481 }
482 crate::ArraySize::Dynamic => {
483 TypeFlags::DATA | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE
487 }
488 };
489
490 TypeInfo {
491 flags: base_info.flags & type_info_mask,
492 uniform_layout,
493 storage_layout,
494 }
495 }
496 Ti::Struct { ref members, span } => {
497 if members.is_empty() {
498 return Err(TypeError::EmptyStruct);
499 }
500
501 let mut ti = TypeInfo::new(
502 TypeFlags::DATA
503 | TypeFlags::SIZED
504 | TypeFlags::COPY
505 | TypeFlags::HOST_SHAREABLE
506 | TypeFlags::IO_SHAREABLE
507 | TypeFlags::ARGUMENT
508 | TypeFlags::CONSTRUCTIBLE,
509 Alignment::ONE,
510 );
511 ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
512
513 let mut min_offset = 0;
514 let mut prev_struct_data: Option<(u32, u32)> = None;
515
516 for (i, member) in members.iter().enumerate() {
517 let base_info = &self.types[member.ty.index()];
518 if !base_info.flags.contains(TypeFlags::DATA) {
519 return Err(TypeError::InvalidData(member.ty));
520 }
521 if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
522 if ti.uniform_layout.is_ok() {
523 ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
524 }
525 if ti.storage_layout.is_ok() {
526 ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
527 }
528 }
529 ti.flags &= base_info.flags;
530
531 if member.offset < min_offset {
532 if member.offset == 0 {
536 ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
537 } else {
538 return Err(TypeError::MemberOverlap {
539 index: i as u32,
540 offset: member.offset,
541 });
542 }
543 }
544
545 let base_size = gctx.types[member.ty].inner.size(gctx);
546 min_offset = member.offset + base_size;
547 if min_offset > span {
548 return Err(TypeError::MemberOutOfBounds {
549 index: i as u32,
550 offset: member.offset,
551 size: base_size,
552 span,
553 });
554 }
555
556 check_member_layout(
557 &mut ti.uniform_layout,
558 member,
559 i as u32,
560 base_info.uniform_layout,
561 handle,
562 );
563 check_member_layout(
564 &mut ti.storage_layout,
565 member,
566 i as u32,
567 base_info.storage_layout,
568 handle,
569 );
570
571 if let Some((span, offset)) = prev_struct_data {
575 let diff = member.offset - offset;
576 let min = Alignment::MIN_UNIFORM.round_up(span);
577 if diff < min {
578 ti.uniform_layout = Err((
579 handle,
580 Disalignment::MemberOffsetAfterStruct {
581 index: i as u32,
582 offset: member.offset,
583 expected: offset + min,
584 },
585 ));
586 }
587 };
588
589 prev_struct_data = match gctx.types[member.ty].inner {
590 crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
591 _ => None,
592 };
593
594 if !base_info.flags.contains(TypeFlags::SIZED) {
596 let is_array = match gctx.types[member.ty].inner {
597 crate::TypeInner::Array { .. } => true,
598 _ => false,
599 };
600 if !is_array || i + 1 != members.len() {
601 let name = member.name.clone().unwrap_or_default();
602 return Err(TypeError::InvalidDynamicArray(name, member.ty));
603 }
604 if ti.uniform_layout.is_ok() {
605 ti.uniform_layout =
606 Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
607 }
608 }
609 }
610
611 let alignment = self.layouter[handle].alignment;
612 if !alignment.is_aligned(span) {
613 ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
614 ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
615 }
616
617 ti
618 }
619 Ti::Image {
620 dim,
621 arrayed,
622 class,
623 } => {
624 if arrayed && matches!(dim, crate::ImageDimension::D3) {
625 return Err(TypeError::UnsupportedImageType {
626 dim,
627 arrayed,
628 class,
629 });
630 }
631 if arrayed && matches!(dim, crate::ImageDimension::Cube) {
632 self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
633 }
634 TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
635 }
636 Ti::Sampler { .. } => TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE),
637 Ti::AccelerationStructure => {
638 self.require_type_capability(Capabilities::RAY_QUERY)?;
639 TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
640 }
641 Ti::RayQuery => {
642 self.require_type_capability(Capabilities::RAY_QUERY)?;
643 TypeInfo::new(
644 TypeFlags::DATA | TypeFlags::CONSTRUCTIBLE | TypeFlags::SIZED,
645 Alignment::ONE,
646 )
647 }
648 Ti::BindingArray { base, size } => {
649 if base >= handle {
650 return Err(TypeError::InvalidArrayBaseType(base));
651 }
652 let type_info_mask = match size {
653 crate::ArraySize::Constant(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
654 crate::ArraySize::Dynamic => {
655 TypeFlags::HOST_SHAREABLE
657 }
658 };
659 let base_info = &self.types[base.index()];
660
661 if base_info.flags.contains(TypeFlags::DATA) {
662 match gctx.types[base].inner {
664 crate::TypeInner::Struct { .. } => {}
665 crate::TypeInner::Array { .. } => {}
666 _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
667 };
668 }
669
670 TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
671 }
672 })
673 }
674}