From ac5a0b688ad97c783ce87adc2120bb3d9e71fa3d Mon Sep 17 00:00:00 2001 From: yujiaxu-ops Date: Fri, 13 Nov 2026 23:02:32 +0800 Subject: [PATCH] CI runs the renderer's unit tests; the binding counts #63 and #65 left stale in the docs corrected CI's renderer job built the renderer and ran none of its ~130 unit tests, though all but a few need no GPU. It now runs `cargo test --release --locked` after the build: on the runner, which has no GPU, the seven tests that open a device say "skipped" and return (checked on a Mac with no adapter: 124 pass, those seven among them). The job is named for it; no check is required by name. The cleanup (#63, the reservoirs out) and the buffer layout (#65, 28 -> 24 live storage buffers, one bind group) left docs/RUNTIME.md describing the kernel's 20 and the live passes' 28 storage buffers, seven group-1 buffers with the reservoirs, the history's two bind groups; merged.rs and gpu.rs comments had the old counts. ROADMAP "Known issues": under software traversal 24 tests fail on main now, the 23 recorded and wind/pivot-sway-live (9.241 for 10; hardware traversal passes it). WORKFLOW.md says software traversal is in no gate. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 6 +++++- docs/ROADMAP.md | 3 +++ docs/RUNTIME.md | 16 ++++++++-------- docs/WORKFLOW.md | 5 +++-- renderer/src/gpu.rs | 3 ++- renderer/src/merged.rs | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89e9196..f7ebc3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: - run: node tools/npm-pack.ts renderer: - name: "renderer: release build" + name: "renderer: release build, unit tests" needs: changes if: needs.changes.outputs.renderer == 'true' runs-on: ubuntu-26.04 @@ -96,6 +96,10 @@ jobs: with: workspaces: renderer - run: cd renderer && cargo build --release --locked + # The renderer's unit tests (the merge gate's `cargo test --release`). The runner has no GPU: the few tests that + # open a device say "skipped" and return, the rest (scene, world, script, voxel, physics, the merged layout's + # naga checks, the kernel's source switches) run as on a Mac. + - run: cd renderer && cargo test --release --locked # The voxel bodies and the physics on the CPU, through the engine's own path (ROADMAP § 6 step 8), on the # physics' own threads as the renderer steps (src/physics.rs `in_pool`; tools/physics-determinism.ts steps from # outside any pool, which is not that path): the recorded hashes at 2 and 4 threads (native parallel = native diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b20eb4b..5e7d338 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -4835,6 +4835,9 @@ benchmark level. The order follows the dependencies; a step that meets a further Basis: the scene is one plane with the library's facade windows and no instance, so nothing of step 7b is in its kernel (the source is main's byte for byte), and main's build reads the same 0.3934 in both layouts. The cause was not looked for. + - 2026-11-13 (main `8746130`, 190 tests): 24 fail, the 23 above and `wind/pivot-sway-live`, which reads 9.241 for + 10 (its offline twin `wind/pivot-sway` passes; hardware traversal passes both). The same 24 in the merged layout. + Not in any gate: CI runs no render test, and the merge gate runs them on hardware traversal only. - Offline renders of `bistro`, `bistro-slice`, `bistro-district`, `zero-day` and `zero-day-animated` are not the same bytes twice under hardware traversal (measured 2026-11-11 in § 6 step 7b's merge gate; the last entry here has Bistro's ties between coincident surfaces at 2–4 %). Main `a707e1c` against itself, 320 × 180, 4 spp: 1,782 / diff --git a/docs/RUNTIME.md b/docs/RUNTIME.md index 66fba4d..942d9fb 100644 --- a/docs/RUNTIME.md +++ b/docs/RUNTIME.md @@ -688,19 +688,19 @@ tree (ROADMAP § 6). the node host as `GameSession` does, and sends the canvas' clicks down the same path. `node tools/build-web.ts [game.ts] --serve` builds and serves it (needs the wasm-bindgen CLI of the crate's version). - **The merged binding layout** (`renderer/src/merged.rs`). WebGPU gives a stage 8 storage buffers by default; the - kernel binds 20 and the live passes 28. There is one source: the browser's shaders are written from + kernel binds 19 and the live passes 24. There is one source: the browser's shaders are written from `pathtrace.wgsl` and `live.wgsl` as text when a module is compiled, and the native ones are untouched. - The kernel's seven tables become the arrays of one struct in one buffer; their lengths are in the source, each a power of two above a floor, so a kernel is compiled again only when a table doubles. The renderer keeps writing its own buffers and a frame copies them into the struct's. - - The kernel's seven group-1 buffers (radiance cache, reservoirs, what the live passes read of a trace) are ranges - of one buffer, the arrays of a second struct. - - The live passes' buffers of one element type are ranges of a slab each, indexed from offsets in a uniform: the - history's two bind groups are two sets of offsets. + - The kernel's six group-1 buffers (the radiance cache, what the live passes read of a trace) are ranges of one + buffer, the arrays of a second struct. + - The live passes' buffers of one element type are ranges of a slab each, indexed from offsets in a uniform. A + lasting buffer's two halves (this frame's and the last, `LiveParams.state`) lie in its range, as natively. - A use of a merged name the generator does not know, a declaration not found exactly once, a name it would add that is taken: each stops the renderer, with the line. Locals that shadow a name are followed through their scopes. A test reads the result with naga: valid WGSL, 8 storage buffers for the kernel and for the live passes - (20 and 28 as written), the structs' arrays where the Rust side puts them. + (19 and 24 as written), the structs' arrays where the Rust side puts them. - `THREERT_MERGED=1` runs it natively (software traversal, the device held to 8). - **The loop guard** (`merged::guard`). The kernel's traversals follow indices they read from buffers and end only on sound data. On 2026-11-11 two dispatches that did not return took this machine's display down (WindowServer's @@ -1205,10 +1205,10 @@ on the device", and differs where it says so. | `emitters` (6) | the emitter CDF and the light tree | storage: megabytes in a lit scene | | `env_cdf` (10) | the environment's CDF, then the procedural geometry's data | storage (megabytes); its light sampling is off on Android, below | | `tri_verts` (1), `attrs` (2), `nodes` (3), `prev_verts` (16) | per triangle | storage (megabytes) | - | `accum` (11), group 1 (radiance cache, reservoirs, the trace's outputs, the loop guard's count) | per pixel, per cache entry | written by the kernel: cannot be uniform | + | `accum` (11), group 1 (radiance cache, the trace's outputs, the loop guard's count) | per pixel, per cache entry | written by the kernel: cannot be uniform | | group 2, the translucency pass (`timg`, `vpts`, `vcount`, `tlow`) | per pixel | written by the pass | | the particle passes' `systems`, `spawns`, `grid`; the grid programs' `srcs` | small, read-only | storage: other shaders, of eleven bindings at most and no acceleration structure; not known to misread (the fix on the device left them alone) | - | the live passes (`live.wgsl`: 28 storage buffers, `ae_bins` and `ae_state` among them) | per pixel; two tiny | storage: written by the passes; the device showed their output right | + | the live passes (`live.wgsl`: 24 storage buffers, `ae_bins` and `ae_state` among them) | per pixel; two tiny | storage: written by the passes; the device showed their output right | Two of these are what a device run should look at first: `prev_verts` is the one storage buffer left after the acceleration structure's binding (13) and feeds the motion vectors of moving objects and the vertex colours; diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 8ce0438..dbb2bf6 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -141,7 +141,8 @@ main. Branches are kept. `.github/workflows/ci.yml` runs on every push to main and every PR, as parallel jobs: - `ts`: `npm run typecheck`, `npm test`; -- `renderer`: the renderer's release build; +- `renderer`: the renderer's release build and its unit tests (`cargo test --release`; the runner has no GPU, so the + tests that open a device skip themselves); - `voxel`: the voxel core's wasm32 build and tests, and native / wasm32 edit determinism; - `physics`: the physics core's wasm32 build and tests, and native / wasm32 determinism (a script and 1,200 random worlds); - `wasm`: the engine library's wasm32 build; @@ -150,7 +151,7 @@ main. Branches are kept. A PR runs a job only when it changes that job's inputs (the paths in the workflow's `changes` job; a change to the workflows runs them all); a push to main runs them all. `threert` sums them up and is the check to read before merging: it fails when a job failed or was cancelled, and passes when each one passed or was skipped by its paths. -The GPU checks (`test:render`, the perf smoke, the benches, the browser test `tools/web-hello.ts`) run on a Mac, per the merge gate above. +The GPU checks (`test:render`, the perf smoke, the benches, the browser test `tools/web-hello.ts`) run on a Mac, per the merge gate above, on hardware traversal only: what software traversal draws (the browser's, a device without ray queries) is in no gate yet (ROADMAP "Known issues"). ## The npm packages diff --git a/renderer/src/gpu.rs b/renderer/src/gpu.rs index a92217a..e400f4d 100644 --- a/renderer/src/gpu.rs +++ b/renderer/src/gpu.rs @@ -1447,7 +1447,8 @@ impl Gpu { // layout with more does not validate). let merged = cfg!(target_arch = "wasm32") || std::env::var("THREERT_MERGED").is_ok_and(|v| v == "1"); limits.max_storage_buffers_per_shader_stage = if merged { crate::merged::STORAGE_BUFFERS } else { al.max_storage_buffers_per_shader_stage.min(31) }; - // Storage + uniform buffers together (the live passes bind 29; Metal allows 31). + // Storage + uniform buffers together (the kernel binds 19 storage buffers, the live passes 24, each beside its + // uniforms; Metal allows 31). limits.max_buffers_and_acceleration_structures_per_shader_stage = al.max_buffers_and_acceleration_structures_per_shader_stage.min(31); limits.max_acceleration_structures_per_shader_stage = al.max_acceleration_structures_per_shader_stage; limits.max_blas_geometry_count = al.max_blas_geometry_count; diff --git a/renderer/src/merged.rs b/renderer/src/merged.rs index 2d564f4..f461f53 100644 --- a/renderer/src/merged.rs +++ b/renderer/src/merged.rs @@ -654,7 +654,7 @@ mod tests { assert!(out.ends_with("{ return objects[min(u32(a[1] * 3u + 1u), 4095u)].w + tex_info[min(u32(u32(objects[min(u32(k), 4095u)].x)), 4095u)].y + media[min(u32(0), 31u)].b.x; }\n"), "{out}"); } - /// The same for the live passes (28 as written). + /// The same for the live passes (24 as written). #[test] fn the_merged_live_passes_fit_eight_storage_buffers() { let (m, info) = parse(LIVE);