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}