naga/valid/
type.rs

1use alloc::string::String;
2
3use super::Capabilities;
4use crate::{arena::Handle, proc::Alignment};
5
6bitflags::bitflags! {
7    /// Flags associated with [`Type`]s by [`Validator`].
8    ///
9    /// [`Type`]: crate::Type
10    /// [`Validator`]: crate::valid::Validator
11    #[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        /// Can be used for data variables.
17        ///
18        /// This flag is required on types of local variables, function
19        /// arguments, array elements, and struct members.
20        ///
21        /// This includes all types except [`Image`], [`Sampler`],
22        /// and some [`Pointer`] types.
23        ///
24        /// [`Image`]: crate::TypeInner::Image
25        /// [`Sampler`]: crate::TypeInner::Sampler
26        /// [`Pointer`]: crate::TypeInner::Pointer
27        const DATA = 0x1;
28
29        /// The data type has a size known by pipeline creation time.
30        ///
31        /// Unsized types are quite restricted. The only unsized types permitted
32        /// by Naga, other than the non-[`DATA`] types like [`Image`] and
33        /// [`Sampler`], are dynamically-sized [`Array`]s, and [`Struct`]s whose
34        /// last members are such arrays. See the documentation for those types
35        /// for details.
36        ///
37        /// [`DATA`]: TypeFlags::DATA
38        /// [`Image`]: crate::TypeInner::Image
39        /// [`Sampler`]: crate::TypeInner::Sampler
40        /// [`Array`]: crate::TypeInner::Array
41        /// [`Struct`]: crate::TypeInner::Struct
42        const SIZED = 0x2;
43
44        /// The data can be copied around.
45        const COPY = 0x4;
46
47        /// Can be be used for user-defined IO between pipeline stages.
48        ///
49        /// This covers anything that can be in [`Location`] binding:
50        /// non-bool scalars and vectors, matrices, and structs and
51        /// arrays containing only interface types.
52        ///
53        /// [`Location`]: crate::Binding::Location
54        const IO_SHAREABLE = 0x8;
55
56        /// Can be used for host-shareable structures.
57        const HOST_SHAREABLE = 0x10;
58
59        /// The set of types with a fixed size at shader-creation time (ie. everything
60        /// except arrays sized by an override-expression)
61        const CREATION_RESOLVED = 0x20;
62
63        /// This type can be passed as a function argument.
64        const ARGUMENT = 0x40;
65
66        /// A WGSL [constructible] type.
67        ///
68        /// The constructible types are scalars, vectors, matrices, fixed-size
69        /// arrays of constructible types, and structs whose members are all
70        /// constructible.
71        ///
72        /// [constructible]: https://gpuweb.github.io/gpuweb/wgsl/#constructible
73        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
179// Only makes sense if `flags.contains(HOST_SHAREABLE)`
180type 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
209/// Determine whether a pointer in `space` can be passed as an argument.
210///
211/// If a pointer in `space` is permitted to be passed as an argument to a
212/// user-defined function, return `TypeFlags::ARGUMENT`. Otherwise, return
213/// `TypeFlags::empty()`.
214///
215/// Pointers passed as arguments to user-defined functions must be in the
216/// `Function` or `Private` address space.
217const 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                // Runtime-sized values can only live in the `Storage` address
453                // space, so it's useless to have a pointer to such a type in
454                // any other space.
455                //
456                // Detecting this problem here prevents the definition of
457                // functions like:
458                //
459                //     fn f(p: ptr<workgroup, UnsizedType>) -> ... { ... }
460                //
461                // which would otherwise be permitted, but uncallable. (They
462                // may also present difficulties in code generation).
463                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                // `Validator::validate_function` actually checks the address
473                // space of pointer arguments explicitly before checking the
474                // `ARGUMENT` flag, to give better error messages. But it seems
475                // best to set `ARGUMENT` accurately anyway.
476                let argument_flag = ptr_space_argument_flag(space);
477
478                // Pointers cannot be stored in variables, structure members, or
479                // array elements, so we do not mark them as `DATA`.
480                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                // ValuePointer should be treated the same way as the equivalent
494                // Pointer / Scalar / Vector combination, so each step in those
495                // variants' match arms should have a counterpart here.
496                //
497                // However, some cases are trivial: All our implicit base types
498                // are DATA and SIZED, so we can never return
499                // `InvalidPointerBase` or `InvalidPointerToUnsized`.
500                let _ = self.check_width(scalar)?;
501
502                // `Validator::validate_function` actually checks the address
503                // space of pointer arguments explicitly before checking the
504                // `ARGUMENT` flag, to give better error messages. But it seems
505                // best to set `ARGUMENT` accurately anyway.
506                let argument_flag = ptr_space_argument_flag(space);
507
508                // Pointers cannot be stored in variables, structure members, or
509                // array elements, so we do not mark them as `DATA`.
510                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                        // Non-SIZED types may only appear as the last element of a structure.
573                        // This is enforced by checks for SIZED-ness for all compound types,
574                        // and a special case for structs.
575                        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                        // HACK: this could be nicer. We want to allow some structures
630                        // to not bother with offsets/alignments if they are never
631                        // used for host sharing.
632                        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                    // Validate rule: If a structure member itself has a structure type S,
673                    // then the number of bytes between the start of that member and
674                    // the start of any following member must be at least roundUp(16, SizeOf(S)).
675                    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                    // The last field may be an unsized array.
696                    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                        // Final type is non-sized
775                        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                    // Currently Naga only supports binding arrays of structs for non-handle types.
782                    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}