spirv_cross2/reflect/
resources.rs

1use crate::error::{SpirvCrossError, ToContextError};
2use crate::handle::{Handle, TypeId, VariableId};
3use crate::sealed::Sealed;
4use crate::string::CompilerStr;
5use crate::{error, Compiler, PhantomCompiler, ToStatic};
6use spirv_cross_sys as sys;
7use spirv_cross_sys::{
8    spvc_reflected_builtin_resource, spvc_reflected_resource, spvc_resources_s, spvc_set,
9};
10use std::ptr::NonNull;
11use std::slice;
12
13/// The type of built-in resources to query.
14pub use spirv_cross_sys::BuiltinResourceType;
15
16use crate::iter::impl_iterator;
17/// The type of resource to query.
18pub use spirv_cross_sys::ResourceType;
19
20/// A handle to shader resources.
21pub struct ShaderResources(NonNull<spvc_resources_s>, PhantomCompiler);
22
23impl<T> Compiler<T> {
24    /// Query shader resources, use ids with reflection interface to modify or query binding points, etc.
25    pub fn shader_resources(&self) -> crate::error::Result<ShaderResources> {
26        // SAFETY: 'ctx is Ok
27        // since this gets allocated forever
28        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1925
29        unsafe {
30            let mut resources = std::ptr::null_mut();
31            sys::spvc_compiler_create_shader_resources(self.ptr.as_ptr(), &mut resources)
32                .ok(self)?;
33
34            let Some(resources) = NonNull::new(resources) else {
35                return Err(SpirvCrossError::OutOfMemory(String::from("Out of memory")));
36            };
37
38            Ok(ShaderResources(resources, self.phantom()))
39        }
40    }
41
42    /// Query shader resources, but only return the variables which are part of active_variables.
43    /// E.g.: get_shader_resources(get_active_variables()) to only return the variables which are statically
44    /// accessed.
45    pub fn shader_resources_for_active_variables(
46        &self,
47        set: InterfaceVariableSet,
48    ) -> error::Result<ShaderResources> {
49        // SAFETY: 'ctx is Ok
50        // since this gets allocated forever
51        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1925
52        unsafe {
53            let mut resources = std::ptr::null_mut();
54            sys::spvc_compiler_create_shader_resources_for_active_variables(
55                self.ptr.as_ptr(),
56                &mut resources,
57                set.0,
58            )
59            .ok(self)?;
60
61            let Some(resources) = NonNull::new(resources) else {
62                return Err(SpirvCrossError::OutOfMemory(String::from("Out of memory")));
63            };
64
65            Ok(ShaderResources(resources, self.phantom()))
66        }
67    }
68}
69
70/// A handle to a set of interface variables.
71pub struct InterfaceVariableSet(spvc_set, Handle<()>, PhantomCompiler);
72
73impl InterfaceVariableSet {
74    /// Get the SPIR-V IDs for the active interface variables.
75    ///
76    /// This is only meant to be used for reflection. It is not possible
77    /// to modify the contents of an [`InterfaceVariableSet`].
78    pub fn to_handles(&self) -> Vec<Handle<VariableId>> {
79        unsafe {
80            // Get the length of allocation
81            let mut length = 0;
82            spirv_cross_sys::spvc_rs_expose_set(self.0, std::ptr::null_mut(), &mut length);
83
84            // write into the vec
85            let mut vec = vec![0; length];
86            spirv_cross_sys::spvc_rs_expose_set(self.0, vec.as_mut_ptr(), &mut length);
87
88            let mut handles: Vec<Handle<VariableId>> = vec
89                .into_iter()
90                .map(|id| self.2.create_handle(VariableId::from(id)))
91                .collect();
92
93            handles.sort_by_key(|h| h.id());
94
95            handles
96        }
97    }
98}
99
100// reflection
101impl<T> Compiler<T> {
102    /// Returns a set of all global variables which are statically accessed
103    /// by the control flow graph from the current entry point.
104    /// Only variables which change the interface for a shader are returned, that is,
105    /// variables with storage class of Input, Output, Uniform, UniformConstant, PushConstant and AtomicCounter
106    /// storage classes are returned.
107    ///
108    /// To use the returned set as the filter for which variables are used during compilation,
109    /// this set can be moved to set_enabled_interface_variables().
110    ///
111    /// The return object is opaque to Rust, but its contents inspected by using [`InterfaceVariableSet::to_handles`].
112    /// There is no way to modify the contents or use your own `InterfaceVariableSet`.
113    pub fn active_interface_variables(&self) -> error::Result<InterfaceVariableSet> {
114        unsafe {
115            let mut set = std::ptr::null();
116
117            // SAFETY: 'ctx is sound here
118            // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1888
119
120            sys::spvc_compiler_get_active_interface_variables(self.ptr.as_ptr(), &mut set)
121                .ok(self)?;
122
123            Ok(InterfaceVariableSet(
124                set,
125                self.create_handle(()),
126                self.phantom(),
127            ))
128        }
129    }
130
131    /// Sets the interface variables which are used during compilation.
132    /// By default, all variables are used.
133    /// Once set, [`Compiler::compile`] will only consider the set in active_variables.
134    pub fn set_enabled_interface_variables(
135        &mut self,
136        set: InterfaceVariableSet,
137    ) -> error::Result<()> {
138        if !self.handle_is_valid(&set.1) {
139            return Err(SpirvCrossError::InvalidOperation(String::from(
140                "The interface variable set is invalid for this compiler instance.",
141            )));
142        }
143        unsafe {
144            sys::spvc_compiler_set_enabled_interface_variables(self.ptr.as_ptr(), set.0)
145                .ok(&*self)?;
146            Ok(())
147        }
148    }
149}
150
151/// Iterator over reflected resources, created by [`ShaderResources::resources_for_type`].
152pub struct ResourceIter<'a>(PhantomCompiler, slice::Iter<'a, spvc_reflected_resource>);
153
154impl_iterator!(ResourceIter<'a>: Resource<'a> as map |s, o: &'a spvc_reflected_resource| {
155    Resource::from_raw(s.0.clone(), o)
156} for <'a> [1]);
157
158/// Iterator over reflected builtin resources, created by [`ShaderResources::builtin_resources_for_type`].
159pub struct BuiltinResourceIter<'a>(
160    PhantomCompiler,
161    slice::Iter<'a, spvc_reflected_builtin_resource>,
162);
163
164impl_iterator!(BuiltinResourceIter<'a>: BuiltinResource<'a> as and_then |s, o: &'a spvc_reflected_builtin_resource| {
165    BuiltinResource::from_raw(s.0.clone(), o)
166} for <'a> [1]);
167
168/// Description of a shader resource.
169#[derive(Debug)]
170pub struct Resource<'a> {
171    /// A handle to the variable this resource points to.
172    pub id: Handle<VariableId>,
173    /// A handle to the base type of this resource.
174    pub base_type_id: Handle<TypeId>,
175    /// A handle to the type of this resource, often a pointer
176    /// or array.
177    pub type_id: Handle<TypeId>,
178    /// The name of this resource.
179    pub name: CompilerStr<'a>,
180}
181
182impl<'a> Resource<'a> {
183    fn from_raw(comp: PhantomCompiler, value: &'a spvc_reflected_resource) -> Self {
184        Self {
185            id: comp.create_handle(value.id),
186            base_type_id: comp.create_handle(value.base_type_id),
187            type_id: comp.create_handle(value.type_id),
188            // There should never be invalid UTF-8 in a shader.
189            // as per SPIR-V spec: The character set is Unicode in the UTF-8 encoding scheme.
190            // so this will be free 100% of the time.
191            name: unsafe { CompilerStr::from_ptr(value.name, comp.ctx.clone()) },
192        }
193    }
194}
195
196impl<'a, 'b> From<&'a Resource<'b>> for Handle<VariableId> {
197    fn from(value: &'a Resource<'b>) -> Self {
198        value.id
199    }
200}
201
202impl From<Resource<'_>> for Handle<VariableId> {
203    fn from(value: Resource<'_>) -> Self {
204        value.id
205    }
206}
207
208impl Sealed for Resource<'_> {}
209impl ToStatic for Resource<'_> {
210    type Static<'a>
211
212    = Resource<'static>
213    where
214        'a: 'static;
215
216    fn to_static(&self) -> Self::Static<'static> {
217        Resource {
218            id: self.id,
219            base_type_id: self.base_type_id,
220            type_id: self.type_id,
221            name: CompilerStr::from_string(self.name.to_string()),
222        }
223    }
224}
225
226impl Clone for Resource<'_> {
227    fn clone(&self) -> Resource<'static> {
228        self.to_static()
229    }
230}
231
232/// Description of a built-in shader resource.
233#[derive(Debug)]
234pub struct BuiltinResource<'a> {
235    /// The SPIR-V built-in for this resource.
236    pub builtin: spirv::BuiltIn,
237    /// A handle to the type ID of the value.
238    pub value_type_id: Handle<TypeId>,
239    /// The resource data for this built-in resource.
240    pub resource: Resource<'a>,
241}
242
243impl<'a, 'b> From<&'a BuiltinResource<'b>> for Handle<VariableId> {
244    fn from(value: &'a BuiltinResource<'b>) -> Self {
245        value.resource.id
246    }
247}
248
249impl From<BuiltinResource<'_>> for Handle<VariableId> {
250    fn from(value: BuiltinResource<'_>) -> Self {
251        value.resource.id
252    }
253}
254
255impl<'a> BuiltinResource<'a> {
256    fn from_raw(comp: PhantomCompiler, value: &'a spvc_reflected_builtin_resource) -> Option<Self> {
257        // builtin is potentially uninit, we need to check.
258        let Some(builtin) = spirv::BuiltIn::from_u32(value.builtin.0 as u32) else {
259            if cfg!(debug_assertions) {
260                panic!("Unexpected SpvBuiltIn in spvc_reflected_builtin_resource!")
261            } else {
262                return None;
263            }
264        };
265
266        Some(Self {
267            builtin,
268            value_type_id: comp.create_handle(value.value_type_id),
269            resource: Resource::from_raw(comp, &value.resource),
270        })
271    }
272}
273
274impl Sealed for BuiltinResource<'_> {}
275impl ToStatic for BuiltinResource<'_> {
276    type Static<'a>
277
278    = BuiltinResource<'static>
279    where
280        'a: 'static;
281
282    fn to_static(&self) -> Self::Static<'static> {
283        let resource: BuiltinResource<'static> = BuiltinResource {
284            builtin: self.builtin,
285            value_type_id: self.value_type_id,
286            resource: self.resource.to_static(),
287        };
288        resource
289    }
290}
291
292impl Clone for BuiltinResource<'_> {
293    fn clone(&self) -> BuiltinResource<'static> {
294        self.to_static()
295    }
296}
297
298/// All SPIR-V resources declared in the module.
299#[derive(Debug)]
300pub struct AllResources<'a> {
301    /// Uniform buffer (UBOs) resources.
302    pub uniform_buffers: Vec<Resource<'a>>,
303    /// Storage buffer (SSBO) resources.
304    pub storage_buffers: Vec<Resource<'a>>,
305    /// Shader stage inputs.
306    pub stage_inputs: Vec<Resource<'a>>,
307    /// Shader stage outputs.
308    pub stage_outputs: Vec<Resource<'a>>,
309    /// Shader subpass inputs.
310    pub subpass_inputs: Vec<Resource<'a>>,
311    /// Storage images (i.e. `imageND`).
312    pub storage_images: Vec<Resource<'a>>,
313    /// Sampled images (i.e. `samplerND`).
314    pub sampled_images: Vec<Resource<'a>>,
315    /// Atomic counters.
316    pub atomic_counters: Vec<Resource<'a>>,
317    /// Acceleration structures.
318    pub acceleration_structures: Vec<Resource<'a>>,
319    /// Legacy OpenGL plain uniforms.
320    pub gl_plain_uniforms: Vec<Resource<'a>>,
321
322    /// Push constant buffers.
323    ///
324    /// There is only ever at most one push constant buffer,
325    /// but this is multiplicit in case this restriction is lifted.
326    pub push_constant_buffers: Vec<Resource<'a>>,
327    /// Record buffers.
328    pub shader_record_buffers: Vec<Resource<'a>>,
329
330    /// For Vulkan GLSL and HLSL sources, split images (i.e. `textureND`).
331    pub separate_images: Vec<Resource<'a>>,
332
333    /// For Vulkan GLSL and HLSL sources, split samplers (i.e. `sampler`).
334    pub separate_samplers: Vec<Resource<'a>>,
335
336    /// Shader built-in inputs.
337    pub builtin_inputs: Vec<BuiltinResource<'a>>,
338    /// Shader built-in outputs.
339    pub builtin_outputs: Vec<BuiltinResource<'a>>,
340}
341
342impl Sealed for AllResources<'_> {}
343impl ToStatic for AllResources<'_> {
344    type Static<'a>
345
346    = AllResources<'static>
347    where
348        'a: 'static;
349
350    fn to_static(&self) -> Self::Static<'static> {
351        AllResources {
352            uniform_buffers: self
353                .uniform_buffers
354                .iter()
355                .map(ToStatic::to_static)
356                .collect(),
357            storage_buffers: self
358                .storage_buffers
359                .iter()
360                .map(ToStatic::to_static)
361                .collect(),
362            stage_inputs: self.stage_inputs.iter().map(ToStatic::to_static).collect(),
363            stage_outputs: self.stage_outputs.iter().map(ToStatic::to_static).collect(),
364            subpass_inputs: self
365                .subpass_inputs
366                .iter()
367                .map(ToStatic::to_static)
368                .collect(),
369            storage_images: self
370                .storage_images
371                .iter()
372                .map(ToStatic::to_static)
373                .collect(),
374            sampled_images: self
375                .sampled_images
376                .iter()
377                .map(ToStatic::to_static)
378                .collect(),
379            atomic_counters: self
380                .atomic_counters
381                .iter()
382                .map(ToStatic::to_static)
383                .collect(),
384            acceleration_structures: self
385                .acceleration_structures
386                .iter()
387                .map(ToStatic::to_static)
388                .collect(),
389            gl_plain_uniforms: self
390                .gl_plain_uniforms
391                .iter()
392                .map(ToStatic::to_static)
393                .collect(),
394            push_constant_buffers: self
395                .push_constant_buffers
396                .iter()
397                .map(ToStatic::to_static)
398                .collect(),
399            shader_record_buffers: self
400                .shader_record_buffers
401                .iter()
402                .map(ToStatic::to_static)
403                .collect(),
404            separate_images: self
405                .separate_images
406                .iter()
407                .map(ToStatic::to_static)
408                .collect(),
409            separate_samplers: self
410                .separate_samplers
411                .iter()
412                .map(ToStatic::to_static)
413                .collect(),
414            builtin_inputs: self
415                .builtin_inputs
416                .iter()
417                .map(ToStatic::to_static)
418                .collect(),
419            builtin_outputs: self
420                .builtin_outputs
421                .iter()
422                .map(ToStatic::to_static)
423                .collect(),
424        }
425    }
426}
427
428impl Clone for AllResources<'_> {
429    fn clone(&self) -> AllResources<'static> {
430        self.to_static()
431    }
432}
433
434impl ShaderResources {
435    /// Get an iterator for all resources of the given type.
436    pub fn resources_for_type(&self, ty: ResourceType) -> error::Result<ResourceIter<'static>> {
437        // SAFETY: 'ctx is sound here,
438        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1802
439        // Furthermore, once allocated, the lifetime of spvc_resources_s is tied to that of the context.
440        // so all child resources inherit the lifetime.
441        let mut count = 0;
442        let mut out = std::ptr::null();
443        unsafe {
444            spirv_cross_sys::spvc_resources_get_resource_list_for_type(
445                self.0.as_ptr(),
446                ty,
447                &mut out,
448                &mut count,
449            )
450            .ok(&self.1)?;
451        }
452
453        let slice = unsafe { slice::from_raw_parts(out, count) };
454
455        Ok(ResourceIter(self.1.clone(), slice.iter()))
456    }
457
458    /// Get an iterator for all builtin resources of the given type.
459    pub fn builtin_resources_for_type(
460        &self,
461        ty: BuiltinResourceType,
462    ) -> error::Result<BuiltinResourceIter<'static>> {
463        let mut count = 0;
464        let mut out = std::ptr::null();
465
466        // SAFETY: 'ctx is sound here,
467        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1826
468        // Furthermore, once allocated, the lifetime of spvc_resources_s is tied to that of the context.
469        // so all child resources inherit the lifetime.
470        unsafe {
471            spirv_cross_sys::spvc_resources_get_builtin_resource_list_for_type(
472                self.0.as_ptr(),
473                ty,
474                &mut out,
475                &mut count,
476            )
477            .ok(&self.1)?;
478        }
479
480        let slice = unsafe { slice::from_raw_parts(out.cast(), count) };
481
482        Ok(BuiltinResourceIter(self.1.clone(), slice.iter()))
483    }
484
485    /// Get all resources declared in the shader.
486    ///
487    /// This will allocate a `Vec` for every resource type.
488    #[rustfmt::skip]
489    pub fn all_resources(&self) -> error::Result<AllResources<'static>> {
490          // SAFETY: 'ctx is sound by transitive property of resources_for_type
491        Ok(AllResources {
492                uniform_buffers: self.resources_for_type(ResourceType::UniformBuffer)?.collect(),
493                storage_buffers: self.resources_for_type(ResourceType::StorageBuffer)?.collect(),
494                stage_inputs: self.resources_for_type(ResourceType::StageInput)?.collect(),
495                stage_outputs: self.resources_for_type(ResourceType::StageOutput)?.collect(),
496                subpass_inputs: self.resources_for_type(ResourceType::SubpassInput)?.collect(),
497                storage_images: self.resources_for_type(ResourceType::StorageImage)?.collect(),
498                sampled_images: self.resources_for_type(ResourceType::SampledImage)?.collect(),
499                atomic_counters: self.resources_for_type(ResourceType::AtomicCounter)?.collect(),
500                acceleration_structures: self.resources_for_type(ResourceType::AccelerationStructure)?.collect(),
501                gl_plain_uniforms: self.resources_for_type(ResourceType::GlPlainUniform)?.collect(),
502                push_constant_buffers: self.resources_for_type(ResourceType::PushConstant)?.collect(),
503                shader_record_buffers: self.resources_for_type(ResourceType::ShaderRecordBuffer)?.collect(),
504                separate_images: self.resources_for_type(ResourceType::SeparateImage)?.collect(),
505                separate_samplers: self.resources_for_type(ResourceType::SeparateSamplers)?.collect(),
506                builtin_inputs: self.builtin_resources_for_type(BuiltinResourceType::StageInput)?.collect(),
507                builtin_outputs: self.builtin_resources_for_type(BuiltinResourceType::StageOutput)?.collect(),
508        })
509    }
510}