Skip to main content

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