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.