Skip to main content

nox_spirv/
reflect.rs

1//! Contains everything reflection relevant.
2
3#![warn(missing_docs)]
4
5mod error;
6
7use core::ffi::CStr;
8
9use crate::{
10    core::*,
11    op,
12    module::*,
13};
14
15pub use error::*;
16
17/// An Id to an instruction, which has a [`result`][1].
18///
19/// [1]: op::IdResult
20pub type Id = op::IdRef;
21
22/// Represents an constant value.
23#[derive(Clone, Copy, Debug)]
24pub enum Constant<'a> {
25    /// A non-specialization constant boolean.
26    Bool(bool),
27    /// A non-specialization constant.
28    Constant(Literal),
29    /// A non-specialization constant composite.
30    Composite {
31        /// The type [`Id`] of the composite type.
32        ty: Id,
33        /// The constituents of composite value.
34        constituents: &'a [Id],
35    },
36    /// A constant sampler.
37    Sampler {
38        /// The addressing mode of the sampler.
39        addressing_mode: op::SamplerAddressingMode,
40        /// Whether the sampler uses normalized coordinates.
41        is_normalized: bool,
42        /// The filter mode of the sampler.
43        filter_mode: op::SamplerFilterMode,
44    },
45    /// A null value.
46    Null {
47        /// The type [`Id`] of the null value.
48        ty: Id,
49    },
50    /// A specialization constant boolean.
51    SpecBool(bool),
52    /// A specialization constant.
53    SpecConstant(Literal),
54    /// A specialization constant composite.
55    SpecConstantComposite {
56        /// The type [`Id`] of the composite type.
57        ty: Id,
58        /// The constituents of the composite value.
59        constituents: &'a [Id],
60    },
61    /// A specialization constant operation.
62    SpecConstantOp {
63        /// The resultant type [`Id`] of the operation.
64        ty: Id,
65        /// The [`code`][1] specifying the operation.
66        ///
67        /// [1]: op::Code
68        opcode: op::Code,
69        /// The operands of the operation.
70        operands: &'a [Id]
71    },
72}
73
74/// Represents a scalar type.
75#[derive(Clone, Copy, Debug)]
76pub enum ScalarType {
77    /// A boolean type.
78    Bool,
79    /// An integer type.
80    Int {
81        /// The width of the integer in bits.
82        width: u32,
83        /// Whether the integer is signed.
84        is_signed: bool,
85    },
86    /// A floating point type.
87    Float {
88        /// The width of the floating point type in bits.
89        width: u32,
90    },
91}
92
93/// Represents a specialization constant
94#[derive(Clone, Copy, Debug)]
95pub struct SpecConstant {
96    /// The scalar type of the constant.
97    pub ty: ScalarType,
98    /// The constant id assigned to the constant.
99    pub constant_id: u32,
100    /// The [`Id`] of the constant.
101    pub id: Id,
102}
103
104/// A type specifying, which resources to [`reflect`][1].
105///
106/// [1]: Reflector::resources_for_type
107#[derive(Clone, Copy, Debug)]
108pub enum ResourceType {
109    /// A uniform buffer resource.
110    UniformBuffer,
111    /// A storage buffer resource.
112    StorageBuffer,
113    /// A push constant resource.
114    PushConstant,
115    /// An atomic counter resource.
116    AtomicCounter,
117    /// An input attachment resource.
118    InputAttachment,
119    /// A storage image resource.
120    StorageImage,
121    /// A sampled image containing both a sampler and an image.
122    CombinedImageSampler,
123    /// A sampled image without a sampler.
124    SeparateImage,
125    /// An sampler without an image.
126    SeparateSampler,
127    /// A uniform buffer interpreted as an image.
128    UniformTexelBuffer,
129    /// A storage buffer interpreted as an image.
130    StorageTexelBuffer,
131}
132
133/// A hole created from the declaration of a runtime array.
134///
135/// Can be resolved by giving the number of elements in the array.
136#[derive(Clone, Debug)]
137pub struct RuntimeArrayHole {
138    stride: usize,
139}
140
141impl RuntimeArrayHole {
142
143    /// Gets the declared size of runtime array, which is just its stride.
144    #[inline]
145    pub fn declared(&self) -> usize {
146        self.stride
147    }
148
149    /// Resolves the hole with `count`.
150    #[inline]
151    pub fn resolve(&self, count: usize) -> usize {
152        self.stride * count
153    }
154}
155
156/// A hole created from the declaration of an array without the [`array stride decoration`][1].
157///
158/// Can be resolved, by resolving the element type [`hint`][2].
159///
160/// [1]: op::Decoration::ArrayStride
161/// [2]: TypeSizeHint
162#[derive(Clone, Debug)]
163pub struct UnknownArrayStrideHole {
164    element: Box<TypeSizeHint>,
165    count: usize,
166}
167
168impl UnknownArrayStrideHole {
169
170    /// Gets the declared size of the hole.
171    #[inline]
172    pub fn declared(&self) -> usize {
173        self.element.declared() * self.count
174    }
175
176    /// Resolves the hole by resolving the element type [`hint`][1].
177    ///
178    /// [1]: TypeSizeHint
179    #[inline]
180    pub fn resolve<F>(&self, f: F) -> usize
181        where F: FnOnce(&TypeSizeHint) -> usize
182    {
183        f(&self.element) * self.count
184    }
185}
186
187/// A hole created from the usage of a matrix type without the [`matrix stride decoration`][1].
188///
189/// Can be resolved by specifying the stride and whether the matrix is row-major.
190///
191/// [1]: op::Decoration::MatrixStride
192#[derive(Clone, Debug)]
193pub struct MatrixStrideHole {
194    columns: u32,
195    rows: u32,
196    declared: usize
197}
198
199impl MatrixStrideHole {
200
201    /// Gets the declared size of the hole.
202    #[inline]
203    pub fn declared(&self) -> usize {
204        self.declared
205    }
206
207    /// Resolves the hole with a stride and whether the matrix is row-major.
208    #[inline]
209    pub fn resolve(&self, stride: usize, is_row_major: bool) -> usize {
210        if is_row_major {
211            stride * self.rows as usize
212        } else {
213            stride * self.columns as usize
214        }
215    }
216}
217
218/// A hole created from a struct, which has a runtime array as the last member.
219///
220/// Can be resolved by giving the number of elements in the runtime array.
221#[derive(Clone, Debug)]
222pub struct StructHole {
223    last_offset: usize,
224    hole: RuntimeArrayHole,
225}
226
227impl StructHole {
228
229    /// Gets the declared size of the hole.
230    #[inline]
231    pub fn declared(&self) -> usize {
232        self.last_offset + self.hole.declared()
233    }
234
235    /// Resolves the hole by giving the number of elements in the runtime array.
236    #[inline]
237    pub fn resolve(&self, count: usize) -> usize {
238        self.last_offset + self.hole.resolve(count)
239    }
240}
241
242/// A hint of the size of a [`Type`].
243///
244/// It can be a statically known size, or it can have a hole which can be resolved.
245#[derive(Clone, Debug)]
246pub enum TypeSizeHint {
247    /// A statically known size.
248    Static(usize),
249    /// A runtime array hole.
250    RuntimeArray(RuntimeArrayHole),
251    /// A matrix stride hole.
252    Matrix(MatrixStrideHole),
253    /// An unknown array stride hole.
254    UnknownArrayStride(UnknownArrayStrideHole),
255    /// A hole caused by a runtime array at the end of a struct.
256    Struct(StructHole),
257}
258
259impl TypeSizeHint {
260
261    /// Gets the declared minimum size of the type.
262    #[inline]
263    pub fn declared(&self) -> usize {
264        match self {
265            &Self::Static(size) => size,
266            Self::RuntimeArray(hole) => hole.declared(),
267            Self::Matrix(hole) => hole.declared(),
268            Self::UnknownArrayStride(hole) => hole.declared(),
269            Self::Struct(hole) => hole.declared(),
270        }
271    } 
272}
273
274/// Represents resource count in [`Resource`].
275#[derive(Clone, Copy, Debug)]
276pub enum ResourceCount {
277    /// The size is known statically.
278    Static(usize),
279    /// The resource is in a runtime array.
280    Runtime {
281        /// The declared minimum count of resources.
282        declared: usize,
283    },
284}
285
286impl ResourceCount {
287
288    /// Gets the declared count.
289    #[inline]
290    pub fn declared(self) -> usize {
291        match self {
292            Self::Static(size) => size,
293            Self::Runtime { declared } => declared,
294        }
295    }
296}
297
298/// Represents a shader resource.
299#[derive(Clone, Copy)]
300pub struct Resource<'a> {
301    /// The outer type of the resource.
302    pub type_id: Id,
303    /// The inner type of the resource.
304    pub base_type_id: Id,
305    /// The variable id of the resource.
306    pub variable_id: Id,
307    /// The variable name string of the resource.
308    pub name: Option<CompilerStr<'a>>,
309    /// The number of [`base type`][1] values in an array.
310    ///
311    /// [1]: Self::base_type_id
312    pub count: ResourceCount,
313    /// Offset of the first member in a struct used for [`push constants`][1].
314    ///
315    /// [1]: ResourceType::PushConstant
316    pub offset: Option<u32>,
317}
318
319/// Describes a type.
320pub struct Type<'a> {
321    /// The [`Id`] of the type.
322    pub id: Id,
323    /// The name of the type.
324    pub name: Option<CompilerStr<'a>>,
325    /// A [`hint`][1] of the size of the type.
326    ///
327    /// [1]: TypeSizeHint
328    pub size_hint: TypeSizeHint,
329}
330
331#[derive(Clone, Copy)]
332struct Name<'a> {
333    target: Id,
334    member: Option<u32>,
335    name: CompilerStr<'a>,
336}
337
338/// A decoration added to an instruction, or a member of a struct.
339#[derive(Clone, Copy)]
340pub struct Decorate<'a> {
341    /// The target [`Id`] of the decoration.
342    pub target: Id,
343    /// The member index within [`target`][1], if this decorates a member of a struct.
344    ///
345    /// [1]: Self::target
346    pub member: Option<u32>,
347    /// The [`Decoration`][1] assigned to the target.
348    ///
349    /// [1]: op::Decoration
350    pub decoration: op::Decoration<'a>,
351}
352
353/// Represents a variable.
354#[derive(Clone, Copy)]
355pub struct Variable {
356    /// The resultant type [`Id`] of the variable.
357    pub result_type: Id,
358    /// The [`Id`] of the variable.
359    pub id_result: Id,
360    /// The storage class of the variable.
361    pub storage_class: op::StorageClass,
362}
363
364#[derive(Clone, Copy)]
365struct EntryPoint<'a> {
366    _execution_model: op::ExecutionModel,
367    _name: CompilerStr<'a>,
368    interface: &'a [Id],
369}
370
371/// Reflects SPIR-V code through a [`module`][1].
372///
373/// # Example
374/// ``` rust
375/// use nox_spirv::op;
376/// use nox_spirv::Module;
377/// use nox_spirv::reflect::{Reflector, ResourceType};
378/// 
379/// let spirv: &[u32] = ...;
380/// let module = Module::new(spirv).unwrap();
381/// let mut reflector = Reflector::new(module).unwrap();
382/// reflector.set_entry_point(c"main", op::ExecutionModel::FRAGMENT).unwrap();
383/// for push_constant in reflector.resources_for_type(ResourceType::PushConstant).unwrap() {
384///     let push_constant = push_constant.unwrap();
385///     let size = reflector
386///         .type_description(push_constant.base_type_id)
387///         .unwrap().size_hint.declared();
388///     println!("Push constant (size {size}): {}", push_constant.name.unwrap_or_default());
389/// }
390/// ```
391///
392/// [1]: Module
393pub struct Reflector<'a> {
394    module: Module<'a>,
395    decorates: Vec<Decorate<'a>>,
396    current_entry_point: Option<EntryPoint<'a>>,
397}
398
399impl<'a> Reflector<'a> {
400
401    /// Creates a reflector from a [`module`][1].
402    ///
403    /// Automatically calls [`parse_full`][2].
404    ///
405    /// [1]: Module
406    /// [2]: Module::parse_full
407    pub fn new(
408        mut module: Module<'a>,
409    ) -> ReflectResult<Reflector<'a>>
410    {
411        module.parse_full()?;
412        let mut decorates = vec![];
413        for mut stream in module.all_instructions() {
414            if matches!(
415                stream.code(),
416                op::Code::DECORATE | op::Code::DECORATE_ID | op::Code::DECORATE_STRING,
417            ) {
418                decorates.push(Decorate {
419                    target: Id::parse_one(&mut stream)?,
420                    member: None,
421                    decoration: op::Decoration::parse_one(&mut stream)?,
422                });
423            } else if matches!(
424                stream.code(),
425                op::Code::MEMBER_DECORATE | op::Code::MEMBER_DECORATE_STRING,
426            ) {
427                decorates.push(Decorate {
428                    target: Id::parse_one(&mut stream)?,
429                    member: Some(stream.read()?),
430                    decoration: op::Decoration::parse_one(&mut stream)?,
431                });
432            }
433        }
434        Ok(Self {
435            decorates,
436            module,
437            current_entry_point: None,
438        })
439    }
440
441    /// Returns an iterator over all variables.
442    #[inline]
443    pub fn variables(&self) -> impl Iterator<Item = ReflectResult<Variable>>
444    {
445        self.module
446            .results()
447            .map(|(_, mut stream)| {
448                if stream.code() == op::Code::VARIABLE {
449                    let result_type = Id::parse_one(&mut stream)?;
450                    let id_result = Id::parse_one(&mut stream)?;
451                    let storage_class = op::StorageClass::parse_one(&mut stream)?;
452                    return Ok(Some(Variable {
453                        result_type,
454                        id_result,
455                        storage_class,
456                    }))
457                }
458                ReflectResult::Ok(None)
459            }).filter_map(|variable| {
460                match variable {
461                    Ok(var) => var.map(Ok),
462                    Err(err) => Some(Err(err)),
463                }
464            })
465    }
466
467    /// Sets the entry point to reflect.
468    ///
469    /// This *must* be set before [`reflecting any resources`][1].
470    ///
471    /// [1]: Self::resources_for_type
472    #[inline]
473    pub fn set_entry_point(
474        &mut self,
475        name: &CStr,
476        execution_model: op::ExecutionModel,
477    ) -> ReflectResult<()>
478    {
479        for mut stream in self.module.all_instructions() {
480            if stream.code() == op::Code::ENTRY_POINT {
481                let exec_model = op::ExecutionModel::parse_one(&mut stream)?;
482                if exec_model == execution_model {
483                    stream.advance(1)?;
484                    let cname = stream.read_string()?;
485                    if name == cname.to_cstr()? {
486                        self.current_entry_point = Some(EntryPoint {
487                            _execution_model: exec_model,
488                            _name: cname,
489                            interface: Id::parse_eos(&mut stream)?,
490                        });
491                        return Ok(())
492                    }
493                }
494            }
495        }
496        Err(ReflectError::EntryPointNotFound(name.to_owned(), execution_model))
497    }
498
499    fn names(&self) -> impl Iterator<Item = ReflectResult<Name<'a>>> {
500        self.module
501            .all_instructions()
502            .filter(|stream| matches!(stream.code(), op::Code::NAME | op::Code::MEMBER_NAME))
503            .map(|mut stream| {
504                if stream.code() == op::Code::NAME {
505                    Ok(Name {
506                        target: Id::parse_one(&mut stream)?,
507                        member: None,
508                        name: stream.read_string()?,
509                    })
510                } else if stream.code() == op::Code::MEMBER_NAME {
511                    Ok(Name {
512                        target: Id::parse_one(&mut stream)?,
513                        member: Some(stream.read()?),
514                        name: stream.read_string()?,
515                    })
516                } else { unreachable!() }
517            })
518        }
519
520    /// Returns an iterator over the statically known values and constant ids of all
521    /// specialization constants.
522    #[inline]
523    pub fn spec_constants(&self) -> impl Iterator<Item = ReflectResult<SpecConstant>> {
524        self.module
525            .results()
526            .filter_map(|(id, stream)| 
527                if matches!(
528                    stream.code(),
529                    op::Code::SPEC_CONSTANT_TRUE
530                    | op::Code::SPEC_CONSTANT_FALSE
531                    | op::Code::SPEC_CONSTANT
532                ) {
533                    self.decorates
534                        .iter()
535                        .find_map(|&decorate|
536                            if decorate.target == id &&
537                                let op::Decoration::SpecId { specialization_constant_id } =
538                                decorate.decoration
539                            {
540                                Some(specialization_constant_id)
541                            } else { None }
542                        ).map(|constant_id| (constant_id, id, stream))
543                } else { None }
544            )
545            .map(|(constant_id, id, mut stream)| {
546                match stream.code() {
547                    op::Code::SPEC_CONSTANT_TRUE | op::Code::SPEC_CONSTANT_FALSE => {
548                        Ok(SpecConstant {
549                            ty: ScalarType::Bool,
550                            constant_id,
551                            id,
552                        })
553                    },
554                    op::Code::SPEC_CONSTANT => {
555                        let result_type = op::IdResultType::parse_one(&mut stream)?;
556                        stream.advance(1)?;
557                        let ctx = &mut ParseContext {
558                            result_type: Some(result_type)
559                        };
560                        let value = Literal::parse_one(&self.module, &mut stream, ctx)?;
561                        let ty = match value {
562                            Literal::F16(_) => ScalarType::Float { width: 2, },
563                            Literal::F32(_) => ScalarType::Float { width: 4, },
564                            Literal::F64(_) => ScalarType::Float { width: 8, },
565                            Literal::I8(_) => ScalarType::Int { width: 1, is_signed: true, },
566                            Literal::I16(_) => ScalarType::Int { width: 2, is_signed: true, },
567                            Literal::I32(_) => ScalarType::Int { width: 4, is_signed: true, },
568                            Literal::I64(_) => ScalarType::Int { width: 8, is_signed: true, },
569                            Literal::U8(_) => ScalarType::Int { width: 1, is_signed: false, },
570                            Literal::U16(_) => ScalarType::Int { width: 2, is_signed: false, },
571                            Literal::U32(_) => ScalarType::Int { width: 4, is_signed: false, },
572                            Literal::U64(_) => ScalarType::Int { width: 8, is_signed: false, },
573                        };
574                        Ok(SpecConstant {
575                            ty,
576                            constant_id,
577                            id,
578                        })
579                    },
580                    _ => unreachable!(),
581                }
582            })
583    }
584
585    /// Gets a specific constant with `id`.
586    pub fn constant(
587        &self,
588        id: Id,
589    ) -> ReflectResult<Constant<'a>>
590    {
591        let mut stream = self.module.get_result(id).ok_or(ReflectError::InvalidConstantId(id))?;
592        match stream.code() {
593            op::Code::CONSTANT_TRUE => {
594                Ok(Constant::Bool(true))
595            },
596            op::Code::CONSTANT_FALSE => {
597                Ok(Constant::Bool(false))
598            },
599            op::Code::CONSTANT => {
600                let result_type = op::IdResultType::parse_one(&mut stream)?;
601                stream.advance(1)?;
602                let ctx = &mut ParseContext {
603                    result_type: Some(result_type)
604                };
605                let value = Literal::parse_one(&self.module, &mut stream, ctx)?;
606                Ok(Constant::Constant(value))
607            },
608            op::Code::CONSTANT_COMPOSITE => {
609                let ty = Id::parse_one(&mut stream)?;
610                stream.advance(1)?;
611                Ok(Constant::Composite {
612                    ty,
613                    constituents: Id::parse_eos(&mut stream)?,
614                })
615            },
616            op::Code::CONSTANT_SAMPLER => {
617                stream.advance(2)?;
618                let addressing_mode = op::SamplerAddressingMode::parse_one(&mut stream)?;
619                let is_normalized = stream.read()? == 1;
620                let filter_mode = op::SamplerFilterMode::parse_one(&mut stream)?;
621                Ok(Constant::Sampler { addressing_mode, is_normalized, filter_mode })
622            },
623            op::Code::CONSTANT_NULL => {
624                let ty = Id::parse_one(&mut stream)?;
625                Ok(Constant::Null { ty })
626            },
627            op::Code::SPEC_CONSTANT_TRUE => {
628                Ok(Constant::SpecBool(true))
629            },
630            op::Code::SPEC_CONSTANT_FALSE => {
631                Ok(Constant::SpecBool(false))
632            },
633            op::Code::SPEC_CONSTANT => {
634                let result_type = op::IdResultType::parse_one(&mut stream)?;
635                stream.advance(1)?;
636                let ctx = &mut ParseContext {
637                    result_type: Some(result_type)
638                };
639                let value = Literal::parse_one(&self.module, &mut stream, ctx)?;
640                Ok(Constant::SpecConstant(value))
641            },
642            op::Code::SPEC_CONSTANT_COMPOSITE => {
643                let ty = Id::parse_one(&mut stream)?;
644                stream.advance(1)?;
645                Ok(Constant::SpecConstantComposite {
646                    ty,
647                    constituents: Id::parse_eos(&mut stream)?,
648                })
649            },
650            op::Code::SPEC_CONSTANT_OP => {
651                let ty = Id::parse_one(&mut stream)?;
652                stream.advance(1)?;
653                let code = op::LiteralSpecConstantOpInteger::parse_one(&mut stream)?;
654                Ok(Constant::SpecConstantOp { ty, opcode: code.code, operands: code.operands })
655            },
656            _ => Err(ReflectError::InvalidConstantId(id))
657        }
658    }
659
660    #[inline]
661    /// Returns a name assigned to an [`Id`], or optionally to a struct member of a struct.
662    pub fn name(&self, id: Id, struct_member: Option<u32>) -> ReflectResult<Option<CompilerStr<'a>>> {
663        for name in self.names() {
664            let name = name?;
665            if name.target == id &&
666                name.member == struct_member
667            {
668                return Ok(Some(name.name))
669            }
670        }
671        Ok(None)
672    }
673
674    /// Returns an iterator over all resources of [`type`][1].
675    ///
676    /// Entry point *must* be [`set`][2] before calling this function.
677    ///
678    /// If the SPIR-V version is >= 1.4, the resources are filtered to only include ones used by
679    /// the current entry point.
680    ///
681    /// [1]: ResourceType
682    /// [2]: Self::set_entry_point
683    pub fn resources_for_type(&self, ty: ResourceType) -> ReflectResult<
684        impl Iterator<Item = ReflectResult<Resource<'a>>>
685    >
686    {
687        let Some(entry_point) = self.current_entry_point else {
688            return Err(ReflectError::EntryPointNotSet)
689        };
690        let resource_class = match ty {
691            ResourceType::UniformBuffer => op::StorageClass::UNIFORM,
692            ResourceType::StorageBuffer => op::StorageClass::STORAGE_BUFFER,
693            ResourceType::PushConstant => op::StorageClass::PUSH_CONSTANT,
694            ResourceType::AtomicCounter => op::StorageClass::ATOMIC_COUNTER,
695            ResourceType::InputAttachment
696            | ResourceType::StorageImage
697            | ResourceType::CombinedImageSampler
698            | ResourceType::SeparateImage
699            | ResourceType::SeparateSampler
700            | ResourceType::UniformTexelBuffer
701            | ResourceType::StorageTexelBuffer => op::StorageClass::UNIFORM_CONSTANT,
702        };
703        Ok(self.variables().map(move |op_variable| {
704            let op_variable = op_variable?;
705            if self.module.version() >= VERSION_1_4 &&
706                !entry_point.interface.contains(&op_variable.id_result)
707            {
708                return Ok(None)
709            }
710            let mut base_type = op_variable.result_type;
711            let mut count = ResourceCount::Static(1);
712            loop {
713                let mut stream = self.module
714                    .get_result(base_type)
715                    .ok_or(ReflectError::InvalidTypeId(base_type))?;
716                base_type = match stream.code()
717                {
718                    op::Code::TYPE_POINTER => {
719                        stream.advance(1)?;
720                        let storage_class = op::StorageClass::parse_one(&mut stream)?;
721                        if storage_class != resource_class {
722                            return Ok(None)
723                        }
724                        Id::parse_one(&mut stream)?
725                    },
726                    op::Code::TYPE_ARRAY => {
727                        stream.advance(1)?;
728                        let id = Id::parse_one(&mut stream)?;
729                        let length = match self.constant(Id::parse_one(&mut stream)?)?
730                        {
731                            Constant::Constant(literal) => {
732                                literal.as_usize()
733                                    .ok_or(ReflectError::NonIntegerLiteral(literal))?
734                            },
735                            Constant::SpecConstant(literal) => {
736                                literal.as_usize()
737                                    .ok_or(ReflectError::NonIntegerLiteral(literal))?
738                            },
739                            x => return Err(ReflectError::ExpectedConstantLiteral {
740                                found: format!("{x:?}")
741                            })
742                        };
743                        count = match count {
744                            ResourceCount::Static(count) =>
745                                ResourceCount::Static(count * length),
746                            ResourceCount::Runtime { declared } =>
747                                ResourceCount::Runtime { declared: declared * length },
748                        };
749                        id
750                    },
751                    op::Code::TYPE_RUNTIME_ARRAY => {
752                        stream.advance(1)?;
753                        count = match count {
754                            ResourceCount::Static(count) =>
755                                ResourceCount::Runtime { declared: count, },
756                            ResourceCount::Runtime { declared } =>
757                                ResourceCount::Runtime { declared },
758
759                        };
760                        Id::parse_one(&mut stream)?
761                    },
762                    _ => {
763                        break
764                    },
765                };
766            }
767            let name = self.names().find_map(|op_name| {
768                if let Ok(name) = op_name &&
769                    name.target == op_variable.id_result
770                {
771                    Some(name.name)
772                } else { None }
773            }); 
774            if resource_class == op::StorageClass::UNIFORM_CONSTANT {
775                match ty {
776                    ResourceType::InputAttachment => {
777                        let mut stream = self.module
778                            .get_result(base_type)
779                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
780                        if matches!(stream.code(), op::Code::TYPE_IMAGE) {
781                            stream.advance(2)?;
782                            let dim = op::Dim::parse_one(&mut stream)?;
783                            if dim == op::Dim::SUBPASS_DATA {
784                                return Ok(Some(Resource {
785                                    type_id: op_variable.result_type,
786                                    base_type_id: base_type,
787                                    variable_id: op_variable.id_result,
788                                    name,
789                                    count,
790                                    offset: None,
791                                }))
792                            }
793                        }
794                        Ok(None)
795                    },
796                    ResourceType::StorageImage => {
797                        let mut stream = self.module
798                            .get_result(base_type)
799                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
800                        if matches!(stream.code(), op::Code::TYPE_IMAGE) {
801                            stream.advance(2)?;
802                            let dim = op::Dim::parse_one(&mut stream)?;
803                            stream.advance(3)?;
804                            let sampled = stream.read()?;
805                            if sampled == 2 &&
806                                matches!(
807                                    dim,
808                                    op::Dim::TYPE_1D
809                                    | op::Dim::TYPE_2D
810                                    | op::Dim::TYPE_3D
811                                    | op::Dim::CUBE
812                                    | op::Dim::RECT
813                                )
814                            {
815                                return Ok(Some(Resource {
816                                    type_id: op_variable.result_type,
817                                    base_type_id: base_type,
818                                    variable_id: op_variable.id_result,
819                                    name,
820                                    count,
821                                    offset: None,
822                                }))
823                            }
824                        }
825                        Ok(None)
826                    },
827                    ResourceType::CombinedImageSampler => {
828                        let stream = self.module
829                            .get_result(base_type)
830                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
831                        if matches!(stream.code(), op::Code::TYPE_SAMPLED_IMAGE) {
832                            Ok(Some(Resource {
833                                type_id: op_variable.result_type,
834                                base_type_id: base_type,
835                                variable_id: op_variable.id_result,
836                                name,
837                                count,
838                                offset: None,
839                            }))
840                        } else { Ok(None) }
841                    },
842                    ResourceType::SeparateImage => {
843                        let mut stream = self.module
844                            .get_result(base_type)
845                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
846                        if matches!(
847                            stream.code(),
848                            op::Code::TYPE_IMAGE,
849                        ) {
850                            stream.advance(2)?;
851                            let dim = op::Dim::parse_one(&mut stream)?;
852                            stream.advance(3)?;
853                            let sampled = stream.read()?;
854                            if sampled != 2 &&
855                                matches!(
856                                    dim,
857                                    op::Dim::TYPE_1D
858                                    | op::Dim::TYPE_2D
859                                    | op::Dim::TYPE_3D
860                                    | op::Dim::CUBE
861                                    | op::Dim::RECT
862                                )
863                            {
864                                return Ok(Some(Resource {
865                                    type_id: op_variable.result_type,
866                                    base_type_id: base_type,
867                                    variable_id: op_variable.id_result,
868                                    name,
869                                    count,
870                                    offset: None,
871                                }))
872                            }
873                        } 
874                        Ok(None)
875                    },
876                    ResourceType::SeparateSampler => {
877                        let stream = self.module
878                            .get_result(base_type)
879                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
880                        if matches!(stream.code(), op::Code::TYPE_SAMPLER) {
881                            Ok(Some(Resource {
882                                type_id: op_variable.result_type,
883                                base_type_id: base_type,
884                                variable_id: op_variable.id_result,
885                                name,
886                                count,
887                                offset: None,
888                            }))
889                        } else { Ok(None) }
890                    },
891                    ResourceType::UniformTexelBuffer => {
892                        let mut stream = self.module
893                            .get_result(base_type)
894                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
895                        if matches!(stream.code(), op::Code::TYPE_IMAGE) {
896                            stream.advance(2)?;
897                            let dim = op::Dim::parse_one(&mut stream)?;
898                            stream.advance(3)?;
899                            let sampled = stream.read()?;
900                            if sampled != 2 && dim == op::Dim::BUFFER {
901                                return  Ok(Some(Resource {
902                                    type_id: op_variable.result_type,
903                                    base_type_id: base_type,
904                                    variable_id: op_variable.id_result, 
905                                    name,
906                                    count,
907                                    offset: None,
908                                }))
909                            }
910                        }
911                        Ok(None)
912                    },
913                    ResourceType::StorageTexelBuffer => {
914                        let mut stream = self.module
915                            .get_result(base_type)
916                            .ok_or(ReflectError::InvalidTypeId(base_type))?;
917                        if matches!(stream.code(), op::Code::TYPE_IMAGE) {
918                            stream.advance(2)?;
919                            let dim = op::Dim::parse_one(&mut stream)?;
920                            stream.advance(3)?;
921                            let sampled = stream.read()?;
922                            if sampled == 2 && dim == op::Dim::BUFFER {
923                                return  Ok(Some(Resource {
924                                    type_id: op_variable.result_type,
925                                    base_type_id: base_type,
926                                    variable_id: op_variable.id_result, 
927                                    name,
928                                    count,
929                                    offset: None,
930                                }))
931                            }
932                        }
933                        Ok(None)
934                    },
935                    _ => unreachable!()
936                }
937            } else {
938                let offset = match ty {
939                    ResourceType::PushConstant => {
940                        self.decorations(base_type)
941                            .filter_map(|dec| {
942                                if let op::Decoration::Offset { byte_offset } = dec.decoration {
943                                    Some(byte_offset)
944                                } else { None }
945                            }).min()
946                    },
947                    _ => None
948                };
949                Ok((op_variable.storage_class == resource_class).then_some(
950                    Resource {
951                        type_id: op_variable.result_type,
952                        base_type_id: base_type,
953                        variable_id: op_variable.id_result,
954                        name,
955                        count,
956                        offset,
957                    }
958                ))
959            }
960        }).filter_map(|resource| {
961            match resource {
962                Ok(res) => res.map(Ok),
963                Err(err) => Some(Err(err)),
964            }
965        }))
966    } 
967
968    /// Returns all [`decorations`][1] added to a given [`Id`].
969    ///
970    /// [1]: op::Decoration
971    #[inline]
972    pub fn decorations(
973        &self,
974        target_id: Id,
975    ) -> impl Iterator<Item = Decorate<'a>>
976    {
977        self.decorates
978            .iter()
979            .filter_map(move |&dec|
980                (dec.target == target_id).then_some(
981                    dec
982                )
983            )
984    }
985
986    /// Gets a [`type description`][1] of a given [`Id`] pointing to a type.
987    ///
988    /// The description notably contains a [`hint`][2] of its size, which can be resolved to a
989    /// known size when needed.
990    ///
991    /// [1]: Type
992    /// [2]: TypeSizeHint
993    pub fn type_description(&self, id: Id) -> ReflectResult<Type<'a>> {
994        let mut stream = self.module
995            .get_result(id)
996            .ok_or(ReflectError::InvalidTypeId(id))?;
997        let name = self.names().find_map(|name| {
998            if let Ok(name) = name &&
999                name.target == id
1000            {
1001                Some(name.name)
1002            } else { None }
1003        });
1004        match stream.code() {
1005            op::Code::TYPE_BOOL => Ok(Type {
1006                id,
1007                name,
1008                size_hint: TypeSizeHint::Static(4),
1009            }),
1010            op::Code::TYPE_INT | op::Code::TYPE_FLOAT => {
1011                stream.advance(1)?;
1012                let width = stream.read()?;
1013                Ok(Type {
1014                    id,
1015                    name,
1016                    size_hint: TypeSizeHint::Static(width as usize / 8),
1017                })
1018            },
1019            op::Code::TYPE_VECTOR => {
1020                stream.advance(1)?;
1021                let component_type = Id::parse_one(&mut stream)?;
1022                let component_count = stream.read()?;
1023                let mut stream = self.module
1024                    .get_result(component_type)
1025                    .ok_or(ReflectError::InvalidTypeId(id))?;
1026                let size = match stream.code() {
1027                    op::Code::TYPE_BOOL => 4 * component_count as usize,
1028                    op::Code::TYPE_INT | op::Code::TYPE_FLOAT => {
1029                        stream.advance(1)?;
1030                        let width = stream.read()?;
1031                        (width / 8 * component_count) as usize
1032                    },
1033                    x => return Err(ReflectError::ExpectedScalarType { found: x })
1034                };
1035                Ok(Type {
1036                    id,
1037                    name,
1038                    size_hint: TypeSizeHint::Static(size),
1039                })
1040            }
1041            op::Code::TYPE_MATRIX => {
1042                stream.advance(1)?;
1043                let column_type = Id::parse_one(&mut stream)?;
1044                let column_count = stream.read()?;
1045                let mut stream = self.module
1046                    .get_result(column_type)
1047                    .ok_or(ReflectError::InvalidTypeId(id))?;
1048                if !matches!(stream.code(), op::Code::TYPE_VECTOR) {
1049                    return Err(ReflectError::ExpectedVectorType { found: stream.code() })
1050                }
1051                stream.advance(1)?;
1052                let component_type = Id::parse_one(&mut stream)?;
1053                let component_count = stream.read()?;
1054                let mut stream = self.module
1055                    .get_result(component_type)
1056                    .ok_or(ReflectError::InvalidTypeId(id))?;
1057                let column_size = match stream.code() {
1058                    op::Code::TYPE_BOOL => 4,
1059                    op::Code::TYPE_INT | op::Code::TYPE_FLOAT => {
1060                        stream.advance(1)?;
1061                        stream.read()? as usize / 8 
1062                    },
1063                    x => return Err(ReflectError::ExpectedScalarType {
1064                        found: x,
1065                    })
1066                };
1067                Ok(Type {
1068                    id,
1069                    name,
1070                    size_hint: TypeSizeHint::Matrix(MatrixStrideHole {
1071                        columns: column_count,
1072                        rows: component_count,
1073                        declared: column_size * (column_count as usize),
1074                    }),
1075                })
1076            },
1077            op::Code::TYPE_ARRAY => {
1078                stream.advance(1)?;
1079                let element_type = Id::parse_one(&mut stream)?;
1080                let length = Id::parse_one(&mut stream)?;
1081                let count = {
1082                    let constant = self.constant(length)?;
1083                    match constant {
1084                        Constant::Constant(literal) =>
1085                            literal.as_usize().ok_or(ReflectError::NonIntegerLiteral(literal))?,
1086                        Constant::SpecConstant(literal) =>
1087                            literal.as_usize().ok_or(ReflectError::NonIntegerLiteral(literal))?,
1088                        x => return Err(ReflectError::ExpectedConstantLiteral {
1089                            found: format!("{x:?}"),
1090                        })
1091                    }
1092                };
1093                let size_hint = {
1094                    if let Some(stride) =
1095                        self.decorations(id)
1096                        .find_map(|dec| {
1097                            let op::Decoration::ArrayStride { array_stride } = dec.decoration else {
1098                                return None
1099                            };
1100                            Some(array_stride as usize)
1101                        })
1102                    {
1103                        TypeSizeHint::Static(count * stride)
1104                    } else {
1105                        let element_size_hint = self.type_description(element_type)?.size_hint;
1106                        match element_size_hint {
1107                            TypeSizeHint::Static(size) => TypeSizeHint::Static(count * size),
1108                            TypeSizeHint::Matrix(_) | TypeSizeHint::UnknownArrayStride(_)
1109                                => TypeSizeHint::UnknownArrayStride(
1110                                UnknownArrayStrideHole {
1111                                    element: Box::new(element_size_hint),
1112                                    count,
1113                                }),
1114                            TypeSizeHint::RuntimeArray(_) | TypeSizeHint::Struct(_)
1115                                => return Err(ReflectError::InvalidRuntimeArray),
1116                        }
1117                    }
1118                };
1119                Ok(Type {
1120                    id,
1121                    name,
1122                    size_hint
1123                })
1124            },
1125            op::Code::TYPE_RUNTIME_ARRAY => {
1126                let stride = self
1127                    .decorations(id)
1128                    .find_map(|dec| {
1129                        let op::Decoration::ArrayStride { array_stride } = dec.decoration else {
1130                            return None
1131                        };
1132                        Some(array_stride as usize)
1133                    }).ok_or(ReflectError::MissingRequiredDecoration("ArrayStride"))?;
1134                Ok(Type {
1135                    id,
1136                    name,
1137                    size_hint: TypeSizeHint::RuntimeArray(RuntimeArrayHole { stride }),
1138                })
1139            }
1140            op::Code::TYPE_STRUCT => {
1141                stream.advance(1)?;
1142                let member_types = Id::parse_eos(&mut stream)?;
1143                let mut min = u32::MAX;
1144                let (desc, member, offset) = self.decorations(id)
1145                    .filter_map(|dec| {
1146                        if let Some(member) = dec.member &&
1147                            let op::Decoration::Offset { byte_offset } = dec.decoration &&
1148                            let Some(&ty) = member_types.get(member as usize) &&
1149                            let Ok(desc) = self.type_description(ty)
1150                        {
1151                            Some((desc, member, byte_offset))
1152                        } else { None }
1153                    }).max_by_key(|(_, _, offset)| {
1154                        min = min.min(*offset);
1155                        *offset
1156                    }).ok_or(ReflectError::MissingRequiredDecoration("Offset"))?;
1157                let offset = (offset - min) as usize;
1158                let size_hint = match desc.size_hint {
1159                    TypeSizeHint::Static(size) => TypeSizeHint::Static(offset + size),
1160                    TypeSizeHint::RuntimeArray(hole) => TypeSizeHint::Struct(StructHole {
1161                        last_offset: offset,
1162                        hole,
1163                    }),
1164                    TypeSizeHint::Matrix(hole) => {
1165                        let mut stride = None;
1166                        let mut is_row_major = false;
1167                        for dec in self.decorations(id) {
1168                            if let Some(dec_member) = dec.member &&
1169                                dec_member == member
1170                            {
1171                                if let op::Decoration::MatrixStride { matrix_stride } = dec.decoration {
1172                                    stride = Some(matrix_stride)
1173                                } else if let op::Decoration::RowMajor = dec.decoration {
1174                                    is_row_major = true
1175                                }
1176                            }
1177                        }
1178                        TypeSizeHint::Static(offset + hole
1179                            .resolve(
1180                                stride.ok_or(
1181                                    ReflectError::MissingRequiredDecoration("MatrixStride")
1182                                )? as usize,
1183                                is_row_major
1184                            ))
1185                    },
1186                    TypeSizeHint::UnknownArrayStride(_) => return Err(
1187                        ReflectError::MissingRequiredDecoration("ArrayStride")
1188                    ),
1189                    TypeSizeHint::Struct(_) => return Err(
1190                        ReflectError::InvalidRuntimeArray
1191                    ),
1192                };
1193                Ok(Type {
1194                    id,
1195                    name,
1196                    size_hint,
1197                })
1198            },
1199            op::Code::TYPE_POINTER => {
1200                stream.advance(2)?;
1201                self.type_description(Id::parse_one(&mut stream)?)
1202            },
1203            _ => Ok(Type {
1204                id,
1205                name,
1206                size_hint: TypeSizeHint::Static(0),
1207            })
1208        }
1209    }
1210}