spirv_cross2/compile/
mod.rs

1use crate::error::{Result, ToContextError};
2use crate::sealed::Sealed;
3use crate::targets::Target;
4use crate::{error, Compiler, CompilerStr, ContextRooted};
5use spirv_cross_sys as sys;
6use std::fmt::{Display, Formatter};
7use std::ops::Deref;
8
9/// GLSL compile options.
10#[cfg(feature = "glsl")]
11#[cfg_attr(docsrs, doc(cfg(feature = "glsl")))]
12pub mod glsl;
13
14/// HLSL compile options.
15#[cfg(feature = "hlsl")]
16#[cfg_attr(docsrs, doc(cfg(feature = "hlsl")))]
17pub mod hlsl;
18
19/// MSL compile options.
20#[cfg(feature = "msl")]
21#[cfg_attr(docsrs, doc(cfg(feature = "msl")))]
22pub mod msl;
23
24impl Sealed for CommonOptions {}
25
26/// Compile options common to all backends.
27#[derive(Debug, spirv_cross2_derive::CompilerOptions)]
28pub struct CommonOptions {
29    // common options
30    /// Debug option to always emit temporary variables for all expressions.
31    #[option(SPVC_COMPILER_OPTION_FORCE_TEMPORARY, false)]
32    pub force_temporary: bool,
33
34    /// Flattens multidimensional arrays, e.g. `float foo[a][b][c]` into single-dimensional arrays,
35    /// e.g. `float foo[a * b * c]`.
36    /// This function does not change the actual type of any object.
37    /// Only the generated code, including declarations of interface variables
38    /// are changed to be single array dimension.
39    #[option(SPVC_COMPILER_OPTION_FLATTEN_MULTIDIMENSIONAL_ARRAYS, false)]
40    pub flatten_multidimensional_arrays: bool,
41
42    /// In vertex-like shaders, inverts gl_Position.y or equivalent.
43    #[option(SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, false)]
44    pub flip_vertex_y: bool,
45
46    /// GLSL: In vertex-like shaders, rewrite [0, w] depth (Vulkan/D3D style) to [-w, w] depth (GL style).
47    /// MSL: In vertex-like shaders, rewrite [-w, w] depth (GL style) to [0, w] depth.
48    /// HLSL: In vertex-like shaders, rewrite [-w, w] depth (GL style) to [0, w] depth.
49    #[option(SPVC_COMPILER_OPTION_FIXUP_DEPTH_CONVENTION, false)]
50    pub fixup_clipspace: bool,
51
52    /// Emit OpLine directives if present in the module.
53    /// May not correspond exactly to original source, but should be a good approximation.
54    #[option(SPVC_COMPILER_OPTION_EMIT_LINE_DIRECTIVES, false)]
55    pub emit_line_directives: bool,
56
57    /// On some targets (WebGPU), uninitialized variables are banned.
58    /// If this is enabled, all variables (temporaries, Private, Function)
59    /// which would otherwise be uninitialized will now be initialized to 0 instead.
60    #[option(SPVC_COMPILER_OPTION_FORCE_ZERO_INITIALIZED_VARIABLES, false)]
61    pub force_zero_initialized_variables: bool,
62
63    /// In cases where readonly/writeonly decoration are not used at all,
64    /// we try to deduce which qualifier(s) we should actually used, since actually emitting
65    /// read-write decoration is very rare, and older glslang/HLSL compilers tend to just emit readwrite as a matter of fact.
66    /// The default (true) is to enable automatic deduction for these cases, but if you trust the decorations set
67    /// by the SPIR-V, it's recommended to set this to false.
68    #[option(SPVC_COMPILER_OPTION_ENABLE_STORAGE_IMAGE_QUALIFIER_DEDUCTION, true)]
69    pub enable_storage_image_qualifier_deduction: bool,
70
71    /// For opcodes where we have to perform explicit additional nan checks, very ugly code is generated.
72    /// If we opt-in, ignore these requirements.
73    /// In opcodes like NClamp/NMin/NMax and FP compare, ignore NaN behavior.
74    /// Use FClamp/FMin/FMax semantics for clamps and lets implementation choose ordered or unordered
75    /// compares.
76    #[option(SPVC_COMPILER_OPTION_RELAX_NAN_CHECKS, false)]
77    pub relax_nan_checks: bool,
78}
79
80/// The output of a SPIRV-Cross compilation.
81///
82/// [`CompiledArtifact`] implements [`Display`] with the
83/// value of the compiled source code.
84///
85/// Reflection is still available, but the [`Compiler`]
86/// instance can no longer be mutated once compiled.
87pub struct CompiledArtifact<T> {
88    compiler: Compiler<T>,
89    source: CompilerStr<'static>,
90}
91
92impl<T> AsRef<str> for CompiledArtifact<T> {
93    fn as_ref(&self) -> &str {
94        self.source.as_ref()
95    }
96}
97
98impl<T> Display for CompiledArtifact<T> {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        Display::fmt(&self.source, f)
101    }
102}
103
104impl<'a, T> Deref for CompiledArtifact<T> {
105    type Target = Compiler<T>;
106
107    fn deref(&self) -> &Self::Target {
108        &self.compiler
109    }
110}
111
112/// Cross-compilation related methods.
113impl<T: CompilableTarget> Compiler<T> {
114    /// Adds a line in valid header position.
115    ///
116    /// For example, in the GLSL backend this would be right after #version.
117    ///
118    /// This is useful for enabling custom extensions which are outside the scope of SPIRV-Cross.
119    /// This can be combined with variable remapping.
120    ///
121    /// A new-line will be added after this line.
122    ///
123    /// While this function is a more generic way of adding arbitrary text to the header
124    /// of an output file, [`Compiler::require_extension`] should be used when adding extensions since it will
125    /// avoid creating collisions with SPIRV-Cross generated extensions.
126    pub fn add_header_line<'str>(&mut self, line: impl Into<CompilerStr<'str>>) -> Result<()> {
127        let line = line.into();
128        let cstring = line.into_cstring_ptr()?;
129        unsafe {
130            sys::spvc_compiler_add_header_line(self.ptr.as_ptr(), cstring.as_ptr()).ok(&*self)
131        }
132    }
133
134    /// Adds an extension which is required to run this shader, e.g.
135    /// `require_extension("GL_KHR_my_extension");`
136    pub fn require_extension<'str>(&mut self, ext: impl Into<CompilerStr<'str>>) -> Result<()> {
137        let ext = ext.into();
138        let cstring = ext.into_cstring_ptr()?;
139
140        unsafe {
141            sys::spvc_compiler_require_extension(self.ptr.as_ptr(), cstring.as_ptr().cast())
142                .ok(&*self)
143        }
144    }
145
146    /// Apply the set of compiler options to the compiler instance.
147    fn set_compiler_options(&mut self, options: &T::Options) -> error::Result<()> {
148        use crate::compile::sealed::ApplyCompilerOptions;
149        unsafe {
150            let mut handle = std::ptr::null_mut();
151
152            sys::spvc_compiler_create_compiler_options(self.ptr.as_ptr(), &mut handle)
153                .ok(&*self)?;
154
155            options.apply(handle, &*self)?;
156
157            sys::spvc_compiler_install_compiler_options(self.ptr.as_ptr(), handle).ok(&*self)?;
158
159            Ok(())
160        }
161    }
162
163    /// Consume the compilation instance, and compile source code to the
164    /// output target.
165    pub fn compile(mut self, options: &T::Options) -> error::Result<CompiledArtifact<T>> {
166        self.set_compiler_options(options)?;
167
168        unsafe {
169            let mut src = std::ptr::null();
170            sys::spvc_compiler_compile(self.ptr.as_ptr(), &mut src).ok(&self)?;
171
172            // SAFETY: 'static is OK to return here
173            // https://github.com/KhronosGroup/SPIRV-Cross/blob/6a1fb66eef1bdca14acf7d0a51a3f883499d79f0/spirv_cross_c.cpp#L1782
174            let src = CompilerStr::from_ptr(src, self.ctx.drop_guard());
175            Ok(CompiledArtifact {
176                compiler: self,
177                source: src,
178            })
179        }
180    }
181}
182
183/// Marker trait for compiler options.
184pub trait CompilerOptions: Default + sealed::ApplyCompilerOptions {}
185
186pub(crate) mod sealed {
187    use crate::error;
188    use crate::error::ContextRooted;
189    use crate::sealed::Sealed;
190    use spirv_cross_sys::spvc_compiler_options;
191
192    pub trait ApplyCompilerOptions: Sealed {
193        #[doc(hidden)]
194        unsafe fn apply(
195            &self,
196            options: spvc_compiler_options,
197            root: impl ContextRooted + Copy,
198        ) -> error::Result<()>;
199    }
200}
201
202#[cfg(test)]
203mod test {
204    use crate::error::SpirvCrossError;
205    use crate::targets;
206    use crate::Compiler;
207    use crate::Module;
208
209    const BASIC_SPV: &[u8] = include_bytes!("../../basic.spv");
210
211    #[test]
212    pub fn create_compiler() -> Result<(), SpirvCrossError> {
213        let vec = Vec::from(BASIC_SPV);
214        let words = Module::from_words(bytemuck::cast_slice(&vec));
215
216        let compiler: Compiler<targets::None> = Compiler::new(words)?;
217        Ok(())
218    }
219}
220
221impl Sealed for NoOptions {}
222
223/// No compilation options.
224///
225/// Used for compiler backends that take no options.
226#[derive(Debug, Default, spirv_cross2_derive::CompilerOptions)]
227pub struct NoOptions;
228
229/// Marker trait for a compiler target that can have compiler outputs.
230pub trait CompilableTarget: Target {
231    /// The options that this target accepts.
232    type Options: CompilerOptions;
233
234    /// Create a new instance of compiler options for this target.
235    fn options() -> Self::Options {
236        Self::Options::default()
237    }
238}