feat(webgpu-rt): atomic pixel accumulator + raysPerPixel for >1 ray/pixel/bounce (#30)
wfAccum becomes array<atomic<u32>> (4 slots/pixel, f32 bit patterns — same 16 B/pixel footprint) and rtAccumulate CASes each channel, so N rays for one pixel may resolve in the same SHADE pass without the read-modify-write racing. GENERATE clears with atomicStore (bitcast(0.0) == 0u); RESOLVE atomicLoads + bitcasts the vec4 it hands runResolve. Capacity half: RTPass::raysPerPixel (default 1) scales the wavefront ray/hit/payload buffers to raysPerPixel·W·H rays per bounce so the per-light emits actually fit instead of being dropped by rtEmitRay's capacity guard. The accumulator stays per-pixel. No flag-gating: uncontended the CAS succeeds first try — RTStress SHADE stays at its documented ~1.0 ms and master-vs-branch renders are byte-identical, so single-ray consumers pay nothing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c58424d519
commit
27d7e84cb3
6 changed files with 89 additions and 19 deletions
|
|
@ -270,7 +270,13 @@ void PipelineRTWebGPU::Init(WebGPUCommandEncoderRef /*cmd*/,
|
|||
wgsl += "fn wfGenerate(@builtin(global_invocation_id) gid: vec3<u32>) {\n";
|
||||
wgsl += " if (gid.x >= wfParams.surfaceW || gid.y >= wfParams.surfaceH) { return; }\n";
|
||||
wgsl += " let pixel = gid.y * wfParams.surfaceW + gid.x;\n";
|
||||
wgsl += " wfAccum[pixel] = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n";
|
||||
// wfAccum is 4 atomic<u32> slots per pixel (f32 bit patterns; see the
|
||||
// bindings prelude JS-side). bitcast<u32>(0.0) == 0u, so plain zeroes.
|
||||
wgsl += " let accumBase = pixel * 4u;\n";
|
||||
wgsl += " atomicStore(&wfAccum[accumBase + 0u], 0u);\n";
|
||||
wgsl += " atomicStore(&wfAccum[accumBase + 1u], 0u);\n";
|
||||
wgsl += " atomicStore(&wfAccum[accumBase + 2u], 0u);\n";
|
||||
wgsl += " atomicStore(&wfAccum[accumBase + 3u], 0u);\n";
|
||||
wgsl += " _wfPixel = pixel;\n";
|
||||
wgsl += " ";
|
||||
wgsl += raygenEntryFn;
|
||||
|
|
@ -300,7 +306,16 @@ void PipelineRTWebGPU::Init(WebGPUCommandEncoderRef /*cmd*/,
|
|||
wgsl += "fn wfResolve(@builtin(global_invocation_id) gid: vec3<u32>) {\n";
|
||||
wgsl += " if (gid.x >= wfParams.surfaceW || gid.y >= wfParams.surfaceH) { return; }\n";
|
||||
wgsl += " let pixel = gid.y * wfParams.surfaceW + gid.x;\n";
|
||||
wgsl += " let outc = runResolve(gid.xy, wfAccum[pixel]);\n";
|
||||
// Reassemble the vec4<f32> the resolve hook expects from the pixel's 4
|
||||
// atomic<u32> accumulator slots (no contention here — RESOLVE runs in
|
||||
// its own pass — but atomics may only be accessed atomically in WGSL).
|
||||
wgsl += " let accumBase = pixel * 4u;\n";
|
||||
wgsl += " let hdr = vec4<f32>(\n";
|
||||
wgsl += " bitcast<f32>(atomicLoad(&wfAccum[accumBase + 0u])),\n";
|
||||
wgsl += " bitcast<f32>(atomicLoad(&wfAccum[accumBase + 1u])),\n";
|
||||
wgsl += " bitcast<f32>(atomicLoad(&wfAccum[accumBase + 2u])),\n";
|
||||
wgsl += " bitcast<f32>(atomicLoad(&wfAccum[accumBase + 3u])));\n";
|
||||
wgsl += " let outc = runResolve(gid.xy, hdr);\n";
|
||||
wgsl += " textureStore(outImage, vec2<i32>(i32(gid.x), i32(gid.y)), outc);\n";
|
||||
wgsl += "}\n";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue