Skip to content

The live passes in a browser as natively: every pass at most eight storage buffers, merged.rs's live half gone - #69

Merged
yujiaxu-ops merged 1 commit into
mainfrom
live-split2
Nov 13, 2026
Merged

yujiaxu-ops merged 1 commit into
mainfrom
live-split2

Conversation

@yujiaxu-ops

@yujiaxu-ops yujiaxu-ops commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Step 2 of the plan for the live passes' layout, after #68. Three passes bound more than a browser's eight storage buffers per stage, so the browser needed its own merged layout: a rewrite of the live source and slabs indexed through an offsets uniform. This PR rearranges those three so every pass binds at most eight. The browser now uses the native layout, one per pass. Tier R1/R2: pictures change only by rounding (see the checks below).

The change

  • post (13 storage buffers): the view modes become passes of their own before it (view_raw, view_hdr, view_sample). A frame showing the lit picture does not run them. The per-pass profile times an empty pass for each, so its timestamps keep PASSES' order.
    • One behaviour change: an HDR snapshot taken in a view mode is now that view's signal. Before, it was the lit image. Only a scratch tool switches view modes.
  • compose (11): the upscaler's per-sample state (Flicker, Lock) moves to a pass of its own after it, track.
  • dtemporal (10): the surfaces' compact copy (sb) is now written by refl, which reads each sample whole anyway. A separate pass for it cost 0.18 ms, because it read the samples a second time.
  • The sample's parts are read separately (sample_diffuse, sample_specular, sample_emission), so a pass binds the resolved reflection only if it takes the specular part.
  • merged.rs's live half is gone: the slabs, the offsets uniform and the rewrite of the live source.
    • The kernel's outputs that the live passes read now start at multiples of 256 bytes in its merged buffer (@align(256), with the struct's size rounded to match), so a page can bind those ranges directly.
  • Two guards: a unit test (every_pass_fits_a_browser) holds every pass to eight, and a view in the merged layout checks the same when it starts.

Merge gate

check result
cargo test --release 125 passed
render-identity vs #68's build, native 168 byte-identical, 19 within run-to-run spread, 3 differ by rounding: particles-motion-repose (277 pixels, at most ~3e-6 nits, all on dark pixels), wind-pivot-sway-live and voxel-cut-body-push (~3e-5 relative)
render-identity, THREERT_MERGED=1 170 / 17 / 3 by rounding: skinning-shared-pose-split-live, volume-grid-uniform-live, wind-pivot-sway-live (at most 7.5e-5 nits, on values of 1 to 4)
view modes 1–8 (material tests, THREERT_VIEW_MODE) every PNG byte-identical in every mode
tools/web-hello.ts in Chrome on this GPU pass (the adapter reports 10 storage buffers per stage; the device is held to 8)
cost +0.18 ms, almost all from the compose / track split (track 0.27 ms, compose 0.08 ms less). Bistro, 1440p at 0.375, alternated: the passes after the trace 6.00–6.08 → 6.19–6.24 ms. The sb copy in refl costs +0.02; the view passes cost nothing in the lit picture.

The rounding differences come from code that moved: the same expressions, split or merged differently, compile to arithmetic that differs in the last bits, and a temporal test carries those bits along. The largest absolute difference is 7.5e-5 nits.

ROADMAP § 1a records the step.

🤖 Generated with Claude Code


Note

Medium Risk
Touches the full live rendering pipeline and browser binding path; identity checks show tiny rounding drift and a small perf hit, but view modes stay byte-identical.

Overview
Rearranges the live post chain so every compute pass binds at most eight storage buffers, letting the browser use the same per-pass bind layouts as native and removing merged.rs's live shader rewrite (slabs, offsets uniform, merged bind group).

Shader / pass split: view-mode visualization moves out of post into view_raw, view_hdr, and view_sample (skipped in the normal lit frame). Upscaling flicker/lock state moves from compose to new track. Surface buffer sb is written in refl instead of dtemporal. sample_parts becomes sample_diffuse / sample_specular / sample_emission so passes only pull the buffers they need.

Rust: live/targets.rs always builds one bind group layout per pass; kernel merged buffers expose live-readable ranges at 256-byte alignment for WebGPU offset rules. layout::every_pass_fits_a_browser and runtime checks in merged mode enforce the eight-buffer cap. Docs (ROADMAP, RUNTIME) reflect the new model and measured +0.18 ms cost from the compose/track split.

Reviewed by Cursor Bugbot for commit 14b5f39. Bugbot is set up for automated code reviews on this repo. Configure here.

…orage buffers, merged.rs's live half gone

The three passes over a browser's eight, rearranged; the browser's layout is now the native one, a layout a pass.

- post (13): the view modes are passes of their own before it, not run in the lit picture (view_raw, view_hdr,
  view_sample); an HDR snapshot in a view mode is now that signal.
- compose (11): the upscaler's state of each sample (Flicker, Lock) is a pass of its own after it, track.
- dtemporal (10): the surfaces' compact copy (sb) is written by refl, which reads each sample whole anyway.
- The sample's parts are read apart (sample_diffuse, _specular, _emission).
- merged.rs: the slabs, the offsets' uniform and the live source's rewrite are gone; the kernel's outputs the passes
  read start at multiples of 256 bytes in its merged buffer (@align(256); the struct's size rounded to it), so a page
  binds those ranges. A unit test holds every pass to eight, and a view in the merged layout checks it as it starts.

Checks: cargo test 125 passed. render-identity against the build before, native: 3 of 190 outputs differ by rounding
(particles-motion-repose 277 pixels, at most ~3e-6 nits on dark ones; wind-pivot-sway-live, voxel-cut-body-push
~3e-5 relative); THREERT_MERGED=1: 3 (at most 7.5e-5 nits on values of 1 to 4). View modes 1-8: every PNG
identical (the material tests). tools/web-hello.ts in Chrome: pass. Cost +0.18 ms (Bistro, 1440p at 0.375,
alternated: track 0.27 less compose's 0.08; the surfaces' copy in refl +0.02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yujiaxu-ops
yujiaxu-ops merged commit 48b7e6f into main Nov 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant