Skip to main content

concinnity_core/render/slang_programs/
vk.rs

1// Single-source engine shader programs for the Vulkan backend.
2//
3// Each program compiles a `.slang` file under `src/shaders/` (the
4// backend-neutral single-source directory) to SPIR-V at renderer init by
5// invoking slangc through `concinnity-slang`, cached in the content-addressed
6// shader cache exactly like the GLSL programs in
7// `builtins`. The runtime `POOL_SIZE` / `MAX_PROBES` values are injected as
8// `#define` lines into the source text, so the cache keys them the same way
9// the GLSL `{POOL_SIZE}` substitution always has.
10//
11// The `[[vk::binding]]` annotations (and ParameterBlock member order) in the
12// sources reproduce the engine's existing descriptor-set layouts, so these
13// programs are drop-in replacements for the GLSL they superseded. slangc
14// names every single-entry SPIR-V entry point `main`, so pipeline stage
15// creation is unchanged too.
16
17// Which runtime capacities a program bakes in as `#define`s. They ride the
18// source text rather than a command line so the shader cache keys them.
19#[derive(Clone, Copy, PartialEq, Eq)]
20/// Which runtime capacities a program bakes into its source.
21pub enum Sizes {
22    /// No size defines.
23    None,
24    /// The reflection-probe array length only: the SSR resolve binds the
25    /// forward global set's probe cubes but none of the texture pool.
26    Probes,
27    /// The bindless texture-pool capacity and the probe array length.
28    PoolAndProbes,
29}
30
31/// One SPIR-V program: which shader file, which entry point, under which
32/// variant gates and baked capacities.
33pub struct SlangProgram {
34    /// File name under `src/shaders/` for the `cn debug` disk-first resolve;
35    /// also the embedded fallback's origin.
36    pub file: &'static str,
37    /// Entry point compiled out of that file.
38    pub entry: &'static str,
39    /// Diagnostic label (compile errors + cache miss logs + export report).
40    pub label: &'static str,
41    /// Fixed variant gates (e.g. HIZ_INIT_MSAA), each injected as
42    /// `#define <gate> 1`. More than one where a variant is the intersection of
43    /// two, like the textured ray-traced glass fragment.
44    pub gates: &'static [&'static str],
45    /// Runtime capacities injected from the context.
46    pub sizes: Sizes,
47    /// Inject `#define USE_MSAA {0|1}` from `Ctx::msaa`. A HOST difference
48    /// rather than a target one: only the fog fragment declares its depth source
49    /// by the main pass's sample count.
50    pub msaa: bool,
51}
52
53/// `vertex_main_bindless` from `main_bindless.slang`.
54pub static MAIN_BINDLESS_VERT: SlangProgram = SlangProgram {
55    file: "main_bindless.slang",
56    entry: "vertex_main_bindless",
57    label: "vert_bindless.slang",
58    gates: &[],
59    sizes: Sizes::PoolAndProbes,
60    msaa: false,
61};
62/// `fragment_main_bindless` from `main_bindless.slang`.
63pub static MAIN_BINDLESS_FRAG: SlangProgram = SlangProgram {
64    file: "main_bindless.slang",
65    entry: "fragment_main_bindless",
66    label: "frag_bindless.slang",
67    gates: &[],
68    sizes: Sizes::PoolAndProbes,
69    msaa: false,
70};
71/// `light_cull_kernel` from `light_cull.slang`.
72pub static LIGHT_CULL: SlangProgram = SlangProgram {
73    file: "light_cull.slang",
74    entry: "light_cull_kernel",
75    label: "light_cull.slang",
76    gates: &[],
77    sizes: Sizes::None,
78    msaa: false,
79};
80/// `hiz_init_msaa` from `hiz_build.slang`.
81pub static HIZ_INIT_MSAA: SlangProgram = SlangProgram {
82    file: "hiz_build.slang",
83    entry: "hiz_init_msaa",
84    label: "hiz_init_msaa.slang",
85    gates: &["HIZ_INIT_MSAA"],
86    sizes: Sizes::None,
87    msaa: false,
88};
89/// `hiz_init_single` from `hiz_build.slang`.
90pub static HIZ_INIT_SINGLE: SlangProgram = SlangProgram {
91    file: "hiz_build.slang",
92    entry: "hiz_init_single",
93    label: "hiz_init_single.slang",
94    gates: &["HIZ_INIT_SINGLE"],
95    sizes: Sizes::None,
96    msaa: false,
97};
98/// `hiz_downsample` from `hiz_build.slang`.
99pub static HIZ_DOWNSAMPLE: SlangProgram = SlangProgram {
100    file: "hiz_build.slang",
101    entry: "hiz_downsample",
102    label: "hiz_downsample.slang",
103    gates: &["HIZ_DOWNSAMPLE"],
104    sizes: Sizes::None,
105    msaa: false,
106};
107
108// The G-buffer pre-pass and shadow families. Every entry is its own program so
109// it declares only the resources it binds; the `[[vk::binding]]` annotations
110// reproduce the descriptor sets the GLSL declared, so the SPIR-V is a drop-in
111// against the untouched pipeline layouts.
112/// `gbuffer_prepass_vertex` from `gbuffer_prepass.slang`.
113pub static GBUFFER_PREPASS_VERT: SlangProgram = SlangProgram {
114    file: "gbuffer_prepass.slang",
115    entry: "gbuffer_prepass_vertex",
116    label: "gbuffer_prepass_vert.slang",
117    gates: &["GB_STATIC"],
118    sizes: Sizes::None,
119    msaa: false,
120};
121/// `gbuffer_prepass_vertex_instanced` from `gbuffer_prepass.slang`.
122pub static GBUFFER_PREPASS_VERT_INSTANCED: SlangProgram = SlangProgram {
123    file: "gbuffer_prepass.slang",
124    entry: "gbuffer_prepass_vertex_instanced",
125    label: "gbuffer_prepass_vert_instanced.slang",
126    gates: &["GB_INSTANCED"],
127    sizes: Sizes::None,
128    msaa: false,
129};
130/// `gbuffer_prepass_vertex_skinned` from `gbuffer_prepass.slang`.
131pub static GBUFFER_PREPASS_VERT_SKINNED: SlangProgram = SlangProgram {
132    file: "gbuffer_prepass.slang",
133    entry: "gbuffer_prepass_vertex_skinned",
134    label: "gbuffer_prepass_vert_skinned.slang",
135    gates: &["GB_SKINNED"],
136    sizes: Sizes::None,
137    msaa: false,
138};
139/// `gbuffer_prepass_vertex_bindless` from `gbuffer_prepass.slang`.
140pub static GBUFFER_BINDLESS_VERT: SlangProgram = SlangProgram {
141    file: "gbuffer_prepass.slang",
142    entry: "gbuffer_prepass_vertex_bindless",
143    label: "gbuffer_prepass_vert_bindless.slang",
144    gates: &["GB_BINDLESS"],
145    sizes: Sizes::None,
146    msaa: false,
147};
148/// `gbuffer_prepass_fragment` from `gbuffer_prepass.slang`.
149pub static GBUFFER_PREPASS_FRAG: SlangProgram = SlangProgram {
150    file: "gbuffer_prepass.slang",
151    entry: "gbuffer_prepass_fragment",
152    label: "gbuffer_prepass_frag.slang",
153    gates: &["GB_FRAGMENT"],
154    sizes: Sizes::None,
155    msaa: false,
156};
157/// `gbuffer_prepass_fragment_bindless` from `gbuffer_prepass.slang`.
158pub static GBUFFER_BINDLESS_FRAG: SlangProgram = SlangProgram {
159    file: "gbuffer_prepass.slang",
160    entry: "gbuffer_prepass_fragment_bindless",
161    label: "gbuffer_prepass_frag_bindless.slang",
162    gates: &["GB_FRAGMENT_BINDLESS"],
163    sizes: Sizes::None,
164    msaa: false,
165};
166/// `shadow_vertex_main` from `shadow.slang`.
167pub static SHADOW_VERT: SlangProgram = SlangProgram {
168    file: "shadow.slang",
169    entry: "shadow_vertex_main",
170    label: "shadow_vert.slang",
171    gates: &["SHADOW_STATIC"],
172    sizes: Sizes::None,
173    msaa: false,
174};
175/// `shadow_vertex_main_skinned` from `shadow.slang`.
176pub static SKINNED_SHADOW_VERT: SlangProgram = SlangProgram {
177    file: "shadow.slang",
178    entry: "shadow_vertex_main_skinned",
179    label: "shadow_vert_skinned.slang",
180    gates: &["SHADOW_SKINNED"],
181    sizes: Sizes::None,
182    msaa: false,
183};
184/// `shadow_vertex_bindless` from `shadow.slang`.
185pub static SHADOW_BINDLESS_VERT: SlangProgram = SlangProgram {
186    file: "shadow.slang",
187    entry: "shadow_vertex_bindless",
188    label: "shadow_vert_bindless.slang",
189    gates: &["SHADOW_BINDLESS"],
190    sizes: Sizes::None,
191    msaa: false,
192};
193
194// The fullscreen-triangle vertex stage every ported post pass pairs with; one
195// module serves them all, the way `composite.vert` served the GLSL ones.
196/// `fullscreen_vertex` from `fullscreen.slang`.
197pub static FULLSCREEN_VERT: SlangProgram = SlangProgram {
198    file: "fullscreen.slang",
199    entry: "fullscreen_vertex",
200    label: "fullscreen_vert.slang",
201    gates: &[],
202    sizes: Sizes::None,
203    msaa: false,
204};
205/// `taa_fragment_main` from `taa.slang`.
206pub static TAA_FRAG: SlangProgram = SlangProgram {
207    file: "taa.slang",
208    entry: "taa_fragment_main",
209    label: "taa_frag.slang",
210    gates: &[],
211    sizes: Sizes::None,
212    msaa: false,
213};
214/// `bloom_prefilter_fragment` from `bloom.slang`.
215pub static BLOOM_PREFILTER: SlangProgram = SlangProgram {
216    file: "bloom.slang",
217    entry: "bloom_prefilter_fragment",
218    label: "bloom_prefilter.slang",
219    gates: &["BLOOM_PREFILTER"],
220    sizes: Sizes::None,
221    msaa: false,
222};
223/// `bloom_downsample_fragment` from `bloom.slang`.
224pub static BLOOM_DOWNSAMPLE: SlangProgram = SlangProgram {
225    file: "bloom.slang",
226    entry: "bloom_downsample_fragment",
227    label: "bloom_downsample.slang",
228    gates: &["BLOOM_DOWNSAMPLE"],
229    sizes: Sizes::None,
230    msaa: false,
231};
232/// `bloom_upsample_fragment` from `bloom.slang`.
233pub static BLOOM_UPSAMPLE: SlangProgram = SlangProgram {
234    file: "bloom.slang",
235    entry: "bloom_upsample_fragment",
236    label: "bloom_upsample.slang",
237    gates: &["BLOOM_UPSAMPLE"],
238    sizes: Sizes::None,
239    msaa: false,
240};
241/// `composite_fragment` from `composite.slang`.
242pub static COMPOSITE_FRAG: SlangProgram = SlangProgram {
243    file: "composite.slang",
244    entry: "composite_fragment",
245    label: "composite_frag.slang",
246    gates: &[],
247    sizes: Sizes::None,
248    msaa: false,
249};
250/// `ssao_kernel_fragment` from `ssao.slang`.
251pub static SSAO_KERNEL: SlangProgram = SlangProgram {
252    file: "ssao.slang",
253    entry: "ssao_kernel_fragment",
254    label: "ssao_kernel.slang",
255    gates: &["SSAO_KERNEL"],
256    sizes: Sizes::None,
257    msaa: false,
258};
259/// `ssao_blur_fragment` from `ssao.slang`.
260pub static SSAO_BLUR: SlangProgram = SlangProgram {
261    file: "ssao.slang",
262    entry: "ssao_blur_fragment",
263    label: "ssao_blur.slang",
264    gates: &["SSAO_BLUR"],
265    sizes: Sizes::None,
266    msaa: false,
267};
268/// `ssr_resolve_fragment` from `ssr.slang`.
269pub static SSR_RESOLVE: SlangProgram = SlangProgram {
270    file: "ssr.slang",
271    entry: "ssr_resolve_fragment",
272    label: "ssr_resolve.slang",
273    gates: &[],
274    sizes: Sizes::Probes,
275    msaa: false,
276};
277/// `ssgi_gather_fragment` from `ssgi.slang`.
278pub static SSGI_GATHER: SlangProgram = SlangProgram {
279    file: "ssgi.slang",
280    entry: "ssgi_gather_fragment",
281    label: "ssgi_gather.slang",
282    gates: &["SSGI_GATHER"],
283    sizes: Sizes::None,
284    msaa: false,
285};
286/// `ssgi_composite_fragment` from `ssgi.slang`.
287pub static SSGI_COMPOSITE: SlangProgram = SlangProgram {
288    file: "ssgi.slang",
289    entry: "ssgi_composite_fragment",
290    label: "ssgi_composite.slang",
291    gates: &["SSGI_COMPOSITE"],
292    sizes: Sizes::None,
293    msaa: false,
294};
295/// `reflection_blur_fragment` from `reflection.slang`.
296pub static REFLECTION_BLUR: SlangProgram = SlangProgram {
297    file: "reflection.slang",
298    entry: "reflection_blur_fragment",
299    label: "reflection_blur.slang",
300    gates: &["REFLECTION_BLUR"],
301    sizes: Sizes::None,
302    msaa: false,
303};
304/// `reflection_composite_fragment` from `reflection.slang`.
305pub static REFLECTION_COMPOSITE: SlangProgram = SlangProgram {
306    file: "reflection.slang",
307    entry: "reflection_composite_fragment",
308    label: "reflection_composite.slang",
309    gates: &["REFLECTION_COMPOSITE"],
310    sizes: Sizes::None,
311    msaa: false,
312};
313
314// The compute kernels and the fog family. The fog fragment is the only program
315// whose assembly depends on the host's MSAA mode.
316/// `fog_froxel_kernel` from `fog.slang`.
317pub static FOG_FROXEL: SlangProgram = SlangProgram {
318    file: "fog.slang",
319    entry: "fog_froxel_kernel",
320    label: "fog_froxel.slang",
321    gates: &["FOG_FROXEL"],
322    sizes: Sizes::None,
323    msaa: false,
324};
325/// `fog_fragment` from `fog.slang`.
326pub static FOG_FRAG: SlangProgram = SlangProgram {
327    file: "fog.slang",
328    entry: "fog_fragment",
329    label: "fog_frag.slang",
330    gates: &[],
331    sizes: Sizes::None,
332    msaa: true,
333};
334/// `histogram_build` from `auto_exposure.slang`.
335pub static AUTO_EXPOSURE_BUILD: SlangProgram = SlangProgram {
336    file: "auto_exposure.slang",
337    entry: "histogram_build",
338    label: "auto_exposure_build.slang",
339    gates: &["AE_BUILD"],
340    sizes: Sizes::None,
341    msaa: false,
342};
343/// `histogram_average` from `auto_exposure.slang`.
344pub static AUTO_EXPOSURE_AVERAGE: SlangProgram = SlangProgram {
345    file: "auto_exposure.slang",
346    entry: "histogram_average",
347    label: "auto_exposure_average.slang",
348    gates: &["AE_AVERAGE"],
349    sizes: Sizes::None,
350    msaa: false,
351};
352/// `rt_skin` from `rt_skin.slang`.
353pub static RT_SKIN: SlangProgram = SlangProgram {
354    file: "rt_skin.slang",
355    entry: "rt_skin",
356    label: "rt_skin.slang",
357    gates: &[],
358    sizes: Sizes::None,
359    msaa: false,
360};
361/// `particle_simulate` from `particle_simulate.slang`.
362pub static PARTICLE_SIMULATE: SlangProgram = SlangProgram {
363    file: "particle_simulate.slang",
364    entry: "particle_simulate",
365    label: "particle_simulate.slang",
366    gates: &[],
367    sizes: Sizes::None,
368    msaa: false,
369};
370
371// The remaining raster families: the particle billboard pair, the projected
372// decal, world-space lines and the text / sprite overlay. Each has real vertex
373// geometry, so unlike the post passes they keep their own vertex entry rather
374// than pairing with `fullscreen.slang`. Only the two depth-reading fragments
375// take the host's sample count; their vertex stages never name the depth source.
376/// `particle_vertex` from `particle.slang`.
377pub static PARTICLE_VERT: SlangProgram = SlangProgram {
378    file: "particle.slang",
379    entry: "particle_vertex",
380    label: "particle_vert.slang",
381    gates: &[],
382    sizes: Sizes::None,
383    msaa: false,
384};
385/// `particle_fragment` from `particle.slang`.
386pub static PARTICLE_FRAG: SlangProgram = SlangProgram {
387    file: "particle.slang",
388    entry: "particle_fragment",
389    label: "particle_frag.slang",
390    gates: &[],
391    sizes: Sizes::None,
392    msaa: false,
393};
394/// `decal_vertex` from `decal.slang`.
395pub static DECAL_VERT: SlangProgram = SlangProgram {
396    file: "decal.slang",
397    entry: "decal_vertex",
398    label: "decal_vert.slang",
399    gates: &[],
400    sizes: Sizes::None,
401    msaa: false,
402};
403/// `decal_fragment` from `decal.slang`.
404pub static DECAL_FRAG: SlangProgram = SlangProgram {
405    file: "decal.slang",
406    entry: "decal_fragment",
407    label: "decal_frag.slang",
408    gates: &[],
409    sizes: Sizes::None,
410    msaa: true,
411};
412/// `line_vertex` from `line.slang`.
413pub static LINE_VERT: SlangProgram = SlangProgram {
414    file: "line.slang",
415    entry: "line_vertex",
416    label: "line_vert.slang",
417    gates: &[],
418    sizes: Sizes::None,
419    msaa: false,
420};
421/// `line_fragment` from `line.slang`.
422pub static LINE_FRAG: SlangProgram = SlangProgram {
423    file: "line.slang",
424    entry: "line_fragment",
425    label: "line_frag.slang",
426    gates: &[],
427    sizes: Sizes::None,
428    msaa: true,
429};
430/// `text_vertex_main` from `text.slang`.
431pub static TEXT_VERT: SlangProgram = SlangProgram {
432    file: "text.slang",
433    entry: "text_vertex_main",
434    label: "text_vert.slang",
435    gates: &[],
436    sizes: Sizes::None,
437    msaa: false,
438};
439/// `text_fragment_main` from `text.slang`.
440pub static TEXT_FRAG: SlangProgram = SlangProgram {
441    file: "text.slang",
442    entry: "text_fragment_main",
443    label: "text_frag.slang",
444    gates: &[],
445    sizes: Sizes::None,
446    msaa: false,
447};
448
449// Every declared program, iterated by the export-time precompile. Both Hi-Z
450// init variants are enumerated: which one a device runs depends on its MSAA
451// The ray-traced families, compiled only where `VK_KHR_ray_query` is present:
452// slangc emits `SPV_KHR_ray_query` (SPIR-V 1.5), so these need the Vulkan 1.2
453// device the extension already implies. The flat variants declare no bindless
454// pool at all, which is what keeps a non-bindless world from binding one.
455/// `rt_reflections_fragment` from `rt_reflections.slang`.
456pub static RT_REFLECTIONS_FRAG: SlangProgram = SlangProgram {
457    file: "rt_reflections.slang",
458    entry: "rt_reflections_fragment",
459    label: "rt_reflections.slang",
460    gates: &[],
461    sizes: Sizes::Probes,
462    msaa: false,
463};
464/// `rt_reflections_fragment` from `rt_reflections.slang`.
465pub static RT_REFLECTIONS_FRAG_TEXTURED: SlangProgram = SlangProgram {
466    file: "rt_reflections.slang",
467    entry: "rt_reflections_fragment",
468    label: "rt_reflections_textured.slang",
469    gates: &["RT_TEXTURED"],
470    sizes: Sizes::PoolAndProbes,
471    msaa: false,
472};
473/// `glass_vertex` from `glass.slang`.
474pub static GLASS_VERT: SlangProgram = SlangProgram {
475    file: "glass.slang",
476    entry: "glass_vertex",
477    label: "glass_vert.slang",
478    gates: &[],
479    sizes: Sizes::Probes,
480    msaa: true,
481};
482/// `glass_fragment` from `glass.slang`.
483pub static GLASS_FRAG: SlangProgram = SlangProgram {
484    file: "glass.slang",
485    entry: "glass_fragment",
486    label: "glass_frag.slang",
487    gates: &[],
488    sizes: Sizes::Probes,
489    msaa: true,
490};
491/// `glass_rt_fragment` from `glass.slang`.
492pub static GLASS_FRAG_RT: SlangProgram = SlangProgram {
493    file: "glass.slang",
494    entry: "glass_rt_fragment",
495    label: "glass_frag_rt.slang",
496    gates: &["GLASS_RT"],
497    sizes: Sizes::Probes,
498    msaa: true,
499};
500/// `glass_rt_fragment` from `glass.slang`.
501pub static GLASS_FRAG_RT_TEXTURED: SlangProgram = SlangProgram {
502    file: "glass.slang",
503    entry: "glass_rt_fragment",
504    label: "glass_frag_rt_textured.slang",
505    gates: &["GLASS_RT", "RT_TEXTURED"],
506    sizes: Sizes::PoolAndProbes,
507    msaa: true,
508};
509
510// The see-through glass MESH family, the transparent pass's third producer.
511// Ray-traced only -- the per-pixel trace is what makes the mesh see-through
512// rather than the opaque reflective glass the main pass draws -- so there is no
513// base pair. Its vertex stage is its own (it applies the per-draw model matrix)
514// but shares every descriptor binding with the rest of the pass.
515/// `glass_mesh_vertex` from `glass_mesh.slang`.
516pub static GLASS_MESH_VERT: SlangProgram = SlangProgram {
517    file: "glass_mesh.slang",
518    entry: "glass_mesh_vertex",
519    label: "glass_mesh_vert.slang",
520    gates: &[],
521    sizes: Sizes::Probes,
522    msaa: true,
523};
524/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
525pub static GLASS_MESH_FRAG_RT: SlangProgram = SlangProgram {
526    file: "glass_mesh.slang",
527    entry: "glass_mesh_rt_fragment",
528    label: "glass_mesh_frag_rt.slang",
529    gates: &[],
530    sizes: Sizes::Probes,
531    msaa: true,
532};
533/// `glass_mesh_rt_fragment` from `glass_mesh.slang`.
534pub static GLASS_MESH_FRAG_RT_TEXTURED: SlangProgram = SlangProgram {
535    file: "glass_mesh.slang",
536    entry: "glass_mesh_rt_fragment",
537    label: "glass_mesh_frag_rt_textured.slang",
538    gates: &["RT_TEXTURED"],
539    sizes: Sizes::PoolAndProbes,
540    msaa: true,
541};
542
543// The water surface family, the transparent pass's other producer. Same shape as
544// the glass table above, and deliberately the same descriptor bindings, so
545// `transparent.rs` builds one set of set layouts and both producers draw under
546// them.
547/// `water_vertex` from `water.slang`.
548pub static WATER_VERT: SlangProgram = SlangProgram {
549    file: "water.slang",
550    entry: "water_vertex",
551    label: "water_vert.slang",
552    gates: &[],
553    sizes: Sizes::Probes,
554    msaa: true,
555};
556/// `water_fragment` from `water.slang`.
557pub static WATER_FRAG: SlangProgram = SlangProgram {
558    file: "water.slang",
559    entry: "water_fragment",
560    label: "water_frag.slang",
561    gates: &[],
562    sizes: Sizes::Probes,
563    msaa: true,
564};
565/// `water_rt_fragment` from `water.slang`.
566pub static WATER_FRAG_RT: SlangProgram = SlangProgram {
567    file: "water.slang",
568    entry: "water_rt_fragment",
569    label: "water_frag_rt.slang",
570    gates: &["WATER_RT"],
571    sizes: Sizes::Probes,
572    msaa: true,
573};
574/// `water_rt_fragment` from `water.slang`.
575pub static WATER_FRAG_RT_TEXTURED: SlangProgram = SlangProgram {
576    file: "water.slang",
577    entry: "water_rt_fragment",
578    label: "water_frag_rt_textured.slang",
579    gates: &["WATER_RT", "RT_TEXTURED"],
580    sizes: Sizes::PoolAndProbes,
581    msaa: true,
582};
583
584// mode, and a bundle should be warm for either.
585/// Every declared program, which the renderer and the build script both
586/// iterate: one compiles them at init, the other ahead of time.
587pub static ALL: &[&SlangProgram] = &[
588    &MAIN_BINDLESS_VERT,
589    &MAIN_BINDLESS_FRAG,
590    &LIGHT_CULL,
591    &RT_SKIN,
592    &HIZ_INIT_MSAA,
593    &HIZ_INIT_SINGLE,
594    &HIZ_DOWNSAMPLE,
595    &GBUFFER_PREPASS_VERT,
596    &GBUFFER_PREPASS_VERT_INSTANCED,
597    &GBUFFER_PREPASS_VERT_SKINNED,
598    &GBUFFER_BINDLESS_VERT,
599    &GBUFFER_PREPASS_FRAG,
600    &GBUFFER_BINDLESS_FRAG,
601    &SHADOW_VERT,
602    &SKINNED_SHADOW_VERT,
603    &SHADOW_BINDLESS_VERT,
604    &FULLSCREEN_VERT,
605    &TAA_FRAG,
606    &BLOOM_PREFILTER,
607    &BLOOM_DOWNSAMPLE,
608    &BLOOM_UPSAMPLE,
609    &COMPOSITE_FRAG,
610    &SSAO_KERNEL,
611    &SSAO_BLUR,
612    &SSR_RESOLVE,
613    &SSGI_GATHER,
614    &SSGI_COMPOSITE,
615    &REFLECTION_BLUR,
616    &REFLECTION_COMPOSITE,
617    &FOG_FROXEL,
618    &FOG_FRAG,
619    &AUTO_EXPOSURE_BUILD,
620    &AUTO_EXPOSURE_AVERAGE,
621    &PARTICLE_SIMULATE,
622    &PARTICLE_VERT,
623    &PARTICLE_FRAG,
624    &DECAL_VERT,
625    &DECAL_FRAG,
626    &LINE_VERT,
627    &LINE_FRAG,
628    &TEXT_VERT,
629    &TEXT_FRAG,
630    &RT_REFLECTIONS_FRAG,
631    &RT_REFLECTIONS_FRAG_TEXTURED,
632    &GLASS_VERT,
633    &GLASS_FRAG,
634    &GLASS_FRAG_RT,
635    &GLASS_FRAG_RT_TEXTURED,
636    &GLASS_MESH_VERT,
637    &GLASS_MESH_FRAG_RT,
638    &GLASS_MESH_FRAG_RT_TEXTURED,
639    &WATER_VERT,
640    &WATER_FRAG,
641    &WATER_FRAG_RT,
642    &WATER_FRAG_RT_TEXTURED,
643];
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648    use alloc::vec::Vec;
649
650    // `label` keys each precompiled SPIR-V artifact, paired with the sample
651    // count for the programs that read it, so two programs sharing a label
652    // would hand one's bytes to the other.
653    #[test]
654    fn every_program_has_a_distinct_label() {
655        let mut labels: Vec<&str> = ALL.iter().map(|p| p.label).collect();
656        let declared = labels.len();
657        labels.sort_unstable();
658        labels.dedup();
659        assert_eq!(
660            labels.len(),
661            declared,
662            "two programs share a label, so they would share a precompiled artifact"
663        );
664    }
665
666    #[test]
667    fn every_program_names_an_embedded_shader() {
668        for p in ALL {
669            assert!(
670                crate::render::shaders::embedded(p.file).is_some(),
671                "{}: no embedded {}",
672                p.label,
673                p.file
674            );
675        }
676    }
677
678    // Only the sample count varies per host among the enumerable dimensions;
679    // the sizes are baked at their ceilings. A program that gained a dimension
680    // the build script does not enumerate would leave that variant uncovered.
681    #[test]
682    fn the_only_per_host_variant_is_the_sample_count() {
683        let sampled = ALL.iter().filter(|p| p.msaa).count();
684        assert!(sampled > 0, "the MSAA dimension went missing");
685        assert!(sampled < ALL.len(), "every program cannot read the depth");
686        for p in ALL {
687            assert!(!p.entry.is_empty(), "{}", p.label);
688        }
689    }
690}