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
286impl crate::ShaderStage {
287    pub const fn to_hlsl_str(self) -> &'static str {
288        match self {
289            Self::Vertex => "vs",
290            Self::Fragment => "ps",
291            Self::Compute => "cs",
292            Self::Task => "as",
293            Self::Mesh => "ms",
294            Self::RayGeneration | Self::AnyHit | Self::ClosestHit | Self::Miss => "lib",
295        }
296    }
297}
298
299impl crate::ImageDimension {
300    const fn to_hlsl_str(self) -> &'static str {
301        match self {
302            Self::D1 => "1D",
303            Self::D2 => "2D",
304            Self::D3 => "3D",
305            Self::Cube => "Cube",
306        }
307    }
308}
309
310#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
311#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
312#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
313pub struct SamplerIndexBufferKey {
314    pub group: u32,
315}
316
317#[derive(Clone, Debug, Hash, PartialEq, Eq)]
318#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
319#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
320#[cfg_attr(feature = "deserialize", serde(default))]
321pub struct SamplerHeapBindTargets {
322    pub standard_samplers: BindTarget,
323    pub comparison_samplers: BindTarget,
324}
325
326impl Default for SamplerHeapBindTargets {
327    fn default() -> Self {
328        Self {
329            standard_samplers: BindTarget {
330                space: 0,
331                register: 0,
332                binding_array_size: None,
333                dynamic_storage_buffer_offsets_index: None,
334                restrict_indexing: false,
335            },
336            comparison_samplers: BindTarget {
337                space: 1,
338                register: 0,
339                binding_array_size: None,
340                dynamic_storage_buffer_offsets_index: None,
341                restrict_indexing: false,
342            },
343        }
344    }
345}
346
347#[cfg(feature = "deserialize")]
348#[derive(serde::Deserialize)]
349struct SamplerIndexBufferBindingSerialization {
350    group: u32,
351    bind_target: BindTarget,
352}
353
354#[cfg(feature = "deserialize")]
355fn deserialize_sampler_index_buffer_bindings<'de, D>(
356    deserializer: D,
357) -> Result<SamplerIndexBufferBindingMap, D::Error>
358where
359    D: serde::Deserializer<'de>,
360{
361    use serde::Deserialize;
362
363    let vec = Vec::<SamplerIndexBufferBindingSerialization>::deserialize(deserializer)?;
364    let mut map = SamplerIndexBufferBindingMap::default();
365    for item in vec {
366        map.insert(
367            SamplerIndexBufferKey { group: item.group },
368            item.bind_target,
369        );
370    }
371    Ok(map)
372}
373
374// We use a BTreeMap here so that we can hash it.
375pub type SamplerIndexBufferBindingMap =
376    alloc::collections::BTreeMap<SamplerIndexBufferKey, BindTarget>;
377
378#[cfg(feature = "deserialize")]
379#[derive(serde::Deserialize)]
380struct DynamicStorageBufferOffsetTargetSerialization {
381    index: u32,
382    bind_target: OffsetsBindTarget,
383}
384
385#[cfg(feature = "deserialize")]
386fn deserialize_storage_buffer_offsets<'de, D>(
387    deserializer: D,
388) -> Result<DynamicStorageBufferOffsetsTargets, D::Error>
389where
390    D: serde::Deserializer<'de>,
391{
392    use serde::Deserialize;
393
394    let vec = Vec::<DynamicStorageBufferOffsetTargetSerialization>::deserialize(deserializer)?;
395    let mut map = DynamicStorageBufferOffsetsTargets::default();
396    for item in vec {
397        map.insert(item.index, item.bind_target);
398    }
399    Ok(map)
400}
401
402pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap<u32, OffsetsBindTarget>;
403
404/// HLSL binding information for a Naga [`External`] image global variable.
405///
406/// See the module documentation's section on [External textures][mod] for details.
407///
408/// [`External`]: crate::ir::ImageClass::External
409/// [mod]: #external-textures
410#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
411#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
412#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
413pub struct ExternalTextureBindTarget {
414    /// HLSL binding information for the individual plane textures.
415    ///
416    /// Each of these should refer to an HLSL `Texture2D<float4>` holding one
417    /// plane of data for the external texture. The exact meaning of each plane
418    /// varies at runtime depending on where the external texture's data
419    /// originated.
420    pub planes: [BindTarget; 3],
421
422    /// HLSL binding information for a buffer holding the sampling parameters.
423    ///
424    /// This should refer to a cbuffer of type `NagaExternalTextureParams`, that
425    /// the code Naga generates for `textureSampleBaseClampToEdge` consults to
426    /// decide how to combine the data in [`planes`] to get the result required
427    /// by the spec.
428    ///
429    /// [`planes`]: Self::planes
430    pub params: BindTarget,
431}
432
433#[cfg(feature = "deserialize")]
434#[derive(serde::Deserialize)]
435struct ExternalTextureBindingMapSerialization {
436    resource_binding: crate::ResourceBinding,
437    bind_target: ExternalTextureBindTarget,
438}
439
440#[cfg(feature = "deserialize")]
441fn deserialize_external_texture_binding_map<'de, D>(
442    deserializer: D,
443) -> Result<ExternalTextureBindingMap, D::Error>
444where
445    D: serde::Deserializer<'de>,
446{
447    use serde::Deserialize;
448
449    let vec = Vec::<ExternalTextureBindingMapSerialization>::deserialize(deserializer)?;
450    let mut map = ExternalTextureBindingMap::default();
451    for item in vec {
452        map.insert(item.resource_binding, item.bind_target);
453    }
454    Ok(map)
455}
456pub type ExternalTextureBindingMap =
457    alloc::collections::BTreeMap<crate::ResourceBinding, ExternalTextureBindTarget>;
458
459/// Shorthand result used internally by the backend
460type BackendResult = Result<(), Error>;
461
462#[derive(Clone, Debug, PartialEq, thiserror::Error)]
463#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
464#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
465pub enum EntryPointError {
466    #[error("mapping of {0:?} is missing")]
467    MissingBinding(crate::ResourceBinding),
468}
469
470/// Configuration used in the [`Writer`].
471#[derive(Clone, Debug, Hash, PartialEq, Eq)]
472#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
473#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
474#[cfg_attr(feature = "deserialize", serde(default))]
475pub struct Options {
476    /// The hlsl shader model to be used
477    pub shader_model: ShaderModel,
478
479    /// HLSL binding information for each Naga global variable.
480    ///
481    /// This maps Naga [`GlobalVariable`]'s [`ResourceBinding`]s to a
482    /// [`BindTarget`] specifying its register number and space, along with
483    /// other details necessary to generate a full HLSL declaration for it,
484    /// or to access its value.
485    ///
486    /// This must provide a [`BindTarget`] for every [`GlobalVariable`] in the
487    /// [`Module`] that has a [`binding`].
488    ///
489    /// [`GlobalVariable`]: crate::ir::GlobalVariable
490    /// [`ResourceBinding`]: crate::ir::ResourceBinding
491    /// [`Module`]: crate::ir::Module
492    /// [`binding`]: crate::ir::GlobalVariable::binding
493    #[cfg_attr(
494        feature = "deserialize",
495        serde(deserialize_with = "deserialize_binding_map")
496    )]
497    pub binding_map: BindingMap,
498
499    /// Don't panic on missing bindings, instead generate any HLSL.
500    pub fake_missing_bindings: bool,
501    /// Add special constants to `SV_VertexIndex` and `SV_InstanceIndex`,
502    /// to make them work like in Vulkan/Metal, with help of the host.
503    pub special_constants_binding: Option<BindTarget>,
504
505    /// HLSL binding information for the [`Immediate`] global, if present.
506    ///
507    /// If a module contains a global in the [`Immediate`] address space, the
508    /// `dx12` backend stores its value directly in the root signature as a
509    /// series of [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`], whose binding
510    /// information is given here.
511    ///
512    /// [`Immediate`]: crate::ir::AddressSpace::Immediate
513    /// [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`]: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_root_parameter_type
514    pub immediates_target: Option<BindTarget>,
515
516    /// HLSL binding information for the sampler heap and comparison sampler heap.
517    pub sampler_heap_target: SamplerHeapBindTargets,
518
519    /// Mapping of each bind group's sampler index buffer to a bind target.
520    #[cfg_attr(
521        feature = "deserialize",
522        serde(deserialize_with = "deserialize_sampler_index_buffer_bindings")
523    )]
524    pub sampler_buffer_binding_map: SamplerIndexBufferBindingMap,
525    /// Bind target for dynamic storage buffer offsets
526    #[cfg_attr(
527        feature = "deserialize",
528        serde(deserialize_with = "deserialize_storage_buffer_offsets")
529    )]
530    pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets,
531    #[cfg_attr(
532        feature = "deserialize",
533        serde(deserialize_with = "deserialize_external_texture_binding_map")
534    )]
535
536    /// HLSL binding information for [`External`] image global variables.
537    ///
538    /// See [`ExternalTextureBindTarget`] for details.
539    ///
540    /// [`External`]: crate::ir::ImageClass::External
541    pub external_texture_binding_map: ExternalTextureBindingMap,
542
543    /// Should workgroup variables be zero initialized (by polyfilling)?
544    pub zero_initialize_workgroup_memory: bool,
545    /// Should we restrict indexing of vectors, matrices and arrays?
546    pub restrict_indexing: bool,
547    /// If set, loops will have code injected into them, forcing the compiler
548    /// to think the number of iterations is bounded.
549    pub force_loop_bounding: bool,
550
551    /// Limits to the mesh shader dispatch group a task workgroup can dispatch.
552    ///
553    /// Metal for example limits to 1024 workgroups per task shader dispatch. Dispatching more is
554    /// undefined behavior, so this would validate that to dispatch zero workgroups.
555    pub task_dispatch_limits: Option<TaskDispatchLimits>,
556
557    /// If true, naga may generate checks that the primitive indices are valid in the output.
558    ///
559    /// Currently this validation is unimplemented.
560    pub mesh_shader_primitive_indices_clamp: bool,
561    /// if set, ray queries will get a variable to track their state to prevent
562    /// misuse.
563    pub ray_query_initialization_tracking: bool,
564}
565
566impl Default for Options {
567    fn default() -> Self {
568        Options {
569            shader_model: ShaderModel::V5_1,
570            binding_map: BindingMap::default(),
571            fake_missing_bindings: true,
572            special_constants_binding: None,
573            sampler_heap_target: SamplerHeapBindTargets::default(),
574            sampler_buffer_binding_map: alloc::collections::BTreeMap::default(),
575            immediates_target: None,
576            dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(),
577            external_texture_binding_map: ExternalTextureBindingMap::default(),
578            zero_initialize_workgroup_memory: true,
579            restrict_indexing: true,
580            force_loop_bounding: true,
581            task_dispatch_limits: None,
582            mesh_shader_primitive_indices_clamp: true,
583            ray_query_initialization_tracking: true,
584        }
585    }
586}
587
588impl Options {
589    fn resolve_resource_binding(
590        &self,
591        res_binding: &crate::ResourceBinding,
592    ) -> Result<BindTarget, EntryPointError> {
593        match self.binding_map.get(res_binding) {
594            Some(target) => Ok(*target),
595            None if self.fake_missing_bindings => Ok(BindTarget {
596                space: res_binding.group as u8,
597                register: res_binding.binding,
598                binding_array_size: None,
599                dynamic_storage_buffer_offsets_index: None,
600                restrict_indexing: false,
601            }),
602            None => Err(EntryPointError::MissingBinding(*res_binding)),
603        }
604    }
605
606    fn resolve_external_texture_resource_binding(
607        &self,
608        res_binding: &crate::ResourceBinding,
609    ) -> Result<ExternalTextureBindTarget, EntryPointError> {
610        match self.external_texture_binding_map.get(res_binding) {
611            Some(target) => Ok(*target),
612            None if self.fake_missing_bindings => {
613                let fake = BindTarget {
614                    space: res_binding.group as u8,
615                    register: res_binding.binding,
616                    binding_array_size: None,
617                    dynamic_storage_buffer_offsets_index: None,
618                    restrict_indexing: false,
619                };
620                Ok(ExternalTextureBindTarget {
621                    planes: [fake, fake, fake],
622                    params: fake,
623                })
624            }
625            None => Err(EntryPointError::MissingBinding(*res_binding)),
626        }
627    }
628}
629
630/// Reflection info for entry point names.
631#[derive(Default)]
632pub struct ReflectionInfo {
633    /// Mapping of the entry point names.
634    ///
635    /// Each item in the array corresponds to an entry point index. The real entry point name may be different if one of the
636    /// reserved words are used.
637    ///
638    /// Note: Some entry points may fail translation because of missing bindings.
639    pub entry_point_names: Vec<Result<String, EntryPointError>>,
640}
641
642/// A subset of options that are meant to be changed per pipeline.
643#[derive(Debug, Default, Clone)]
644#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
645#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
646#[cfg_attr(feature = "deserialize", serde(default))]
647pub struct PipelineOptions {
648    /// The entry point to write.
649    ///
650    /// Entry points are identified by a shader stage specification,
651    /// and a name.
652    ///
653    /// If `None`, all entry points will be written. If `Some` and the entry
654    /// point is not found, an error will be thrown while writing.
655    pub entry_point: Option<(ir::ShaderStage, String)>,
656}
657
658#[derive(Error, Debug)]
659pub enum Error {
660    #[error(transparent)]
661    IoError(#[from] FmtError),
662    #[error("A scalar with an unsupported width was requested: {0:?}")]
663    UnsupportedScalar(crate::Scalar),
664    #[error("{0}")]
665    Unimplemented(String), // TODO: Error used only during development
666    #[error("{0}")]
667    Custom(String),
668    #[error("overrides should not be present at this stage")]
669    Override,
670    #[error(transparent)]
671    ResolveArraySizeError(#[from] proc::ResolveArraySizeError),
672    #[error("entry point with stage {0:?} and name '{1}' not found")]
673    EntryPointNotFound(ir::ShaderStage, String),
674    #[error("requires shader model {1:?} for reason: {0}")]
675    ShaderModelTooLow(String, ShaderModel),
676}
677
678#[derive(PartialEq, Eq, Hash)]
679enum WrappedType {
680    ZeroValue(help::WrappedZeroValue),
681    ArrayLength(help::WrappedArrayLength),
682    ImageSample(help::WrappedImageSample),
683    ImageQuery(help::WrappedImageQuery),
684    ImageLoad(help::WrappedImageLoad),
685    ImageLoadScalar(crate::Scalar),
686    Constructor(help::WrappedConstructor),
687    StructMatrixAccess(help::WrappedStructMatrixAccess),
688    MatCx2(help::WrappedMatCx2),
689    Math(help::WrappedMath),
690    UnaryOp(help::WrappedUnaryOp),
691    BinaryOp(help::WrappedBinaryOp),
692    Cast(help::WrappedCast),
693}
694
695#[derive(Default)]
696struct Wrapped {
697    types: crate::FastHashSet<WrappedType>,
698    /// If true, the sampler heaps have been written out.
699    sampler_heaps: bool,
700    // Mapping from SamplerIndexBufferKey to the name the namer returned.
701    sampler_index_buffers: crate::FastHashMap<SamplerIndexBufferKey, String>,
702}
703
704impl Wrapped {
705    fn insert(&mut self, r#type: WrappedType) -> bool {
706        self.types.insert(r#type)
707    }
708
709    fn clear(&mut self) {
710        self.types.clear();
711    }
712}
713
714/// A fragment entry point to be considered when generating HLSL for the output interface of vertex
715/// entry points.
716///
717/// This is provided as an optional parameter to [`Writer::write`].
718///
719/// If this is provided, vertex outputs will be removed if they are not inputs of this fragment
720/// entry point. This is necessary for generating correct HLSL when some of the vertex shader
721/// outputs are not consumed by the fragment shader.
722pub struct FragmentEntryPoint<'a> {
723    module: &'a crate::Module,
724    func: &'a crate::Function,
725}
726
727impl<'a> FragmentEntryPoint<'a> {
728    /// Returns `None` if the entry point with the provided name can't be found or isn't a fragment
729    /// entry point.
730    pub fn new(module: &'a crate::Module, ep_name: &'a str) -> Option<Self> {
731        module
732            .entry_points
733            .iter()
734            .find(|ep| ep.name == ep_name)
735            .filter(|ep| ep.stage == crate::ShaderStage::Fragment)
736            .map(|ep| Self {
737                module,
738                func: &ep.function,
739            })
740    }
741}
742
743pub struct Writer<'a, W> {
744    out: W,
745    names: crate::FastHashMap<proc::NameKey, String>,
746    namer: proc::Namer,
747    /// HLSL backend options
748    options: &'a Options,
749    /// Per-stage backend options
750    pipeline_options: &'a PipelineOptions,
751    /// Information about entry point arguments and result types.
752    entry_point_io: crate::FastHashMap<usize, writer::EntryPointInterface>,
753    /// Set of expressions that have associated temporary variables
754    named_expressions: crate::NamedExpressions,
755    wrapped: Wrapped,
756    written_committed_intersection: bool,
757    written_candidate_intersection: bool,
758    continue_ctx: back::continue_forward::ContinueCtx,
759
760    /// A reference to some part of a global variable, lowered to a series of
761    /// byte offset calculations.
762    ///
763    /// See the [`storage`] module for background on why we need this.
764    ///
765    /// Each [`SubAccess`] in the vector is a lowering of some [`Access`] or
766    /// [`AccessIndex`] expression to the level of byte strides and offsets. See
767    /// [`SubAccess`] for details.
768    ///
769    /// This field is a member of [`Writer`] solely to allow re-use of
770    /// the `Vec`'s dynamic allocation. The value is no longer needed
771    /// once HLSL for the access has been generated.
772    ///
773    /// [`Storage`]: crate::AddressSpace::Storage
774    /// [`SubAccess`]: storage::SubAccess
775    /// [`Access`]: crate::Expression::Access
776    /// [`AccessIndex`]: crate::Expression::AccessIndex
777    temp_access_chain: Vec<storage::SubAccess>,
778    need_bake_expressions: back::NeedBakeExpressions,
779
780    function_task_payload_var:
781        crate::FastHashMap<Handle<crate::Function>, Handle<crate::GlobalVariable>>,
782}
783
784pub fn supported_capabilities() -> crate::valid::Capabilities {
785    use crate::valid::Capabilities as Caps;
786    Caps::IMMEDIATES
787        | Caps::FLOAT64 // Unsupported by wgpu but supported by naga
788        | Caps::PRIMITIVE_INDEX
789        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY
790        // No BUFFER_BINDING_ARRAY
791        | Caps::STORAGE_TEXTURE_BINDING_ARRAY
792        // No STORAGE_BUFFER_BINDING_ARRAY
793        | Caps::ACCELERATION_STRUCTURE_BINDING_ARRAY
794        // No CLIP_DISTANCES
795        // No CULL_DISTANCE
796        | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
797        | Caps::MULTIVIEW
798        // No EARLY_DEPTH_TEST
799        | Caps::MULTISAMPLED_SHADING
800        | Caps::RAY_QUERY
801        | Caps::DUAL_SOURCE_BLENDING
802        | Caps::CUBE_ARRAY_TEXTURES
803        | Caps::SHADER_INT64
804        | Caps::SUBGROUP
805        // No SUBGROUP_BARRIER
806        // No SUBGROUP_VERTEX_STAGE
807        | Caps::SHADER_INT64_ATOMIC_MIN_MAX
808        | Caps::SHADER_INT64_ATOMIC_ALL_OPS
809        // No SHADER_FLOAT32_ATOMIC
810        | Caps::TEXTURE_ATOMIC
811        | Caps::TEXTURE_INT64_ATOMIC
812        // No RAY_HIT_VERTEX_POSITION
813        | Caps::SHADER_FLOAT16
814        | Caps::TEXTURE_EXTERNAL
815        | Caps::SHADER_FLOAT16_IN_FLOAT32
816        | Caps::SHADER_BARYCENTRICS
817        | Caps::MESH_SHADER
818        // No MESH_SHADER_POINT_TOPOLOGY
819        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
820        // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
821        | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
822        | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
823        // No COOPERATIVE_MATRIX
824        // No PER_VERTEX
825        // No RAY_TRACING_PIPELINE
826        // No DRAW_INDEX
827        // No MEMORY_DECORATION_VOLATILE
828        | Caps::MEMORY_DECORATION_COHERENT
829}