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