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> {}