Do not use the GPU merely because it is possible.
Every component in this library has to clear a six-question gate. Components that fail it are rejected — including ones that would look impressive in a screenshot.
Six questions, all of which must pass.
Does the workload exceed what one CPU frame can touch?
The ceiling is primitives issued per frame from JS: ~5k DOM nodes, ~50k Canvas2D fillRects at 60fps. Instancing changes the unit from one call per primitive to one call per million.
Is there per-element work that is data-parallel?
Colour mapping, thresholding, normalisation, projection, LOD bucketing, min/max reduction.
Does interaction re-derive the whole dataset?
Zoom, pan, brush and filter over an immutable dataset is the ideal case: upload once, re-render from a changed uniform. CPU pipelines re-walk the data every time.
Can we avoid CPU↔GPU round-trips?
Anything needing a synchronous readback per frame is disqualified. vgpu is explicit that read() is for tests and diagnostics, not a hot path.
Is the text budget bounded?
Text is the tax. More than ~1k glyph runs per frame means the glyph atlas has to exist first.
Does it produce reusable runtime primitives?
A component needing a bespoke pipeline nobody else reuses is a demo, not a library investment.
vgpu/scene for 3D meshes — duplicating it is an explicit non-goal.The ceiling, live.
Every span in the dataset is on screen, so every span is drawn every frame. This is the case a GPU instanced draw collapses into a single call.
What runs where, and what must not move.
Data-parallel, per-frame
- Instanced primitive rendering from a storage buffer
- Viewport transform — pan/zoom is a uniform write
- Colour mapping, normalisation, thresholding
- LOD and density binning with atomics
- Reductions for auto-ranging and histograms
- Selection as a bitset the shader branches on
- ID-buffer picking where a CPU index is impractical
Once per dataset
- Parsing — JSON, OTLP, Arrow
- Building columnar typed arrays
- Sorting by (track, start) — once, never per frame
- Building the spatial index for CPU hit-testing
- String interning
- Transfer via Transferable, zero copy
Stays on CPU, deliberately
- All string handling — formatting, search, collation
- Track and column layout
- Hit-testing via the sorted index — O(log n), exact, immediate
- Tooltip contents
- The accessibility tree
Where this argument gets uncomfortable.
Three things that will be true in the published benchmarks, stated here before anyone finds them.
WebGL2 instancing is also fast
For raw quad throughput, WebGPU’s advantage over a competent WebGL2 instanced renderer may be modest. This was the biggest technical risk in the project. It was measured before any runtime code was written, and the comparison is published whether or not it flatters us. The durable WebGPU advantages are compute in the data path, indirect draws and dispatches, and storage-buffer-driven vertex work — not fill rate.
Canvas2D is better than people assume
At viewport scale, Canvas2D is sufficient — which is precisely why a canvas data grid already scrolls millions of rows at 60fps today. Anyone selling you a GPU grid on scroll performance is selling you something you already have.
Below the crossover, the GPU path is slower
Upload cost and pipeline overhead dominate at small N. The hypothesis is a crossover somewhere around 20k–50k primitives. The measured number goes in each component’s docs, in a section titled “When NOT to use this”, along with a recommendation for what to use instead.