Skip to main content

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