naga/back/hlsl/mod.rs
1/*!
2Backend for [HLSL][hlsl] (High-Level Shading Language).
3
4# Supported shader model versions:
5- 5.0
6- 5.1
7- 6.0
8
9# Layout of values in `uniform` buffers
10
11WGSL's ["Internal Layout of Values"][ilov] rules specify how each WGSL
12type should be stored in `uniform` and `storage` buffers. The HLSL we
13generate must access values in that form, even when it is not what
14HLSL would use normally.
15
16Matching the WGSL memory layout is a concern only for `uniform`
17variables. WGSL `storage` buffers are translated as HLSL
18`ByteAddressBuffers`, for which we generate `Load` and `Store` method
19calls with explicit byte offsets. WGSL pipeline inputs must be scalars
20or vectors; they cannot be matrices, which is where the interesting
21problems arise. However, when an affected type appears in a struct
22definition, the transformations described here are applied without
23consideration of where the struct is used.
24
25Access to storage buffers is implemented in `storage.rs`. Access to
26uniform buffers is implemented where applicable in `writer.rs`.
27
28## Row- and column-major ordering for matrices
29
30WGSL specifies that matrices in uniform buffers are stored in
31column-major order. This matches HLSL's default, so one might expect
32things to be straightforward. Unfortunately, WGSL and HLSL disagree on
33what indexing a matrix means: in WGSL, `m[i]` retrieves the `i`'th
34*column* of `m`, whereas in HLSL it retrieves the `i`'th *row*. We
35want to avoid translating `m[i]` into some complicated reassembly of a
36vector from individually fetched components, so this is a problem.
37
38However, with a bit of trickery, it is possible to use HLSL's `m[i]`
39as the translation of WGSL's `m[i]`:
40
41- We declare all matrices in uniform buffers in HLSL with the
42 `row_major` qualifier, and transpose the row and column counts: a
43 WGSL `mat3x4<f32>`, say, becomes an HLSL `row_major float3x4`. (Note
44 that WGSL and HLSL type names put the row and column in reverse
45 order.) Since the HLSL type is the transpose of how WebGPU directs
46 the user to store the data, HLSL will load all matrices transposed.
47
48- Since matrices are transposed, an HLSL indexing expression retrieves
49 the "columns" of the intended WGSL value, as desired.
50
51- For vector-matrix multiplication, since `mul(transpose(m), v)` is
52 equivalent to `mul(v, m)` (note the reversal of the arguments), and
53 `mul(v, transpose(m))` is equivalent to `mul(m, v)`, we can
54 translate WGSL `m * v` and `v * m` to HLSL by simply reversing the
55 arguments to `mul`.
56
57## Padding in two-row matrices
58
59An HLSL `row_major floatKx2` matrix has padding between its rows that
60the WGSL `matKx2<f32>` matrix it represents does not. HLSL stores all
61matrix rows [aligned on 16-byte boundaries][16bb], whereas WGSL says
62that the columns of a `matKx2<f32>` need only be [aligned as required
63for `vec2<f32>`][ilov], which is [eight-byte alignment][8bb].
64
65To compensate for this, any time a `matKx2<f32>` appears in a WGSL
66`uniform` value or as part of a struct/array, we actually emit `K`
67separate `float2` members, and assemble/disassemble the matrix from its
68columns (in WGSL; rows in HLSL) upon load and store.
69
70For example, the following WGSL struct type:
71
72```ignore
73struct Baz {
74 m: mat3x2<f32>,
75}
76```
77
78is rendered as the HLSL struct type:
79
80```ignore
81struct Baz {
82 float2 m_0; float2 m_1; float2 m_2;
83};
84```
85
86The `wrapped_struct_matrix` functions in `help.rs` generate HLSL
87helper functions to access such members, converting between the stored
88form and the HLSL matrix types appropriately. For example, for reading
89the member `m` of the `Baz` struct above, we emit:
90
91```ignore
92float3x2 GetMatmOnBaz(Baz obj) {
93 return float3x2(obj.m_0, obj.m_1, obj.m_2);
94}
95```
96
97We also emit an analogous `Set` function, as well as functions for
98accessing individual columns by dynamic index.
99
100## Sampler Handling
101
102Due to limitations in how sampler heaps work in D3D12, we need to access samplers
103through a layer of indirection. Instead of directly binding samplers, we bind the entire
104sampler heap as both a standard and a comparison sampler heap. We then use a sampler
105index buffer for each bind group. This buffer is accessed in the shader to get the actual
106sampler index within the heap. See the wgpu_hal dx12 backend documentation for more
107information.
108
109# External textures
110
111Support for [`crate::ImageClass::External`] textures is implemented by lowering
112each external texture global variable to 3 `Texture2D<float4>`s, and a `cbuffer`
113of type `NagaExternalTextureParams`. This provides up to 3 planes of texture
114data (for example single planar RGBA, or separate Y, Cb, and Cr planes), and the
115parameters buffer containing information describing how to handle these
116correctly. The bind target to use for each of these globals is specified via
117[`Options::external_texture_binding_map`].
118
119External textures are supported by WGSL's `textureDimensions()`,
120`textureLoad()`, and `textureSampleBaseClampToEdge()` built-in functions. These
121are implemented using helper functions. See the following functions for how
122these are generated:
123 * `Writer::write_wrapped_image_query_function`
124 * `Writer::write_wrapped_image_load_function`
125 * `Writer::write_wrapped_image_sample_function`
126
127Ideally the set of global variables could be wrapped in a single struct that
128could conveniently be passed around. But, alas, HLSL does not allow structs to
129have `Texture2D` members. Fortunately, however, external textures can only be
130used as arguments to either built-in or user-defined functions. We therefore
131expand any external texture function argument to four consecutive arguments (3
132textures and the params struct) when declaring user-defined functions, and
133ensure our built-in function implementations take the same arguments. Then,
134whenever we need to emit an external texture in `Writer::write_expr`, which
135fortunately can only ever be for a global variable or function argument, we
136simply emit the variable name of each of the three textures and the parameters
137struct in a comma-separated list. This won't win any awards for elegance, but
138it works for our purposes.
139
140[hlsl]: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl
141[ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout
142[16bb]: https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing#constant-buffer-packing
143[8bb]: https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size
144*/
145
146mod conv;
147mod help;
148mod keywords;
149mod mesh_shader;
150mod ray;
151mod storage;
152mod writer;
153
154use alloc::{string::String, vec::Vec};
155use core::fmt::Error as FmtError;
156
157use thiserror::Error;
158
159use crate::{
160 back::{self, TaskDispatchLimits},
161 ir, proc, Handle,
162};
163
164/// Direct3D 12 binding information for a global variable.
165///
166/// This type provides the HLSL-specific information Naga needs to declare and
167/// access an HLSL global variable that cannot be derived from the `Module`
168/// itself.
169///
170/// An HLSL global variable declaration includes details that the Direct3D API
171/// will use to refer to it. For example:
172///
173/// RWByteAddressBuffer s_sasm : register(u0, space2);
174///
175/// This defines a global `s_sasm` that a Direct3D root signature would refer to
176/// as register `0` in register space `2` in a `UAV` descriptor range. Naga can
177/// infer the register's descriptor range type from the variable's address class
178/// (writable [`Storage`] variables are implemented by Direct3D Unordered Access
179/// Views, the `u` register type), but the register number and register space
180/// must be supplied by the user.
181///
182/// The [`back::hlsl::Options`] structure provides `BindTarget`s for various
183/// situations in which Naga may need to generate an HLSL global variable, like
184/// [`binding_map`] for Naga global variables, or [`immediates_target`] for
185/// a module's sole [`Immediate`] variable. See those fields' documentation
186/// for details.
187///
188/// [`Storage`]: crate::ir::AddressSpace::Storage
189/// [`back::hlsl::Options`]: Options
190/// [`binding_map`]: Options::binding_map
191/// [`immediates_target`]: Options::immediates_target
192/// [`Immediate`]: crate::ir::AddressSpace::Immediate
193#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
194#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
195#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
196pub struct BindTarget {
197 pub space: u8,
198 /// For regular bindings this is the register number.
199 ///
200 /// For sampler bindings, this is the index to use into the bind group's sampler index buffer.
201 pub register: u32,
202 /// If the binding is an unsized binding array, this overrides the size.
203 pub binding_array_size: Option<u32>,
204 /// This is the index in the buffer at [`Options::dynamic_storage_buffer_offsets_targets`].
205 pub dynamic_storage_buffer_offsets_index: Option<u32>,
206 /// This is a hint that we need to restrict indexing of vectors, matrices and arrays.
207 ///
208 /// If [`Options::restrict_indexing`] is also `true`, we will restrict indexing.
209 #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
210 pub restrict_indexing: bool,
211}
212
213#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
214#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
215#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
216/// BindTarget for dynamic storage buffer offsets
217pub struct OffsetsBindTarget {
218 pub space: u8,
219 pub register: u32,
220 pub size: u32,
221}
222
223#[cfg(feature = "deserialize")]
224#[derive(serde::Deserialize)]
225struct BindingMapSerialization {
226 resource_binding: crate::ResourceBinding,
227 bind_target: BindTarget,
228}
229
230#[cfg(feature = "deserialize")]
231fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
232where
233 D: serde::Deserializer<'de>,
234{
235 use serde::Deserialize;
236
237 let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
238 let mut map = BindingMap::default();
239 for item in vec {
240 map.insert(item.resource_binding, item.bind_target);
241 }
242 Ok(map)
243}
244
245// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
246pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
247
248/// A HLSL shader model version.
249#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd)]
250#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
251#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
252pub enum ShaderModel {
253 V5_0,
254 V5_1,
255 V6_0,
256 V6_1,
257 V6_2,
258 V6_3,
259 V6_4,
260 V6_5,
261 V6_6,
262 V6_7,
263 V6_8,
264 V6_9,
265}
266
267impl ShaderModel {
268 pub const fn to_str(self) -> &'static str {
269 match self {
270 Self::V5_0 => "5_0",
271 Self::V5_1 => "5_1",
272 Self::V6_0 => "6_0",
273 Self::V6_1 => "6_1",
274 Self::V6_2 => "6_2",
275 Self::V6_3 => "6_3",
276 Self::V6_4 => "6_4",
277 Self::V6_5 => "6_5",
278 Self::V6_6 => "6_6",
279 Self::V6_7 => "6_7",
280 Self::V6_8 => "6_8",
281 Self::V6_9 => "6_9",
282 }
283 }
284}
285
286pub const fn shader_stage_to_hlsl_str(st: nt::ShaderStage) -> &'static str {
287 match st {
288 nt::ShaderStage::Vertex => "vs",
289 nt::ShaderStage::Fragment => "ps",
290 nt::ShaderStage::Compute => "cs",
291 nt::ShaderStage::Task => "as",
292 nt::ShaderStage::Mesh => "ms",
293 nt::ShaderStage::RayGeneration
294 | nt::ShaderStage::AnyHit
295 | nt::ShaderStage::ClosestHit
296 | nt::ShaderStage::Miss => "lib",
297 }
298}
299
300impl crate::ImageDimension {
301 const fn to_hlsl_str(self) -> &'static str {
302 match self {
303 Self::D1 => "1D",
304 Self::D2 => "2D",
305 Self::D3 => "3D",
306 Self::Cube => "Cube",
307 }
308 }
309}
310
311#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
312#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
313#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
314pub struct SamplerIndexBufferKey {
315 pub group: u32,
316}
317
318#[derive(Clone, Debug, Hash, PartialEq, Eq)]
319#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
320#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
321#[cfg_attr(feature = "deserialize", serde(default))]
322pub struct SamplerHeapBindTargets {
323 pub standard_samplers: BindTarget,
324 pub comparison_samplers: BindTarget,
325}
326
327impl Default for SamplerHeapBindTargets {
328 fn default() -> Self {
329 Self {
330 standard_samplers: BindTarget {
331 space: 0,
332 register: 0,
333 binding_array_size: None,
334 dynamic_storage_buffer_offsets_index: None,
335 restrict_indexing: false,
336 },
337 comparison_samplers: BindTarget {
338 space: 1,
339 register: 0,
340 binding_array_size: None,
341 dynamic_storage_buffer_offsets_index: None,
342 restrict_indexing: false,
343 },
344 }
345 }
346}
347
348#[cfg(feature = "deserialize")]
349#[derive(serde::Deserialize)]
350struct SamplerIndexBufferBindingSerialization {
351 group: u32,
352 bind_target: BindTarget,
353}
354
355#[cfg(feature = "deserialize")]
356fn deserialize_sampler_index_buffer_bindings<'de, D>(
357 deserializer: D,
358) -> Result<SamplerIndexBufferBindingMap, D::Error>
359where
360 D: serde::Deserializer<'de>,
361{
362 use serde::Deserialize;
363
364 let vec = Vec::<SamplerIndexBufferBindingSerialization>::deserialize(deserializer)?;
365 let mut map = SamplerIndexBufferBindingMap::default();
366 for item in vec {
367 map.insert(
368 SamplerIndexBufferKey { group: item.group },
369 item.bind_target,
370 );
371 }
372 Ok(map)
373}
374
375// We use a BTreeMap here so that we can hash it.
376pub type SamplerIndexBufferBindingMap =
377 alloc::collections::BTreeMap<SamplerIndexBufferKey, BindTarget>;
378
379#[cfg(feature = "deserialize")]
380#[derive(serde::Deserialize)]
381struct DynamicStorageBufferOffsetTargetSerialization {
382 index: u32,
383 bind_target: OffsetsBindTarget,
384}
385
386#[cfg(feature = "deserialize")]
387fn deserialize_storage_buffer_offsets<'de, D>(
388 deserializer: D,
389) -> Result<DynamicStorageBufferOffsetsTargets, D::Error>
390where
391 D: serde::Deserializer<'de>,
392{
393 use serde::Deserialize;
394
395 let vec = Vec::<DynamicStorageBufferOffsetTargetSerialization>::deserialize(deserializer)?;
396 let mut map = DynamicStorageBufferOffsetsTargets::default();
397 for item in vec {
398 map.insert(item.index, item.bind_target);
399 }
400 Ok(map)
401}
402
403pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap<u32, OffsetsBindTarget>;
404
405/// HLSL binding information for a Naga [`External`] image global variable.
406///
407/// See the module documentation's section on [External textures][mod] for details.
408///
409/// [`External`]: crate::ir::ImageClass::External
410/// [mod]: #external-textures
411#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
412#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
413#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
414pub struct ExternalTextureBindTarget {
415 /// HLSL binding information for the individual plane textures.
416 ///
417 /// Each of these should refer to an HLSL `Texture2D<float4>` holding one
418 /// plane of data for the external texture. The exact meaning of each plane
419 /// varies at runtime depending on where the external texture's data
420 /// originated.
421 pub planes: [BindTarget; 3],
422
423 /// HLSL binding information for a buffer holding the sampling parameters.
424 ///
425 /// This should refer to a cbuffer of type `NagaExternalTextureParams`, that
426 /// the code Naga generates for `textureSampleBaseClampToEdge` consults to
427 /// decide how to combine the data in [`planes`] to get the result required
428 /// by the spec.
429 ///
430 /// [`planes`]: Self::planes
431 pub params: BindTarget,
432}
433
434#[cfg(feature = "deserialize")]
435#[derive(serde::Deserialize)]
436struct ExternalTextureBindingMapSerialization {
437 resource_binding: crate::ResourceBinding,
438 bind_target: ExternalTextureBindTarget,
439}
440
441#[cfg(feature = "deserialize")]
442fn deserialize_external_texture_binding_map<'de, D>(
443 deserializer: D,
444) -> Result<ExternalTextureBindingMap, D::Error>
445where
446 D: serde::Deserializer<'de>,
447{
448 use serde::Deserialize;
449
450 let vec = Vec::<ExternalTextureBindingMapSerialization>::deserialize(deserializer)?;
451 let mut map = ExternalTextureBindingMap::default();
452 for item in vec {
453 map.insert(item.resource_binding, item.bind_target);
454 }
455 Ok(map)
456}
457pub type ExternalTextureBindingMap =
458 alloc::collections::BTreeMap<crate::ResourceBinding, ExternalTextureBindTarget>;
459
460/// Shorthand result used internally by the backend
461type BackendResult = Result<(), Error>;
462
463#[derive(Clone, Debug, PartialEq, thiserror::Error)]
464#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
465#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
466pub enum EntryPointError {
467 #[error("mapping of {0:?} is missing")]
468 MissingBinding(crate::ResourceBinding),
469}
470
471/// Configuration used in the [`Writer`].
472#[derive(Clone, Debug, Hash, PartialEq, Eq)]
473#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
474#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
475#[cfg_attr(feature = "deserialize", serde(default))]
476pub struct Options {
477 /// The hlsl shader model to be used
478 pub shader_model: ShaderModel,
479
480 /// HLSL binding information for each Naga global variable.
481 ///
482 /// This maps Naga [`GlobalVariable`]'s [`ResourceBinding`]s to a
483 /// [`BindTarget`] specifying its register number and space, along with
484 /// other details necessary to generate a full HLSL declaration for it,
485 /// or to access its value.
486 ///
487 /// This must provide a [`BindTarget`] for every [`GlobalVariable`] in the
488 /// [`Module`] that has a [`binding`].
489 ///
490 /// [`GlobalVariable`]: crate::ir::GlobalVariable
491 /// [`ResourceBinding`]: crate::ir::ResourceBinding
492 /// [`Module`]: crate::ir::Module
493 /// [`binding`]: crate::ir::GlobalVariable::binding
494 #[cfg_attr(
495 feature = "deserialize",
496 serde(deserialize_with = "deserialize_binding_map")
497 )]
498 pub binding_map: BindingMap,
499
500 /// Don't panic on missing bindings, instead generate any HLSL.
501 pub fake_missing_bindings: bool,
502 /// Add special constants to `SV_VertexIndex` and `SV_InstanceIndex`,
503 /// to make them work like in Vulkan/Metal, with help of the host.
504 pub special_constants_binding: Option<BindTarget>,
505
506 /// HLSL binding information for the [`Immediate`] global, if present.
507 ///
508 /// If a module contains a global in the [`Immediate`] address space, the
509 /// `dx12` backend stores its value directly in the root signature as a
510 /// series of [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`], whose binding
511 /// information is given here.
512 ///
513 /// [`Immediate`]: crate::ir::AddressSpace::Immediate
514 /// [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`]: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_root_parameter_type
515 pub immediates_target: Option<BindTarget>,
516
517 /// HLSL binding information for the sampler heap and comparison sampler heap.
518 pub sampler_heap_target: SamplerHeapBindTargets,
519
520 /// Mapping of each bind group's sampler index buffer to a bind target.
521 #[cfg_attr(
522 feature = "deserialize",
523 serde(deserialize_with = "deserialize_sampler_index_buffer_bindings")
524 )]
525 pub sampler_buffer_binding_map: SamplerIndexBufferBindingMap,
526 /// Bind target for dynamic storage buffer offsets
527 #[cfg_attr(
528 feature = "deserialize",
529 serde(deserialize_with = "deserialize_storage_buffer_offsets")
530 )]
531 pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets,
532 #[cfg_attr(
533 feature = "deserialize",
534 serde(deserialize_with = "deserialize_external_texture_binding_map")
535 )]
536
537 /// HLSL binding information for [`External`] image global variables.
538 ///
539 /// See [`ExternalTextureBindTarget`] for details.
540 ///
541 /// [`External`]: crate::ir::ImageClass::External
542 pub external_texture_binding_map: ExternalTextureBindingMap,
543
544 /// Should workgroup variables be zero initialized (by polyfilling)?
545 pub zero_initialize_workgroup_memory: bool,
546 /// Should we restrict indexing of vectors, matrices and arrays?
547 pub restrict_indexing: bool,
548 /// If set, loops will have code injected into them, forcing the compiler
549 /// to think the number of iterations is bounded.
550 pub force_loop_bounding: bool,
551
552 /// Limits to the mesh shader dispatch group a task workgroup can dispatch.
553 ///
554 /// Metal for example limits to 1024 workgroups per task shader dispatch. Dispatching more is
555 /// undefined behavior, so this would validate that to dispatch zero workgroups.
556 pub task_dispatch_limits: Option<TaskDispatchLimits>,
557
558 /// If true, naga may generate checks that the primitive indices are valid in the output.
559 ///
560 /// Currently this validation is unimplemented.
561 pub mesh_shader_primitive_indices_clamp: bool,
562 /// if set, ray queries will get a variable to track their state to prevent
563 /// misuse.
564 pub ray_query_initialization_tracking: bool,
565}
566
567impl Default for Options {
568 fn default() -> Self {
569 Options {
570 shader_model: ShaderModel::V5_1,
571 binding_map: BindingMap::default(),
572 fake_missing_bindings: true,
573 special_constants_binding: None,
574 sampler_heap_target: SamplerHeapBindTargets::default(),
575 sampler_buffer_binding_map: alloc::collections::BTreeMap::default(),
576 immediates_target: None,
577 dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(),
578 external_texture_binding_map: ExternalTextureBindingMap::default(),
579 zero_initialize_workgroup_memory: true,
580 restrict_indexing: true,
581 force_loop_bounding: true,
582 task_dispatch_limits: None,
583 mesh_shader_primitive_indices_clamp: true,
584 ray_query_initialization_tracking: true,
585 }
586 }
587}
588
589impl Options {
590 fn resolve_resource_binding(
591 &self,
592 res_binding: &crate::ResourceBinding,
593 ) -> Result<BindTarget, EntryPointError> {
594 match self.binding_map.get(res_binding) {
595 Some(target) => Ok(*target),
596 None if self.fake_missing_bindings => Ok(BindTarget {
597 space: res_binding.group as u8,
598 register: res_binding.binding,
599 binding_array_size: None,
600 dynamic_storage_buffer_offsets_index: None,
601 restrict_indexing: false,
602 }),
603 None => Err(EntryPointError::MissingBinding(*res_binding)),
604 }
605 }
606
607 fn resolve_external_texture_resource_binding(
608 &self,
609 res_binding: &crate::ResourceBinding,
610 ) -> Result<ExternalTextureBindTarget, EntryPointError> {
611 match self.external_texture_binding_map.get(res_binding) {
612 Some(target) => Ok(*target),
613 None if self.fake_missing_bindings => {
614 let fake = BindTarget {
615 space: res_binding.group as u8,
616 register: res_binding.binding,
617 binding_array_size: None,
618 dynamic_storage_buffer_offsets_index: None,
619 restrict_indexing: false,
620 };
621 Ok(ExternalTextureBindTarget {
622 planes: [fake, fake, fake],
623 params: fake,
624 })
625 }
626 None => Err(EntryPointError::MissingBinding(*res_binding)),
627 }
628 }
629}
630
631/// Reflection info for entry point names.
632#[derive(Debug, Default)]
633pub struct ReflectionInfo {
634 /// Mapping of the entry point names.
635 ///
636 /// Each item in the array corresponds to an entry point index. The real entry point name may be different if one of the
637 /// reserved words are used.
638 ///
639 /// Note: Some entry points may fail translation because of missing bindings.
640 pub entry_point_names: Vec<Result<String, EntryPointError>>,
641}
642
643/// A subset of options that are meant to be changed per pipeline.
644#[derive(Debug, Default, Clone)]
645#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
646#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
647#[cfg_attr(feature = "deserialize", serde(default))]
648pub struct PipelineOptions {
649 /// The entry point to write.
650 ///
651 /// Entry points are identified by a shader stage specification,
652 /// and a name.
653 ///
654 /// If `None`, all entry points will be written. If `Some` and the entry
655 /// point is not found, an error will be thrown while writing.
656 pub entry_point: Option<(ir::ShaderStage, String)>,
657}
658
659#[derive(Error, Debug)]
660pub enum Error {
661 #[error(transparent)]
662 IoError(#[from] FmtError),
663 #[error("A scalar with an unsupported width was requested: {0:?}")]
664 UnsupportedScalar(crate::Scalar),
665 #[error("{0}")]
666 Unimplemented(String), // TODO: Error used only during development
667 #[error("{0}")]
668 Custom(String),
669 #[error("overrides should not be present at this stage")]
670 Override,
671 #[error(transparent)]
672 ResolveArraySizeError(#[from] proc::ResolveArraySizeError),
673 #[error("entry point with stage {0:?} and name '{1}' not found")]
674 EntryPointNotFound(ir::ShaderStage, String),
675 #[error("requires shader model {1:?} for reason: {0}")]
676 ShaderModelTooLow(String, ShaderModel),
677}
678
679#[derive(PartialEq, Eq, Hash)]
680enum WrappedType {
681 ZeroValue(help::WrappedZeroValue),
682 ArrayLength(help::WrappedArrayLength),
683 ImageSample(help::WrappedImageSample),
684 ImageQuery(help::WrappedImageQuery),
685 ImageLoad(help::WrappedImageLoad),
686 ImageLoadScalar(crate::Scalar),
687 Constructor(help::WrappedConstructor),
688 StructMatrixAccess(help::WrappedStructMatrixAccess),
689 MatCx2(help::WrappedMatCx2),
690 Math(help::WrappedMath),
691 UnaryOp(help::WrappedUnaryOp),
692 BinaryOp(help::WrappedBinaryOp),
693 Cast(help::WrappedCast),
694}
695
696#[derive(Default)]
697struct Wrapped {
698 types: crate::FastHashSet<WrappedType>,
699 /// If true, the sampler heaps have been written out.
700 sampler_heaps: bool,
701 // Mapping from SamplerIndexBufferKey to the name the namer returned.
702 sampler_index_buffers: crate::FastHashMap<SamplerIndexBufferKey, String>,
703}
704
705impl Wrapped {
706 fn insert(&mut self, r#type: WrappedType) -> bool {
707 self.types.insert(r#type)
708 }
709
710 fn clear(&mut self) {
711 self.types.clear();
712 }
713}
714
715/// A fragment entry point to be considered when generating HLSL for the output interface of vertex
716/// entry points.
717///
718/// This is provided as an optional parameter to [`Writer::write`].
719///
720/// If this is provided, vertex outputs will be removed if they are not inputs of this fragment
721/// entry point. This is necessary for generating correct HLSL when some of the vertex shader
722/// outputs are not consumed by the fragment shader.
723#[derive(Debug)]
724pub struct FragmentEntryPoint<'a> {
725 module: &'a crate::Module,
726 func: &'a crate::Function,
727}
728
729impl<'a> FragmentEntryPoint<'a> {
730 /// Returns `None` if the entry point with the provided name can't be found or isn't a fragment
731 /// entry point.
732 pub fn new(module: &'a crate::Module, ep_name: &'a str) -> Option<Self> {
733 module
734 .entry_points
735 .iter()
736 .find(|ep| ep.name == ep_name)
737 .filter(|ep| ep.stage == crate::ShaderStage::Fragment)
738 .map(|ep| Self {
739 module,
740 func: &ep.function,
741 })
742 }
743}
744
745#[expect(missing_debug_implementations, reason = "would be way too verbose?")]
746pub struct Writer<'a, W> {
747 out: W,
748 names: crate::FastHashMap<proc::NameKey, String>,
749 namer: proc::Namer,
750 /// HLSL backend options
751 options: &'a Options,
752 /// Per-stage backend options
753 pipeline_options: &'a PipelineOptions,
754 /// Information about entry point arguments and result types.
755 entry_point_io: crate::FastHashMap<usize, writer::EntryPointInterface>,
756 /// Set of expressions that have associated temporary variables
757 named_expressions: crate::NamedExpressions,
758 wrapped: Wrapped,
759 written_committed_intersection: bool,
760 written_candidate_intersection: bool,
761 continue_ctx: back::continue_forward::ContinueCtx,
762
763 /// A reference to some part of a global variable, lowered to a series of
764 /// byte offset calculations.
765 ///
766 /// See the [`storage`] module for background on why we need this.
767 ///
768 /// Each [`SubAccess`] in the vector is a lowering of some [`Access`] or
769 /// [`AccessIndex`] expression to the level of byte strides and offsets. See
770 /// [`SubAccess`] for details.
771 ///
772 /// This field is a member of [`Writer`] solely to allow re-use of
773 /// the `Vec`'s dynamic allocation. The value is no longer needed
774 /// once HLSL for the access has been generated.
775 ///
776 /// [`Storage`]: crate::AddressSpace::Storage
777 /// [`SubAccess`]: storage::SubAccess
778 /// [`Access`]: crate::Expression::Access
779 /// [`AccessIndex`]: crate::Expression::AccessIndex
780 temp_access_chain: Vec<storage::SubAccess>,
781 need_bake_expressions: back::NeedBakeExpressions,
782
783 function_task_payload_var:
784 crate::FastHashMap<Handle<crate::Function>, Handle<crate::GlobalVariable>>,
785}
786
787pub fn supported_capabilities() -> crate::valid::Capabilities {
788 use crate::valid::Capabilities as Caps;
789 Caps::IMMEDIATES
790 | Caps::FLOAT64 // Unsupported by wgpu but supported by naga
791 | Caps::PRIMITIVE_INDEX
792 | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY
793 // No BUFFER_BINDING_ARRAY
794 | Caps::STORAGE_TEXTURE_BINDING_ARRAY
795 // No STORAGE_BUFFER_BINDING_ARRAY
796 | Caps::ACCELERATION_STRUCTURE_BINDING_ARRAY
797 // No CLIP_DISTANCES
798 // No CULL_DISTANCE
799 | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
800 | Caps::MULTIVIEW
801 // No EARLY_DEPTH_TEST
802 | Caps::MULTISAMPLED_SHADING
803 | Caps::RAY_QUERY
804 | Caps::DUAL_SOURCE_BLENDING
805 | Caps::CUBE_ARRAY_TEXTURES
806 | Caps::SHADER_INT64
807 | Caps::SUBGROUP
808 // No SUBGROUP_BARRIER
809 // No SUBGROUP_VERTEX_STAGE
810 | Caps::SHADER_INT64_ATOMIC_MIN_MAX
811 | Caps::SHADER_INT64_ATOMIC_ALL_OPS
812 // No SHADER_FLOAT32_ATOMIC
813 | Caps::TEXTURE_ATOMIC
814 | Caps::TEXTURE_INT64_ATOMIC
815 // No RAY_HIT_VERTEX_POSITION
816 | Caps::SHADER_FLOAT16
817 | Caps::SHADER_INT16
818 | Caps::TEXTURE_EXTERNAL
819 | Caps::SHADER_FLOAT16_IN_FLOAT32
820 | Caps::SHADER_BARYCENTRICS
821 | Caps::MESH_SHADER
822 // No MESH_SHADER_POINT_TOPOLOGY
823 | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
824 // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
825 | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
826 | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
827 // No COOPERATIVE_MATRIX
828 | Caps::PER_VERTEX
829 // No RAY_TRACING_PIPELINE
830 // No DRAW_INDEX
831 // No MEMORY_DECORATION_VOLATILE
832 | Caps::MEMORY_DECORATION_COHERENT
833}