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("Binding arrays of external textures are not yet supported")]
137    BindingArrayBaseExternalTextures,
138    #[error("Structure member[{index}] at {offset} overlaps the previous member")]
139    MemberOverlap { index: u32, offset: u32 },
140    #[error(
141        "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
142    )]
143    MemberOutOfBounds {
144        index: u32,
145        offset: u32,
146        size: u32,
147        span: u32,
148    },
149    #[error("Structure types must have at least one member")]
150    EmptyStruct,
151    #[error(transparent)]
152    WidthError(#[from] WidthError),
153    #[error(
154        "The base handle {0:?} has an override-expression that didn't get resolved to a constant"
155    )]
156    UnresolvedOverride(Handle<crate::Type>),
157}
158
159#[derive(Clone, Debug, thiserror::Error)]
160#[cfg_attr(test, derive(PartialEq))]
161pub enum WidthError {
162    #[error("The {0:?} scalar width {1} is not supported")]
163    Invalid(crate::ScalarKind, crate::Bytes),
164    #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")]
165    MissingCapability {
166        name: &'static str,
167        flag: &'static str,
168    },
169
170    #[error("Abstract types may only appear in constant expressions")]
171    Abstract,
172}
173
174#[derive(Clone, Debug, thiserror::Error)]
175#[cfg_attr(test, derive(PartialEq))]
176pub enum PushConstantError {
177    #[error("The scalar type {0:?} is not supported in push constants")]
178    InvalidScalar(crate::Scalar),
179}
180
181// Only makes sense if `flags.contains(HOST_SHAREABLE)`
182type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
183type PushConstantCompatibility = Result<(), PushConstantError>;
184
185fn check_member_layout(
186    accum: &mut LayoutCompatibility,
187    member: &crate::StructMember,
188    member_index: u32,
189    member_layout: LayoutCompatibility,
190    parent_handle: Handle<crate::Type>,
191) {
192    *accum = match (*accum, member_layout) {
193        (Ok(cur_alignment), Ok(alignment)) => {
194            if alignment.is_aligned(member.offset) {
195                Ok(cur_alignment.max(alignment))
196            } else {
197                Err((
198                    parent_handle,
199                    Disalignment::MemberOffset {
200                        index: member_index,
201                        offset: member.offset,
202                        alignment,
203                    },
204                ))
205            }
206        }
207        (Err(e), _) | (_, Err(e)) => Err(e),
208    };
209}
210
211/// Determine whether a pointer in `space` can be passed as an argument.
212///
213/// If a pointer in `space` is permitted to be passed as an argument to a
214/// user-defined function, return `TypeFlags::ARGUMENT`. Otherwise, return
215/// `TypeFlags::empty()`.
216///
217/// Pointers passed as arguments to user-defined functions must be in the
218/// `Function` or `Private` address space.
219const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
220    use crate::AddressSpace as As;
221    match space {
222        As::Function | As::Private => TypeFlags::ARGUMENT,
223        As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => {
224            TypeFlags::empty()
225        }
226    }
227}
228
229#[derive(Clone, Debug)]
230pub(super) struct TypeInfo {
231    pub flags: TypeFlags,
232    pub uniform_layout: LayoutCompatibility,
233    pub storage_layout: LayoutCompatibility,
234    pub push_constant_compatibility: PushConstantCompatibility,
235}
236
237impl TypeInfo {
238    const fn dummy() -> Self {
239        TypeInfo {
240            flags: TypeFlags::empty(),
241            uniform_layout: Ok(Alignment::ONE),
242            storage_layout: Ok(Alignment::ONE),
243            push_constant_compatibility: Ok(()),
244        }
245    }
246
247    const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
248        TypeInfo {
249            flags,
250            uniform_layout: Ok(alignment),
251            storage_layout: Ok(alignment),
252            push_constant_compatibility: Ok(()),
253        }
254    }
255}
256
257impl super::Validator {
258    const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
259        if self.capabilities.contains(capability) {
260            Ok(())
261        } else {
262            Err(TypeError::MissingCapability(capability))
263        }
264    }
265
266    /// Check whether `scalar` is a permitted scalar width.
267    ///
268    /// If `scalar` is not a width allowed by the selected [`Capabilities`],
269    /// return an error explaining why.
270    ///
271    /// If `scalar` is allowed, return a [`PushConstantCompatibility`] result
272    /// that says whether `scalar` is allowed specifically in push constants.
273    ///
274    /// [`Capabilities`]: crate::valid::Capabilities
275    pub(super) const fn check_width(
276        &self,
277        scalar: crate::Scalar,
278    ) -> Result<PushConstantCompatibility, WidthError> {
279        let mut push_constant_compatibility = Ok(());
280        let good = match scalar.kind {
281            crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH,
282            crate::ScalarKind::Float => match scalar.width {
283                8 => {
284                    if !self.capabilities.contains(Capabilities::FLOAT64) {
285                        return Err(WidthError::MissingCapability {
286                            name: "f64",
287                            flag: "FLOAT64",
288                        });
289                    }
290                    true
291                }
292                2 => {
293                    if !self.capabilities.contains(Capabilities::SHADER_FLOAT16) {
294                        return Err(WidthError::MissingCapability {
295                            name: "f16",
296                            flag: "FLOAT16",
297                        });
298                    }
299
300                    push_constant_compatibility = Err(PushConstantError::InvalidScalar(scalar));
301
302                    true
303                }
304                _ => scalar.width == 4,
305            },
306            crate::ScalarKind::Sint => {
307                if scalar.width == 8 {
308                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
309                        return Err(WidthError::MissingCapability {
310                            name: "i64",
311                            flag: "SHADER_INT64",
312                        });
313                    }
314                    true
315                } else {
316                    scalar.width == 4
317                }
318            }
319            crate::ScalarKind::Uint => {
320                if scalar.width == 8 {
321                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
322                        return Err(WidthError::MissingCapability {
323                            name: "u64",
324                            flag: "SHADER_INT64",
325                        });
326                    }
327                    true
328                } else {
329                    scalar.width == 4
330                }
331            }
332            crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
333                return Err(WidthError::Abstract);
334            }
335        };
336        if good {
337            Ok(push_constant_compatibility)
338        } else {
339            Err(WidthError::Invalid(scalar.kind, scalar.width))
340        }
341    }
342
343    pub(super) fn reset_types(&mut self, size: usize) {
344        self.types.clear();
345        self.types.resize(size, TypeInfo::dummy());
346        self.layouter.clear();
347    }
348
349    pub(super) fn validate_type(
350        &self,
351        handle: Handle<crate::Type>,
352        gctx: crate::proc::GlobalCtx,
353    ) -> Result<TypeInfo, TypeError> {
354        use crate::TypeInner as Ti;
355        Ok(match gctx.types[handle].inner {
356            Ti::Scalar(scalar) => {
357                let push_constant_compatibility = self.check_width(scalar)?;
358                let shareable = if scalar.kind.is_numeric() {
359                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
360                } else {
361                    TypeFlags::empty()
362                };
363                let mut type_info = TypeInfo::new(
364                    TypeFlags::DATA
365                        | TypeFlags::SIZED
366                        | TypeFlags::COPY
367                        | TypeFlags::ARGUMENT
368                        | TypeFlags::CONSTRUCTIBLE
369                        | TypeFlags::CREATION_RESOLVED
370                        | shareable,
371                    Alignment::from_width(scalar.width),
372                );
373                type_info.push_constant_compatibility = push_constant_compatibility;
374                type_info
375            }
376            Ti::Vector { size, scalar } => {
377                let push_constant_compatibility = self.check_width(scalar)?;
378                let shareable = if scalar.kind.is_numeric() {
379                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
380                } else {
381                    TypeFlags::empty()
382                };
383                let mut type_info = TypeInfo::new(
384                    TypeFlags::DATA
385                        | TypeFlags::SIZED
386                        | TypeFlags::COPY
387                        | TypeFlags::ARGUMENT
388                        | TypeFlags::CONSTRUCTIBLE
389                        | TypeFlags::CREATION_RESOLVED
390                        | shareable,
391                    Alignment::from(size) * Alignment::from_width(scalar.width),
392                );
393                type_info.push_constant_compatibility = push_constant_compatibility;
394                type_info
395            }
396            Ti::Matrix {
397                columns: _,
398                rows,
399                scalar,
400            } => {
401                if scalar.kind != crate::ScalarKind::Float {
402                    return Err(TypeError::MatrixElementNotFloat);
403                }
404                let push_constant_compatibility = self.check_width(scalar)?;
405                let mut type_info = TypeInfo::new(
406                    TypeFlags::DATA
407                        | TypeFlags::SIZED
408                        | TypeFlags::COPY
409                        | TypeFlags::HOST_SHAREABLE
410                        | TypeFlags::ARGUMENT
411                        | TypeFlags::CONSTRUCTIBLE
412                        | TypeFlags::CREATION_RESOLVED,
413                    Alignment::from(rows) * Alignment::from_width(scalar.width),
414                );
415                type_info.push_constant_compatibility = push_constant_compatibility;
416                type_info
417            }
418            Ti::Atomic(scalar) => {
419                match scalar {
420                    crate::Scalar {
421                        kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
422                        width: _,
423                    } => {
424                        if scalar.width == 8
425                            && !self.capabilities.intersects(
426                                Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
427                                    | Capabilities::SHADER_INT64_ATOMIC_MIN_MAX,
428                            )
429                        {
430                            return Err(TypeError::MissingCapability(
431                                Capabilities::SHADER_INT64_ATOMIC_ALL_OPS,
432                            ));
433                        }
434                    }
435                    crate::Scalar::F32 => {
436                        if !self
437                            .capabilities
438                            .contains(Capabilities::SHADER_FLOAT32_ATOMIC)
439                        {
440                            return Err(TypeError::MissingCapability(
441                                Capabilities::SHADER_FLOAT32_ATOMIC,
442                            ));
443                        }
444                    }
445                    _ => return Err(TypeError::InvalidAtomicWidth(scalar.kind, scalar.width)),
446                };
447                TypeInfo::new(
448                    TypeFlags::DATA
449                        | TypeFlags::SIZED
450                        | TypeFlags::HOST_SHAREABLE
451                        | TypeFlags::CREATION_RESOLVED,
452                    Alignment::from_width(scalar.width),
453                )
454            }
455            Ti::Pointer { base, space } => {
456                use crate::AddressSpace as As;
457
458                let base_info = &self.types[base.index()];
459                if !base_info.flags.contains(TypeFlags::DATA) {
460                    return Err(TypeError::InvalidPointerBase(base));
461                }
462
463                // Runtime-sized values can only live in the `Storage` address
464                // space, so it's useless to have a pointer to such a type in
465                // any other space.
466                //
467                // Detecting this problem here prevents the definition of
468                // functions like:
469                //
470                //     fn f(p: ptr<workgroup, UnsizedType>) -> ... { ... }
471                //
472                // which would otherwise be permitted, but uncallable. (They
473                // may also present difficulties in code generation).
474                if !base_info.flags.contains(TypeFlags::SIZED) {
475                    match space {
476                        As::Storage { .. } => {}
477                        _ => {
478                            return Err(TypeError::InvalidPointerToUnsized { base, space });
479                        }
480                    }
481                }
482
483                // `Validator::validate_function` actually checks the address
484                // space of pointer arguments explicitly before checking the
485                // `ARGUMENT` flag, to give better error messages. But it seems
486                // best to set `ARGUMENT` accurately anyway.
487                let argument_flag = ptr_space_argument_flag(space);
488
489                // Pointers cannot be stored in variables, structure members, or
490                // array elements, so we do not mark them as `DATA`.
491                TypeInfo::new(
492                    argument_flag
493                        | TypeFlags::SIZED
494                        | TypeFlags::COPY
495                        | TypeFlags::CREATION_RESOLVED,
496                    Alignment::ONE,
497                )
498            }
499            Ti::ValuePointer {
500                size: _,
501                scalar,
502                space,
503            } => {
504                // ValuePointer should be treated the same way as the equivalent
505                // Pointer / Scalar / Vector combination, so each step in those
506                // variants' match arms should have a counterpart here.
507                //
508                // However, some cases are trivial: All our implicit base types
509                // are DATA and SIZED, so we can never return
510                // `InvalidPointerBase` or `InvalidPointerToUnsized`.
511                let _ = self.check_width(scalar)?;
512
513                // `Validator::validate_function` actually checks the address
514                // space of pointer arguments explicitly before checking the
515                // `ARGUMENT` flag, to give better error messages. But it seems
516                // best to set `ARGUMENT` accurately anyway.
517                let argument_flag = ptr_space_argument_flag(space);
518
519                // Pointers cannot be stored in variables, structure members, or
520                // array elements, so we do not mark them as `DATA`.
521                TypeInfo::new(
522                    argument_flag
523                        | TypeFlags::SIZED
524                        | TypeFlags::COPY
525                        | TypeFlags::CREATION_RESOLVED,
526                    Alignment::ONE,
527                )
528            }
529            Ti::Array { base, size, stride } => {
530                let base_info = &self.types[base.index()];
531                if !base_info
532                    .flags
533                    .contains(TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::CREATION_RESOLVED)
534                {
535                    return Err(TypeError::InvalidArrayBaseType(base));
536                }
537
538                let base_layout = self.layouter[base];
539                let general_alignment = base_layout.alignment;
540                let uniform_layout = match base_info.uniform_layout {
541                    Ok(base_alignment) => {
542                        let alignment = base_alignment
543                            .max(general_alignment)
544                            .max(Alignment::MIN_UNIFORM);
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                let storage_layout = match base_info.storage_layout {
554                    Ok(base_alignment) => {
555                        let alignment = base_alignment.max(general_alignment);
556                        if alignment.is_aligned(stride) {
557                            Ok(alignment)
558                        } else {
559                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
560                        }
561                    }
562                    Err(e) => Err(e),
563                };
564
565                let type_info_mask = match size {
566                    crate::ArraySize::Constant(_) => {
567                        TypeFlags::DATA
568                            | TypeFlags::SIZED
569                            | TypeFlags::COPY
570                            | TypeFlags::HOST_SHAREABLE
571                            | TypeFlags::ARGUMENT
572                            | TypeFlags::CONSTRUCTIBLE
573                            | TypeFlags::CREATION_RESOLVED
574                    }
575                    crate::ArraySize::Pending(_) => {
576                        TypeFlags::DATA
577                            | TypeFlags::SIZED
578                            | TypeFlags::COPY
579                            | TypeFlags::HOST_SHAREABLE
580                            | TypeFlags::ARGUMENT
581                    }
582                    crate::ArraySize::Dynamic => {
583                        // Non-SIZED types may only appear as the last element of a structure.
584                        // This is enforced by checks for SIZED-ness for all compound types,
585                        // and a special case for structs.
586                        TypeFlags::DATA
587                            | TypeFlags::COPY
588                            | TypeFlags::HOST_SHAREABLE
589                            | TypeFlags::CREATION_RESOLVED
590                    }
591                };
592
593                TypeInfo {
594                    flags: base_info.flags & type_info_mask,
595                    uniform_layout,
596                    storage_layout,
597                    push_constant_compatibility: base_info.push_constant_compatibility.clone(),
598                }
599            }
600            Ti::Struct { ref members, span } => {
601                if members.is_empty() {
602                    return Err(TypeError::EmptyStruct);
603                }
604
605                let mut ti = TypeInfo::new(
606                    TypeFlags::DATA
607                        | TypeFlags::SIZED
608                        | TypeFlags::COPY
609                        | TypeFlags::HOST_SHAREABLE
610                        | TypeFlags::IO_SHAREABLE
611                        | TypeFlags::ARGUMENT
612                        | TypeFlags::CONSTRUCTIBLE
613                        | TypeFlags::CREATION_RESOLVED,
614                    Alignment::ONE,
615                );
616                ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
617
618                let mut min_offset = 0;
619                let mut prev_struct_data: Option<(u32, u32)> = None;
620
621                for (i, member) in members.iter().enumerate() {
622                    let base_info = &self.types[member.ty.index()];
623                    if !base_info
624                        .flags
625                        .contains(TypeFlags::DATA | TypeFlags::CREATION_RESOLVED)
626                    {
627                        return Err(TypeError::InvalidData(member.ty));
628                    }
629                    if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
630                        if ti.uniform_layout.is_ok() {
631                            ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
632                        }
633                        if ti.storage_layout.is_ok() {
634                            ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
635                        }
636                    }
637                    ti.flags &= base_info.flags;
638
639                    if member.offset < min_offset {
640                        // HACK: this could be nicer. We want to allow some structures
641                        // to not bother with offsets/alignments if they are never
642                        // used for host sharing.
643                        if member.offset == 0 {
644                            ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
645                        } else {
646                            return Err(TypeError::MemberOverlap {
647                                index: i as u32,
648                                offset: member.offset,
649                            });
650                        }
651                    }
652
653                    let base_size = gctx.types[member.ty].inner.size(gctx);
654                    min_offset = member.offset + base_size;
655                    if min_offset > span {
656                        return Err(TypeError::MemberOutOfBounds {
657                            index: i as u32,
658                            offset: member.offset,
659                            size: base_size,
660                            span,
661                        });
662                    }
663
664                    check_member_layout(
665                        &mut ti.uniform_layout,
666                        member,
667                        i as u32,
668                        base_info.uniform_layout,
669                        handle,
670                    );
671                    check_member_layout(
672                        &mut ti.storage_layout,
673                        member,
674                        i as u32,
675                        base_info.storage_layout,
676                        handle,
677                    );
678                    if base_info.push_constant_compatibility.is_err() {
679                        ti.push_constant_compatibility =
680                            base_info.push_constant_compatibility.clone();
681                    }
682
683                    // Validate rule: If a structure member itself has a structure type S,
684                    // then the number of bytes between the start of that member and
685                    // the start of any following member must be at least roundUp(16, SizeOf(S)).
686                    if let Some((span, offset)) = prev_struct_data {
687                        let diff = member.offset - offset;
688                        let min = Alignment::MIN_UNIFORM.round_up(span);
689                        if diff < min {
690                            ti.uniform_layout = Err((
691                                handle,
692                                Disalignment::MemberOffsetAfterStruct {
693                                    index: i as u32,
694                                    offset: member.offset,
695                                    expected: offset + min,
696                                },
697                            ));
698                        }
699                    };
700
701                    prev_struct_data = match gctx.types[member.ty].inner {
702                        crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
703                        _ => None,
704                    };
705
706                    // The last field may be an unsized array.
707                    if !base_info.flags.contains(TypeFlags::SIZED) {
708                        let is_array = match gctx.types[member.ty].inner {
709                            crate::TypeInner::Array { .. } => true,
710                            _ => false,
711                        };
712                        if !is_array || i + 1 != members.len() {
713                            let name = member.name.clone().unwrap_or_default();
714                            return Err(TypeError::InvalidDynamicArray(name, member.ty));
715                        }
716                        if ti.uniform_layout.is_ok() {
717                            ti.uniform_layout =
718                                Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
719                        }
720                    }
721                }
722
723                let alignment = self.layouter[handle].alignment;
724                if !alignment.is_aligned(span) {
725                    ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
726                    ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
727                }
728
729                ti
730            }
731            Ti::Image {
732                dim,
733                arrayed,
734                class,
735            } => {
736                if arrayed && matches!(dim, crate::ImageDimension::D3) {
737                    return Err(TypeError::UnsupportedImageType {
738                        dim,
739                        arrayed,
740                        class,
741                    });
742                }
743                if arrayed && matches!(dim, crate::ImageDimension::Cube) {
744                    self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
745                }
746                if matches!(class, crate::ImageClass::External) {
747                    if dim != crate::ImageDimension::D2 || arrayed {
748                        return Err(TypeError::UnsupportedImageType {
749                            dim,
750                            arrayed,
751                            class,
752                        });
753                    }
754                    self.require_type_capability(Capabilities::TEXTURE_EXTERNAL)?;
755                }
756                TypeInfo::new(
757                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
758                    Alignment::ONE,
759                )
760            }
761            Ti::Sampler { .. } => TypeInfo::new(
762                TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
763                Alignment::ONE,
764            ),
765            Ti::AccelerationStructure { vertex_return } => {
766                self.require_type_capability(Capabilities::RAY_QUERY)?;
767                if vertex_return {
768                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
769                }
770                TypeInfo::new(
771                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
772                    Alignment::ONE,
773                )
774            }
775            Ti::RayQuery { vertex_return } => {
776                self.require_type_capability(Capabilities::RAY_QUERY)?;
777                if vertex_return {
778                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
779                }
780                TypeInfo::new(
781                    TypeFlags::DATA
782                        | TypeFlags::CONSTRUCTIBLE
783                        | TypeFlags::SIZED
784                        | TypeFlags::CREATION_RESOLVED,
785                    Alignment::ONE,
786                )
787            }
788            Ti::BindingArray { base, size } => {
789                let type_info_mask = match size {
790                    crate::ArraySize::Constant(_) => {
791                        TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
792                    }
793                    crate::ArraySize::Pending(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
794                    crate::ArraySize::Dynamic => {
795                        // Final type is non-sized
796                        TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
797                    }
798                };
799                let base_info = &self.types[base.index()];
800
801                if base_info.flags.contains(TypeFlags::DATA) {
802                    // Currently Naga only supports binding arrays of structs for non-handle types.
803                    match gctx.types[base].inner {
804                        crate::TypeInner::Struct { .. } => {}
805                        _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
806                    };
807                }
808                if matches!(
809                    gctx.types[base].inner,
810                    crate::TypeInner::Image {
811                        class: crate::ImageClass::External,
812                        ..
813                    }
814                ) {
815                    // Binding arrays of external textures are not yet supported.
816                    // https://github.com/gfx-rs/wgpu/issues/8027
817                    return Err(TypeError::BindingArrayBaseExternalTextures);
818                }
819
820                if !base_info.flags.contains(TypeFlags::CREATION_RESOLVED) {
821                    return Err(TypeError::InvalidData(base));
822                }
823
824                TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
825            }
826        })
827    }
828}