// DOM-mode port of inverse-circle.comp.glsl. Inverts RGB inside each // user-supplied circle; passes through every other pixel so the // ping-pong carries the prior dispatch's scene forward. // // Bind-group contract (mirrors :WebGPUComputeShader.cppm): // 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) // group 2 binding 0 — items SSBO declared by the C++ side via // UICustomBinding { group=2, binding=0, // kind=Buffer, pushOffset=offsetof(hdr.itemBuffer) } 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; struct InverseCircleItem { centerRadius: vec4, }; @group(2) @binding(0) var items : array; @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 sp = vec2(f32(gid.x) + 0.5, f32(gid.y) + 0.5); // Max-coverage over all items, so overlapping circles don't // double-invert back to the original. var coverage: f32 = 0.0; for (var i: u32 = 0u; i < hdr.itemCount; i = i + 1u) { let it = items[i]; let c = it.centerRadius.xy; let r = it.centerRadius.z; let d = length(sp - c) - r; coverage = max(coverage, clamp(0.5 - d, 0.0, 1.0)); } var dst = textureLoad(prevTex, coord, 0); if (coverage > 0.0) { dst = vec4(mix(dst.rgb, vec3(1.0) - dst.rgb, coverage), dst.a); } textureStore(outTex, coord, dst); }