Skip to main content

concinnity_core/render/slang_programs/
dx.rs

1// Single-source engine shader programs for the DirectX backend.
2//
3// Each program compiles a `.slang` file under `src/shaders/` (the
4// backend-neutral single-source directory) to a signed DXIL container, at
5// build time where the host can and at renderer init otherwise, cached in the
6// content-addressed shader cache. slangc resolves dxil.dll itself, so the
7// containers it emits are already signed for D3D12.
8//
9// The bindless main pair compiles the source's `DXIL_ABI` block, whose
10// register() annotations reproduce the bindless main root signature in
11// `init/pipelines.rs` slot for slot: that layout is a contract, since a world
12// Shader asset builds its own PSO against the same root signature (see
13// world_shaders.rs). `assert_slang_dxil_abi` in build.rs locks it. The two
14// compute kernels need no ABI block: slangc assigns b0/t0/u0 from declaration
15// order, which is what their root signatures already bind.
16//
17// The G-buffer pre-pass and shadow families take the same `DXIL_ABI` block for
18// a weaker reason: nothing outside the engine binds them, but their root
19// signatures hand the same declarations entirely different slots than the Metal
20// and Vulkan hosts do, so the shared source cannot carry one set of registers
21// for all three. `assert_slang_dxil_abi` in build.rs locks every one of them,
22// which is what a macOS edit to the shared file runs into under
23// `dx_crosscheck.sh`.
24//
25// The fullscreen post passes need no ABI block either, and for a stronger
26// reason than the compute kernels: nothing outside the engine binds them at
27// all. slangc splits each top-level `Sampler2D` into a `Texture2D` + a
28// `SamplerState` and numbers both from declaration order, so a pass with N
29// sources lands on t0..tN-1 *and* s0..sN-1 -- where the hand HLSL declared one
30// sampler for all of them. The root signatures name the samplers they hand out
31// (a pass's static samplers are the same descriptor repeated).
32//
33// Declaration order is what the root signatures follow, and it is not always
34// the order the hand HLSL used: the SSR resolve's probe cube array lands at t4
35// (not the hand shader's t7) with its `ProbeSet` at b1 (not b4), and the
36// reflection composite reads scene / G-buffer / roughness at t1 / t2 / t3 where
37// the HLSL had roughness first.
38//
39// These are shader model 6.0 rather than the FXC path's 5.1: the bindless pool
40// index is non-uniform across a fragment wave, and `NonUniformResourceIndex`
41// is an SM 6.0 construct.
42
43/// One DXIL program: which shader file, which entry point, at which
44/// shader-model profile, under which variant defines.
45pub struct SlangProgram {
46    /// File name under this crate's `src/shaders/` for the hot-reload disk-first resolve;
47    /// also the embedded fallback's origin and the name slangc diagnostics use.
48    pub file: &'static str,
49    /// Entry point compiled out of that file.
50    pub entry: &'static str,
51    /// Shader-model profile for the DXIL container (stage + feature floor).
52    pub profile: &'static str,
53    /// Diagnostic label (compile errors + cache miss logs + export report).
54    pub label: &'static str,
55    /// Variant defines injected as `#define` lines ahead of the source.
56    pub defines: &'static [(&'static str, &'static str)],
57}
58
59// The bindless main pair's variant defines. `MAX_PROBES` sizes the probe cube
60// array the root signature's descriptor table covers; `probe_cube_count_matches`
61// locks it to the host constant. `POOL_SIZE` is deliberately absent: the DXIL
62// pool is an unbounded array, so the shader never over-declares the per-frame
63// descriptor region the host actually wrote.
64const MAIN_DEFINES: &[(&str, &str)] = &[("DXIL_ABI", "1"), ("MAX_PROBES", "8")];
65
66// The SSR resolve reads the probe array but none of the texture pool, so it
67// takes the probe count alone. Same lock as `MAIN_DEFINES`.
68// `SPLIT_PROBE_SAMPLER` declares that array as a texture array plus one
69// sampler: D3D12 binds a shader sampler *array* only through a descriptor
70// table, so the combined form Metal and Vulkan use cannot be covered by static
71// samplers here (see the declaration in `ssr.slang`).
72const SSR_DEFINES: &[(&str, &str)] = &[("MAX_PROBES", "8"), ("SPLIT_PROBE_SAMPLER", "1")];
73
74/// `vertex_main_bindless` from `main_bindless.slang`.
75pub static MAIN_BINDLESS_VERT: SlangProgram = SlangProgram {
76    file: "main_bindless.slang",
77    entry: "vertex_main_bindless",
78    profile: "vs_6_0",
79    label: "vert_bindless.slang",
80    defines: MAIN_DEFINES,
81};
82/// `fragment_main_bindless` from `main_bindless.slang`.
83pub static MAIN_BINDLESS_FRAG: SlangProgram = SlangProgram {
84    file: "main_bindless.slang",
85    entry: "fragment_main_bindless",
86    profile: "ps_6_0",
87    label: "frag_bindless.slang",
88    defines: MAIN_DEFINES,
89};
90// The draw cull's host-shape gate. DirectX fuses the frustum/bucket constants
91// and the Hi-Z reprojection into one b0 root-constant block where Vulkan splits
92// them across a push constant and a set-1 uniform buffer.
93const CULL_DEFINES: &[(&str, &str)] = &[("DXIL_ABI", "1")];
94
95/// `cull_kernel` from `cull.slang`: phase-1 draw cull.
96pub static CULL: SlangProgram = SlangProgram {
97    file: "cull.slang",
98    entry: "cull_kernel",
99    profile: "cs_6_0",
100    label: "cull.slang",
101    defines: CULL_DEFINES,
102};
103/// `cull_kernel` from `cull.slang` under `CULL_PHASE2`: the two-pass occlusion
104/// re-test against the rebuilt Hi-Z pyramid.
105pub static CULL_PHASE2: SlangProgram = SlangProgram {
106    file: "cull.slang",
107    entry: "cull_kernel",
108    profile: "cs_6_0",
109    label: "cull_phase2.slang",
110    defines: &[("DXIL_ABI", "1"), ("CULL_PHASE2", "1")],
111};
112/// `cull_kernel` from `cull.slang` under `SHADOW_CULL`: light-frustum only.
113pub static CULL_SHADOW: SlangProgram = SlangProgram {
114    file: "cull.slang",
115    entry: "cull_kernel",
116    profile: "cs_6_0",
117    label: "cull_shadow.slang",
118    defines: &[("DXIL_ABI", "1"), ("SHADOW_CULL", "1")],
119};
120/// `light_cull_kernel` from `light_cull.slang`.
121pub static LIGHT_CULL: SlangProgram = SlangProgram {
122    file: "light_cull.slang",
123    entry: "light_cull_kernel",
124    profile: "cs_6_0",
125    label: "light_cull.slang",
126    defines: &[],
127};
128/// `hiz_init_single` from `hiz_build.slang`.
129pub static HIZ_INIT_SINGLE: SlangProgram = SlangProgram {
130    file: "hiz_build.slang",
131    entry: "hiz_init_single",
132    profile: "cs_6_0",
133    label: "hiz_init_single.slang",
134    defines: &[("HIZ_INIT_SINGLE", "1")],
135};
136/// `hiz_init_msaa` from `hiz_build.slang`.
137pub static HIZ_INIT_MSAA: SlangProgram = SlangProgram {
138    file: "hiz_build.slang",
139    entry: "hiz_init_msaa",
140    profile: "cs_6_0",
141    label: "hiz_init_msaa.slang",
142    defines: &[("HIZ_INIT_MSAA", "1")],
143};
144/// `hiz_downsample` from `hiz_build.slang`.
145pub static HIZ_DOWNSAMPLE: SlangProgram = SlangProgram {
146    file: "hiz_build.slang",
147    entry: "hiz_downsample",
148    profile: "cs_6_0",
149    label: "hiz_downsample.slang",
150    defines: &[("HIZ_DOWNSAMPLE", "1")],
151};
152
153/// `probe_mip0` from `probe_prefilter.slang`.
154pub static PROBE_MIP0: SlangProgram = SlangProgram {
155    file: "probe_prefilter.slang",
156    entry: "probe_mip0",
157    profile: "cs_6_0",
158    label: "probe_mip0.slang",
159    defines: &[("PROBE_MIP0", "1")],
160};
161/// `probe_downsample` from `probe_prefilter.slang`.
162pub static PROBE_DOWNSAMPLE: SlangProgram = SlangProgram {
163    file: "probe_prefilter.slang",
164    entry: "probe_downsample",
165    profile: "cs_6_0",
166    label: "probe_downsample.slang",
167    defines: &[("PROBE_DOWNSAMPLE", "1")],
168};
169/// `probe_ggx` from `probe_prefilter.slang`.
170pub static PROBE_GGX: SlangProgram = SlangProgram {
171    file: "probe_prefilter.slang",
172    entry: "probe_ggx",
173    profile: "cs_6_0",
174    label: "probe_ggx.slang",
175    defines: &[("PROBE_GGX", "1")],
176};
177
178// The G-buffer pre-pass and shadow families. Every entry is its own program so
179// each variant declares exactly the resources its root signature binds; the
180// `DXIL_ABI` gate pins those registers to the signatures in `post/gbuffer.rs`,
181// `init/pipelines.rs` and `resources.rs`.
182const GB_BINDLESS: &[(&str, &str)] = &[("GB_BINDLESS", "1"), ("DXIL_ABI", "1")];
183const GB_FRAGMENT_BINDLESS: &[(&str, &str)] = &[("GB_FRAGMENT_BINDLESS", "1"), ("DXIL_ABI", "1")];
184const SHADOW_STATIC: &[(&str, &str)] = &[("SHADOW_STATIC", "1"), ("DXIL_ABI", "1")];
185const SHADOW_SKINNED: &[(&str, &str)] = &[("SHADOW_SKINNED", "1"), ("DXIL_ABI", "1")];
186const SHADOW_BINDLESS: &[(&str, &str)] = &[("SHADOW_BINDLESS", "1"), ("DXIL_ABI", "1")];
187
188/// `gbuffer_prepass_vertex_bindless` from `gbuffer_prepass.slang`.
189pub static GBUFFER_BINDLESS_VERT: SlangProgram = SlangProgram {
190    file: "gbuffer_prepass.slang",
191    entry: "gbuffer_prepass_vertex_bindless",
192    profile: "vs_6_0",
193    label: "gbuffer_prepass_vert_bindless.slang",
194    defines: GB_BINDLESS,
195};
196/// `gbuffer_prepass_fragment_bindless` from `gbuffer_prepass.slang`.
197pub static GBUFFER_BINDLESS_FRAG: SlangProgram = SlangProgram {
198    file: "gbuffer_prepass.slang",
199    entry: "gbuffer_prepass_fragment_bindless",
200    profile: "ps_6_0",
201    label: "gbuffer_prepass_frag_bindless.slang",
202    defines: GB_FRAGMENT_BINDLESS,
203};
204/// `shadow_vertex_main` from `shadow.slang`.
205pub static SHADOW_VERT: SlangProgram = SlangProgram {
206    file: "shadow.slang",
207    entry: "shadow_vertex_main",
208    profile: "vs_6_0",
209    label: "shadow_vert.slang",
210    defines: SHADOW_STATIC,
211};
212/// `shadow_vertex_main_skinned` from `shadow.slang`.
213pub static SKINNED_SHADOW_VERT: SlangProgram = SlangProgram {
214    file: "shadow.slang",
215    entry: "shadow_vertex_main_skinned",
216    profile: "vs_6_0",
217    label: "shadow_vert_skinned.slang",
218    defines: SHADOW_SKINNED,
219};
220/// `shadow_vertex_bindless` from `shadow.slang`.
221pub static SHADOW_BINDLESS_VERT: SlangProgram = SlangProgram {
222    file: "shadow.slang",
223    entry: "shadow_vertex_bindless",
224    profile: "vs_6_0",
225    label: "shadow_vert_bindless.slang",
226    defines: SHADOW_BINDLESS,
227};
228
229// The fog family, auto-exposure and the particle simulation kernel. None takes
230// an ABI block: slangc assigns b/t/u/s from declaration order here, which is
231// exactly what the root signatures in `fog.rs`, `auto_exposure.rs` and
232// `particle.rs` already bind. The fog fragment's vertex half is
233// `FULLSCREEN_VERT` below -- the last per-family fullscreen vertex to retire.
234//
235// Two DirectX-only gates ride these. `DXIL_SPLIT` declares the cascade array as
236// a texture plus a comparison sampler rather than the combined
237// `Sampler2DArray` the other two backends bind, because `SampleCmp` on a
238// combined type mis-lowers on DXIL -- the same split `main_bindless.slang`
239// carries for the same reason. `DXIL_STAGE_PACKING` makes the fog fragment
240// declare the fullscreen varying it never reads, because D3D links the two
241// stages by semantic *and* register and fog is the one post fragment that takes
242// no varying; see the declaration in `fog.slang`.
243const FOG_FROXEL_DEFINES: &[(&str, &str)] = &[("FOG_FROXEL", "1"), ("DXIL_SPLIT", "1")];
244
245/// `fog_froxel_kernel` from `fog.slang`.
246pub static FOG_FROXEL: SlangProgram = SlangProgram {
247    file: "fog.slang",
248    entry: "fog_froxel_kernel",
249    profile: "cs_6_0",
250    label: "fog_froxel.slang",
251    defines: FOG_FROXEL_DEFINES,
252};
253
254// The fog fragment declares its depth source by the main pass's sample count,
255// which is a host difference rather than a target one, so it takes two
256// programs the way the Hi-Z init pair does: the caller picks by MSAA state and
257// the export-time precompile leaves a bundle warm for either.
258/// `fog_fragment` from `fog.slang`.
259pub static FOG_FRAG: SlangProgram = SlangProgram {
260    file: "fog.slang",
261    entry: "fog_fragment",
262    profile: "ps_6_0",
263    label: "fog_frag.slang",
264    defines: &[("USE_MSAA", "0"), ("DXIL_STAGE_PACKING", "1")],
265};
266/// `fog_fragment` from `fog.slang`.
267pub static FOG_FRAG_MSAA: SlangProgram = SlangProgram {
268    file: "fog.slang",
269    entry: "fog_fragment",
270    profile: "ps_6_0",
271    label: "fog_frag_msaa.slang",
272    defines: &[("USE_MSAA", "1"), ("DXIL_STAGE_PACKING", "1")],
273};
274
275/// `histogram_build` from `auto_exposure.slang`.
276pub static AUTO_EXPOSURE_BUILD: SlangProgram = SlangProgram {
277    file: "auto_exposure.slang",
278    entry: "histogram_build",
279    profile: "cs_6_0",
280    label: "auto_exposure_build.slang",
281    defines: &[("AE_BUILD", "1")],
282};
283/// `histogram_average` from `auto_exposure.slang`.
284pub static AUTO_EXPOSURE_AVERAGE: SlangProgram = SlangProgram {
285    file: "auto_exposure.slang",
286    entry: "histogram_average",
287    profile: "cs_6_0",
288    label: "auto_exposure_average.slang",
289    defines: &[("AE_AVERAGE", "1")],
290};
291
292// The RT skinning kernel takes the same shader model as the RT reflection
293// resolve it feeds; its own body needs no SM 6.5 feature.
294/// `rt_skin` from `rt_skin.slang`.
295pub static RT_SKIN: SlangProgram = SlangProgram {
296    file: "rt_skin.slang",
297    entry: "rt_skin",
298    profile: "cs_6_5",
299    label: "rt_skin.slang",
300    defines: &[],
301};
302
303/// `particle_simulate` from `particle_simulate.slang`.
304pub static PARTICLE_SIMULATE: SlangProgram = SlangProgram {
305    file: "particle_simulate.slang",
306    entry: "particle_simulate",
307    profile: "cs_6_0",
308    label: "particle_simulate.slang",
309    defines: &[],
310};
311
312// The fullscreen-triangle vertex stage every post pass pairs with; one module
313// serves them all, retiring the four per-family `*_vert.hlsl` copies.
314/// `fullscreen_vertex` from `fullscreen.slang`.
315pub static FULLSCREEN_VERT: SlangProgram = SlangProgram {
316    file: "fullscreen.slang",
317    entry: "fullscreen_vertex",
318    profile: "vs_6_0",
319    label: "fullscreen_vert.slang",
320    defines: &[],
321};
322/// `taa_fragment_main` from `taa.slang`.
323pub static TAA_FRAG: SlangProgram = SlangProgram {
324    file: "taa.slang",
325    entry: "taa_fragment_main",
326    profile: "ps_6_0",
327    label: "taa_frag.slang",
328    defines: &[],
329};
330/// `bloom_prefilter_fragment` from `bloom.slang`.
331pub static BLOOM_PREFILTER: SlangProgram = SlangProgram {
332    file: "bloom.slang",
333    entry: "bloom_prefilter_fragment",
334    profile: "ps_6_0",
335    label: "bloom_prefilter.slang",
336    defines: &[("BLOOM_PREFILTER", "1")],
337};
338/// `bloom_downsample_fragment` from `bloom.slang`.
339pub static BLOOM_DOWNSAMPLE: SlangProgram = SlangProgram {
340    file: "bloom.slang",
341    entry: "bloom_downsample_fragment",
342    profile: "ps_6_0",
343    label: "bloom_downsample.slang",
344    defines: &[("BLOOM_DOWNSAMPLE", "1")],
345};
346/// `bloom_upsample_fragment` from `bloom.slang`.
347pub static BLOOM_UPSAMPLE: SlangProgram = SlangProgram {
348    file: "bloom.slang",
349    entry: "bloom_upsample_fragment",
350    profile: "ps_6_0",
351    label: "bloom_upsample.slang",
352    defines: &[("BLOOM_UPSAMPLE", "1")],
353};
354/// `composite_fragment` from `composite.slang`.
355pub static COMPOSITE_FRAG: SlangProgram = SlangProgram {
356    file: "composite.slang",
357    entry: "composite_fragment",
358    profile: "ps_6_0",
359    label: "composite_frag.slang",
360    defines: &[],
361};
362/// `ssao_kernel_fragment` from `ssao.slang`.
363pub static SSAO_KERNEL: SlangProgram = SlangProgram {
364    file: "ssao.slang",
365    entry: "ssao_kernel_fragment",
366    profile: "ps_6_0",
367    label: "ssao_kernel.slang",
368    defines: &[("SSAO_KERNEL", "1")],
369};
370/// `ssao_blur_fragment` from `ssao.slang`.
371pub static SSAO_BLUR: SlangProgram = SlangProgram {
372    file: "ssao.slang",
373    entry: "ssao_blur_fragment",
374    profile: "ps_6_0",
375    label: "ssao_blur.slang",
376    defines: &[("SSAO_BLUR", "1")],
377};
378/// `ssr_resolve_fragment` from `ssr.slang`.
379pub static SSR_RESOLVE: SlangProgram = SlangProgram {
380    file: "ssr.slang",
381    entry: "ssr_resolve_fragment",
382    profile: "ps_6_0",
383    label: "ssr_resolve.slang",
384    defines: SSR_DEFINES,
385};
386/// `ssgi_gather_fragment` from `ssgi.slang`.
387pub static SSGI_GATHER: SlangProgram = SlangProgram {
388    file: "ssgi.slang",
389    entry: "ssgi_gather_fragment",
390    profile: "ps_6_0",
391    label: "ssgi_gather.slang",
392    defines: &[("SSGI_GATHER", "1")],
393};
394/// `ssgi_composite_fragment` from `ssgi.slang`.
395pub static SSGI_COMPOSITE: SlangProgram = SlangProgram {
396    file: "ssgi.slang",
397    entry: "ssgi_composite_fragment",
398    profile: "ps_6_0",
399    label: "ssgi_composite.slang",
400    defines: &[("SSGI_COMPOSITE", "1")],
401};
402/// `reflection_blur_fragment` from `reflection.slang`.
403pub static REFLECTION_BLUR: SlangProgram = SlangProgram {
404    file: "reflection.slang",
405    entry: "reflection_blur_fragment",
406    profile: "ps_6_0",
407    label: "reflection_blur.slang",
408    defines: &[("REFLECTION_BLUR", "1")],
409};
410/// `reflection_composite_fragment` from `reflection.slang`.
411pub static REFLECTION_COMPOSITE: SlangProgram = SlangProgram {
412    file: "reflection.slang",
413    entry: "reflection_composite_fragment",
414    profile: "ps_6_0",
415    label: "reflection_composite.slang",
416    defines: &[("REFLECTION_COMPOSITE", "1")],
417};
418
419// The ray-traced reflection resolve and the glass family. Both take the
420// `DXIL_ABI` block for the reason the pre-pass family does: their root
421// signatures in `post/rt_reflections.rs` and `glass.rs` hand the same
422// declarations entirely different slots than the Metal and Vulkan hosts do, and
423// `assert_slang_dxil_abi` in build.rs locks every one of them. No `POOL_SIZE`:
424// the DXIL pool is an unbounded array, as it is for the bindless main pass.
425//
426// The three ray-query entries are `*_6_5`, above the 6.0 floor the rest of the
427// single-source shaders compile at: `RayQuery` is an SM 6.5 construct. Nothing
428// else here needs it, so the glass vertex and the base fragment stay at 6.0.
429//
430// `USE_MSAA` is a host difference rather than a target one -- the glass fragment
431// declares its depth source by the main pass's sample count -- so each glass
432// fragment comes as a pair the way the fog fragment does, and the caller picks
433// by MSAA state. The vertex stage reads no depth and takes neither.
434/// `rt_reflections_fragment` from `rt_reflections.slang`.
435pub static RT_REFLECTIONS_FRAG: SlangProgram = SlangProgram {
436    file: "rt_reflections.slang",
437    entry: "rt_reflections_fragment",
438    profile: "ps_6_5",
439    label: "rt_reflections.slang",
440    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8")],
441};
442/// `rt_reflections_fragment` from `rt_reflections.slang`.
443pub static RT_REFLECTIONS_FRAG_TEXTURED: SlangProgram = SlangProgram {
444    file: "rt_reflections.slang",
445    entry: "rt_reflections_fragment",
446    profile: "ps_6_5",
447    label: "rt_reflections_textured.slang",
448    defines: &[("DXIL_ABI", "1"), ("RT_TEXTURED", "1"), ("MAX_PROBES", "8")],
449};
450
451// One vertex stage for both glass pipelines: the base pass and the ray-traced
452// one differ only in where the reflection comes from, and both root signatures
453// put the transparent view CBV at b0.
454/// `glass_vertex` from `glass.slang`.
455pub static GLASS_VERT: SlangProgram = SlangProgram {
456    file: "glass.slang",
457    entry: "glass_vertex",
458    profile: "vs_6_0",
459    label: "glass_vert.slang",
460    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8")],
461};
462/// `glass_fragment` from `glass.slang`.
463pub static GLASS_FRAG: SlangProgram = SlangProgram {
464    file: "glass.slang",
465    entry: "glass_fragment",
466    profile: "ps_6_0",
467    label: "glass_frag.slang",
468    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "0")],
469};
470/// `glass_fragment` from `glass.slang`.
471pub static GLASS_FRAG_MSAA: SlangProgram = SlangProgram {
472    file: "glass.slang",
473    entry: "glass_fragment",
474    profile: "ps_6_0",
475    label: "glass_frag_msaa.slang",
476    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "1")],
477};
478/// `glass_rt_fragment` from `glass.slang`.
479pub static GLASS_RT_FRAG: SlangProgram = SlangProgram {
480    file: "glass.slang",
481    entry: "glass_rt_fragment",
482    profile: "ps_6_5",
483    label: "glass_frag_rt.slang",
484    defines: &[
485        ("DXIL_ABI", "1"),
486        ("GLASS_RT", "1"),
487        ("MAX_PROBES", "8"),
488        ("USE_MSAA", "0"),
489    ],
490};
491/// `glass_rt_fragment` from `glass.slang`.
492pub static GLASS_RT_FRAG_MSAA: SlangProgram = SlangProgram {
493    file: "glass.slang",
494    entry: "glass_rt_fragment",
495    profile: "ps_6_5",
496    label: "glass_frag_rt_msaa.slang",
497    defines: &[
498        ("DXIL_ABI", "1"),
499        ("GLASS_RT", "1"),
500        ("MAX_PROBES", "8"),
501        ("USE_MSAA", "1"),
502    ],
503};
504/// `glass_rt_fragment` from `glass.slang`.
505pub static GLASS_RT_FRAG_TEXTURED: SlangProgram = SlangProgram {
506    file: "glass.slang",
507    entry: "glass_rt_fragment",
508    profile: "ps_6_5",
509    label: "glass_frag_rt_textured.slang",
510    defines: &[
511        ("DXIL_ABI", "1"),
512        ("GLASS_RT", "1"),
513        ("RT_TEXTURED", "1"),
514        ("MAX_PROBES", "8"),
515        ("USE_MSAA", "0"),
516    ],
517};
518/// `glass_rt_fragment` from `glass.slang`.
519pub static GLASS_RT_FRAG_TEXTURED_MSAA: SlangProgram = SlangProgram {
520    file: "glass.slang",
521    entry: "glass_rt_fragment",
522    profile: "ps_6_5",
523    label: "glass_frag_rt_textured_msaa.slang",
524    defines: &[
525        ("DXIL_ABI", "1"),
526        ("GLASS_RT", "1"),
527        ("RT_TEXTURED", "1"),
528        ("MAX_PROBES", "8"),
529        ("USE_MSAA", "1"),
530    ],
531};
532
533const GLASS_MESH_DEFINES: &[(&str, &str)] =
534    &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "0")];
535const GLASS_MESH_MSAA_DEFINES: &[(&str, &str)] =
536    &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "1")];
537const GLASS_MESH_TEXTURED_DEFINES: &[(&str, &str)] = &[
538    ("DXIL_ABI", "1"),
539    ("RT_TEXTURED", "1"),
540    ("MAX_PROBES", "8"),
541    ("USE_MSAA", "0"),
542];
543const GLASS_MESH_TEXTURED_MSAA_DEFINES: &[(&str, &str)] = &[
544    ("DXIL_ABI", "1"),
545    ("RT_TEXTURED", "1"),
546    ("MAX_PROBES", "8"),
547    ("USE_MSAA", "1"),
548];
549
550// The see-through glass MESH family, the transparent pass's third producer.
551// Ray-traced only -- the per-pixel trace is what makes the mesh see-through
552// rather than the opaque reflective glass the main pass draws -- so there is no
553// base pair, only the MSAA x hit-shading matrix at SM 6.5. Its vertex stage is
554// its own (it applies the per-draw model matrix) but shares b0/b1 with the rest
555// of the pass.
556/// `glass_mesh_vertex` from `glass_mesh.slang`.
557pub static GLASS_MESH_VERT: SlangProgram = SlangProgram {
558    file: "glass_mesh.slang",
559    entry: "glass_mesh_vertex",
560    profile: "vs_6_0",
561    label: "glass_mesh_vert.slang",
562    defines: GLASS_MESH_DEFINES,
563};
564
565/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
566pub static GLASS_MESH_RT_FRAG: SlangProgram = SlangProgram {
567    file: "glass_mesh.slang",
568    entry: "glass_mesh_rt_fragment",
569    profile: "ps_6_5",
570    label: "glass_mesh_frag_rt.slang",
571    defines: GLASS_MESH_DEFINES,
572};
573
574/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
575pub static GLASS_MESH_RT_FRAG_MSAA: SlangProgram = SlangProgram {
576    file: "glass_mesh.slang",
577    entry: "glass_mesh_rt_fragment",
578    profile: "ps_6_5",
579    label: "glass_mesh_frag_rt_msaa.slang",
580    defines: GLASS_MESH_MSAA_DEFINES,
581};
582
583/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
584pub static GLASS_MESH_RT_FRAG_TEXTURED: SlangProgram = SlangProgram {
585    file: "glass_mesh.slang",
586    entry: "glass_mesh_rt_fragment",
587    profile: "ps_6_5",
588    label: "glass_mesh_frag_rt_textured.slang",
589    defines: GLASS_MESH_TEXTURED_DEFINES,
590};
591
592/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
593pub static GLASS_MESH_RT_FRAG_TEXTURED_MSAA: SlangProgram = SlangProgram {
594    file: "glass_mesh.slang",
595    entry: "glass_mesh_rt_fragment",
596    profile: "ps_6_5",
597    label: "glass_mesh_frag_rt_textured_msaa.slang",
598    defines: GLASS_MESH_TEXTURED_MSAA_DEFINES,
599};
600
601// The water surface family, the transparent pass's other producer. Same shape as
602// the glass table above -- one vertex stage for every pipeline, an MSAA pair of
603// base fragments, and the two ray-traced fragments at shader model 6.5 -- and
604// deliberately the same registers, so `transparent.rs` builds one root signature
605// per path and both producers draw under it.
606/// `water_vertex` from `water.slang`.
607pub static WATER_VERT: SlangProgram = SlangProgram {
608    file: "water.slang",
609    entry: "water_vertex",
610    profile: "vs_6_0",
611    label: "water_vert.slang",
612    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8")],
613};
614/// `water_fragment` from `water.slang`.
615pub static WATER_FRAG: SlangProgram = SlangProgram {
616    file: "water.slang",
617    entry: "water_fragment",
618    profile: "ps_6_0",
619    label: "water_frag.slang",
620    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "0")],
621};
622/// `water_fragment` from `water.slang`.
623pub static WATER_FRAG_MSAA: SlangProgram = SlangProgram {
624    file: "water.slang",
625    entry: "water_fragment",
626    profile: "ps_6_0",
627    label: "water_frag_msaa.slang",
628    defines: &[("DXIL_ABI", "1"), ("MAX_PROBES", "8"), ("USE_MSAA", "1")],
629};
630/// `water_rt_fragment` from `water.slang`.
631pub static WATER_RT_FRAG: SlangProgram = SlangProgram {
632    file: "water.slang",
633    entry: "water_rt_fragment",
634    profile: "ps_6_5",
635    label: "water_frag_rt.slang",
636    defines: &[
637        ("DXIL_ABI", "1"),
638        ("WATER_RT", "1"),
639        ("MAX_PROBES", "8"),
640        ("USE_MSAA", "0"),
641    ],
642};
643/// `water_rt_fragment` from `water.slang`.
644pub static WATER_RT_FRAG_MSAA: SlangProgram = SlangProgram {
645    file: "water.slang",
646    entry: "water_rt_fragment",
647    profile: "ps_6_5",
648    label: "water_frag_rt_msaa.slang",
649    defines: &[
650        ("DXIL_ABI", "1"),
651        ("WATER_RT", "1"),
652        ("MAX_PROBES", "8"),
653        ("USE_MSAA", "1"),
654    ],
655};
656/// `water_rt_fragment` from `water.slang`.
657pub static WATER_RT_FRAG_TEXTURED: SlangProgram = SlangProgram {
658    file: "water.slang",
659    entry: "water_rt_fragment",
660    profile: "ps_6_5",
661    label: "water_frag_rt_textured.slang",
662    defines: &[
663        ("DXIL_ABI", "1"),
664        ("WATER_RT", "1"),
665        ("RT_TEXTURED", "1"),
666        ("MAX_PROBES", "8"),
667        ("USE_MSAA", "0"),
668    ],
669};
670/// `water_rt_fragment` from `water.slang`.
671pub static WATER_RT_FRAG_TEXTURED_MSAA: SlangProgram = SlangProgram {
672    file: "water.slang",
673    entry: "water_rt_fragment",
674    profile: "ps_6_5",
675    label: "water_frag_rt_textured_msaa.slang",
676    defines: &[
677        ("DXIL_ABI", "1"),
678        ("WATER_RT", "1"),
679        ("RT_TEXTURED", "1"),
680        ("MAX_PROBES", "8"),
681        ("USE_MSAA", "1"),
682    ],
683};
684
685// The remaining raster families: the particle billboard pair, the projected
686// decal, world-space lines and the text / sprite overlay. Each has real vertex
687// geometry, so unlike the post passes they keep their own vertex entry rather
688// than pairing with `fullscreen.slang`.
689//
690// Only the particle pair takes an ABI block, and only to swap two constant
691// buffers: its root signature in `particle.rs` binds the view at b0 and the
692// per-emitter params at b1, where the Metal buffer indices are 1 and 2. Decal,
693// line and text need none -- declaration order already lands each resource on
694// the register its root signature declares, which the rows in build.rs's
695// `SLANG_DXIL_ENTRY_ABI` pin so a slangc release cannot move one silently.
696//
697// The two depth-reading fragments come as MSAA pairs the way the fog and glass
698// fragments do: the sample count is a host difference, so the caller picks by
699// MSAA state and the export-time precompile leaves a bundle warm for either.
700// Their vertex stages never name the depth source and take neither.
701const PARTICLE_ABI: &[(&str, &str)] = &[("DXIL_ABI", "1")];
702
703/// `particle_vertex` from `particle.slang`.
704pub static PARTICLE_VERT: SlangProgram = SlangProgram {
705    file: "particle.slang",
706    entry: "particle_vertex",
707    profile: "vs_6_0",
708    label: "particle_vert.slang",
709    defines: PARTICLE_ABI,
710};
711/// `particle_fragment` from `particle.slang`.
712pub static PARTICLE_FRAG: SlangProgram = SlangProgram {
713    file: "particle.slang",
714    entry: "particle_fragment",
715    profile: "ps_6_0",
716    label: "particle_frag.slang",
717    defines: PARTICLE_ABI,
718};
719/// `decal_vertex` from `decal.slang`.
720pub static DECAL_VERT: SlangProgram = SlangProgram {
721    file: "decal.slang",
722    entry: "decal_vertex",
723    profile: "vs_6_0",
724    label: "decal_vert.slang",
725    defines: &[],
726};
727/// `decal_fragment` from `decal.slang`.
728pub static DECAL_FRAG: SlangProgram = SlangProgram {
729    file: "decal.slang",
730    entry: "decal_fragment",
731    profile: "ps_6_0",
732    label: "decal_frag.slang",
733    defines: &[("USE_MSAA", "0")],
734};
735/// `decal_fragment` from `decal.slang`.
736pub static DECAL_FRAG_MSAA: SlangProgram = SlangProgram {
737    file: "decal.slang",
738    entry: "decal_fragment",
739    profile: "ps_6_0",
740    label: "decal_frag_msaa.slang",
741    defines: &[("USE_MSAA", "1")],
742};
743/// `line_vertex` from `line.slang`.
744pub static LINE_VERT: SlangProgram = SlangProgram {
745    file: "line.slang",
746    entry: "line_vertex",
747    profile: "vs_6_0",
748    label: "line_vert.slang",
749    defines: &[],
750};
751/// `line_fragment` from `line.slang`.
752pub static LINE_FRAG: SlangProgram = SlangProgram {
753    file: "line.slang",
754    entry: "line_fragment",
755    profile: "ps_6_0",
756    label: "line_frag.slang",
757    defines: &[("USE_MSAA", "0")],
758};
759/// `line_fragment` from `line.slang`.
760pub static LINE_FRAG_MSAA: SlangProgram = SlangProgram {
761    file: "line.slang",
762    entry: "line_fragment",
763    profile: "ps_6_0",
764    label: "line_frag_msaa.slang",
765    defines: &[("USE_MSAA", "1")],
766};
767/// `text_vertex_main` from `text.slang`.
768pub static TEXT_VERT: SlangProgram = SlangProgram {
769    file: "text.slang",
770    entry: "text_vertex_main",
771    profile: "vs_6_0",
772    label: "text_vert.slang",
773    defines: &[],
774};
775/// `text_fragment_main` from `text.slang`.
776pub static TEXT_FRAG: SlangProgram = SlangProgram {
777    file: "text.slang",
778    entry: "text_fragment_main",
779    profile: "ps_6_0",
780    label: "text_frag.slang",
781    defines: &[],
782};
783
784// Every declared program, iterated by the export-time precompile. Both Hi-Z
785// init variants are enumerated, and both MSAA halves of every fragment that has
786// them: which one a device runs depends on its MSAA mode, and a bundle should
787// be warm for either.
788/// Every declared program, which the renderer and the build script both
789/// iterate: one compiles them at init, the other ahead of time.
790pub static ALL: &[&SlangProgram] = &[
791    &MAIN_BINDLESS_VERT,
792    &MAIN_BINDLESS_FRAG,
793    &LIGHT_CULL,
794    &CULL,
795    &CULL_PHASE2,
796    &CULL_SHADOW,
797    &RT_SKIN,
798    &HIZ_INIT_SINGLE,
799    &HIZ_INIT_MSAA,
800    &HIZ_DOWNSAMPLE,
801    &PROBE_MIP0,
802    &PROBE_DOWNSAMPLE,
803    &PROBE_GGX,
804    &GBUFFER_BINDLESS_VERT,
805    &GBUFFER_BINDLESS_FRAG,
806    &SHADOW_VERT,
807    &SKINNED_SHADOW_VERT,
808    &SHADOW_BINDLESS_VERT,
809    &FOG_FROXEL,
810    &FOG_FRAG,
811    &FOG_FRAG_MSAA,
812    &AUTO_EXPOSURE_BUILD,
813    &AUTO_EXPOSURE_AVERAGE,
814    &PARTICLE_SIMULATE,
815    &FULLSCREEN_VERT,
816    &TAA_FRAG,
817    &BLOOM_PREFILTER,
818    &BLOOM_DOWNSAMPLE,
819    &BLOOM_UPSAMPLE,
820    &COMPOSITE_FRAG,
821    &SSAO_KERNEL,
822    &SSAO_BLUR,
823    &SSR_RESOLVE,
824    &SSGI_GATHER,
825    &SSGI_COMPOSITE,
826    &REFLECTION_BLUR,
827    &REFLECTION_COMPOSITE,
828    &RT_REFLECTIONS_FRAG,
829    &RT_REFLECTIONS_FRAG_TEXTURED,
830    &GLASS_VERT,
831    &GLASS_FRAG,
832    &GLASS_FRAG_MSAA,
833    &GLASS_RT_FRAG,
834    &GLASS_RT_FRAG_MSAA,
835    &GLASS_RT_FRAG_TEXTURED,
836    &GLASS_RT_FRAG_TEXTURED_MSAA,
837    &GLASS_MESH_VERT,
838    &GLASS_MESH_RT_FRAG,
839    &GLASS_MESH_RT_FRAG_MSAA,
840    &GLASS_MESH_RT_FRAG_TEXTURED,
841    &GLASS_MESH_RT_FRAG_TEXTURED_MSAA,
842    &WATER_VERT,
843    &WATER_FRAG,
844    &WATER_FRAG_MSAA,
845    &WATER_RT_FRAG,
846    &WATER_RT_FRAG_MSAA,
847    &WATER_RT_FRAG_TEXTURED,
848    &WATER_RT_FRAG_TEXTURED_MSAA,
849    &PARTICLE_VERT,
850    &PARTICLE_FRAG,
851    &DECAL_VERT,
852    &DECAL_FRAG,
853    &DECAL_FRAG_MSAA,
854    &LINE_VERT,
855    &LINE_FRAG,
856    &LINE_FRAG_MSAA,
857    &TEXT_VERT,
858    &TEXT_FRAG,
859];
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use alloc::vec::Vec;
865
866    // `label` is the key the build script files each precompiled DXIL artifact
867    // under and the renderer looks it up by, so two programs sharing one would
868    // silently hand one variant's bytes to the other. Nothing else is unique:
869    // `fog_fragment` at `ps_6_0` is both the MSAA and the non-MSAA program, and
870    // `glass_rt_fragment` at `ps_6_5` is four of them, separated only by their
871    // defines.
872    #[test]
873    fn every_program_has_a_distinct_label() {
874        let mut labels: Vec<&str> = ALL.iter().map(|p| p.label).collect();
875        let declared = labels.len();
876        labels.sort_unstable();
877        labels.dedup();
878        assert_eq!(
879            labels.len(),
880            declared,
881            "two programs share a label, so they would share a precompiled artifact"
882        );
883    }
884
885    // A program naming a file that is not embedded would assemble to nothing
886    // and fail at slangc rather than here.
887    #[test]
888    fn every_program_names_an_embedded_shader() {
889        for p in ALL {
890            assert!(
891                crate::render::shaders::embedded(p.file).is_some(),
892                "{}: no embedded {}",
893                p.label,
894                p.file
895            );
896        }
897    }
898
899    // The table is the compile set: a declared program left out of it is never
900    // precompiled and never reached.
901    #[test]
902    fn the_table_carries_every_declared_program() {
903        for p in ALL {
904            assert!(!p.entry.is_empty() && !p.profile.is_empty(), "{}", p.label);
905        }
906        assert!(
907            ALL.len() >= 60,
908            "the DirectX program set shrank unexpectedly"
909        );
910    }
911}