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.