Skip to main content

GBUFFER_PREPASS

Constant GBUFFER_PREPASS 

Source
pub const GBUFFER_PREPASS: &str = "// Unified geometry G-buffer pre-pass: single source for every backend.\n//\n// One jittered traversal of the visible set writes, in a single MRT, everything\n// the screen-space + temporal passes need:\n//   target(0) RGBA16F  view-space normal (xyz) + positive linear view depth (a)\n//   target(1) R8       perceptual roughness\n//   target(2) RG16F    screen-space motion (prev_uv - cur_uv)\n// The rasterised position uses the JITTERED VP so coverage matches the main\n// pass; the motion vector comes from the UN-jittered cur/prev VPs so jitter\n// never leaks into it. Alpha 0 in target(0) marks \"no geometry\".\n//\n// One entry per compile, selected by a define, so each variant declares exactly\n// the resources it binds and nothing reserves a slot a host never writes:\n//\n//   GB_STATIC            - per-draw static geometry\n//   GB_INSTANCED         - GPU-instanced clusters (transforms immutable)\n//   GB_SKINNED           - per-draw skinned meshes, cur + prev joint palettes\n//   GB_BINDLESS          - GPU-driven: object id via first-instance, model and\n//                          roughness from the per-frame GpuObjectData buffer\n//   GB_FRAGMENT          - the per-draw fragment (roughness from a constant)\n//   GB_FRAGMENT_BINDLESS - the GPU-driven fragment (roughness from a varying)\n//\n// METAL_BINDINGS selects the Metal *host\'s* constant shape, which is a host\n// difference rather than a target one: Vulkan hands both stages one push\n// constant block holding the model pair and the roughness, while the Metal\n// encoder writes the model pair to vertex buffer(2) and the roughness to\n// fragment buffer(0). DirectX splits them the same way Metal does, at its own\n// slots. Everything else carries both a `[[vk::binding]]` and a `register()` on\n// one declaration, so the Vulkan descriptor sets and the Metal buffer indices\n// both reproduce what the hosts already bind.\n//\n// DXIL_ABI pins every register to the root signatures in\n// directx/post/gbuffer.rs, which are the host\'s slots rather than the Metal\n// buffer indices the declarations otherwise carry: the per-draw entries put the\n// view CBV at b0 and the model pair at b1, the GPU-driven one gives b0 to the\n// indirect command\'s object id and pushes the view CBV to b1, and every\n// structured buffer starts from t0. The fragment\'s roughness constants also sit\n// at b0 -- pixel-visible, so they never collide with a vertex CBV.\n//\n// DXIL_ABI also selects how DirectX delivers a per-draw index, matching what\n// main_bindless.slang already does there: the object id rides that b0 root\n// constant, and instanced clusters bind their bucket\'s transforms from zero, so\n// neither entry declares SV_StartInstanceLocation (which would raise the\n// container\'s floor to shader model 6.8 to reproduce an index the engine\n// already has).\n\n{OBJECT_COMMON}\n\n// Layout matches `GBufferView` / `GbViewUniforms` (4 x float4x4, 256 B).\nstruct GbView\n{\n    float4x4 jittered_vp;\n    float4x4 cur_vp;\n    float4x4 prev_vp;\n    float4x4 view_mat;\n};\n\n#if defined(GB_STATIC) || defined(GB_INSTANCED) || defined(GB_SKINNED) || defined(GB_BINDLESS)\n#define GB_VERTEX_STAGE 1\n#endif\n\n#ifdef GB_VERTEX_STAGE\n#if defined(DXIL_ABI) && defined(GB_BINDLESS)\n// b0 belongs to the indirect command\'s object-id root constant, so the view\n// CBV follows it at b1.\nConstantBuffer<GbView> gb_view : register(b1);\n#else\n[[vk::binding(0, 0)]] ConstantBuffer<GbView> gb_view : register(b0);\n#endif\n#endif\n\n// ---- The per-draw model pair and roughness ----\n\n#if defined(METAL_BINDINGS) || defined(DXIL_ABI)\n\n// Layout matches `VelocityModelUniforms` / `GbModelPush` (128 B), at vertex\n// buffer(2) on Metal and b1 on DirectX.\nstruct GbModel\n{\n    float4x4 cur_model;\n    float4x4 prev_model;\n};\n\n// Layout matches `SsrPrepassMat` (16 B), at fragment buffer(0) on Metal and the\n// pixel-visible b0 on DirectX. Padded with plain floats: a float3 would force\n// 16-byte alignment and bloat the struct.\nstruct GbMat\n{\n    float roughness;\n    float _pad0;\n    float _pad1;\n    float _pad2;\n};\n\n#if defined(GB_STATIC) || defined(GB_SKINNED)\n#ifdef DXIL_ABI\nConstantBuffer<GbModel> gb_model : register(b1);\n#else\nConstantBuffer<GbModel> gb_model : register(b2);\n#endif\n#endif\n#ifdef GB_FRAGMENT\nConstantBuffer<GbMat> gb_mat : register(b0);\n#endif\n\n#define GB_CUR_MODEL  gb_model.cur_model\n#define GB_PREV_MODEL gb_model.prev_model\n#define GB_ROUGHNESS  gb_mat.roughness\n\n#else\n\n// Layout matches `GbModelPush` (144 B). Both stages see the whole block; the\n// vertex reads the model pair, the fragment only `roughness` at offset 128.\nstruct GbModelPush\n{\n    float4x4 cur_model;\n    float4x4 prev_model;\n    float roughness;\n    float _pad0;\n    float _pad1;\n    float _pad2;\n};\n\n#if defined(GB_STATIC) || defined(GB_SKINNED) || defined(GB_FRAGMENT)\n[[vk::push_constant]] ConstantBuffer<GbModelPush> gb_push;\n#endif\n\n#define GB_CUR_MODEL  gb_push.cur_model\n#define GB_PREV_MODEL gb_push.prev_model\n#define GB_ROUGHNESS  gb_push.roughness\n\n#endif\n\n// ---- Per-variant geometry sources ----\n\n#ifdef GB_INSTANCED\n// Per-instance world matrices. Instance transforms are immutable, so cur ==\n// prev and the motion vector is camera-only.\n#ifdef DXIL_ABI\nStructuredBuffer<float4x4> instances : register(t0);\n#else\n[[vk::binding(0, 1)]] StructuredBuffer<float4x4> instances : register(t6);\n#endif\n#endif\n\n#ifdef GB_SKINNED\n// The current and previous frame\'s joint palettes, so per-vertex skin\n// deformation produces a motion vector. Both reuse the main-pass joint set.\n#ifdef DXIL_ABI\nStructuredBuffer<float4x4> cur_joints : register(t0);\nStructuredBuffer<float4x4> prev_joints : register(t1);\n#else\n[[vk::binding(0, 1)]] StructuredBuffer<float4x4> cur_joints : register(t8);\n[[vk::binding(0, 2)]] StructuredBuffer<float4x4> prev_joints : register(t9);\n#endif\n#endif\n\n#ifdef GB_BINDLESS\n// The cull-produced per-frame records, indexed by the object id the cull baked\n// into each indirect command\'s first-instance value, and the parallel\n// previous-frame model matrices indexed identically.\n#ifdef DXIL_ABI\nstruct ObjectId { uint value; };\nConstantBuffer<ObjectId> objid_cb : register(b0);\nStructuredBuffer<GpuObjectData> objects : register(t0);\nStructuredBuffer<float4x4> prev_models : register(t1);\n#else\n[[vk::binding(0, 1)]] StructuredBuffer<GpuObjectData> objects : register(t9);\n[[vk::binding(1, 0)]] StructuredBuffer<float4x4> prev_models : register(t10);\n#endif\n#endif\n\n// ---- Stage interfaces ----\n\nstruct GbVertexIn\n{\n    [[vk::location(0)]] float3 pos     : POSITION;\n    [[vk::location(1)]] float3 normal  : NORMAL;\n    [[vk::location(2)]] float3 tangent : TANGENT;\n    [[vk::location(3)]] float3 color   : COLOR0;\n    [[vk::location(4)]] float2 uv      : TEXCOORD0;\n};\n\nstruct GbSkinnedVertexIn\n{\n    [[vk::location(0)]] float3 pos     : POSITION;\n    [[vk::location(1)]] float3 normal  : NORMAL;\n    [[vk::location(2)]] float3 tangent : TANGENT;\n    [[vk::location(3)]] float3 color   : COLOR0;\n    [[vk::location(4)]] float2 uv      : TEXCOORD0;\n    [[vk::location(5)]] uint4  joints  : BLENDINDICES;\n    [[vk::location(6)]] float4 weights : BLENDWEIGHT;\n};\n\n// The GPU-driven variant reads a second vertex stream: the previous frame\'s\n// position, at attribute 5. The static + instance + chunk prefix binds the same\n// buffer to both streams (prev_pos == cur_pos, so motion is the model delta\n// plus camera); the skinned tail binds the previous-frame deformed buffer.\n//\n// The previous position takes a name of its own rather than POSITION1: slangc\n// appends its own index to whatever the semantic spells, so a trailing digit is\n// multiplied by ten on the way out (POSITION1 lands as POSITION index 10). That\n// is harmless between two stages that agree, which is why the varyings below\n// keep their TEXCOORDn names, but a vertex input has to match the index a host\n// input layout declares.\nstruct GbBindlessVertexIn\n{\n    [[vk::location(0)]] float3 pos      : POSITION;\n    [[vk::location(1)]] float3 normal   : NORMAL;\n    [[vk::location(3)]] float3 color    : COLOR0;\n    [[vk::location(5)]] float3 prev_pos : PREVPOSITION;\n};\n\nstruct GbVertexOut\n{\n    float4 position : SV_Position;\n    [[vk::location(0)]] float3 view_normal : TEXCOORD0;\n    // Positive view-space depth (-z); the consumers rebuild view position from it.\n    [[vk::location(1)]] float  view_depth  : TEXCOORD1;\n    [[vk::location(2)]] float4 cur_clip    : TEXCOORD2;\n    [[vk::location(3)]] float4 prev_clip   : TEXCOORD3;\n};\n\nstruct GbVertexOutBindless\n{\n    float4 position : SV_Position;\n    [[vk::location(0)]] float3 view_normal : TEXCOORD0;\n    [[vk::location(1)]] float  view_depth  : TEXCOORD1;\n    [[vk::location(2)]] float4 cur_clip    : TEXCOORD2;\n    [[vk::location(3)]] float4 prev_clip   : TEXCOORD3;\n    // Sourced from the object record, so the fragment needs no per-draw constant.\n    [[vk::location(4)]] nointerpolation float roughness : TEXCOORD4;\n};\n\nstruct GbFragmentOut\n{\n    float4 nd    : SV_Target0;\n    float  rough : SV_Target1;\n    float2 vel   : SV_Target2;\n};\n\n// ---- Shared body ----\n\n#ifdef GB_VERTEX_STAGE\n\n// Everything but roughness, from a world-space position pair and the model\n// matrix whose normal transform the surface normal rides.\nGbVertexOut gb_project(float4x4 model, float4 cur_world, float4 prev_world, float3 model_normal)\n{\n    GbVertexOut o;\n    o.position  = mul(gb_view.jittered_vp, cur_world);\n    o.cur_clip  = mul(gb_view.cur_vp,  cur_world);\n    o.prev_clip = mul(gb_view.prev_vp, prev_world);\n    // Inverse-transpose, matching the forward pass this feeds: a non-uniform\n    // scale rotates a plain model-matrix normal off the surface, which would\n    // leave SSAO / SSR / SSGI shading a different normal than the lighting.\n    float3 world_n = normalize(mul(normal_matrix(model), model_normal));\n    o.view_normal  = mul((float3x3)gb_view.view_mat, world_n);\n    o.view_depth   = -mul(gb_view.view_mat, cur_world).z;\n    return o;\n}\n\n// Skybox vertices carry a blue channel of 2.0: pin them to the far plane so the\n// sky never occludes scene geometry.\nfloat4 gb_sky_pin(float4 position, float3 color)\n{\n    if (color.b > 1.5)\n    {\n        position.z = position.w * (1.0 - 1e-6);\n    }\n    return position;\n}\n\n// 4-influence linear blend skinning from one joint palette.\nfloat4x4 gb_skin(StructuredBuffer<float4x4> palette, uint4 joints, float4 weights)\n{\n    return weights.x * palette[joints.x]\n         + weights.y * palette[joints.y]\n         + weights.z * palette[joints.z]\n         + weights.w * palette[joints.w];\n}\n\n#endif\n\n// Stored so the TAA pass can do `prev_uv = uv + motion`. Image-space UV with\n// 0 = top, matching the upright resolve the readers sample.\nfloat2 gb_motion(float4 cur_clip, float4 prev_clip)\n{\n    float2 cur_ndc  = cur_clip.xy  / cur_clip.w;\n    float2 prev_ndc = prev_clip.xy / prev_clip.w;\n    float2 cur_uv  = float2(cur_ndc.x  * 0.5 + 0.5, 0.5 - cur_ndc.y  * 0.5);\n    float2 prev_uv = float2(prev_ndc.x * 0.5 + 0.5, 0.5 - prev_ndc.y * 0.5);\n    return prev_uv - cur_uv;\n}\n\n// ---- Entry points ----\n\n#ifdef GB_STATIC\n\n[shader(\"vertex\")]\nGbVertexOut gbuffer_prepass_vertex(GbVertexIn v)\n{\n    float4 cur_world  = mul(GB_CUR_MODEL,  float4(v.pos, 1.0));\n    float4 prev_world = mul(GB_PREV_MODEL, float4(v.pos, 1.0));\n    GbVertexOut o = gb_project(GB_CUR_MODEL, cur_world, prev_world, v.normal);\n    o.position = gb_sky_pin(o.position, v.color);\n    return o;\n}\n\n#endif\n\n#ifdef GB_INSTANCED\n\n// Vulkan partitions one cluster-wide transform array by LOD bucket and walks\n// `firstInstance` across it, so the index has to include the base; Metal and\n// DirectX bind each bucket\'s slice from zero.\n[shader(\"vertex\")]\nGbVertexOut gbuffer_prepass_vertex_instanced(\n    GbVertexIn v,\n#ifdef DXIL_ABI\n    uint instance_id : SV_InstanceID)\n{\n    uint iid = instance_id;\n#else\n    uint instance_id : SV_InstanceID,\n    uint first_instance : SV_StartInstanceLocation)\n{\n    uint iid = object_instance_index(instance_id, first_instance);\n#endif\n    float4x4 model = instances[iid];\n    float4 world = mul(model, float4(v.pos, 1.0));\n    // Immutable transforms: prev == cur, so the motion is camera-only.\n    return gb_project(model, world, world, v.normal);\n}\n\n#endif\n\n#ifdef GB_SKINNED\n\n[shader(\"vertex\")]\nGbVertexOut gbuffer_prepass_vertex_skinned(GbSkinnedVertexIn v)\n{\n    float4x4 cur_skin  = gb_skin(cur_joints, v.joints, v.weights);\n    float4x4 prev_skin = gb_skin(prev_joints, v.joints, v.weights);\n    float4 cur_world  = mul(GB_CUR_MODEL,  mul(cur_skin,  float4(v.pos, 1.0)));\n    float4 prev_world = mul(GB_PREV_MODEL, mul(prev_skin, float4(v.pos, 1.0)));\n    float3 skinned_n  = mul((float3x3)cur_skin, v.normal);\n    return gb_project(GB_CUR_MODEL, cur_world, prev_world, skinned_n);\n}\n\n#endif\n\n#ifdef GB_BINDLESS\n\n[shader(\"vertex\")]\nGbVertexOutBindless gbuffer_prepass_vertex_bindless(\n    GbBindlessVertexIn v\n#ifdef DXIL_ABI\n    )\n{\n    uint oid = objid_cb.value;\n#else\n    ,\n    uint instance_id : SV_InstanceID,\n    uint first_instance : SV_StartInstanceLocation)\n{\n    uint oid = object_instance_index(instance_id, first_instance);\n#endif\n    GpuObjectData obj = objects[oid];\n    float4 cur_world  = mul(obj.model, float4(v.pos, 1.0));\n    float4 prev_world = mul(prev_models[oid], float4(v.prev_pos, 1.0));\n    GbVertexOut p = gb_project(obj.model, cur_world, prev_world, v.normal);\n\n    GbVertexOutBindless o;\n    o.position    = gb_sky_pin(p.position, v.color);\n    o.view_normal = p.view_normal;\n    o.view_depth  = p.view_depth;\n    o.cur_clip    = p.cur_clip;\n    o.prev_clip   = p.prev_clip;\n    o.roughness   = obj.tint_roughness.w;\n    return o;\n}\n\n#endif\n\n#ifdef GB_FRAGMENT\n\n[shader(\"fragment\")]\nGbFragmentOut gbuffer_prepass_fragment(GbVertexOut p)\n{\n    GbFragmentOut o;\n    o.nd    = float4(normalize(p.view_normal), p.view_depth);\n    o.rough = GB_ROUGHNESS;\n    o.vel   = gb_motion(p.cur_clip, p.prev_clip);\n    return o;\n}\n\n#endif\n\n#ifdef GB_FRAGMENT_BINDLESS\n\n[shader(\"fragment\")]\nGbFragmentOut gbuffer_prepass_fragment_bindless(GbVertexOutBindless p)\n{\n    GbFragmentOut o;\n    o.nd    = float4(normalize(p.view_normal), p.view_depth);\n    o.rough = p.roughness;\n    o.vel   = gb_motion(p.cur_clip, p.prev_clip);\n    return o;\n}\n\n#endif\n";
Expand description

gbuffer_prepass.slang.