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}