pub const SSR: &str = "// Screen-space reflection resolve: a fullscreen ray-march over the pre-pass\n// G-buffer that writes reflected radiance (.rgb) and the composite weight (.a)\n// the reflection blur + composite then blends over the scene. Single source for\n// every backend; pairs with `fullscreen_vertex` in fullscreen.slang.\n//\n// Compile with -DMAX_PROBES=<n> (the reflection-probe array length the device\n// binds). Vulkan gets set 0 bindings 0-3 plus the forward global set\'s probe\n// bindings (7 + 8) at set 1, which is how the hand-written GLSL bound them;\n// Metal gets texture(0..3) for the screen sources and the prefilter cube,\n// texture(4..4+MAX_PROBES) for the probe cubes, the push constant at buffer(0)\n// and the probe set at buffer(1). DXIL gets t0..t3 / s0..s3 for the screen\n// sources, b0 for the params and b1 for the probe set, and (under\n// SPLIT_PROBE_SAMPLER) the probe cube array at t4.. with its one sampler at s4.\n\n#ifndef MAX_PROBES\n#define MAX_PROBES 8\n#endif\n\n{POST_COMMON}\n\n// Layout matches `SsrParams` in render_types.rs (144 B).\nstruct SsrParams\n{\n float intensity;\n float max_distance;\n float tan_half_fov_y;\n float aspect;\n float stride;\n float thickness;\n // IBL prefilter cubemap mip count; 0 means no EnvironmentMap is bound and\n // the cube fallback is skipped (missed rays keep the base shading).\n float prefilter_mip_count;\n float _pad;\n // Camera-to-world transform (the rigid inverse of the view matrix): its 3x3\n // turns the view-space reflection ray into the world-space direction the\n // cubemap is sampled with, and its translation column lets the resolve\n // rebuild the world-space surface position a probe box-projects against.\n float4x4 inv_view;\n // Rows of the rotation from world space into the sky cube\'s baked frame;\n // identity when the sky does not turn.\n float4 sky_rot[3];\n};\n\n// A world direction in the sky cube\'s own frame; the sky fallback goes through\n// it so a resolved reflection matches the sky the main pass shows.\n#define SKY_DIR(d) float3(dot(params.sky_rot[0].xyz, (d)), \\\n dot(params.sky_rot[1].xyz, (d)), \\\n dot(params.sky_rot[2].xyz, (d)))\n\n{PROBE_TYPES}\n\n// The push constant leads so it lands on Metal\'s buffer(0), where every post\n// pass puts its params and where the host writes it.\n[[vk::push_constant]]\nConstantBuffer<SsrParams> params;\n\n[[vk::binding(0, 0)]] Sampler2D<float4> scene;\n[[vk::binding(1, 0)]] Sampler2D<float4> gbuffer;\n[[vk::binding(2, 0)]] Sampler2D<float4> rough_tex;\n[[vk::binding(3, 0)]] SamplerCube<float4> prefilter;\n\n// The forward global set, bound here only for its reflection-probe count +\n// per-probe parallax boxes + cube array: a screen-space ray that escapes the\n// frame falls back to the local probe capture instead of the foreign sky cube.\n[[vk::binding(7, 1)]] ConstantBuffer<ProbeSet> probe_set;\n#define PROBE_SET probe_set\n\n// The probe cube array, in whichever form its host can bind.\n//\n// A top-level combined `SamplerCube` array lowers to a texture array plus a\n// sampler array, and D3D12 binds a shader sampler array only through a\n// descriptor table -- a static sampler covers a single register, so twelve of\n// them still leave s4..s11 \"not fully bound\". SPLIT_PROBE_SAMPLER therefore\n// declares the array separated, with one sampler the root signature hands out\n// statically, the same shape the bindless main pass uses for the same array.\n// Metal takes an argument buffer, which is what keeps the array out of the\n// texture namespace slangc numbers by declaration order. Vulkan keeps the\n// combined form on the forward global set\'s binding 8, which is what it binds.\n#if defined(SPLIT_PROBE_SAMPLER)\nTextureCube<float4> probe_cubes[MAX_PROBES];\nSamplerState probe_cube_sampler;\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cubes[i].SampleBias(probe_cube_sampler, dir, lod).rgb;\n}\n#elif defined(METAL_ABI)\n// Metal reaches the cubes through an argument buffer: a resource array at\n// global scope emits with no [[texture(n)]], and the compiler then places it\n// at whatever slot happens to be unused.\nstruct ProbeCubes\n{\n TextureCube<float4> probe_cubes[MAX_PROBES];\n};\nParameterBlock<ProbeCubes> probe_cube_set : register(b11);\nSamplerState probe_cube_sampler;\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cube_set.probe_cubes[i].SampleBias(probe_cube_sampler, dir, lod).rgb;\n}\n#else\n[[vk::binding(8, 1)]] SamplerCube<float4> probe_cubes[MAX_PROBES];\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cubes[i].SampleBias(dir, lod).rgb;\n}\n#endif\n\nstatic const int SSR_MAX_STEPS = 48;\nstatic const int SSR_REFINE = 5;\n// Surfaces rougher than REFLECTION_ROUGHNESS_CUT get no SSR; glossiness ramps\n// in below it. Locked to concinnity_core::gfx::ssr::REFLECTION_ROUGHNESS_CUT by\n// unit test so the SSR, RT, and composite passes can never disagree on it.\nstatic const float REFLECTION_ROUGHNESS_CUT = 0.6;\n// Dielectric base reflectance (water, glass, polished stone) for the Fresnel.\nstatic const float SSR_F0 = 0.04;\n// UV margin over which a hit near the screen border fades out.\nstatic const float SSR_EDGE_FADE = 0.12;\n\n// Rebuild a view-space position from a UV and its linear (view-space) depth.\n// The inverse of ssr_project; matches ssao_view_pos in the SSAO kernel.\nfloat3 ssr_view_pos(float2 uv, float depth, float tan_y, float aspect)\n{\n float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);\n return float3(ndc.x * tan_y * aspect, ndc.y * tan_y, -1.0) * depth;\n}\n\n// Project a view-space point (z < 0, in front of the camera) to a screen UV.\nfloat2 ssr_project(float3 q, float tan_y, float aspect)\n{\n float inv = 1.0 / max(-q.z, 1e-4);\n float2 ndc = float2(q.x * inv / (tan_y * aspect), q.y * inv / tan_y);\n return float2(ndc.x * 0.5 + 0.5, 1.0 - (ndc.y * 0.5 + 0.5));\n}\n\n{PROBE_COMMON}\n\n[shader(\"fragment\")]\nfloat4 ssr_resolve_fragment([[vk::location(0)]] float2 uv : TEXCOORD0) : SV_Target\n{\n float3 base = scene.Sample(uv).rgb;\n float4 c = gbuffer.Sample(uv);\n float depth = c.a;\n // Background / sky, or a non-reflecting (too-rough) surface: weight 0 so the\n // reflection composite keeps the scene there. The resolve does not blend\n // inline; it writes reflected radiance (.rgb) + composite weight (.a).\n if (depth <= 0.0)\n {\n return float4(base, 0.0);\n }\n\n float roughness = rough_tex.Sample(uv).r;\n // Glossy surfaces reflect sharply; rough ones get nothing.\n float gloss = saturate((REFLECTION_ROUGHNESS_CUT - roughness) / REFLECTION_ROUGHNESS_CUT);\n if (gloss <= 0.0)\n {\n return float4(base, 0.0);\n }\n\n float3 n = normalize(c.xyz);\n float3 p = ssr_view_pos(uv, depth, params.tan_half_fov_y, params.aspect);\n float3 v = normalize(-p); // p in view space, camera at origin\n float3 r_dir = reflect(-v, n); // reflected ray direction\n\n // Environment fallback for a missed (or screen-edge) ray, in the reflected\n // direction at a roughness-keyed mip so a rougher surface reflects a\n // blurrier environment. With a baked reflection probe this is the local\n // scene capture (box-projected + blended across covering probes), the same\n // source the forward IBL specular term uses, rather than the foreign sky\n // HDR; otherwise it is the IBL prefilter cube. With no EnvironmentMap bound\n // there is nothing to fall back to, so missed rays keep the base shading.\n bool ibl = params.prefilter_mip_count > 0.5;\n float3 env = base;\n if (ibl)\n {\n float3 r_world = mul((float3x3)params.inv_view, r_dir);\n float lod = roughness * (params.prefilter_mip_count - 1.0);\n if (probe_set.count > 0u)\n {\n // The full inv_view (its translation column carries the camera\n // position) lifts the view-space surface point p to world space,\n // which the probe box-projection needs.\n float3 world_pos = mul(params.inv_view, float4(p, 1.0)).xyz;\n env = probe_set_specular(world_pos, r_world, lod);\n }\n else\n {\n env = prefilter.SampleLevel(SKY_DIR(r_world), lod).rgb;\n }\n }\n\n float3 step_v = r_dir * params.stride;\n float3 q = p;\n bool hit = false;\n float2 hit_uv = uv;\n int steps_taken = SSR_MAX_STEPS;\n for (int i = 0; i < SSR_MAX_STEPS; i++)\n {\n q += step_v;\n if (q.z >= 0.0) { steps_taken = i; break; } // crossed the camera plane\n float2 march_uv = ssr_project(q, params.tan_half_fov_y, params.aspect);\n if (march_uv.x < 0.0 || march_uv.x > 1.0 || march_uv.y < 0.0 || march_uv.y > 1.0)\n {\n steps_taken = i;\n break;\n }\n float scene_depth = gbuffer.Sample(march_uv).a;\n if (scene_depth <= 0.0) continue; // sky here - keep marching\n float diff = (-q.z) - scene_depth; // > 0: ray is behind the surface\n if (diff > 0.0 && diff < params.thickness)\n {\n // Binary-search refine between the last two samples.\n float3 lo = q - step_v;\n float3 hi = q;\n for (int r = 0; r < SSR_REFINE; r++)\n {\n float3 mid = (lo + hi) * 0.5;\n float2 muv = ssr_project(mid, params.tan_half_fov_y, params.aspect);\n float sd = gbuffer.Sample(muv).a;\n if (sd > 0.0 && (-mid.z) - sd > 0.0) hi = mid; else lo = mid;\n }\n hit_uv = ssr_project(hi, params.tan_half_fov_y, params.aspect);\n hit = true;\n steps_taken = i;\n break;\n }\n }\n\n // The reflected colour: the screen-space hit (a single sharp tap - the\n // reflection composite blurs it by roughness), or the environment cube when\n // the ray missed. A hit near the screen border or at the end of its march\n // fades toward the environment rather than snapping flat to the base\n // shading.\n float3 reflected;\n if (hit)\n {\n float3 hit_color = scene.Sample(hit_uv).rgb;\n float2 e = smoothstep(float2(0.0), float2(SSR_EDGE_FADE), hit_uv)\n * smoothstep(float2(0.0), float2(SSR_EDGE_FADE), float2(1.0) - hit_uv);\n float edge = e.x * e.y;\n float march = float(steps_taken) / float(SSR_MAX_STEPS);\n float dist_fade = 1.0 - smoothstep(0.7, 1.0, march);\n reflected = lerp(env, hit_color, edge * dist_fade);\n }\n else\n {\n reflected = env;\n }\n\n float ndv = saturate(dot(n, v));\n float fresnel = SSR_F0 + (1.0 - SSR_F0) * pow(1.0 - ndv, 5.0);\n float w = saturate(fresnel * gloss * params.intensity);\n // Reflected radiance (.rgb) + composite weight (.a). The reflection\n // composite blurs this by surface roughness and blends it over the scene.\n return float4(reflected, w);\n}\n";Expand description
ssr.slang.