spirv_cross2/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))]
2#![forbid(missing_docs)]
3
4//! Safe and sound Rust bindings to [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross).
5//!
6//! All backends exposed by the SPIRV-Cross C API are fully supported, including
7//!
8//! * [GLSL](targets::Glsl)
9//! * [HLSL](targets::Hlsl)
10//! * [MSL](targets::Msl)
11//! * [JSON](targets::Json)
12//! * [C++](targets::Cpp)
13//! * [Reflection Only](targets::None)
14//!
15//! The API provided is roughly similar to the SPIRV-Cross [`Compiler`](https://github.com/KhronosGroup/SPIRV-Cross/blob/main/spirv_cross.hpp) C++ API,
16//! with some inspiration from [naga](https://docs.rs/naga/latest/naga/index.html). A best effort has been
17//! made to ensure that these bindings are sound, and that mutations occur strictly within Rust's
18//! borrow rules.
19//!
20//! ## Strings
21//! Methods on [`Compiler`] return and accept [`CompilerStr`] instead of a normal string type. A
22//! [`CompilerStr`] may or may not be owned by the compiler, or may come from Rust. Rust string types
23//! can be coerced automatically to [`CompilerStr`] as an input, and [`CompilerStr`] can easily be copied
24//! to a Rust string type.
25//!
26//! If a returned [`CompilerStr`] is backed by immutable memory, it will have a `'static` lifetime.
27//!
28//! If instead the underlying string data could possibly be modified by `set_` functions,
29//! they will only have a lifetime corresponding to the lifetime of the immutable borrow of the [`Compiler`]
30//! that produced them. References to these short-lived strings can not be alive before calling a
31//! mutating function.
32//!
33//! Strings will automatically allocate as needed when passed to FFI. Rust [`String`] and [`&str`](str)
34//! may allocate to create a nul-terminated string. Strings coming from FFI will not reallocate,
35//! and the pointer will be passed directly back. Rust [`&CStr`](std::ffi::CStr) will not reallocate.
36//!
37//! If you are just passing in a string constant using a [C-string literal](https://doc.rust-lang.org/edition-guide/rust-2021/c-string-literals.html)
38//! will be the most efficient. Otherwise, it is always better to work with Rust [`String`] and [`&str`](str),
39//! if you are dynamically building up a string. In particular, [`String`] will not reallocate if
40//! there is enough capacity to append a nul byte before being passed to FFI.
41//!
42//! ## Handles
43//! All reflected SPIR-V IDs are returned as [`Handle<T>`](handle::Handle), where the `u32` ID part can
44//! be retrieved with [`Handle::id`](handle::Handle::id). Handles are tagged with the pointer of the
45//! compiler instance they came from, and are required to ensure safety such that reflection queries
46//! aren't made between different SPIR-V modules.
47//!
48//! Any function that takes or returns SPIR-V handles in the SPIRV-Cross API has been wrapped to accept
49//! [`Handle<T>`](handle::Handle) in this crate.
50//!
51//! Handles can be unsafely forged with [`Compiler::create_handle`], but there are very few if any
52//! situations where this would be needed.
53//!
54//! ## Features
55//! By default, the `glsl`, `hlsl`, and `msl` features are enabled by default. The `cpp` and `json` targets can be enabled
56//! in Cargo.toml
57//!
58//! ```toml
59//! [dependencies]
60//! spirv-cross2 = { features = ["cpp", "json"] }
61//! ```
62//!
63//! SPIRV-Cross will only be built with support for enabled targets. If you want to only perform reflection and shrink the binary size,
64//! you can disable all but the `None` target.
65//!
66//! ```toml
67//! [dependencies]
68//! spirv-cross2 = { default-features = false }
69//! ```
70//!
71//! To enable all features, including `f16` and vector constant support, use the `full` feature.
72//!
73//! ```toml
74//! [dependencies]
75//! spirv-cross2 = { features = ["full"] }
76//! ```
77//!
78//! ### `f16` and vector specialization constants support
79//! When querying specialization constants, spirv-cross2 includes optional support for `f16` via [half](https://crates.io/crates/half) and vector and matrix types
80//! via [glam](https://crates.io/crates/glam) and [gfx-maths](https://crates.io/crates/gfx-maths).
81//!
82//! ```toml
83//! [dependencies]
84//! spirv-cross2 = { features = ["f16", "gfx-maths-types", "glam-types"] }
85//! ```
86//!
87//! ## Usage
88//! Here is an example of using the API to do some reflection and compile to GLSL.
89//!
90//! ```
91//! use spirv_cross2::compile::{CompilableTarget, CompiledArtifact};
92//! use spirv_cross2::{Compiler, Module, SpirvCrossError};
93//! use spirv_cross2::compile::glsl::GlslVersion;
94//! use spirv_cross2::reflect::{DecorationValue, ResourceType};
95//! use spirv_cross2::spirv;
96//! use spirv_cross2::targets::Glsl;
97//!
98//! fn compile_spirv(words: &[u32]) -> Result<CompiledArtifact<Glsl>, SpirvCrossError> {
99//!     let module = Module::from_words(words);
100//!
101//!     let mut compiler = Compiler::<Glsl>::new(module)?;
102//!
103//!     let resources = compiler.shader_resources()?;
104//!
105//!     for resource in resources.resources_for_type(ResourceType::SampledImage)? {
106//!         let Some(DecorationValue::Literal(set)) =
107//!                 compiler.decoration(resource.id, spirv::Decoration::DescriptorSet)? else {
108//!             continue;
109//!         };
110//!         let Some(DecorationValue::Literal(binding)) =
111//!             compiler.decoration(resource.id, spirv::Decoration::Binding)? else {
112//!             continue;
113//!         };
114//!
115//!         println!("Image {} at set = {}, binding = {}", resource.name, set, binding);
116//!
117//!         // Modify the decoration to prepare it for GLSL.
118//!         compiler.set_decoration(resource.id, spirv::Decoration::DescriptorSet,
119//!                 DecorationValue::unset())?;
120//!
121//!         // Some arbitrary remapping if we want.
122//!         compiler.set_decoration(resource.id, spirv::Decoration::Binding,
123//!             Some(set * 16 + binding))?;
124//!     }
125//!
126//!     let mut options = Glsl::options();
127//!     options.version = GlslVersion::Glsl300Es;
128//!
129//!     compiler.compile(&options)
130//! }
131//! ```
132//!
133use spirv_cross_sys::{spvc_compiler_s, SpvId};
134
135use crate::cell::{AllocationDropGuard, CrossAllocationCell};
136use crate::sealed::{ContextRooted, Sealed};
137use crate::targets::Target;
138use std::marker::PhantomData;
139use std::ptr::NonNull;
140
141/// Compilation of SPIR-V to a textual format.
142pub mod compile;
143
144/// Handles to SPIR-V IDs from reflection.
145pub mod handle;
146
147/// SPIR-V reflection helpers and types.
148pub mod reflect;
149
150/// Compiler output targets.
151pub mod targets;
152
153/// Error handling traits and support.
154mod error;
155
156/// Cell helpers
157mod cell;
158
159/// String helpers
160mod string;
161
162/// Iteratator
163mod iter;
164
165/// SPIR-V types and definitions.
166pub mod spirv {
167    pub use spirv::BuiltIn;
168    pub use spirv::Capability;
169    pub use spirv::Decoration;
170    pub use spirv::Dim;
171    pub use spirv::ExecutionMode;
172    pub use spirv::ExecutionModel;
173    pub use spirv::FPRoundingMode;
174    pub use spirv::ImageFormat;
175    pub use spirv::StorageClass;
176}
177
178pub(crate) mod sealed {
179    use spirv_cross_sys::spvc_context_s;
180    use std::ptr::NonNull;
181
182    pub trait Sealed {}
183
184    pub trait ContextRooted {
185        fn context(&self) -> NonNull<spvc_context_s>;
186    }
187}
188
189pub use crate::error::SpirvCrossError;
190pub use crate::string::CompilerStr;
191
192/// A SPIR-V Module represented as SPIR-V words.
193pub struct Module<'a>(&'a [SpvId]);
194
195impl<'a> Module<'a> {
196    /// Create a new `Module` from SPIR-V words.
197    pub fn from_words(words: &'a [u32]) -> Self {
198        Module(bytemuck::must_cast_slice(words))
199    }
200}
201
202/// Helper trait to detach objects with lifetimes attached to
203/// a compiler or context.
204pub trait ToStatic: Sealed {
205    /// The static type to return.
206    type Static<'a>
207    where
208        'a: 'static;
209
210    /// Clone the object into an instance with `'static` lifetime.
211    fn to_static(&self) -> Self::Static<'static>;
212}
213
214/// An instance of a SPIRV-Cross compiler.
215///
216/// Depending on the target, different methods will be
217/// available.
218///
219/// Once compiled into a [`CompiledArtifact`](compile::CompiledArtifact),
220/// reflection methods will still remain available, but the instance will be frozen,
221/// and no more mutation will be available.
222pub struct Compiler<T> {
223    pub(crate) ptr: NonNull<spvc_compiler_s>,
224    ctx: CrossAllocationCell,
225    _pd: PhantomData<T>,
226}
227
228impl<T: Target> Compiler<T> {
229    /// Create a compiler instance from a SPIR-V module.
230    pub fn new(spirv: Module) -> error::Result<Compiler<T>> {
231        let allocs = CrossAllocationCell::new()?;
232        allocs.into_compiler(spirv)
233    }
234
235    /// Create a new compiler instance.
236    ///
237    /// The pointer to the `spvc_compiler_s` must have the same lifetime as the context root.
238    pub(crate) unsafe fn new_from_raw(
239        ptr: NonNull<spvc_compiler_s>,
240        ctx: CrossAllocationCell,
241    ) -> Compiler<T> {
242        Compiler {
243            ptr,
244            ctx,
245            _pd: PhantomData,
246        }
247    }
248}
249
250/// Holds on to the pointer for a compiler instance,
251/// but type erased.
252///
253/// This is used so that child resources of a compiler track the
254/// lifetime of a compiler, or create handles attached with the
255/// compiler instance, without needing to refer to the typed
256/// output of a compiler.
257///
258/// The only thing a [`PhantomCompiler`] is able to do is create handles or
259/// refer to the root context. It's lifetime should be the same as the lifetime
260/// of the **context**, or **shorter**, but at least the lifetime of the compiler.
261///
262/// Anything that holds a PhantomCompiler effectively has static lifetime,
263/// if and only if it points to an allocation that originates from the context.
264///
265/// Because it holds an `AllocationDropGuard`, the compiler instance will always be live.
266#[derive(Clone)]
267pub(crate) struct PhantomCompiler {
268    pub(crate) ptr: NonNull<spvc_compiler_s>,
269    ctx: AllocationDropGuard,
270}
271
272impl<T> Compiler<T> {
273    /// Create a type erased phantom for lifetime tracking purposes.
274    ///
275    /// This function is unsafe because a [`PhantomCompiler`] can be used to
276    /// **safely** create handles originating from the compiler.
277    pub(crate) unsafe fn phantom(&self) -> PhantomCompiler {
278        PhantomCompiler {
279            ptr: self.ptr,
280            ctx: self.ctx.drop_guard(),
281        }
282    }
283}
284
285unsafe impl<T: Send> Send for Compiler<T> {}