pub const DECAL: &str = "// Projected (deferred) decal pass: single source for every backend.\n//\n// Runs after the main HDR pass. The vertex stage draws a unit cube (positions\n// in [-0.5, 0.5]^3 in local space) under the decal\'s model matrix and the\n// camera VP, jittered when TAA is on so the rasterised pixel grid matches the\n// main pass exactly. The fragment stage reconstructs the world-space sample\n// point of each rasterised pixel from the main pass\'s depth attachment,\n// transforms it back into decal-local space, and discards where the point\n// falls outside the unit box. Pixels near the top / bottom face along local +Y\n// fade out so the stamp shows no hard edge where it meets a tilted or curved\n// surface.\n//\n// The composited colour is alpha-blended into the resolved HDR target by the\n// pipeline\'s blend state (Src.A * Src + (1 - Src.A) * Dst). The pass binds no\n// depth attachment: the unit-box test is the depth test.\n//\n// USE_MSAA is a HOST difference rather than a target one: Vulkan reads the\n// multisampled main depth while Metal reads the resolved copy.\n\n#ifndef USE_MSAA\n#define USE_MSAA 0\n#endif\n\n// Per-frame view inputs, 144 B. Mirrors `DecalView` in each backend\'s uniforms\n// module.\nstruct DecalView\n{\n // The main pass\'s view-projection (jittered when TAA is on).\n float4x4 vp;\n // Inverse of the same, for the world-space reconstruction.\n float4x4 inv_vp;\n // Attachment width, height in pixels.\n float2 viewport;\n float2 _pad;\n};\n\n// Per-decal uniforms, 160 B. Mirrors `DecalParams`.\nstruct DecalParams\n{\n float4x4 model; // local -> world\n float4x4 inv_model; // world -> local\n float4 tint; // linear RGB x alpha\n float fade_pow; // exponent on the soft fade along local +Y\n float _pad0;\n float _pad1;\n float _pad2;\n};\n\n// Vulkan binds both as UBOs in the per-frame set (the params through a\n// dynamic offset into one MAX_DECALS-slot ring); Metal writes both with\n// setBytes at the buffer indices the register numbers name.\n[[vk::binding(0, 0)]] ConstantBuffer<DecalView> view : register(b0);\n[[vk::binding(1, 0)]] ConstantBuffer<DecalParams> params : register(b1);\n\n// Declaration order is the Metal texture index: the scene depth at 0 and this\n// decal\'s albedo at 1, which is what that host binds. The albedo lives in its\n// own Vulkan set so swapping decals rebinds set 1 rather than the per-frame set.\n#if USE_MSAA\n[[vk::binding(2, 0)]] Texture2DMS<float> scene_depth;\n#else\n[[vk::binding(2, 0)]] Texture2D<float> scene_depth;\n#endif\n[[vk::binding(0, 1)]] Sampler2D<float4> decal_tex;\n\nstruct DecalVertexIn\n{\n // Unit cube vertex; the bound buffer carries 8 corners in [-0.5, 0.5]^3.\n [[vk::location(0)]] float3 pos : POSITION;\n};\n\n// The cube is only a rasterisation proxy, so the fragment derives everything it\n// needs from the pixel position and carries no varying.\nstruct DecalVertexOut\n{\n float4 position : SV_Position;\n};\n\n[shader(\"vertex\")]\nDecalVertexOut decal_vertex(DecalVertexIn v)\n{\n DecalVertexOut o;\n float4 world = mul(params.model, float4(v.pos, 1.0));\n o.position = mul(view.vp, world);\n return o;\n}\n\nfloat decal_scene_depth(int2 pixel)\n{\n#if USE_MSAA\n return scene_depth.Load(pixel, 0);\n#else\n return scene_depth.Load(int3(pixel, 0));\n#endif\n}\n\n[shader(\"fragment\")]\nfloat4 decal_fragment(DecalVertexOut i) : SV_Target\n{\n // The single-sample depth resolve shares the rasterised pixel grid with the\n // composite target, so integer texel coordinates address it directly.\n int2 pixel = int2(i.position.xy);\n if (pixel.x < 0 || pixel.y < 0 || pixel.x >= int(view.viewport.x)\n || pixel.y >= int(view.viewport.y))\n {\n discard;\n }\n // 1.0 is the cleared / \"no geometry\" sentinel: the main pass left this\n // pixel empty (the sky writes near-far-plane depth instead). Nothing to\n // project onto.\n float depth = decal_scene_depth(pixel);\n if (depth >= 1.0)\n {\n discard;\n }\n\n // Reconstruct world space at this pixel through the inverse VP. Every\n // backend rasterises this pass with the framebuffer\'s Y running downwards\n // (Vulkan through the same negative-height viewport as the main pass), so\n // the NDC flip is shared rather than target-specific.\n float2 ndc_xy = (i.position.xy / view.viewport) * 2.0 - 1.0;\n ndc_xy.y = -ndc_xy.y;\n float4 clip = float4(ndc_xy, depth, 1.0);\n float4 world = mul(view.inv_vp, clip);\n world /= world.w;\n\n // Decal-local clip against the unit box.\n float4 local = mul(params.inv_model, world);\n float3 ab = abs(local.xyz);\n if (ab.x > 0.5 || ab.y > 0.5 || ab.z > 0.5)\n {\n discard;\n }\n\n // Soft fade along the projection axis (local +Y) so the stamp shows no hard\n // band where the surface tilts away from the projection plane: alpha rolls\n // off as |local.y| approaches 0.5.\n float fade = saturate(1.0 - (ab.y * 2.0));\n fade = pow(fade, max(params.fade_pow, 1.0));\n\n // Sample on local X-Z; UV in [0, 1] with V = 0 at the top, matching the\n // rest of the engine\'s textures.\n float2 uv = local.xz + 0.5;\n uv.y = 1.0 - uv.y;\n float4 tex = decal_tex.Sample(uv);\n\n return float4(tex.rgb * params.tint.rgb, tex.a * params.tint.a * fade);\n}\n";Expand description
decal.slang.