Skip to main content

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    /// Use UserSemantic decoration info (if specified), otherwise use default mechanism (such as add_vertex_attribute_remap or TEXCOORD#).
120    #[option(SPVC_COMPILER_OPTION_HLSL_USER_SEMANTIC, false)]
121    pub user_semantic: bool,
122}
123
124/// HLSL Shader model.
125#[derive(Debug, Copy, Clone, Eq, PartialEq)]
126#[non_exhaustive]
127#[derive(Default)]
128pub enum HlslShaderModel {
129    /// Shader Model 3.0 (Direct3D 9.0c).
130    ///
131    /// This is the lowest supported shader model.
132    #[default]
133    ShaderModel3_0,
134    /// Shader Model 4.0 (Direct3D 10.0).
135    ///
136    /// Level 9.x feature levels are not explicitly supported.
137    ShaderModel4_0,
138    /// Shader Model 4.1 (Direct3D 10.1).
139    ShaderModel4_1,
140    /// Shader Model 5.0 (Direct3D 11/11.1)
141    ShaderModel5_0,
142    /// Shader Model 5.1 (Direct3D 12).
143    ShaderModel5_1,
144    /// Shader Model 6.0 (Direct3D 12)
145    ShaderModel6_0,
146    /// Shader Model 6.1 (Direct3D 12)
147    ShaderModel6_1,
148    /// Shader Model 6.2 (Direct3D 12)
149    ShaderModel6_2,
150    /// Shader Model 6.3 (Direct3D 12)
151    ShaderModel6_3,
152    /// Shader Model 6.4 (Direct3D 12)
153    ShaderModel6_4,
154    /// Shader Model 6.5 (Direct3D 12)
155    ShaderModel6_5,
156    /// Shader Model 6.6 (Direct3D 12)
157    ShaderModel6_6,
158    /// Shader Model 6.7 (Direct3D 12)
159    ShaderModel6_7,
160    /// Shader Model 6.8 (Direct3D 12)
161    ShaderModel6_8,
162}
163
164impl From<HlslShaderModel> for u32 {
165    fn from(value: HlslShaderModel) -> Self {
166        match value {
167            HlslShaderModel::ShaderModel3_0 => 30,
168            HlslShaderModel::ShaderModel4_0 => 40,
169            HlslShaderModel::ShaderModel4_1 => 41,
170            HlslShaderModel::ShaderModel5_0 => 50,
171            HlslShaderModel::ShaderModel5_1 => 51,
172            HlslShaderModel::ShaderModel6_0 => 60,
173            HlslShaderModel::ShaderModel6_1 => 61,
174            HlslShaderModel::ShaderModel6_2 => 62,
175            HlslShaderModel::ShaderModel6_3 => 63,
176            HlslShaderModel::ShaderModel6_4 => 64,
177            HlslShaderModel::ShaderModel6_5 => 65,
178            HlslShaderModel::ShaderModel6_6 => 66,
179            HlslShaderModel::ShaderModel6_7 => 67,
180            HlslShaderModel::ShaderModel6_8 => 68,
181        }
182    }
183}
184
185/// Pipeline binding information for a resource.
186///
187/// Used to map a SPIR-V resource to an HLSL buffer.
188#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
189pub enum ResourceBinding {
190    /// A resource with a qualified layout.
191    ///
192    /// i.e. `layout(set = 0, binding = 1)` in GLSL.
193    Qualified {
194        /// The descriptor set of the qualified layout.
195        set: u32,
196        /// The binding number of the qualified layout.
197        binding: u32,
198    },
199    /// The push constant buffer.
200    PushConstantBuffer,
201}
202
203impl ResourceBinding {
204    /// Create a resource binding for a qualified SPIR-V layout
205    /// specifier.
206    pub const fn from_qualified(set: u32, binding: u32) -> Self {
207        Self::Qualified { set, binding }
208    }
209
210    const fn descriptor_set(&self) -> u32 {
211        const PUSH_CONSTANT_DESCRIPTOR_SET: u32 = !0;
212        match self {
213            ResourceBinding::Qualified { set, .. } => *set,
214            ResourceBinding::PushConstantBuffer => PUSH_CONSTANT_DESCRIPTOR_SET,
215        }
216    }
217
218    const fn binding(&self) -> u32 {
219        const PUSH_CONSTANT_BINDING: u32 = 0;
220        match self {
221            ResourceBinding::Qualified { binding, .. } => *binding,
222            ResourceBinding::PushConstantBuffer => PUSH_CONSTANT_BINDING,
223        }
224    }
225}
226
227/// The HLSL buffer to bind a resource to.
228///
229/// A single SPIR-V binding may bind to multiple
230/// registers for multiple resource types.
231///
232/// For example, for combined image samplers, both the `srv` and `sampler`
233/// bindings will be considered.
234#[derive(Debug, Clone, PartialEq, Eq, Hash)]
235pub struct BindTarget {
236    /// Bind to a constant buffer view register.
237    pub cbv: Option<RegisterBinding>,
238    /// Bind to an unordered access view register.
239    pub uav: Option<RegisterBinding>,
240    /// Bind to a shader resource view register.
241    pub srv: Option<RegisterBinding>,
242    /// Bind to a sampler register.
243    pub sampler: Option<RegisterBinding>,
244}
245
246/// An HLSL register location.
247#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
248pub struct RegisterBinding {
249    /// The register for this binding.
250    pub register: u32,
251    /// The address space index for this binding.
252    ///
253    /// This is ignored on Shader Model 5.0 and below.
254    pub space: u32,
255}
256
257impl From<RegisterBinding> for HlslResourceBindingMapping {
258    #[inline(always)]
259    fn from(value: RegisterBinding) -> Self {
260        HlslResourceBindingMapping {
261            register_space: value.space,
262            register_binding: value.register,
263        }
264    }
265}
266
267/// HLSL specific APIs.
268impl Compiler<Hlsl> {
269    /// Add a resource binding to the HLSL compilation.
270    ///
271    /// Register bindings are set based on whether the HLSL resource is a
272    /// CBV, UAV, SRV or Sampler.
273    ///
274    /// A single binding in SPIR-V might contain multiple
275    /// resource types, e.g. `COMBINED_IMAGE_SAMPLER`, and SRV/Sampler bindings will be used respectively.
276    ///
277    /// On SM 5.0 and lower, `register_space` is ignored.
278    ///
279    /// For deeper control of push constants, [`Compiler<Hlsl>::set_root_constant_layout`] can be used instead.
280    ///
281    /// If resource bindings are provided, [`CompiledArtifact<Hlsl>::is_resource_used`] will return true if
282    /// the set/binding combination was used by the HLSL code.
283    pub fn add_resource_binding(
284        &mut self,
285        stage: spirv::ExecutionModel,
286        binding: ResourceBinding,
287        bind_target: &BindTarget,
288    ) -> error::Result<()> {
289        const DEFAULT_BINDING: HlslResourceBindingMapping = HlslResourceBindingMapping {
290            register_space: 0,
291            register_binding: 0,
292        };
293
294        let hlsl_resource_binding = HlslResourceBinding {
295            stage: SpvExecutionModel(stage as u32 as i32),
296            desc_set: binding.descriptor_set(),
297            binding: binding.binding(),
298            cbv: bind_target.cbv.map_or(DEFAULT_BINDING, From::from),
299            uav: bind_target.uav.map_or(DEFAULT_BINDING, From::from),
300            srv: bind_target.srv.map_or(DEFAULT_BINDING, From::from),
301            sampler: bind_target.sampler.map_or(DEFAULT_BINDING, From::from),
302        };
303
304        unsafe {
305            sys::spvc_compiler_hlsl_add_resource_binding(self.ptr.as_ptr(), &hlsl_resource_binding)
306                .ok(&*self)
307        }
308    }
309
310    /// Compiles and remap vertex attribute at specific locations to a fixed semantic.
311    ///
312    /// The default is `TEXCOORD#` where # denotes location.
313    /// Matrices are unrolled to vectors with notation `${SEMANTIC}_#`, where # denotes row.
314    /// `$SEMANTIC` is either `TEXCOORD#` or a semantic name specified here.
315    pub fn remap_vertex_attribute<'str>(
316        &mut self,
317        location: u32,
318        semantic: impl Into<CompilerStr<'str>>,
319    ) -> error::Result<()> {
320        let str = semantic.into();
321        let semantic = str.into_cstring_ptr()?;
322
323        let remap = HlslVertexAttributeRemap {
324            location,
325            semantic: semantic.as_ptr(),
326        };
327
328        unsafe {
329            sys::spvc_compiler_hlsl_add_vertex_attribute_remap(self.ptr.as_ptr(), &remap, 1)
330                .ok(&*self)
331        }
332    }
333
334    /// Optionally specify a custom root constant layout.
335    ///
336    /// Push constants ranges will be split up according to the
337    /// layout specified.
338    pub fn set_root_constant_layout(
339        &mut self,
340        constant_info: &[RootConstants],
341    ) -> error::Result<()> {
342        unsafe {
343            sys::spvc_compiler_hlsl_set_root_constants_layout(
344                self.ptr.as_ptr(),
345                constant_info.as_ptr(),
346                constant_info.len(),
347            )
348            .ok(&*self)
349        }
350    }
351
352    /// Controls how resource bindings are declared in the output HLSL.
353    pub fn set_resource_binding_flags(&mut self, flags: BindingFlags) -> error::Result<()> {
354        unsafe {
355            sys::spvc_compiler_hlsl_set_resource_binding_flags(
356                self.ptr.as_ptr(),
357                HlslBindingFlags(flags.bits()),
358            )
359            .ok(&*self)
360        }
361    }
362
363    /// This is a special HLSL workaround for the NumWorkGroups builtin.
364    /// This does not exist in HLSL, so the calling application must create a dummy cbuffer in
365    /// which the application will store this builtin.
366    ///
367    /// The cbuffer layout will be:
368    ///
369    /// ```hlsl
370    ///  cbuffer SPIRV_Cross_NumWorkgroups : register(b#, space#) {
371    ///     uint3 SPIRV_Cross_NumWorkgroups_count;
372    /// };
373    /// ```
374    ///
375    /// This must be called before [`Compiler::compile`] if the `NumWorkgroups` builtin is used,
376    /// or compilation will fail.
377    ///
378    /// The function returns None if NumWorkGroups builtin is not statically used in the shader
379    /// from the current entry point.
380    ///
381    /// If Some, returns the variable ID of a cbuffer which corresponds to
382    /// the cbuffer declared above.
383    ///
384    /// By default, no binding or descriptor set decoration is set,
385    /// so the calling application should declare explicit bindings on this ID before calling
386    /// [`Compiler::compile`].
387    pub fn remap_num_workgroups_builtin(&mut self) -> Option<Handle<VariableId>> {
388        unsafe {
389            let id = sys::spvc_compiler_hlsl_remap_num_workgroups_builtin(self.ptr.as_ptr());
390            self.create_handle_if_not_zero(id)
391        }
392    }
393
394    /// Mask a stage output by location.
395    ///
396    /// If a shader output is active in this stage, but inactive in a subsequent stage,
397    /// this can be signalled here. This can be used to work around certain cross-stage matching problems
398    /// which plagues HLSL in certain scenarios.
399    ///
400    /// An output which matches one of these will not be emitted in stage output interfaces, but rather treated as a private
401    /// variable.
402    ///
403    /// This option is only meaningful for MSL and HLSL, since GLSL matches by location directly.
404    ///
405    pub fn mask_stage_output_by_location(
406        &mut self,
407        location: u32,
408        component: u32,
409    ) -> crate::error::Result<()> {
410        unsafe {
411            sys::spvc_compiler_mask_stage_output_by_location(self.ptr.as_ptr(), location, component)
412                .ok(&*self)
413        }
414    }
415
416    /// Mask a stage output by builtin. Masking builtins only takes effect if the builtin in question is part of the stage output interface.
417    ///
418    /// If a shader output is active in this stage, but inactive in a subsequent stage,
419    /// this can be signalled here. This can be used to work around certain cross-stage matching problems
420    /// which plagues HLSL in certain scenarios.
421    ///
422    /// An output which matches one of these will not be emitted in stage output interfaces, but rather treated as a private
423    /// variable.
424    ///
425    /// This option is only meaningful for MSL and HLSL, since GLSL matches by location directly.
426    /// Masking builtins only takes effect if the builtin in question is part of the stage output interface.
427    pub fn mask_stage_output_by_builtin(
428        &mut self,
429        builtin: spirv::BuiltIn,
430    ) -> crate::error::Result<()> {
431        unsafe {
432            sys::spvc_compiler_mask_stage_output_by_builtin(
433                self.ptr.as_ptr(),
434                SpvBuiltIn(builtin as u32 as i32),
435            )
436            .ok(&*self)
437        }
438    }
439}
440
441impl CompiledArtifact<Hlsl> {
442    /// Returns whether the set/binding combination provided in [`Compiler<Hlsl>::add_resource_binding`]
443    /// was used.
444    pub fn is_resource_used(&self, model: spirv::ExecutionModel, binding: ResourceBinding) -> bool {
445        unsafe {
446            sys::spvc_compiler_hlsl_is_resource_used(
447                self.compiler.ptr.as_ptr(),
448                SpvExecutionModel(model as u32 as i32),
449                binding.descriptor_set(),
450                binding.binding(),
451            )
452        }
453    }
454}
455
456#[cfg(test)]
457mod test {
458    use crate::compile::hlsl::CompilerOptions;
459    use spirv_cross_sys::spvc_compiler_create_compiler_options;
460
461    use crate::compile::sealed::ApplyCompilerOptions;
462    use crate::error::{SpirvCrossError, ToContextError};
463    use crate::Compiler;
464    use crate::{targets, Module};
465
466    static BASIC_SPV: &[u8] = include_bytes!("../../../basic.spv");
467
468    #[test]
469    pub fn hlsl_opts() -> Result<(), SpirvCrossError> {
470        let words = Vec::from(BASIC_SPV);
471        let words = Module::from_words(bytemuck::cast_slice(&words));
472
473        let compiler: Compiler<targets::Hlsl> = Compiler::new(words)?;
474        let resources = compiler.shader_resources()?.all_resources()?;
475
476        let mut opts_ptr = std::ptr::null_mut();
477
478        unsafe {
479            spvc_compiler_create_compiler_options(compiler.ptr.as_ptr(), &mut opts_ptr)
480                .ok(&compiler)?;
481        }
482
483        // println!("{:#?}", resources);
484        let opts = CompilerOptions::default();
485        unsafe {
486            opts.apply(opts_ptr, &compiler)?;
487        }
488
489        // match ty.inner {
490        //     TypeInner::Struct(ty) => {
491        //         compiler.get_type(ty.members[0].id)?;
492        //     }
493        //     TypeInner::Vector { .. } => {}
494        //     _ => {}
495        // }
496        Ok(())
497    }
498}