spirv_cross2/compile/hlsl/
mod.rs

1use crate::compile::{CommonOptions, CompiledArtifact};
2use crate::targets::Hlsl;
3use crate::{error, Compiler};
4use bitflags::bitflags;
5
6use spirv_cross_sys::HlslResourceBindingMapping;
7use spirv_cross_sys::{HlslResourceBinding, SpvBuiltIn, SpvExecutionModel};
8
9/// Specifies an HLSL root constant layout.
10pub use spirv_cross_sys::HlslRootConstants as RootConstants;
11
12use crate::error::ToContextError;
13use crate::handle::{Handle, VariableId};
14use crate::sealed::Sealed;
15use crate::string::CompilerStr;
16use crate::ContextRooted;
17use spirv_cross_sys as sys;
18use spirv_cross_sys::{HlslBindingFlagBits, HlslBindingFlags, HlslVertexAttributeRemap};
19
20bitflags! {
21    /// Controls how resource bindings are declared in the output HLSL.
22    ///
23    /// For finer control, decorations may be removed from specific resources instead.
24    pub struct BindingFlags: u32 {
25        /// No auto binding of resources.
26        const AUTO_NONE = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_NONE_BIT.0 as u32;
27        /// Push constant (root constant) resources will be declared as CBVs (b-space) without a `register()` declaration.
28        ///
29        /// A register will be automatically assigned by the D3D compiler, but must therefore be reflected in D3D-land.
30        /// Push constants do not normally have a `DecorationBinding` set, but if they do, this can be used to ignore it.
31        const AUTO_PUSH_CONSTANT = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT.0 as u32;
32        /// cbuffer resources will be declared as CBVs (b-space) without a `register()` declaration.
33        ///
34        /// A register will be automatically assigned, but must be reflected in D3D-land.
35        const AUTO_CBV = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_CBV_BIT.0 as u32;
36        /// All SRVs (t-space) will be declared without a `register()` declaration.
37        const AUTO_SRV = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_SRV_BIT.0 as u32;
38        /// All UAVs (u-space) will be declared without a `register()` declaration.
39        const AUTO_UAV = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_UAV_BIT.0 as u32;
40        /// All samplers (s-space) will be declared without a `register()` declaration.
41        const AUTO_SAMPLER = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_SAMPLER_BIT.0 as u32;
42        /// No resources will be declared with `register()`.
43        const AUTO_ALL = HlslBindingFlagBits::SPVC_HLSL_BINDING_AUTO_ALL.0 as u32;
44    }
45}
46
47impl Sealed for CompilerOptions {}
48/// HLSL compiler options
49#[non_exhaustive]
50#[derive(Debug, spirv_cross2_derive::CompilerOptions)]
51pub struct CompilerOptions {
52    /// Compile options common to GLSL, HLSL, and MSL.
53    #[expand]
54    pub common: CommonOptions,
55
56    /// The HLSL shader model version to output. The default is SM 3.0
57    #[option(
58        SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL,
59        HlslShaderModel::ShaderModel3_0
60    )]
61    pub shader_model: HlslShaderModel,
62
63    /// Allows the PointSize builtin in SM 4.0+, and ignores it,
64    /// as PointSize is not supported in SM 4+.
65    #[option(SPVC_COMPILER_OPTION_HLSL_POINT_SIZE_COMPAT, false)]
66    pub point_size_compat: bool,
67
68    /// Allows the PointCoord builtin, returns float2(0.5, 0.5),
69    /// as PointCoord is not supported in HLSL.
70    #[option(SPVC_COMPILER_OPTION_HLSL_POINT_COORD_COMPAT, false)]
71    pub point_coord_compat: bool,
72
73    /// If true, the backend will assume that VertexIndex and InstanceIndex will need to apply
74    /// a base offset, and you will need to fill in a cbuffer with offsets.
75    ///
76    /// Set to false if you know you will never use base instance or base vertex
77    /// functionality as it might remove an internal cbuffer.
78    #[option(
79        SPVC_COMPILER_OPTION_HLSL_SUPPORT_NONZERO_BASE_VERTEX_BASE_INSTANCE,
80        false
81    )]
82    pub support_nonzero_base_vertex_base_instance: bool,
83
84    /// Forces a storage buffer to always be declared as UAV, even if the readonly decoration is used.
85    /// By default, a readonly storage buffer will be declared as ByteAddressBuffer (SRV) instead.
86    #[option(SPVC_COMPILER_OPTION_HLSL_FORCE_STORAGE_BUFFER_AS_UAV, false)]
87    pub force_storage_buffer_as_uav: bool,
88
89    /// Forces any storage image type marked as NonWritable to be considered an SRV instead.
90    /// For this to work with function call parameters, NonWritable must be considered to be part of the type system
91    /// so that NonWritable image arguments are also translated to Texture rather than RWTexture.
92    #[option(SPVC_COMPILER_OPTION_HLSL_NONWRITABLE_UAV_TEXTURE_AS_SRV, false)]
93    pub nonwritable_uav_texture_as_srv: bool,
94
95    /// If matrices are used as IO variables, flatten the attribute declaration to use
96    /// `TEXCOORD{N,N+1,N+2,...}` rather than `TEXCOORDN_{0,1,2,3}`.
97    /// If `add_vertex_attribute_remap` is used and this feature is used,
98    /// the semantic name will be queried once per active location.
99    #[option(SPVC_COMPILER_OPTION_HLSL_FLATTEN_MATRIX_VERTEX_INPUT_SEMANTICS, false)]
100    pub flatten_matrix_vertex_input_semantics: bool,
101
102    /// Enables native 16-bit types. Needs SM 6.2.
103    /// Uses half/int16_t/uint16_t instead of min16* types.
104    /// Also adds support for 16-bit load-store from (RW)ByteAddressBuffer.
105    #[option(SPVC_COMPILER_OPTION_HLSL_ENABLE_16BIT_TYPES, false)]
106    pub enable_16bit_types: bool,
107
108    /// Rather than emitting main() for the entry point, use the name in SPIR-V.
109    #[option(SPVC_COMPILER_OPTION_HLSL_USE_ENTRY_POINT_NAME, false)]
110    pub use_entry_point_name: bool,
111
112    /// Preserve (RW)StructuredBuffer types if the input source was HLSL.
113    ///
114    /// This relies on UserTypeGOOGLE to encode the buffer type either as `structuredbuffer or `rwstructuredbuffer
115    /// whereas the type can be extended with an optional subtype, e.g. `structuredbuffer:int`.
116    #[option(SPVC_COMPILER_OPTION_HLSL_PRESERVE_STRUCTURED_BUFFERS, false)]
117    pub preserve_structured_buffers: bool,
118}
119
120/// HLSL Shader model.
121#[derive(Debug, Copy, Clone, Eq, PartialEq)]
122#[non_exhaustive]
123#[derive(Default)]
124pub enum HlslShaderModel {
125    /// Shader Model 3.0 (Direct3D 9.0c).
126    ///
127    /// This is the lowest supported shader model.
128    #[default]
129    ShaderModel3_0,
130    /// Shader Model 4.0 (Direct3D 10.0).
131    ///
132    /// Level 9.x feature levels are not explicitly supported.
133    ShaderModel4_0,
134    /// Shader Model 4.1 (Direct3D 10.1).
135    ShaderModel4_1,
136    /// Shader Model 5.0 (Direct3D 11/11.1)
137    ShaderModel5_0,
138    /// Shader Model 5.1 (Direct3D 12).
139    ShaderModel5_1,
140    /// Shader Model 6.0 (Direct3D 12)
141    ShaderModel6_0,
142    /// Shader Model 6.1 (Direct3D 12)
143    ShaderModel6_1,
144    /// Shader Model 6.2 (Direct3D 12)
145    ShaderModel6_2,
146    /// Shader Model 6.3 (Direct3D 12)
147    ShaderModel6_3,
148    /// Shader Model 6.4 (Direct3D 12)
149    ShaderModel6_4,
150    /// Shader Model 6.5 (Direct3D 12)
151    ShaderModel6_5,
152    /// Shader Model 6.6 (Direct3D 12)
153    ShaderModel6_6,
154    /// Shader Model 6.7 (Direct3D 12)
155    ShaderModel6_7,
156    /// Shader Model 6.8 (Direct3D 12)
157    ShaderModel6_8,
158}
159
160impl From<HlslShaderModel> for u32 {
161    fn from(value: HlslShaderModel) -> Self {
162        match value {
163            HlslShaderModel::ShaderModel3_0 => 30,
164            HlslShaderModel::ShaderModel4_0 => 40,
165            HlslShaderModel::ShaderModel4_1 => 41,
166            HlslShaderModel::ShaderModel5_0 => 50,
167            HlslShaderModel::ShaderModel5_1 => 51,
168            HlslShaderModel::ShaderModel6_0 => 60,
169            HlslShaderModel::ShaderModel6_1 => 61,
170            HlslShaderModel::ShaderModel6_2 => 62,
171            HlslShaderModel::ShaderModel6_3 => 63,
172            HlslShaderModel::ShaderModel6_4 => 64,
173            HlslShaderModel::ShaderModel6_5 => 65,
174            HlslShaderModel::ShaderModel6_6 => 66,
175            HlslShaderModel::ShaderModel6_7 => 67,
176            HlslShaderModel::ShaderModel6_8 => 68,
177        }
178    }
179}
180
181/// Pipeline binding information for a resource.
182///
183/// Used to map a SPIR-V resource to an HLSL buffer.
184#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
185pub enum ResourceBinding {
186    /// A resource with a qualified layout.
187    ///
188    /// i.e. `layout(set = 0, binding = 1)` in GLSL.
189    Qualified {
190        /// The descriptor set of the qualified layout.
191        set: u32,
192        /// The binding number of the qualified layout.
193        binding: u32,
194    },
195    /// The push constant buffer.
196    PushConstantBuffer,
197}
198
199impl ResourceBinding {
200    /// Create a resource binding for a qualified SPIR-V layout
201    /// specifier.
202    pub const fn from_qualified(set: u32, binding: u32) -> Self {
203        Self::Qualified { set, binding }
204    }
205
206    const fn descriptor_set(&self) -> u32 {
207        const PUSH_CONSTANT_DESCRIPTOR_SET: u32 = !0;
208        match self {
209            ResourceBinding::Qualified { set, .. } => *set,
210            ResourceBinding::PushConstantBuffer => PUSH_CONSTANT_DESCRIPTOR_SET,
211        }
212    }
213
214    const fn binding(&self) -> u32 {
215        const PUSH_CONSTANT_BINDING: u32 = 0;
216        match self {
217            ResourceBinding::Qualified { binding, .. } => *binding,
218            ResourceBinding::PushConstantBuffer => PUSH_CONSTANT_BINDING,
219        }
220    }
221}
222
223/// The HLSL buffer to bind a resource to.
224///
225/// A single SPIR-V binding may bind to multiple
226/// registers for multiple resource types.
227///
228/// For example, for combined image samplers, both the `srv` and `sampler`
229/// bindings will be considered.
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
231pub struct BindTarget {
232    /// Bind to a constant buffer view register.
233    pub cbv: Option<RegisterBinding>,
234    /// Bind to an unordered access view register.
235    pub uav: Option<RegisterBinding>,
236    /// Bind to a shader resource view register.
237    pub srv: Option<RegisterBinding>,
238    /// Bind to a sampler register.
239    pub sampler: Option<RegisterBinding>,
240}
241
242/// An HLSL register location.
243#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
244pub struct RegisterBinding {
245    /// The register for this binding.
246    pub register: u32,
247    /// The address space index for this binding.
248    ///
249    /// This is ignored on Shader Model 5.0 and below.
250    pub space: u32,
251}
252
253impl From<RegisterBinding> for HlslResourceBindingMapping {
254    #[inline(always)]
255    fn from(value: RegisterBinding) -> Self {
256        HlslResourceBindingMapping {
257            register_space: value.space,
258            register_binding: value.register,
259        }
260    }
261}
262
263/// HLSL specific APIs.
264impl Compiler<Hlsl> {
265    /// Add a resource binding to the HLSL compilation.
266    ///
267    /// Register bindings are set based on whether the HLSL resource is a
268    /// CBV, UAV, SRV or Sampler.
269    ///
270    /// A single binding in SPIR-V might contain multiple
271    /// resource types, e.g. `COMBINED_IMAGE_SAMPLER`, and SRV/Sampler bindings will be used respectively.
272    ///
273    /// On SM 5.0 and lower, `register_space` is ignored.
274    ///
275    /// For deeper control of push constants, [`Compiler<Hlsl>::set_root_constant_layout`] can be used instead.
276    ///
277    /// If resource bindings are provided, [`CompiledArtifact<Hlsl>::is_resource_used`] will return true if
278    /// the set/binding combination was used by the HLSL code.
279    pub fn add_resource_binding(
280        &mut self,
281        stage: spirv::ExecutionModel,
282        binding: ResourceBinding,
283        bind_target: &BindTarget,
284    ) -> error::Result<()> {
285        const DEFAULT_BINDING: HlslResourceBindingMapping = HlslResourceBindingMapping {
286            register_space: 0,
287            register_binding: 0,
288        };
289
290        let hlsl_resource_binding = HlslResourceBinding {
291            stage: SpvExecutionModel(stage as u32 as i32),
292            desc_set: binding.descriptor_set(),
293            binding: binding.binding(),
294            cbv: bind_target.cbv.map_or(DEFAULT_BINDING, From::from),
295            uav: bind_target.uav.map_or(DEFAULT_BINDING, From::from),
296            srv: bind_target.srv.map_or(DEFAULT_BINDING, From::from),
297            sampler: bind_target.sampler.map_or(DEFAULT_BINDING, From::from),
298        };
299
300        unsafe {
301            sys::spvc_compiler_hlsl_add_resource_binding(self.ptr.as_ptr(), &hlsl_resource_binding)
302                .ok(&*self)
303        }
304    }
305
306    /// Compiles and remap vertex attribute at specific locations to a fixed semantic.
307    ///
308    /// The default is `TEXCOORD#` where # denotes location.
309    /// Matrices are unrolled to vectors with notation `${SEMANTIC}_#`, where # denotes row.
310    /// `$SEMANTIC` is either `TEXCOORD#` or a semantic name specified here.
311    pub fn remap_vertex_attribute<'str>(
312        &mut self,
313        location: u32,
314        semantic: impl Into<CompilerStr<'str>>,
315    ) -> error::Result<()> {
316        let str = semantic.into();
317        let semantic = str.into_cstring_ptr()?;
318
319        let remap = HlslVertexAttributeRemap {
320            location,
321            semantic: semantic.as_ptr(),
322        };
323
324        unsafe {
325            sys::spvc_compiler_hlsl_add_vertex_attribute_remap(self.ptr.as_ptr(), &remap, 1)
326                .ok(&*self)
327        }
328    }
329
330    /// Optionally specify a custom root constant layout.
331    ///
332    /// Push constants ranges will be split up according to the
333    /// layout specified.
334    pub fn set_root_constant_layout(
335        &mut self,
336        constant_info: &[RootConstants],
337    ) -> error::Result<()> {
338        unsafe {
339            sys::spvc_compiler_hlsl_set_root_constants_layout(
340                self.ptr.as_ptr(),
341                constant_info.as_ptr(),
342                constant_info.len(),
343            )
344            .ok(&*self)
345        }
346    }
347
348    /// Controls how resource bindings are declared in the output HLSL.
349    pub fn set_resource_binding_flags(&mut self, flags: BindingFlags) -> error::Result<()> {
350        unsafe {
351            sys::spvc_compiler_hlsl_set_resource_binding_flags(
352                self.ptr.as_ptr(),
353                HlslBindingFlags(flags.bits()),
354            )
355            .ok(&*self)
356        }
357    }
358
359    /// This is a special HLSL workaround for the NumWorkGroups builtin.
360    /// This does not exist in HLSL, so the calling application must create a dummy cbuffer in
361    /// which the application will store this builtin.
362    ///
363    /// The cbuffer layout will be:
364    ///
365    /// ```hlsl
366    ///  cbuffer SPIRV_Cross_NumWorkgroups : register(b#, space#) {
367    ///     uint3 SPIRV_Cross_NumWorkgroups_count;
368    /// };
369    /// ```
370    ///
371    /// This must be called before [`Compiler::compile`] if the `NumWorkgroups` builtin is used,
372    /// or compilation will fail.
373    ///
374    /// The function returns None if NumWorkGroups builtin is not statically used in the shader
375    /// from the current entry point.
376    ///
377    /// If Some, returns the variable ID of a cbuffer which corresponds to
378    /// the cbuffer declared above.
379    ///
380    /// By default, no binding or descriptor set decoration is set,
381    /// so the calling application should declare explicit bindings on this ID before calling
382    /// [`Compiler::compile`].
383    pub fn remap_num_workgroups_builtin(&mut self) -> Option<Handle<VariableId>> {
384        unsafe {
385            let id = sys::spvc_compiler_hlsl_remap_num_workgroups_builtin(self.ptr.as_ptr());
386            self.create_handle_if_not_zero(id)
387        }
388    }
389
390    /// Mask a stage output by location.
391    ///
392    /// If a shader output is active in this stage, but inactive in a subsequent stage,
393    /// this can be signalled here. This can be used to work around certain cross-stage matching problems
394    /// which plagues HLSL in certain scenarios.
395    ///
396    /// An output which matches one of these will not be emitted in stage output interfaces, but rather treated as a private
397    /// variable.
398    ///
399    /// This option is only meaningful for MSL and HLSL, since GLSL matches by location directly.
400    ///
401    pub fn mask_stage_output_by_location(
402        &mut self,
403        location: u32,
404        component: u32,
405    ) -> crate::error::Result<()> {
406        unsafe {
407            sys::spvc_compiler_mask_stage_output_by_location(self.ptr.as_ptr(), location, component)
408                .ok(&*self)
409        }
410    }
411
412    /// Mask a stage output by builtin. Masking builtins only takes effect if the builtin in question is part of the stage output interface.
413    ///
414    /// If a shader output is active in this stage, but inactive in a subsequent stage,
415    /// this can be signalled here. This can be used to work around certain cross-stage matching problems
416    /// which plagues HLSL in certain scenarios.
417    ///
418    /// An output which matches one of these will not be emitted in stage output interfaces, but rather treated as a private
419    /// variable.
420    ///
421    /// This option is only meaningful for MSL and HLSL, since GLSL matches by location directly.
422    /// Masking builtins only takes effect if the builtin in question is part of the stage output interface.
423    pub fn mask_stage_output_by_builtin(
424        &mut self,
425        builtin: spirv::BuiltIn,
426    ) -> crate::error::Result<()> {
427        unsafe {
428            sys::spvc_compiler_mask_stage_output_by_builtin(
429                self.ptr.as_ptr(),
430                SpvBuiltIn(builtin as u32 as i32),
431            )
432            .ok(&*self)
433        }
434    }
435}
436
437impl CompiledArtifact<Hlsl> {
438    /// Returns whether the set/binding combination provided in [`Compiler<Hlsl>::add_resource_binding`]
439    /// was used.
440    pub fn is_resource_used(&self, model: spirv::ExecutionModel, binding: ResourceBinding) -> bool {
441        unsafe {
442            sys::spvc_compiler_hlsl_is_resource_used(
443                self.compiler.ptr.as_ptr(),
444                SpvExecutionModel(model as u32 as i32),
445                binding.descriptor_set(),
446                binding.binding(),
447            )
448        }
449    }
450}
451
452#[cfg(test)]
453mod test {
454    use crate::compile::hlsl::CompilerOptions;
455    use spirv_cross_sys::spvc_compiler_create_compiler_options;
456
457    use crate::compile::sealed::ApplyCompilerOptions;
458    use crate::error::{SpirvCrossError, ToContextError};
459    use crate::Compiler;
460    use crate::{targets, Module};
461
462    static BASIC_SPV: &[u8] = include_bytes!("../../../basic.spv");
463
464    #[test]
465    pub fn hlsl_opts() -> Result<(), SpirvCrossError> {
466        let words = Vec::from(BASIC_SPV);
467        let words = Module::from_words(bytemuck::cast_slice(&words));
468
469        let compiler: Compiler<targets::Hlsl> = Compiler::new(words)?;
470        let resources = compiler.shader_resources()?.all_resources()?;
471
472        let mut opts_ptr = std::ptr::null_mut();
473
474        unsafe {
475            spvc_compiler_create_compiler_options(compiler.ptr.as_ptr(), &mut opts_ptr)
476                .ok(&compiler)?;
477        }
478
479        // println!("{:#?}", resources);
480        let opts = CompilerOptions::default();
481        unsafe {
482            opts.apply(opts_ptr, &compiler)?;
483        }
484
485        // match ty.inner {
486        //     TypeInner::Struct(ty) => {
487        //         compiler.get_type(ty.members[0].id)?;
488        //     }
489        //     TypeInner::Vector { .. } => {}
490        //     _ => {}
491        // }
492        Ok(())
493    }
494}