// HDRBloom composite (UI custom shader, dispatched via UIRenderer). Runs // inside the per-frame UI compute pass, so it owns the ping-pong `out` // texture that gets blitted to the canvas — the app's composite→swapchain // step. It reads the linear HDR scene (group 2 binding 0) and the blurred // bloom mip (group 2 binding 1, sampled through a filtering sampler — float // textures are filterable), adds them, tonemaps (Reinhard) and gamma- // corrects, then writes the rgba8unorm canvas. // // group 0 binding 0 — uniform UIDispatchHeader (auto-injected) // group 1 binding 0 — texture_storage_2d out (auto) // group 1 binding 1 — texture_2d prev (auto, unused) // group 2 binding 0 — texture_2d hdr scene (heap slot in push) // group 2 binding 1 — texture_2d bloom mip (heap slot in push) // group 2 binding 2 — sampler linear clamp (heap slot in push) struct UIDispatchHeader { outImage: u32, itemBuffer: u32, surfaceW: u32, surfaceH: u32, clipX: f32, clipY: f32, clipW: f32, clipH: f32, itemCount: u32, frameIdx: u32, flags: u32, _pad: u32, }; @group(0) @binding(0) var hdr : UIDispatchHeader; @group(1) @binding(0) var outTex : texture_storage_2d; @group(1) @binding(1) var prevTex : texture_2d; @group(2) @binding(0) var hdrTex : texture_2d; @group(2) @binding(1) var bloomTex : texture_2d; @group(2) @binding(2) var samp : sampler; const BLOOM_STRENGTH: f32 = 1.0; @compute @workgroup_size(8, 8, 1) fn main(@builtin(global_invocation_id) gid: vec3) { if (gid.x >= hdr.surfaceW || gid.y >= hdr.surfaceH) { return; } let coord = vec2(i32(gid.x), i32(gid.y)); let scene = textureLoad(hdrTex, coord, 0).rgb; let uv = (vec2(f32(gid.x), f32(gid.y)) + vec2(0.5)) / vec2(f32(hdr.surfaceW), f32(hdr.surfaceH)); let bloom = textureSampleLevel(bloomTex, samp, uv, 0.0).rgb; let lit = scene + bloom * BLOOM_STRENGTH; let mapped = lit / (lit + vec3(1.0)); // Reinhard tonemap let g = pow(mapped, vec3(1.0 / 2.2)); // gamma 2.2 textureStore(outTex, coord, vec4(g, 1.0)); }