concinnity-device 0.19.16

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
// src/shader_layout/programs.rs
//
// The single-source programs the layout check reflects, and the per-target
// invocation that reads their layouts back.
//
// One program per family is enough: a struct's declaration is shared by every
// entry in its file, so the smallest entry that declares it reports the same
// bytes the heaviest one does. Where two files declare the same struct name
// (`ShadowUniforms` is in both `main_bindless.slang` and `fog.slang`) both are
// listed, because they are separate declarations that can drift apart.
//
// The defines mirror the backends' own program tables
// (`{vulkan,directx}/slang_builtins.rs`, `metal/slang_shaders.rs`): a variant
// compiles only with its gate, and each backend adds its own host-shape gate on
// top -- `METAL_ABI` or `METAL_BINDINGS` where the Metal slots are pinned,
// `DXIL_ABI` where the root signature is. Reflecting a family without its gate
// would read a declaration no backend compiles.

use std::collections::BTreeMap;

use concinnity_slang as slang;

use crate::shader_layout::reflect::{self, ShaderStruct};
use crate::slang_source;

type Defines = &'static [(&'static str, &'static str)];

// Which backend's layout rules slangc applies. The split is the point: MSL
// sizes a `float3` at 16 bytes where SPIR-V and DXIL pack a scalar after it,
// so a mirror has to be checked against each.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum Target {
    Metal,
    Vulkan,
    DirectX,
}

impl Target {
    pub(super) const ALL: [Target; 3] = [Target::Metal, Target::Vulkan, Target::DirectX];

    pub(super) fn label(self) -> &'static str {
        match self {
            Target::Metal => "metal",
            Target::Vulkan => "vulkan",
            Target::DirectX => "directx",
        }
    }

    // Reflection reads the same layout from the text targets as from the binary
    // ones (`hlsl` and `dxil` emit byte-identical reflection), and neither needs
    // a platform toolchain: `metallib` wants Xcode, `dxil` wants dxcompiler.
    fn slang_target(self, profile: &'static str) -> slang::SlangTarget {
        match self {
            Target::Metal => slang::SlangTarget::Metal,
            Target::Vulkan => slang::SlangTarget::Spirv,
            Target::DirectX => slang::SlangTarget::Hlsl(profile),
        }
    }
}

// One entry point to reflect.
pub(super) struct Program {
    pub file: &'static str,
    pub entry: &'static str,
    // Shader-model profile for the DirectX leg, from `directx/slang_builtins.rs`.
    pub profile: &'static str,
    // Variant gates and capacities every backend injects for this entry.
    pub common: Defines,
    // What each backend's own program table adds on top.
    pub metal: Defines,
    pub vulkan: Defines,
    pub directx: Defines,
    // Text spliced in at a marker no file in the shader tree can fill. Only the
    // raymarched volumes need one: their source is completed by a world's own
    // distance field, so reflecting them means supplying a stand-in for it.
    pub splices: &'static [(&'static str, &'static str)],
}

impl Program {
    // The exact text the renderer compiles for this variant on `target`.
    fn source(&self, target: Target) -> String {
        let backend = match target {
            Target::Metal => self.metal,
            Target::Vulkan => self.vulkan,
            Target::DirectX => self.directx,
        };
        let defines: Vec<(&str, &str)> = self.common.iter().chain(backend).copied().collect();
        slang_source::assemble(false, self.file, &defines, self.splices)
    }
}

// slangc's reflection of `program` under `target`'s layout rules.
pub(super) fn reflection(program: &Program, target: Target) -> Result<String, String> {
    let source = program.source(target);
    let job = slang::SlangJob {
        source: &source,
        file_name: program.file,
        entries: &[program.entry],
        target: target.slang_target(program.profile),
    };
    let work = crate::compiler_work::dir()?;
    slang::reflect(&job, work.path())
        .map_err(|e| format!("{} ({}): {e}", program.entry, target.label()))
}

// Every struct `program` declares, laid out the way `target` lays it out.
pub(super) fn layouts(
    program: &Program,
    target: Target,
) -> Result<BTreeMap<String, ShaderStruct>, String> {
    reflect::structs(&reflection(program, target)?)
}

// The reflection-probe array length and the bindless texture-pool capacity, as
// the backends bake them in. A const assert in `super` pins the first to the
// Rust constant the mirrored `ProbeSet` array uses; the pool sizes only the
// texture argument buffer, which no mirrored struct reads.
const PROBES: (&str, &str) = ("MAX_PROBES", "8");
const POOL: (&str, &str) = ("POOL_SIZE", "1024");

pub(super) static MAIN_BINDLESS_VERT: Program = Program {
    file: "main_bindless.slang",
    entry: "vertex_main_bindless",
    profile: "vs_6_0",
    common: &[PROBES],
    metal: &[("METAL_ABI", "1"), POOL],
    vulkan: &[POOL],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

// The phase-1 variant: the only one that declares every struct the family has.
// Metal runs it as the decision half, under `METAL_BINDINGS`.
pub(super) static CULL_KERNEL: Program = Program {
    file: "cull.slang",
    entry: "cull_kernel",
    profile: "cs_6_0",
    common: &[],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static LIGHT_CULL_KERNEL: Program = Program {
    file: "light_cull.slang",
    entry: "light_cull_kernel",
    profile: "cs_6_0",
    common: &[],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

// The RT skinning kernel. `METAL_BINDINGS` picks the Metal host's slot
// numbering; the mesh payloads it walks are byte-addressed and so reflect no
// layout of their own (see `mesh_payload_offsets_match_the_kernel`).
pub(super) static RT_SKIN_KERNEL: Program = Program {
    file: "rt_skin.slang",
    entry: "rt_skin",
    profile: "cs_6_5",
    common: &[],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static GBUFFER_PREPASS_VERT: Program = Program {
    file: "gbuffer_prepass.slang",
    entry: "gbuffer_prepass_vertex_bindless",
    profile: "vs_6_0",
    common: &[("GB_BINDLESS", "1")],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static SHADOW_VERT: Program = Program {
    file: "shadow.slang",
    entry: "shadow_vertex_main",
    profile: "vs_6_0",
    common: &[("SHADOW_STATIC", "1")],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static GLASS_VERT: Program = Program {
    file: "glass.slang",
    entry: "glass_vertex",
    profile: "vs_6_0",
    common: &[PROBES],
    metal: &[("METAL_ABI", "1")],
    vulkan: &[("USE_MSAA", "1")],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

// The glass mesh vertex stage declares both of its blocks: it reads the model
// matrix out of the per-mesh params. The file is ray-traced only, but the ray
// query is unreachable from the vertex entry, so slangc compiles it on the Metal
// target too and all three reflect.
pub(super) static GLASS_MESH_VERT: Program = Program {
    file: "glass_mesh.slang",
    entry: "glass_mesh_vertex",
    profile: "vs_6_0",
    common: &[PROBES],
    metal: &[("METAL_ABI", "1")],
    vulkan: &[("USE_MSAA", "1")],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

// The water vertex stage is the smallest entry that declares the whole water
// block set: the Gerstner sum reads the wave table, so `WaterParams` (and the
// `WaterWave` element it arrays) survive into the vertex reflection.
pub(super) static WATER_VERT: Program = Program {
    file: "water.slang",
    entry: "water_vertex",
    profile: "vs_6_0",
    common: &[PROBES],
    metal: &[("METAL_ABI", "1")],
    vulkan: &[("USE_MSAA", "1")],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static RT_REFLECTIONS_FRAG: Program = Program {
    file: "rt_reflections.slang",
    entry: "rt_reflections_fragment",
    profile: "ps_6_5",
    common: &[PROBES],
    metal: &[("METAL_ABI", "1")],
    vulkan: &[],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static DECAL_VERT: Program = Program {
    file: "decal.slang",
    entry: "decal_vertex",
    profile: "vs_6_0",
    common: &[],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static LINE_VERT: Program = Program {
    file: "line.slang",
    entry: "line_vertex",
    profile: "vs_6_0",
    common: &[],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static PARTICLE_VERT: Program = Program {
    file: "particle.slang",
    entry: "particle_vertex",
    profile: "vs_6_0",
    common: &[],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[("DXIL_ABI", "1")],
    splices: &[],
};

pub(super) static TEXT_VERT: Program = Program {
    file: "text.slang",
    entry: "text_vertex_main",
    profile: "vs_6_0",
    common: &[],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static TAA_FRAG: Program = Program {
    file: "taa.slang",
    entry: "taa_fragment_main",
    profile: "ps_6_0",
    common: &[],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static BLOOM_PREFILTER: Program = Program {
    file: "bloom.slang",
    entry: "bloom_prefilter_fragment",
    profile: "ps_6_0",
    common: &[("BLOOM_PREFILTER", "1")],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static COMPOSITE_FRAG: Program = Program {
    file: "composite.slang",
    entry: "composite_fragment",
    profile: "ps_6_0",
    common: &[],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static SSAO_KERNEL: Program = Program {
    file: "ssao.slang",
    entry: "ssao_kernel_fragment",
    profile: "ps_6_0",
    common: &[("SSAO_KERNEL", "1")],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static SSR_RESOLVE: Program = Program {
    file: "ssr.slang",
    entry: "ssr_resolve_fragment",
    profile: "ps_6_0",
    common: &[PROBES],
    metal: &[],
    vulkan: &[],
    directx: &[("SPLIT_PROBE_SAMPLER", "1")],
    splices: &[],
};

pub(super) static SSGI_GATHER: Program = Program {
    file: "ssgi.slang",
    entry: "ssgi_gather_fragment",
    profile: "ps_6_0",
    common: &[("SSGI_GATHER", "1")],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static FOG_FROXEL: Program = Program {
    file: "fog.slang",
    entry: "fog_froxel_kernel",
    profile: "cs_6_0",
    common: &[("FOG_FROXEL", "1")],
    metal: &[],
    vulkan: &[],
    directx: &[("DXIL_SPLIT", "1")],
    splices: &[],
};

pub(super) static AUTO_EXPOSURE_BUILD: Program = Program {
    file: "auto_exposure.slang",
    entry: "histogram_build",
    profile: "cs_6_0",
    common: &[("AE_BUILD", "1")],
    metal: &[("METAL_BINDINGS", "1")],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

pub(super) static HIZ_INIT_SINGLE: Program = Program {
    file: "hiz_build.slang",
    entry: "hiz_init_single",
    profile: "cs_6_0",
    common: &[("HIZ_INIT_SINGLE", "1")],
    metal: &[],
    vulkan: &[],
    directx: &[],
    splices: &[],
};

// A stand-in distance field for the raymarch programs. Their source is only
// complete once a world supplies one, so reflecting them means splicing a field
// here -- synthetic, in source, because a test may not read a world's.
const SDF_STANDIN: (&str, &str) = (
    "{SDF_BODY}",
    "float map(float3 p, SdfParams q, float t) { return sdSphere(p, 0.5); }\n\
     SdfSurface shade(float3 p, float3 n, SdfParams q, float t, float2 uv) {\n\
         SdfSurface s; s.albedo = float3(1.0, 1.0, 1.0); s.roughness = 0.5;\n\
         s.metallic = 0.0; s.emissive = float3(0.0, 0.0, 0.0);\n\
         s.transmitted = float3(0.0, 0.0, 0.0); return s; }\n",
);

pub(super) static RAYMARCH_FRAG: Program = Program {
    file: "raymarch.slang",
    entry: "raymarch_fragment",
    profile: "ps_6_0",
    common: &[("RAYMARCH_SURFACE", "1")],
    metal: &[("RAYMARCH_METAL", "1")],
    vulkan: &[],
    directx: &[("RAYMARCH_DXIL", "1")],
    splices: &[SDF_STANDIN],
};

// The shadow caster is the only entry that binds the cascade block.
pub(super) static RAYMARCH_SHADOW_VERT: Program = Program {
    file: "raymarch.slang",
    entry: "raymarch_shadow_vertex",
    profile: "vs_6_0",
    common: &[("RAYMARCH_SHADOW", "1")],
    metal: &[("RAYMARCH_METAL", "1")],
    vulkan: &[],
    directx: &[("RAYMARCH_DXIL", "1")],
    splices: &[SDF_STANDIN],
};