rspirv_reflect/
lib.rs

1//! Basic SPIR-V reflection library to extract binding information
2//!
3//! ```no_run
4//! # let spirv_blob: &[u8] = todo!();
5//! let info = rspirv_reflect::Reflection::new_from_spirv(&spirv_blob).expect("Invalid SPIR-V");
6//! dbg!(info.get_descriptor_sets().expect("Failed to extract descriptor bindings"));
7//! ```
8
9use rspirv::binary::Parser;
10use rspirv::dr::{Instruction, Loader, Module, Operand};
11use std::collections::BTreeMap;
12use std::convert::TryInto;
13use std::num::TryFromIntError;
14use thiserror::Error;
15
16pub use rspirv;
17pub use rspirv::spirv;
18
19pub struct Reflection(pub Module);
20
21#[derive(Error, Debug)]
22pub enum ReflectError {
23    // NOTE: Instructions are stored as string because they cannot be cloned,
24    // and storing a reference means the shader must live at least as long as
25    // the error bubbling up, which is generally impossible.
26    #[error("{0:?} missing binding decoration")]
27    MissingBindingDecoration(Instruction),
28    #[error("{0:?} missing set decoration")]
29    MissingSetDecoration(Instruction),
30    #[error("Expecting operand {1} in position {2} for instruction {0:?}")]
31    OperandError(Instruction, &'static str, usize),
32    #[error("Expecting operand {1} in position {2} but instruction {0:?} has only {3} operands")]
33    OperandIndexError(Instruction, &'static str, usize, usize),
34    #[error("OpVariable {0:?} lacks a return type")]
35    VariableWithoutReturnType(Instruction),
36    #[error("Unknown storage class {0:?}")]
37    UnknownStorageClass(spirv::StorageClass),
38    #[error("Unknown struct (missing Block or BufferBlock annotation): {0:?}")]
39    UnknownStruct(Instruction),
40    #[error("Unknown value {1} for `sampled` field: {0:?}")]
41    ImageSampledFieldUnknown(Instruction, u32),
42    #[error("Unhandled OpType instruction {0:?}")]
43    UnhandledTypeInstruction(Instruction),
44    #[error("{0:?} does not generate a result")]
45    MissingResultId(Instruction),
46    #[error("No instruction assigns to {0:?}")]
47    UnassignedResultId(u32),
48    #[error("rspirv reflect lacks module header")]
49    MissingHeader,
50    #[error("rspirv reflect lacks `OpMemoryModel`")]
51    MissingMemoryModel,
52    #[error("Accidentally binding global parameter buffer. Global variables are currently not supported in HLSL")]
53    BindingGlobalParameterBuffer,
54    #[error("Only one push constant block can be defined per shader entry")]
55    TooManyPushConstants,
56    #[error("SPIR-V parse error")]
57    ParseError(#[from] rspirv::binary::ParseState),
58    #[error("OpTypeInt cannot have width {0}")]
59    UnexpectedIntWidth(u32),
60    #[error(
61        "Invalid or unimplemented combination of AddressingModel {0:?} and StorageClass {1:?}"
62    )]
63    InvalidAddressingModelAndStorageClass(spirv::AddressingModel, spirv::StorageClass),
64    #[error(transparent)]
65    TryFromIntError(#[from] TryFromIntError),
66}
67
68type Result<V, E = ReflectError> = ::std::result::Result<V, E>;
69
70/// These are bit-exact with ash and the Vulkan specification,
71/// they're mirrored here to prevent a dependency on ash
72#[derive(Copy, Clone, Eq, PartialEq)]
73#[repr(transparent)]
74pub struct DescriptorType(pub u32);
75
76// TODO: Possibly change to a C-like enum to get automatic Debug?
77impl DescriptorType {
78    pub const SAMPLER: Self = Self(0);
79    pub const COMBINED_IMAGE_SAMPLER: Self = Self(1);
80    pub const SAMPLED_IMAGE: Self = Self(2);
81    pub const STORAGE_IMAGE: Self = Self(3);
82    pub const UNIFORM_TEXEL_BUFFER: Self = Self(4);
83    pub const STORAGE_TEXEL_BUFFER: Self = Self(5);
84    pub const UNIFORM_BUFFER: Self = Self(6);
85    pub const STORAGE_BUFFER: Self = Self(7);
86    pub const UNIFORM_BUFFER_DYNAMIC: Self = Self(8);
87    pub const STORAGE_BUFFER_DYNAMIC: Self = Self(9);
88    pub const INPUT_ATTACHMENT: Self = Self(10);
89
90    pub const INLINE_UNIFORM_BLOCK_EXT: Self = Self(1_000_138_000);
91    pub const ACCELERATION_STRUCTURE_KHR: Self = Self(1_000_150_000);
92    pub const ACCELERATION_STRUCTURE_NV: Self = Self(1_000_165_000);
93}
94
95impl std::fmt::Debug for DescriptorType {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.write_str(match *self {
98            Self::SAMPLER => "SAMPLER",
99            Self::COMBINED_IMAGE_SAMPLER => "COMBINED_IMAGE_SAMPLER",
100            Self::SAMPLED_IMAGE => "SAMPLED_IMAGE",
101            Self::STORAGE_IMAGE => "STORAGE_IMAGE",
102            Self::UNIFORM_TEXEL_BUFFER => "UNIFORM_TEXEL_BUFFER",
103            Self::STORAGE_TEXEL_BUFFER => "STORAGE_TEXEL_BUFFER",
104            Self::UNIFORM_BUFFER => "UNIFORM_BUFFER",
105            Self::STORAGE_BUFFER => "STORAGE_BUFFER",
106            Self::UNIFORM_BUFFER_DYNAMIC => "UNIFORM_BUFFER_DYNAMIC",
107            Self::STORAGE_BUFFER_DYNAMIC => "STORAGE_BUFFER_DYNAMIC",
108            Self::INPUT_ATTACHMENT => "INPUT_ATTACHMENT",
109            Self::INLINE_UNIFORM_BLOCK_EXT => "INLINE_UNIFORM_BLOCK_EXT",
110            Self::ACCELERATION_STRUCTURE_KHR => "ACCELERATION_STRUCTURE_KHR",
111            Self::ACCELERATION_STRUCTURE_NV => "ACCELERATION_STRUCTURE_NV",
112            _ => "(UNDEFINED)",
113        })
114    }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum BindingCount {
119    /// A single resource binding.
120    ///
121    /// # Example
122    /// ```hlsl
123    /// StructuredBuffer<uint>
124    /// ```
125    One,
126    /// Predetermined number of resource bindings.
127    ///
128    /// # Example
129    /// ```hlsl
130    /// StructuredBuffer<uint> myBinding[4]
131    /// ```
132    StaticSized(usize),
133    /// Variable number of resource bindings (usually dubbed "bindless").
134    ///
135    /// Count is determined in `vkDescriptorSetLayoutBinding`. No other bindings should follow in this set.
136    ///
137    /// # Example
138    /// ```hlsl
139    /// StructuredBuffer<uint> myBinding[]
140    /// ```
141    Unbounded,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct DescriptorInfo {
146    pub ty: DescriptorType,
147    pub binding_count: BindingCount,
148    pub name: String,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152pub struct PushConstantInfo {
153    pub offset: u32,
154    pub size: u32,
155}
156
157macro_rules! get_ref_operand_at {
158    // TODO: Can't we have a match arm that deals with `ops` containing `&instruction.operands`?
159    ($instr:expr, $op:path, $idx:expr) => {
160        if $idx >= $instr.operands.len() {
161            Err(ReflectError::OperandIndexError(
162                $instr.clone(),
163                stringify!($op),
164                $idx,
165                $instr.operands.len(),
166            ))
167        } else if let $op(val) = &$instr.operands[$idx] {
168            Ok(val)
169        } else {
170            Err(ReflectError::OperandError(
171                $instr.clone(),
172                stringify!($op),
173                $idx,
174            ))
175        }
176    };
177}
178
179macro_rules! get_operand_at {
180    ($ops:expr, $op:path, $idx:expr) => {
181        get_ref_operand_at!($ops, $op, $idx).map(|v| *v)
182    };
183}
184
185impl Reflection {
186    pub fn new(module: Module) -> Self {
187        Self(module)
188    }
189
190    pub fn new_from_spirv(code: &[u8]) -> Result<Self> {
191        Ok(Self::new({
192            let mut loader = Loader::new();
193            let p = Parser::new(code, &mut loader);
194            p.parse()?;
195            loader.module()
196        }))
197    }
198
199    /// Returns all instructions where the first operand (`Instruction::operands[0]`) equals `IdRef(id)`
200    pub fn find_annotations_for_id(
201        annotations: &[Instruction],
202        id: u32,
203    ) -> Result<Vec<&Instruction>> {
204        annotations
205            .iter()
206            .filter_map(|a| {
207                let op = get_operand_at!(a, Operand::IdRef, 0);
208                match op {
209                    Ok(idref) if idref == id => Some(Ok(a)),
210                    Err(e) => Some(Err(e)),
211                    _ => None,
212                }
213            })
214            .collect::<Result<Vec<_>>>()
215    }
216
217    /// Returns the first `Instruction` assigning to `id` (ie. `result_id == Some(id)`)
218    pub fn find_assignment_for(instructions: &[Instruction], id: u32) -> Result<&Instruction> {
219        // TODO: Find unique?
220        instructions
221            .iter()
222            .find(|instr| instr.result_id == Some(id))
223            .ok_or(ReflectError::UnassignedResultId(id))
224    }
225
226    pub fn get_compute_group_size(&self) -> Option<(u32, u32, u32)> {
227        for inst in self.0.global_inst_iter() {
228            if inst.class.opcode == spirv::Op::ExecutionMode {
229                use rspirv::dr::Operand::{ExecutionMode, LiteralBit32};
230                if let [ExecutionMode(
231                    spirv::ExecutionMode::LocalSize | spirv::ExecutionMode::LocalSizeHint,
232                ), LiteralBit32(x), LiteralBit32(y), LiteralBit32(z)] = inst.operands[1..]
233                {
234                    return Some((x, y, z));
235                } else {
236                    // Invalid encoding? Ignoring.
237                }
238            }
239        }
240        None
241    }
242
243    /// Returns the descriptor type for a given variable `type_id`
244    fn get_descriptor_type_for_var(
245        &self,
246        type_id: u32,
247        storage_class: spirv::StorageClass,
248    ) -> Result<DescriptorInfo> {
249        let type_instruction = Self::find_assignment_for(&self.0.types_global_values, type_id)?;
250        self.get_descriptor_type(type_instruction, storage_class)
251    }
252
253    /// Returns the descriptor type for a given `OpType*` `Instruction`
254    fn get_descriptor_type(
255        &self,
256        type_instruction: &Instruction,
257        storage_class: spirv::StorageClass,
258    ) -> Result<DescriptorInfo> {
259        let annotations = type_instruction.result_id.map_or(Ok(vec![]), |result_id| {
260            Reflection::find_annotations_for_id(&self.0.annotations, result_id)
261        })?;
262
263        // Weave with recursive types
264        match type_instruction.class.opcode {
265            spirv::Op::TypeArray => {
266                let element_type_id = get_operand_at!(type_instruction, Operand::IdRef, 0)?;
267                let num_elements_id = get_operand_at!(type_instruction, Operand::IdRef, 1)?;
268                let num_elements =
269                    Self::find_assignment_for(&self.0.types_global_values, num_elements_id)?;
270                assert_eq!(num_elements.class.opcode, spirv::Op::Constant);
271                let num_elements_ty = Self::find_assignment_for(
272                    &self.0.types_global_values,
273                    num_elements.result_type.unwrap(),
274                )?;
275                // Array size can be any width, any signedness
276                assert_eq!(num_elements_ty.class.opcode, spirv::Op::TypeInt);
277                let num_elements = match get_operand_at!(num_elements_ty, Operand::LiteralBit32, 0)?
278                {
279                    32 => get_operand_at!(num_elements, Operand::LiteralBit32, 0)?.try_into()?,
280                    64 => get_operand_at!(num_elements, Operand::LiteralBit64, 0)?.try_into()?,
281                    x => return Err(ReflectError::UnexpectedIntWidth(x)),
282                };
283                assert!(num_elements >= 1);
284                return Ok(DescriptorInfo {
285                    binding_count: BindingCount::StaticSized(num_elements),
286                    ..self.get_descriptor_type_for_var(element_type_id, storage_class)?
287                });
288            }
289            spirv::Op::TypeRuntimeArray => {
290                let element_type_id = get_operand_at!(type_instruction, Operand::IdRef, 0)?;
291                return Ok(DescriptorInfo {
292                    binding_count: BindingCount::Unbounded,
293                    ..self.get_descriptor_type_for_var(element_type_id, storage_class)?
294                });
295            }
296            spirv::Op::TypePointer => {
297                let ptr_storage_class =
298                    get_operand_at!(type_instruction, Operand::StorageClass, 0)?;
299                let element_type_id = get_operand_at!(type_instruction, Operand::IdRef, 1)?;
300                assert_eq!(storage_class, ptr_storage_class);
301                return self.get_descriptor_type_for_var(element_type_id, storage_class);
302            }
303            spirv::Op::TypeSampledImage => {
304                let element_type_id = get_operand_at!(type_instruction, Operand::IdRef, 0)?;
305
306                let image_instruction =
307                    Self::find_assignment_for(&self.0.types_global_values, element_type_id)?;
308
309                let descriptor = self.get_descriptor_type(image_instruction, storage_class)?;
310
311                let dim = get_operand_at!(image_instruction, Operand::Dim, 1)?;
312                assert_ne!(dim, spirv::Dim::DimSubpassData);
313
314                return Ok(if dim == spirv::Dim::DimBuffer {
315                    if descriptor.ty != DescriptorType::UNIFORM_TEXEL_BUFFER
316                        && descriptor.ty != DescriptorType::STORAGE_TEXEL_BUFFER
317                    {
318                        todo!("Unexpected sampled image type {:?}", descriptor.ty)
319                    }
320                    descriptor
321                } else {
322                    DescriptorInfo {
323                        ty: DescriptorType::COMBINED_IMAGE_SAMPLER,
324                        ..descriptor
325                    }
326                });
327            }
328            _ => {}
329        }
330
331        let descriptor_type = match type_instruction.class.opcode {
332            spirv::Op::TypeSampler => DescriptorType::SAMPLER,
333            spirv::Op::TypeImage => {
334                let dim = get_operand_at!(type_instruction, Operand::Dim, 1)?;
335
336                const IMAGE_SAMPLED: u32 = 1;
337                const IMAGE_STORAGE: u32 = 2;
338
339                // TODO: Should this be modeled as an enum in rspirv??
340                let sampled = get_operand_at!(type_instruction, Operand::LiteralBit32, 5)?;
341
342                if dim == spirv::Dim::DimBuffer {
343                    if sampled == IMAGE_SAMPLED {
344                        DescriptorType::UNIFORM_TEXEL_BUFFER
345                    } else if sampled == IMAGE_STORAGE {
346                        DescriptorType::STORAGE_TEXEL_BUFFER
347                    } else {
348                        return Err(ReflectError::ImageSampledFieldUnknown(
349                            type_instruction.clone(),
350                            sampled,
351                        ));
352                    }
353                } else if dim == spirv::Dim::DimSubpassData {
354                    DescriptorType::INPUT_ATTACHMENT
355                } else if sampled == IMAGE_SAMPLED {
356                    DescriptorType::SAMPLED_IMAGE
357                } else if sampled == IMAGE_STORAGE {
358                    DescriptorType::STORAGE_IMAGE
359                } else {
360                    return Err(ReflectError::ImageSampledFieldUnknown(
361                        type_instruction.clone(),
362                        sampled,
363                    ));
364                }
365            }
366            spirv::Op::TypeStruct => {
367                let mut is_uniform_buffer = false;
368                let mut is_storage_buffer = false;
369
370                for annotation in annotations {
371                    for operand in &annotation.operands {
372                        if let Operand::Decoration(decoration) = operand {
373                            match decoration {
374                                spirv::Decoration::Block => is_uniform_buffer = true,
375                                spirv::Decoration::BufferBlock => is_storage_buffer = true,
376                                _ => { /* println!("Unhandled decoration {:?}", decoration) */ }
377                            }
378                        }
379                    }
380                }
381
382                let version = self
383                    .0
384                    .header
385                    .as_ref()
386                    .ok_or(ReflectError::MissingHeader)?
387                    .version();
388
389                if version <= (1, 3) && is_storage_buffer {
390                    // BufferBlock is still support in 1.3 exactly.
391                    DescriptorType::STORAGE_BUFFER
392                } else if version >= (1, 3) {
393                    // From 1.3, StorageClass is supported.
394                    assert!(
395                        !is_storage_buffer,
396                        "BufferBlock decoration is obsolete in SPIRV > 1.3"
397                    );
398                    assert!(
399                        is_uniform_buffer,
400                        "Struct requires Block annotation in SPIRV > 1.3"
401                    );
402                    match storage_class {
403                        spirv::StorageClass::Uniform | spirv::StorageClass::UniformConstant => {
404                            DescriptorType::UNIFORM_BUFFER
405                        }
406                        spirv::StorageClass::StorageBuffer => DescriptorType::STORAGE_BUFFER,
407                        _ => return Err(ReflectError::UnknownStorageClass(storage_class)),
408                    }
409                } else if is_uniform_buffer {
410                    DescriptorType::UNIFORM_BUFFER
411                } else {
412                    return Err(ReflectError::UnknownStruct(type_instruction.clone()));
413                }
414            }
415            // TODO: spirv_reflect translates nothing to {UNIFORM,STORAGE}_BUFFER_DYNAMIC
416            spirv::Op::TypeAccelerationStructureKHR => DescriptorType::ACCELERATION_STRUCTURE_KHR,
417            _ => {
418                return Err(ReflectError::UnhandledTypeInstruction(
419                    type_instruction.clone(),
420                ))
421            }
422        };
423
424        Ok(DescriptorInfo {
425            ty: descriptor_type,
426            binding_count: BindingCount::One,
427            name: "".to_string(),
428        })
429    }
430
431    /// Returns a nested mapping, where the first level maps descriptor set indices (register spaces)
432    /// and the second level maps descriptor binding indices (registers) to descriptor information.
433    pub fn get_descriptor_sets(&self) -> Result<BTreeMap<u32, BTreeMap<u32, DescriptorInfo>>> {
434        let mut unique_sets = BTreeMap::new();
435        let reflect = &self.0;
436
437        let uniform_variables = reflect
438            .types_global_values
439            .iter()
440            .filter(|i| i.class.opcode == spirv::Op::Variable)
441            .filter_map(|i| {
442                let cls = get_operand_at!(i, Operand::StorageClass, 0);
443                match cls {
444                    Ok(cls)
445                        if cls == spirv::StorageClass::Uniform
446                            || cls == spirv::StorageClass::UniformConstant
447                            || cls == spirv::StorageClass::StorageBuffer =>
448                    {
449                        Some(Ok(i))
450                    }
451                    Err(e) => Some(Err(e)),
452                    _ => None,
453                }
454            })
455            .collect::<Result<Vec<_>, _>>()?;
456
457        let names = reflect
458            .debug_names
459            .iter()
460            .filter(|i| i.class.opcode == spirv::Op::Name)
461            .map(|i| -> Result<(u32, String)> {
462                let element_type_id = get_operand_at!(i, Operand::IdRef, 0)?;
463                let name = get_ref_operand_at!(i, Operand::LiteralString, 1)?;
464                Ok((element_type_id, name.clone()))
465            })
466            .collect::<Result<BTreeMap<_, _>, _>>()?;
467
468        for var in uniform_variables {
469            if let Some(var_id) = var.result_id {
470                let annotations =
471                    Reflection::find_annotations_for_id(&reflect.annotations, var_id)?;
472
473                // TODO: Can also define these as mut
474                let (set, binding) = annotations.iter().filter(|a| a.operands.len() >= 3).fold(
475                    (None, None),
476                    |state, a| {
477                        if let Operand::Decoration(d) = a.operands[1] {
478                            if let Operand::LiteralBit32(i) = a.operands[2] {
479                                if d == spirv::Decoration::DescriptorSet {
480                                    assert!(state.0.is_none(), "Set already has a value!");
481                                    return (Some(i), state.1);
482                                } else if d == spirv::Decoration::Binding {
483                                    assert!(state.1.is_none(), "Binding already has a value!");
484                                    return (state.0, Some(i));
485                                }
486                            }
487                        }
488                        state
489                    },
490                );
491
492                let set = set.ok_or_else(|| ReflectError::MissingSetDecoration(var.clone()))?;
493                let binding =
494                    binding.ok_or_else(|| ReflectError::MissingBindingDecoration(var.clone()))?;
495
496                let current_set = /* &mut */ unique_sets
497                    .entry(set)
498                    .or_insert_with(BTreeMap::<u32, DescriptorInfo>::new);
499
500                let storage_class = get_operand_at!(var, Operand::StorageClass, 0)?;
501
502                let type_id = var
503                    .result_type
504                    .ok_or_else(|| ReflectError::VariableWithoutReturnType(var.clone()))?;
505                let mut descriptor_info =
506                    self.get_descriptor_type_for_var(type_id, storage_class)?;
507
508                if let Some(name) = names.get(&var_id) {
509                    // TODO: Might do this way earlier
510                    if name == "$Globals" {
511                        return Err(ReflectError::BindingGlobalParameterBuffer);
512                    }
513
514                    descriptor_info.name = name.to_owned();
515                }
516
517                let inserted = current_set.insert(binding, descriptor_info);
518                assert!(
519                    inserted.is_none(),
520                    "Can't bind to the same slot twice within the same shader"
521                );
522            }
523        }
524        Ok(unique_sets)
525    }
526
527    fn byte_offset_to_last_var(
528        reflect: &Module,
529        struct_instruction: &Instruction,
530    ) -> Result<u32, ReflectError> {
531        debug_assert!(struct_instruction.class.opcode == spirv::Op::TypeStruct);
532
533        // if there are less then two members there is no offset to use, early out
534        if struct_instruction.operands.len() < 2 {
535            return Ok(0);
536        }
537
538        let result_id = struct_instruction
539            .result_id
540            .ok_or_else(|| ReflectError::MissingResultId(struct_instruction.clone()))?;
541
542        // return the highest offset value
543        Ok(
544            Self::find_annotations_for_id(&reflect.annotations, result_id)?
545                .iter()
546                .filter(|i| i.class.opcode == spirv::Op::MemberDecorate)
547                .filter_map(|&i| match get_operand_at!(i, Operand::Decoration, 2) {
548                    Ok(spirv::Decoration::Offset) => {
549                        Some(get_operand_at!(i, Operand::LiteralBit32, 3))
550                    }
551                    Err(err) => Some(Err(err)),
552                    _ => None,
553                })
554                .collect::<Result<Vec<_>>>()?
555                .into_iter()
556                .max()
557                .unwrap_or(0),
558        )
559    }
560
561    fn calculate_variable_size_bytes(
562        reflect: &Module,
563        type_instruction: &Instruction,
564    ) -> Result<u32, ReflectError> {
565        match type_instruction.class.opcode {
566            spirv::Op::TypeInt | spirv::Op::TypeFloat => {
567                debug_assert!(!type_instruction.operands.is_empty());
568                Ok(get_operand_at!(type_instruction, Operand::LiteralBit32, 0)? / 8)
569            }
570            spirv::Op::TypeVector | spirv::Op::TypeMatrix => {
571                debug_assert!(type_instruction.operands.len() == 2);
572                let type_id = get_operand_at!(type_instruction, Operand::IdRef, 0)?;
573                let var_type_instruction =
574                    Self::find_assignment_for(&reflect.types_global_values, type_id)?;
575                let type_size_bytes =
576                    Self::calculate_variable_size_bytes(reflect, var_type_instruction)?;
577
578                let type_constant_count =
579                    get_operand_at!(type_instruction, Operand::LiteralBit32, 1)?;
580                Ok(type_size_bytes * type_constant_count)
581            }
582            spirv::Op::TypeArray => {
583                debug_assert!(type_instruction.operands.len() == 2);
584                let type_id = get_operand_at!(type_instruction, Operand::IdRef, 0)?;
585                let var_type_instruction =
586                    Self::find_assignment_for(&reflect.types_global_values, type_id)?;
587                let type_size_bytes =
588                    Self::calculate_variable_size_bytes(reflect, var_type_instruction)?;
589
590                let var_constant_id = get_operand_at!(type_instruction, Operand::IdRef, 1)?;
591                let constant_instruction =
592                    Self::find_assignment_for(&reflect.types_global_values, var_constant_id)?;
593                let type_constant_count =
594                    get_operand_at!(constant_instruction, Operand::LiteralBit32, 0)?;
595
596                Ok(type_size_bytes * type_constant_count)
597            }
598            spirv::Op::TypeStruct => {
599                if !type_instruction.operands.is_empty() {
600                    let byte_offset = Self::byte_offset_to_last_var(reflect, type_instruction)?;
601                    let last_var_idx = type_instruction.operands.len() - 1;
602                    let id_ref = get_operand_at!(type_instruction, Operand::IdRef, last_var_idx)?;
603                    let type_instruction =
604                        Self::find_assignment_for(&reflect.types_global_values, id_ref)?;
605                    Ok(byte_offset
606                        + Self::calculate_variable_size_bytes(reflect, type_instruction)?)
607                } else {
608                    Ok(0)
609                }
610            }
611            spirv::Op::TypePointer => {
612                let memory_model = reflect
613                    .memory_model
614                    .as_ref()
615                    .ok_or(ReflectError::MissingMemoryModel)?;
616                let addressing_model = get_operand_at!(memory_model, Operand::AddressingModel, 0)?;
617
618                let storage_class = get_operand_at!(type_instruction, Operand::StorageClass, 0)?;
619
620                // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Addressing_Model
621                // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Storage_Class
622                match (addressing_model, storage_class) {
623                    (
624                        // https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_physical_storage_buffer.asciidoc
625                        spirv::AddressingModel::PhysicalStorageBuffer64,
626                        spirv::StorageClass::PhysicalStorageBuffer,
627                    ) => Ok(8),
628                    (a, s) => Err(ReflectError::InvalidAddressingModelAndStorageClass(a, s)),
629                }
630            }
631            x => panic!("Size computation for {:?} unsupported", x),
632        }
633    }
634
635    pub fn get_push_constant_range(&self) -> Result<Option<PushConstantInfo>, ReflectError> {
636        let reflect = &self.0;
637
638        let push_constants = reflect
639            .types_global_values
640            .iter()
641            .filter(|i| i.class.opcode == spirv::Op::Variable)
642            .filter_map(|i| {
643                let cls = get_operand_at!(*i, Operand::StorageClass, 0);
644                match cls {
645                    Ok(spirv::StorageClass::PushConstant) => Some(Ok(i)),
646                    Err(err) => Some(Err(err)),
647                    _ => None,
648                }
649            })
650            .collect::<Result<Vec<_>>>()?;
651
652        if push_constants.len() > 1 {
653            return Err(ReflectError::TooManyPushConstants);
654        }
655
656        let push_constant = match push_constants.into_iter().next() {
657            Some(push_constant) => push_constant,
658            None => return Ok(None),
659        };
660
661        let instruction = Reflection::find_assignment_for(
662            &reflect.types_global_values,
663            push_constant.result_type.unwrap(),
664        )?;
665
666        // resolve type if the type instruction is a pointer
667        let instruction = if instruction.class.opcode == spirv::Op::TypePointer {
668            let ptr_storage_class = get_operand_at!(instruction, Operand::StorageClass, 0)?;
669            assert_eq!(spirv::StorageClass::PushConstant, ptr_storage_class);
670            let element_type_id = get_operand_at!(instruction, Operand::IdRef, 1)?;
671            Reflection::find_assignment_for(&reflect.types_global_values, element_type_id)?
672        } else {
673            instruction
674        };
675
676        let size_bytes = Self::calculate_variable_size_bytes(reflect, instruction)?;
677
678        Ok(Some(PushConstantInfo {
679            size: size_bytes,
680            offset: 0,
681        }))
682    }
683
684    pub fn disassemble(&self) -> String {
685        use rspirv::binary::Disassemble;
686        self.0.disassemble()
687    }
688}