Performance benchmark report
This report records deterministic cortical-scale measurements rather than general performance claims. Run npm run benchmark:performance for the Node matrix and npm run benchmark:browser for synchronized WebGL2 measurements. CI runs the Node budget check and the browser matrix is part of the Playwright suite.
Method and provenance
- Workloads use 32,492, 163,842, and 324,002 vertices with 1, 4, and 8 RGBA layers. Values are generated by a deterministic integer pattern.
- Node timings are medians of five measured runs after one warmup. Dispersion is reported as median absolute deviation in the JSON output.
- Browser timings are medians of three runs after one warmup. Every measured draw calls
gl.finish(), so timings include submitted WebGL work rather than only JavaScript dispatch. - Node reference: Node 26.7.0, macOS arm64, 2026-08-28.
- Browser reference: Playwright-managed Chromium WebGL2 using ANGLE SwiftShader Vulkan on macOS arm64, 2026-08-28. This is software-renderer evidence, not a claim about a particular physical GPU.
- The table below is the isolated browser run. The stored reference also records the noisiest medians from the full seven-worker Playwright suite (15.6 ms full upload/draw and 28.6 ms resize/draw at 324k × 8); both remain well inside the checked ceilings.
- The checked ceilings and reference maxima live in
benchmarks/performance-baseline.json. Cross-machine ceilings are deliberately above the local medians; changes should ratchet them downward only after hosted-run evidence.
Layer matrix
Times are milliseconds. “GPU prepare” is CPU preparation of all array-texture slices. Browser columns include texture upload and a synchronized draw. Texture memory is the layer array plus the per-slot colormap array.
| Vertices | Layers | CPU composite | GPU prepare | Dirty prepare | Browser full | Browser dirty | Texture MiB |
|---|---|---|---|---|---|---|---|
| 32,492 | 1 | 0.36 | 0.12 | 0.018 | 0.20 | 0.10 | 0.50 |
| 32,492 | 4 | 0.70 | 0.24 | 0.010 | 0.30 | 0.10 | 2.00 |
| 32,492 | 8 | 1.38 | 0.38 | 0.009 | 0.50 | 0.10 | 4.01 |
| 163,842 | 1 | 0.88 | 0.20 | 0.038 | 0.10 | 0.20 | 2.50 |
| 163,842 | 4 | 3.54 | 0.82 | 0.038 | 1.20 | 0.50 | 10.02 |
| 163,842 | 8 | 7.09 | 1.69 | 0.047 | 5.00 | 0.30 | 20.03 |
| 324,002 | 1 | 1.74 | 0.49 | 0.091 | 0.30 | 0.30 | 4.96 |
| 324,002 | 4 | 7.52 | 1.78 | 0.082 | 2.00 | 0.50 | 19.83 |
| 324,002 | 8 | 13.99 | 3.83 | 0.087 | 14.80 | 0.50 | 39.67 |
For the 324,002 × 8 workload, median browser reorder-and-draw was 5.0 ms, visibility-and-draw was 0.4 ms, and resize-and-draw was 10.0 ms. A single dirty layer marks one 5,198,400-byte slice for upload; the other seven slices are not regenerated or marked.
Visibility compacts visible layers into shader slots. Hiding or showing a layer can therefore relocate subsequent slices; those relocated slices are regenerated by design. Reordering similarly regenerates only slots whose layer identity changed.
Picking
| Vertices | Triangles | CPU raycast ms | GPU pick ms |
|---|---|---|---|
| 32,492 | 10,830 | 0.47 | 0.70 |
| 163,842 | 54,614 | 1.72 | 2.60 |
| 324,002 | 108,000 | 3.42 | 5.10 |
The synthetic scene places one hittable triangle on screen and the remaining triangles outside the view. These numbers characterize scaling and regression budgets; they are not a substitute for profiling a user's mesh topology and hardware.
Memory, scheduling, and bundle size
- The largest 324,002 × 8 fixture allocates 44.5 MiB of benchmark RGBA plus composite typed arrays and 39.67 MiB of compositor texture backing arrays.
viewer-lifecycle.test.tsandviewer-render-scheduler.test.tsrequire a settled viewer to have zero recurring animation frames. The browser lifecycle test also proves one coalesced frame after invalidation and none after disposal.- The measured core ESM bundle is 540,422 bytes raw, 144,568 bytes gzip, and 118,506 bytes Brotli. Its enforced limit is 180 kB Brotli. Optional controls/report/React entries retain separate 30/1/3 kB budgets, so they cannot silently leak into the core entry.
Interpreting changes
A timing failure should be reproduced with the same runtime and inspected with the emitted median, MAD, minimum, and maximum. Do not update a ceiling from one outlier. Compare at least two hosted runs, identify whether the regression is CPU work, texture bytes, shader execution, or runner noise, and record the new reference before changing a budget.