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}