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