Skip to main content

AUTO_EXPOSURE

Constant AUTO_EXPOSURE 

Source
pub const AUTO_EXPOSURE: &str = "// Auto-exposure histogram kernels: single source for every backend.\n//\n// One entry per compile, selected by a define, so each variant declares exactly\n// the resources it binds (Metal and DXIL assign slots in declaration order, so\n// an unused declaration would shift the live ones):\n//\n//   AE_BUILD   - histogram_build: one thread per HDR-resolve pixel. Each\n//                threadgroup keeps a local 256-bin histogram in groupshared\n//                memory, atomically increments its own bin per pixel, and\n//                merges the local counts into the global histogram on exit.\n//                The local stage absorbs most of the contention, so the\n//                per-frame global atomics scale to large resolves cheaply.\n//   AE_AVERAGE - histogram_average: one threadgroup of 256 threads reduces the\n//                histogram to a single count-weighted average log-luminance and\n//                clears it for the next frame.\n//\n// The HDR resolve is only ever fetched by integer coordinate, on every backend,\n// so it is declared as a plain texture rather than a combined texture-sampler:\n// Metal then binds no sampler for this pass, exactly as it did before the port.\n// Vulkan\'s descriptor at set 0 binding 0 is a COMBINED_IMAGE_SAMPLER and stays\n// one -- a combined descriptor satisfies a sampled-image declaration, so the\n// set layout is untouched.\n//\n// HISTOGRAM_BINS mirrors gfx::auto_exposure::HISTOGRAM_BINS; AutoExposureParams\n// mirrors the 16-byte struct each backend\'s uniforms module pushes.\n\nstatic const uint HISTOGRAM_BINS = 256u;\n\nstruct AutoExposureParams\n{\n    // Lowest log2(luminance) the bins span. Pixels darker than this fall in\n    // bin 0 and are weighted out by the average pass.\n    float lum_log2_min;\n    // Width of the log2(luminance) range the histogram covers.\n    float lum_log2_range;\n    // Pre-computed HISTOGRAM_BINS / lum_log2_range, so the build kernel maps a\n    // centred log-luminance to a bin index without a per-pixel divide.\n    float lum_to_bin_scale;\n    float _pad;\n};\n\n// The params slot is a host difference, not a target one: Vulkan pushes them\n// and DirectX hands them over as root constants at b0 (which is where a bare\n// push constant lands on that target), while the Metal encoder writes them to a\n// different buffer index per kernel. METAL_BINDINGS carries that index.\n#ifdef METAL_BINDINGS\n#ifdef AE_BUILD\nConstantBuffer<AutoExposureParams> params : register(b1);\n#else\nConstantBuffer<AutoExposureParams> params : register(b2);\n#endif\n#else\n[[vk::push_constant]] ConstantBuffer<AutoExposureParams> params;\n#endif\n\n#ifdef AE_BUILD\n\n[[vk::binding(0, 0)]] Texture2D<float4> hdr_texture;\n[[vk::binding(1, 0)]] RWStructuredBuffer<uint> histogram : register(u0);\n\ngroupshared uint local_hist[HISTOGRAM_BINS];\n\n[shader(\"compute\")]\n[numthreads(16, 16, 1)]\nvoid histogram_build(uint3 gid : SV_DispatchThreadID, uint tid : SV_GroupIndex)\n{\n    // 16x16 == 256 == HISTOGRAM_BINS exactly, so one thread clears one bin.\n    if (tid < HISTOGRAM_BINS)\n    {\n        local_hist[tid] = 0u;\n    }\n    GroupMemoryBarrierWithGroupSync();\n\n    uint w, h;\n    hdr_texture.GetDimensions(w, h);\n    if (gid.x < w && gid.y < h)\n    {\n        float3 c = hdr_texture.Load(int3(int2(gid.xy), 0)).rgb;\n        // Rec. 709 luminance. The 1e-6 floor keeps log2 finite on a fully black\n        // pixel, which would otherwise reach bin 0 through a -inf clamp.\n        float lum = max(dot(c, float3(0.2126, 0.7152, 0.0722)), 1.0e-6);\n        float lum_log2 = clamp(\n            log2(lum),\n            params.lum_log2_min,\n            params.lum_log2_min + params.lum_log2_range);\n        float t = (lum_log2 - params.lum_log2_min) * params.lum_to_bin_scale;\n        uint bin = min(uint(t), HISTOGRAM_BINS - 1u);\n        uint prev;\n        InterlockedAdd(local_hist[bin], 1u, prev);\n    }\n    GroupMemoryBarrierWithGroupSync();\n\n    if (tid < HISTOGRAM_BINS)\n    {\n        uint count = local_hist[tid];\n        if (count > 0u)\n        {\n            uint prev;\n            InterlockedAdd(histogram[tid], count, prev);\n        }\n    }\n}\n\n#elif defined(AE_AVERAGE)\n\n[[vk::binding(0, 0)]] RWStructuredBuffer<uint> histogram : register(u0);\n[[vk::binding(1, 0)]] RWStructuredBuffer<float> output_avg : register(u1);\n\ngroupshared uint reduce_counts[HISTOGRAM_BINS];\ngroupshared float reduce_weighted[HISTOGRAM_BINS];\n\n[shader(\"compute\")]\n[numthreads(256, 1, 1)]\nvoid histogram_average(uint tid : SV_GroupIndex)\n{\n    uint count = histogram[tid];\n    // Clear for the next frame\'s build pass. Safe here because every thread has\n    // already read its bin and the reduction below runs on the groupshared\n    // copies.\n    histogram[tid] = 0u;\n\n    // Drop the sub-floor bin so a mostly-black frame does not peg the average.\n    uint effective_count = (tid == 0u) ? 0u : count;\n    float step = params.lum_log2_range / float(HISTOGRAM_BINS);\n    float centre = params.lum_log2_min + (float(tid) + 0.5) * step;\n    reduce_counts[tid] = effective_count;\n    reduce_weighted[tid] = centre * float(effective_count);\n    GroupMemoryBarrierWithGroupSync();\n\n    for (uint stride = HISTOGRAM_BINS / 2u; stride > 0u; stride >>= 1u)\n    {\n        if (tid < stride)\n        {\n            reduce_counts[tid] += reduce_counts[tid + stride];\n            reduce_weighted[tid] += reduce_weighted[tid + stride];\n        }\n        GroupMemoryBarrierWithGroupSync();\n    }\n\n    if (tid == 0u)\n    {\n        output_avg[0] = (reduce_counts[0] > 0u)\n            ? (reduce_weighted[0] / float(reduce_counts[0]))\n            : params.lum_log2_min;\n    }\n}\n\n#else\n#error \"auto_exposure.slang: define AE_BUILD or AE_AVERAGE\"\n#endif\n";
Expand description

auto_exposure.slang.