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
|
|
@ -21,12 +21,17 @@ compute pass, dispatch sizes driven by `dispatchWorkgroupsIndirect`.
|
|||
- **RESOLVE** (1 thread/pixel, 8×8): reads accum slot, runs user `resolve_main`
|
||||
if present else passthrough; writes outImage.
|
||||
|
||||
## Buffers (rtState, sized to 2*W*H rays)
|
||||
## Buffers (rtState; per-bounce ray capacity = `RTPass::raysPerPixel`·W·H)
|
||||
- `wfRaysA`,`wfRaysB`: array<WfRay>, ping/pong. WfRay = origin,tMin,dir,tMax,
|
||||
pixel,flags,cullMask,missIndex,sbtOffset,payloadSlot,kind,_pad.
|
||||
pixel,flags,cullMask,missIndex,sbtOffset,payloadSlot,kind,_pad. Each holds
|
||||
one bounce's rays; `raysPerPixel` (default 1) scales them so closest-hit
|
||||
can emit several rays per pixel in one bounce before rtEmitRay drops.
|
||||
- `wfHits`: array<HitResult> (sized = ray capacity).
|
||||
- `wfPayload`: array<Payload> — declared in CODEGEN region after user Payload.
|
||||
- `wfAccum`: array<vec4<f32>> per pixel (W*H).
|
||||
- `wfAccum`: array<atomic<u32>>, 4 slots per pixel (W*H, RGBA as f32 bit
|
||||
patterns — 16 B/pixel). `rtAccumulate` CASes each channel, so a SHADE
|
||||
invocation may emit several rays for the same pixel in one bounce (one
|
||||
shadow ray per light, issue #30) without the accumulates racing.
|
||||
- `wfCounters`: atomic counters: emitA, emitB, trace dispatch args, etc.
|
||||
- `wfIndirect`: INDIRECT dispatch-args buffer.
|
||||
|
||||
|
|
@ -74,6 +79,15 @@ maxDepth=1 (primary only). Sponza maxDepth=2 (primary + shadow).
|
|||
- [x] device limits (maxBufferSize / maxStorageBufferBindingSize /
|
||||
maxComputeWorkgroupsPerDimension) + timestamp-query feature
|
||||
- [x] megakernel dead path removed (RT pipeline builds only wavefront)
|
||||
- [x] atomic pixel accumulator (#30) — `rtAccumulate` is a per-channel
|
||||
f32 CAS over `array<atomic<u32>>`, lifting the old one-ray-per-pixel-
|
||||
per-bounce cap so closest-hit can emit one shadow ray per light in a
|
||||
single bounce (multi-light shadowing; 3DForts #153). Same 16 B/pixel
|
||||
footprint; uncontended CAS succeeds first try, so single-ray scenes
|
||||
(VulkanTriangle/Sponza/RTStress) are unaffected. Capacity side:
|
||||
`RTPass::raysPerPixel` (default 1) scales the ray/hit/payload buffers
|
||||
so those N rays/pixel actually fit a bounce instead of being dropped
|
||||
by rtEmitRay's capacity guard. Exercised by `examples/RTMultiShadow`.
|
||||
- [~] binding packing (Phase 7): SKIPPED — target device reports 64 storage
|
||||
buffers/stage (≥12), so the merge is unnecessary (issue makes it
|
||||
conditional on <12). NOTE: this only holds because dom-webgpu.js now
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue