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}