Skip to main content

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        = Resource<'static>
212    where
213        'a: 'static;
214
215    fn to_static(&self) -> Self::Static<'static> {
216        Resource {
217            id: self.id,
218            base_type_id: self.base_type_id,
219            type_id: self.type_id,
220            name: CompilerStr::from_string(self.name.to_string()),
221        }
222    }
223}
224
225impl Clone for Resource<'_> {
226    fn clone(&self) -> Resource<'static> {
227        self.to_static()
228    }
229}
230
231/// Description of a built-in shader resource.
232#[derive(Debug)]
233pub struct BuiltinResource<'a> {
234    /// The SPIR-V built-in for this resource.
235    pub builtin: spirv::BuiltIn,
236    /// A handle to the type ID of the value.
237    pub value_type_id: Handle<TypeId>,
238    /// The resource data for this built-in resource.
239    pub resource: Resource<'a>,
240}
241
242impl<'a, 'b> From<&'a BuiltinResource<'b>> for Handle<VariableId> {
243    fn from(value: &'a BuiltinResource<'b>) -> Self {
244        value.resource.id
245    }
246}
247
248impl From<BuiltinResource<'_>> for Handle<VariableId> {
249    fn from(value: BuiltinResource<'_>) -> Self {
250        value.resource.id
251    }
252}
253
254impl<'a> BuiltinResource<'a> {
255    fn from_raw(comp: PhantomCompiler, value: &'a spvc_reflected_builtin_resource) -> Option<Self> {
256        // builtin is potentially uninit, we need to check.
257        let Some(builtin) = spirv::BuiltIn::from_u32(value.builtin.0 as u32) else {
258            if cfg!(debug_assertions) {
259                panic!("Unexpected SpvBuiltIn in spvc_reflected_builtin_resource!")
260            } else {
261                return None;
262            }
263        };
264
265        Some(Self {
266            builtin,
267            value_type_id: comp.create_handle(value.value_type_id),
268            resource: Resource::from_raw(comp, &value.resource),
269        })
270    }
271}
272
273impl Sealed for BuiltinResource<'_> {}
274impl ToStatic for BuiltinResource<'_> {
275    type Static<'a>
276        = BuiltinResource<'static>
277    where
278        'a: 'static;
279
280    fn to_static(&self) -> Self::Static<'static> {
281        let resource: BuiltinResource<'static> = BuiltinResource {
282            builtin: self.builtin,
283            value_type_id: self.value_type_id,
284            resource: self.resource.to_static(),
285        };
286        resource
287    }
288}
289
290impl Clone for BuiltinResource<'_> {
291    fn clone(&self) -> BuiltinResource<'static> {
292        self.to_static()
293    }
294}
295
296/// All SPIR-V resources declared in the module.
297#[derive(Debug)]
298pub struct AllResources<'a> {
299    /// Uniform buffer (UBOs) resources.
300    pub uniform_buffers: Vec<Resource<'a>>,
301    /// Storage buffer (SSBO) resources.
302    pub storage_buffers: Vec<Resource<'a>>,
303    /// Shader stage inputs.
304    pub stage_inputs: Vec<Resource<'a>>,
305    /// Shader stage outputs.
306    pub stage_outputs: Vec<Resource<'a>>,
307    /// Shader subpass inputs.
308    pub subpass_inputs: Vec<Resource<'a>>,
309    /// Storage images (i.e. `imageND`).
310    pub storage_images: Vec<Resource<'a>>,
311    /// Sampled images (i.e. `samplerND`).
312    pub sampled_images: Vec<Resource<'a>>,
313    /// Atomic counters.
314    pub atomic_counters: Vec<Resource<'a>>,
315    /// Acceleration structures.
316    pub acceleration_structures: Vec<Resource<'a>>,
317    /// Legacy OpenGL plain uniforms.
318    pub gl_plain_uniforms: Vec<Resource<'a>>,
319
320    /// Push constant buffers.
321    ///
322    /// There is only ever at most one push constant buffer,
323    /// but this is multiplicit in case this restriction is lifted.
324    pub push_constant_buffers: Vec<Resource<'a>>,
325    /// Record buffers.
326    pub shader_record_buffers: Vec<Resource<'a>>,
327
328    /// For Vulkan GLSL and HLSL sources, split images (i.e. `textureND`).
329    pub separate_images: Vec<Resource<'a>>,
330
331    /// For Vulkan GLSL and HLSL sources, split samplers (i.e. `sampler`).
332    pub separate_samplers: Vec<Resource<'a>>,
333
334    /// Shader built-in inputs.
335    pub builtin_inputs: Vec<BuiltinResource<'a>>,
336    /// Shader built-in outputs.
337    pub builtin_outputs: Vec<BuiltinResource<'a>>,
338}
339
340impl Sealed for AllResources<'_> {}
341impl ToStatic for AllResources<'_> {
342    type Static<'a>
343        = AllResources<'static>
344    where
345        'a: 'static;
346
347    fn to_static(&self) -> Self::Static<'static> {
348        AllResources {
349            uniform_buffers: self
350                .uniform_buffers
351                .iter()
352                .map(ToStatic::to_static)
353                .collect(),
354            storage_buffers: self
355                .storage_buffers
356                .iter()
357                .map(ToStatic::to_static)
358                .collect(),
359            stage_inputs: self.stage_inputs.iter().map(ToStatic::to_static).collect(),
360            stage_outputs: self.stage_outputs.iter().map(ToStatic::to_static).collect(),
361            subpass_inputs: self
362                .subpass_inputs
363                .iter()
364                .map(ToStatic::to_static)
365                .collect(),
366            storage_images: self
367                .storage_images
368                .iter()
369                .map(ToStatic::to_static)
370                .collect(),
371            sampled_images: self
372                .sampled_images
373                .iter()
374                .map(ToStatic::to_static)
375                .collect(),
376            atomic_counters: self
377                .atomic_counters
378                .iter()
379                .map(ToStatic::to_static)
380                .collect(),
381            acceleration_structures: self
382                .acceleration_structures
383                .iter()
384                .map(ToStatic::to_static)
385                .collect(),
386            gl_plain_uniforms: self
387                .gl_plain_uniforms
388                .iter()
389                .map(ToStatic::to_static)
390                .collect(),
391            push_constant_buffers: self
392                .push_constant_buffers
393                .iter()
394                .map(ToStatic::to_static)
395                .collect(),
396            shader_record_buffers: self
397                .shader_record_buffers
398                .iter()
399                .map(ToStatic::to_static)
400                .collect(),
401            separate_images: self
402                .separate_images
403                .iter()
404                .map(ToStatic::to_static)
405                .collect(),
406            separate_samplers: self
407                .separate_samplers
408                .iter()
409                .map(ToStatic::to_static)
410                .collect(),
411            builtin_inputs: self
412                .builtin_inputs
413                .iter()
414                .map(ToStatic::to_static)
415                .collect(),
416            builtin_outputs: self
417                .builtin_outputs
418                .iter()
419                .map(ToStatic::to_static)
420                .collect(),
421        }
422    }
423}
424
425impl Clone for AllResources<'_> {
426    fn clone(&self) -> AllResources<'static> {
427        self.to_static()
428    }
429}
430
431impl ShaderResources {
432    /// Get an iterator for all resources of the given type.
433    pub fn resources_for_type(&self, ty: ResourceType) -> error::Result<ResourceIter<'static>> {
434        // SAFETY: 'ctx is sound here,
435        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1802
436        // Furthermore, once allocated, the lifetime of spvc_resources_s is tied to that of the context.
437        // so all child resources inherit the lifetime.
438        let mut count = 0;
439        let mut out = std::ptr::null();
440        unsafe {
441            spirv_cross_sys::spvc_resources_get_resource_list_for_type(
442                self.0.as_ptr(),
443                ty,
444                &mut out,
445                &mut count,
446            )
447            .ok(&self.1)?;
448        }
449
450        let slice = unsafe { slice::from_raw_parts(out, count) };
451
452        Ok(ResourceIter(self.1.clone(), slice.iter()))
453    }
454
455    /// Get an iterator for all builtin resources of the given type.
456    pub fn builtin_resources_for_type(
457        &self,
458        ty: BuiltinResourceType,
459    ) -> error::Result<BuiltinResourceIter<'static>> {
460        let mut count = 0;
461        let mut out = std::ptr::null();
462
463        // SAFETY: 'ctx is sound here,
464        // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1826
465        // Furthermore, once allocated, the lifetime of spvc_resources_s is tied to that of the context.
466        // so all child resources inherit the lifetime.
467        unsafe {
468            spirv_cross_sys::spvc_resources_get_builtin_resource_list_for_type(
469                self.0.as_ptr(),
470                ty,
471                &mut out,
472                &mut count,
473            )
474            .ok(&self.1)?;
475        }
476
477        let slice = unsafe { slice::from_raw_parts(out.cast(), count) };
478
479        Ok(BuiltinResourceIter(self.1.clone(), slice.iter()))
480    }
481
482    /// Get all resources declared in the shader.
483    ///
484    /// This will allocate a `Vec` for every resource type.
485    #[rustfmt::skip]
486    pub fn all_resources(&self) -> error::Result<AllResources<'static>> {
487          // SAFETY: 'ctx is sound by transitive property of resources_for_type
488        Ok(AllResources {
489                uniform_buffers: self.resources_for_type(ResourceType::UniformBuffer)?.collect(),
490                storage_buffers: self.resources_for_type(ResourceType::StorageBuffer)?.collect(),
491                stage_inputs: self.resources_for_type(ResourceType::StageInput)?.collect(),
492                stage_outputs: self.resources_for_type(ResourceType::StageOutput)?.collect(),
493                subpass_inputs: self.resources_for_type(ResourceType::SubpassInput)?.collect(),
494                storage_images: self.resources_for_type(ResourceType::StorageImage)?.collect(),
495                sampled_images: self.resources_for_type(ResourceType::SampledImage)?.collect(),
496                atomic_counters: self.resources_for_type(ResourceType::AtomicCounter)?.collect(),
497                acceleration_structures: self.resources_for_type(ResourceType::AccelerationStructure)?.collect(),
498                gl_plain_uniforms: self.resources_for_type(ResourceType::GlPlainUniform)?.collect(),
499                push_constant_buffers: self.resources_for_type(ResourceType::PushConstant)?.collect(),
500                shader_record_buffers: self.resources_for_type(ResourceType::ShaderRecordBuffer)?.collect(),
501                separate_images: self.resources_for_type(ResourceType::SeparateImage)?.collect(),
502                separate_samplers: self.resources_for_type(ResourceType::SeparateSamplers)?.collect(),
503                builtin_inputs: self.builtin_resources_for_type(BuiltinResourceType::StageInput)?.collect(),
504                builtin_outputs: self.builtin_resources_for_type(BuiltinResourceType::StageOutput)?.collect(),
505        })
506    }
507}