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}