concinnity-device 0.18.64

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
// Unified geometry G-buffer pre-pass: single source for every backend.
//
// One jittered traversal of the visible set writes, in a single MRT, everything
// the screen-space + temporal passes need:
//   target(0) RGBA16F  view-space normal (xyz) + positive linear view depth (a)
//   target(1) R8       perceptual roughness
//   target(2) RG16F    screen-space motion (prev_uv - cur_uv)
// The rasterised position uses the JITTERED VP so coverage matches the main
// pass; the motion vector comes from the UN-jittered cur/prev VPs so jitter
// never leaks into it. Alpha 0 in target(0) marks "no geometry".
//
// One entry per compile, selected by a define, so each variant declares exactly
// the resources it binds and nothing reserves a slot a host never writes:
//
//   GB_STATIC            - per-draw static geometry
//   GB_INSTANCED         - GPU-instanced clusters (transforms immutable)
//   GB_SKINNED           - per-draw skinned meshes, cur + prev joint palettes
//   GB_BINDLESS          - GPU-driven: object id via first-instance, model and
//                          roughness from the per-frame GpuObjectData buffer
//   GB_FRAGMENT          - the per-draw fragment (roughness from a constant)
//   GB_FRAGMENT_BINDLESS - the GPU-driven fragment (roughness from a varying)
//
// METAL_BINDINGS selects the Metal *host's* constant shape, which is a host
// difference rather than a target one: Vulkan hands both stages one push
// constant block holding the model pair and the roughness, while the Metal
// encoder writes the model pair to vertex buffer(2) and the roughness to
// fragment buffer(0). DirectX splits them the same way Metal does, at its own
// slots. Everything else carries both a `[[vk::binding]]` and a `register()` on
// one declaration, so the Vulkan descriptor sets and the Metal buffer indices
// both reproduce what the hosts already bind.
//
// DXIL_ABI pins every register to the root signatures in
// directx/post/gbuffer.rs, which are the host's slots rather than the Metal
// buffer indices the declarations otherwise carry: the per-draw entries put the
// view CBV at b0 and the model pair at b1, the GPU-driven one gives b0 to the
// indirect command's object id and pushes the view CBV to b1, and every
// structured buffer starts from t0. The fragment's roughness constants also sit
// at b0 -- pixel-visible, so they never collide with a vertex CBV.
//
// DXIL_ABI also selects how DirectX delivers a per-draw index, matching what
// main_bindless.slang already does there: the object id rides that b0 root
// constant, and instanced clusters bind their bucket's transforms from zero, so
// neither entry declares SV_StartInstanceLocation (which would raise the
// container's floor to shader model 6.8 to reproduce an index the engine
// already has).

{OBJECT_COMMON}

// Layout matches `GBufferView` / `GbViewUniforms` (4 x float4x4, 256 B).
struct GbView
{
    float4x4 jittered_vp;
    float4x4 cur_vp;
    float4x4 prev_vp;
    float4x4 view_mat;
};

#if defined(GB_STATIC) || defined(GB_INSTANCED) || defined(GB_SKINNED) || defined(GB_BINDLESS)
#define GB_VERTEX_STAGE 1
#endif

#ifdef GB_VERTEX_STAGE
#if defined(DXIL_ABI) && defined(GB_BINDLESS)
// b0 belongs to the indirect command's object-id root constant, so the view
// CBV follows it at b1.
ConstantBuffer<GbView> gb_view : register(b1);
#else
[[vk::binding(0, 0)]] ConstantBuffer<GbView> gb_view : register(b0);
#endif
#endif

// ---- The per-draw model pair and roughness ----

#if defined(METAL_BINDINGS) || defined(DXIL_ABI)

// Layout matches `VelocityModelUniforms` / `GbModelPush` (128 B), at vertex
// buffer(2) on Metal and b1 on DirectX.
struct GbModel
{
    float4x4 cur_model;
    float4x4 prev_model;
};

// Layout matches `SsrPrepassMat` (16 B), at fragment buffer(0) on Metal and the
// pixel-visible b0 on DirectX. Padded with plain floats: a float3 would force
// 16-byte alignment and bloat the struct.
struct GbMat
{
    float roughness;
    float _pad0;
    float _pad1;
    float _pad2;
};

#if defined(GB_STATIC) || defined(GB_SKINNED)
#ifdef DXIL_ABI
ConstantBuffer<GbModel> gb_model : register(b1);
#else
ConstantBuffer<GbModel> gb_model : register(b2);
#endif
#endif
#ifdef GB_FRAGMENT
ConstantBuffer<GbMat> gb_mat : register(b0);
#endif

#define GB_CUR_MODEL  gb_model.cur_model
#define GB_PREV_MODEL gb_model.prev_model
#define GB_ROUGHNESS  gb_mat.roughness

#else

// Layout matches `GbModelPush` (144 B). Both stages see the whole block; the
// vertex reads the model pair, the fragment only `roughness` at offset 128.
struct GbModelPush
{
    float4x4 cur_model;
    float4x4 prev_model;
    float roughness;
    float _pad0;
    float _pad1;
    float _pad2;
};

#if defined(GB_STATIC) || defined(GB_SKINNED) || defined(GB_FRAGMENT)
[[vk::push_constant]] ConstantBuffer<GbModelPush> gb_push;
#endif

#define GB_CUR_MODEL  gb_push.cur_model
#define GB_PREV_MODEL gb_push.prev_model
#define GB_ROUGHNESS  gb_push.roughness

#endif

// ---- Per-variant geometry sources ----

#ifdef GB_INSTANCED
// Per-instance world matrices. Instance transforms are immutable, so cur ==
// prev and the motion vector is camera-only.
#ifdef DXIL_ABI
StructuredBuffer<float4x4> instances : register(t0);
#else
[[vk::binding(0, 1)]] StructuredBuffer<float4x4> instances : register(t6);
#endif
#endif

#ifdef GB_SKINNED
// The current and previous frame's joint palettes, so per-vertex skin
// deformation produces a motion vector. Both reuse the main-pass joint set.
#ifdef DXIL_ABI
StructuredBuffer<float4x4> cur_joints : register(t0);
StructuredBuffer<float4x4> prev_joints : register(t1);
#else
[[vk::binding(0, 1)]] StructuredBuffer<float4x4> cur_joints : register(t8);
[[vk::binding(0, 2)]] StructuredBuffer<float4x4> prev_joints : register(t9);
#endif
#endif

#ifdef GB_BINDLESS
// The cull-produced per-frame records, indexed by the object id the cull baked
// into each indirect command's first-instance value, and the parallel
// previous-frame model matrices indexed identically.
#ifdef DXIL_ABI
struct ObjectId { uint value; };
ConstantBuffer<ObjectId> objid_cb : register(b0);
StructuredBuffer<GpuObjectData> objects : register(t0);
StructuredBuffer<float4x4> prev_models : register(t1);
#else
[[vk::binding(0, 1)]] StructuredBuffer<GpuObjectData> objects : register(t9);
[[vk::binding(1, 0)]] StructuredBuffer<float4x4> prev_models : register(t10);
#endif
#endif

// ---- Stage interfaces ----

struct GbVertexIn
{
    [[vk::location(0)]] float3 pos     : POSITION;
    [[vk::location(1)]] float3 normal  : NORMAL;
    [[vk::location(2)]] float3 tangent : TANGENT;
    [[vk::location(3)]] float3 color   : COLOR0;
    [[vk::location(4)]] float2 uv      : TEXCOORD0;
};

struct GbSkinnedVertexIn
{
    [[vk::location(0)]] float3 pos     : POSITION;
    [[vk::location(1)]] float3 normal  : NORMAL;
    [[vk::location(2)]] float3 tangent : TANGENT;
    [[vk::location(3)]] float3 color   : COLOR0;
    [[vk::location(4)]] float2 uv      : TEXCOORD0;
    [[vk::location(5)]] uint4  joints  : BLENDINDICES;
    [[vk::location(6)]] float4 weights : BLENDWEIGHT;
};

// The GPU-driven variant reads a second vertex stream: the previous frame's
// position, at attribute 5. The static + instance + chunk prefix binds the same
// buffer to both streams (prev_pos == cur_pos, so motion is the model delta
// plus camera); the skinned tail binds the previous-frame deformed buffer.
//
// The previous position takes a name of its own rather than POSITION1: slangc
// appends its own index to whatever the semantic spells, so a trailing digit is
// multiplied by ten on the way out (POSITION1 lands as POSITION index 10). That
// is harmless between two stages that agree, which is why the varyings below
// keep their TEXCOORDn names, but a vertex input has to match the index a host
// input layout declares.
struct GbBindlessVertexIn
{
    [[vk::location(0)]] float3 pos      : POSITION;
    [[vk::location(1)]] float3 normal   : NORMAL;
    [[vk::location(3)]] float3 color    : COLOR0;
    [[vk::location(5)]] float3 prev_pos : PREVPOSITION;
};

struct GbVertexOut
{
    float4 position : SV_Position;
    [[vk::location(0)]] float3 view_normal : TEXCOORD0;
    // Positive view-space depth (-z); the consumers rebuild view position from it.
    [[vk::location(1)]] float  view_depth  : TEXCOORD1;
    [[vk::location(2)]] float4 cur_clip    : TEXCOORD2;
    [[vk::location(3)]] float4 prev_clip   : TEXCOORD3;
};

struct GbVertexOutBindless
{
    float4 position : SV_Position;
    [[vk::location(0)]] float3 view_normal : TEXCOORD0;
    [[vk::location(1)]] float  view_depth  : TEXCOORD1;
    [[vk::location(2)]] float4 cur_clip    : TEXCOORD2;
    [[vk::location(3)]] float4 prev_clip   : TEXCOORD3;
    // Sourced from the object record, so the fragment needs no per-draw constant.
    [[vk::location(4)]] nointerpolation float roughness : TEXCOORD4;
};

struct GbFragmentOut
{
    float4 nd    : SV_Target0;
    float  rough : SV_Target1;
    float2 vel   : SV_Target2;
};

// ---- Shared body ----

#ifdef GB_VERTEX_STAGE

// Everything but roughness, from a world-space position pair and the model
// matrix whose normal transform the surface normal rides.
GbVertexOut gb_project(float4x4 model, float4 cur_world, float4 prev_world, float3 model_normal)
{
    GbVertexOut o;
    o.position  = mul(gb_view.jittered_vp, cur_world);
    o.cur_clip  = mul(gb_view.cur_vp,  cur_world);
    o.prev_clip = mul(gb_view.prev_vp, prev_world);
    // Inverse-transpose, matching the forward pass this feeds: a non-uniform
    // scale rotates a plain model-matrix normal off the surface, which would
    // leave SSAO / SSR / SSGI shading a different normal than the lighting.
    float3 world_n = normalize(mul(normal_matrix(model), model_normal));
    o.view_normal  = mul((float3x3)gb_view.view_mat, world_n);
    o.view_depth   = -mul(gb_view.view_mat, cur_world).z;
    return o;
}

// Skybox vertices carry a blue channel of 2.0: pin them to the far plane so the
// sky never occludes scene geometry.
float4 gb_sky_pin(float4 position, float3 color)
{
    if (color.b > 1.5)
    {
        position.z = position.w * (1.0 - 1e-6);
    }
    return position;
}

// 4-influence linear blend skinning from one joint palette.
float4x4 gb_skin(StructuredBuffer<float4x4> palette, uint4 joints, float4 weights)
{
    return weights.x * palette[joints.x]
         + weights.y * palette[joints.y]
         + weights.z * palette[joints.z]
         + weights.w * palette[joints.w];
}

#endif

// Stored so the TAA pass can do `prev_uv = uv + motion`. Image-space UV with
// 0 = top, matching the upright resolve the readers sample.
float2 gb_motion(float4 cur_clip, float4 prev_clip)
{
    float2 cur_ndc  = cur_clip.xy  / cur_clip.w;
    float2 prev_ndc = prev_clip.xy / prev_clip.w;
    float2 cur_uv  = float2(cur_ndc.x  * 0.5 + 0.5, 0.5 - cur_ndc.y  * 0.5);
    float2 prev_uv = float2(prev_ndc.x * 0.5 + 0.5, 0.5 - prev_ndc.y * 0.5);
    return prev_uv - cur_uv;
}

// ---- Entry points ----

#ifdef GB_STATIC

[shader("vertex")]
GbVertexOut gbuffer_prepass_vertex(GbVertexIn v)
{
    float4 cur_world  = mul(GB_CUR_MODEL,  float4(v.pos, 1.0));
    float4 prev_world = mul(GB_PREV_MODEL, float4(v.pos, 1.0));
    GbVertexOut o = gb_project(GB_CUR_MODEL, cur_world, prev_world, v.normal);
    o.position = gb_sky_pin(o.position, v.color);
    return o;
}

#endif

#ifdef GB_INSTANCED

// Vulkan partitions one cluster-wide transform array by LOD bucket and walks
// `firstInstance` across it, so the index has to include the base; Metal and
// DirectX bind each bucket's slice from zero.
[shader("vertex")]
GbVertexOut gbuffer_prepass_vertex_instanced(
    GbVertexIn v,
#ifdef DXIL_ABI
    uint instance_id : SV_InstanceID)
{
    uint iid = instance_id;
#else
    uint instance_id : SV_InstanceID,
    uint first_instance : SV_StartInstanceLocation)
{
    uint iid = object_instance_index(instance_id, first_instance);
#endif
    float4x4 model = instances[iid];
    float4 world = mul(model, float4(v.pos, 1.0));
    // Immutable transforms: prev == cur, so the motion is camera-only.
    return gb_project(model, world, world, v.normal);
}

#endif

#ifdef GB_SKINNED

[shader("vertex")]
GbVertexOut gbuffer_prepass_vertex_skinned(GbSkinnedVertexIn v)
{
    float4x4 cur_skin  = gb_skin(cur_joints, v.joints, v.weights);
    float4x4 prev_skin = gb_skin(prev_joints, v.joints, v.weights);
    float4 cur_world  = mul(GB_CUR_MODEL,  mul(cur_skin,  float4(v.pos, 1.0)));
    float4 prev_world = mul(GB_PREV_MODEL, mul(prev_skin, float4(v.pos, 1.0)));
    float3 skinned_n  = mul((float3x3)cur_skin, v.normal);
    return gb_project(GB_CUR_MODEL, cur_world, prev_world, skinned_n);
}

#endif

#ifdef GB_BINDLESS

[shader("vertex")]
GbVertexOutBindless gbuffer_prepass_vertex_bindless(
    GbBindlessVertexIn v
#ifdef DXIL_ABI
    )
{
    uint oid = objid_cb.value;
#else
    ,
    uint instance_id : SV_InstanceID,
    uint first_instance : SV_StartInstanceLocation)
{
    uint oid = object_instance_index(instance_id, first_instance);
#endif
    GpuObjectData obj = objects[oid];
    float4 cur_world  = mul(obj.model, float4(v.pos, 1.0));
    float4 prev_world = mul(prev_models[oid], float4(v.prev_pos, 1.0));
    GbVertexOut p = gb_project(obj.model, cur_world, prev_world, v.normal);

    GbVertexOutBindless o;
    o.position    = gb_sky_pin(p.position, v.color);
    o.view_normal = p.view_normal;
    o.view_depth  = p.view_depth;
    o.cur_clip    = p.cur_clip;
    o.prev_clip   = p.prev_clip;
    o.roughness   = obj.tint_roughness.w;
    return o;
}

#endif

#ifdef GB_FRAGMENT

[shader("fragment")]
GbFragmentOut gbuffer_prepass_fragment(GbVertexOut p)
{
    GbFragmentOut o;
    o.nd    = float4(normalize(p.view_normal), p.view_depth);
    o.rough = GB_ROUGHNESS;
    o.vel   = gb_motion(p.cur_clip, p.prev_clip);
    return o;
}

#endif

#ifdef GB_FRAGMENT_BINDLESS

[shader("fragment")]
GbFragmentOut gbuffer_prepass_fragment_bindless(GbVertexOutBindless p)
{
    GbFragmentOut o;
    o.nd    = float4(normalize(p.view_normal), p.view_depth);
    o.rough = p.roughness;
    o.vel   = gb_motion(p.cur_clip, p.prev_clip);
    return o;
}

#endif