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