Client performance¶
The browser viewer renders in your browser, with vtk.js on your graphics card, so how fast a drag feels depends on your machine more than on the server. This page lists the machines the viewer has been measured on, the limits a release candidate is held to, and how to measure your own.
Which renderer your browser uses¶
Choose Info > About vibe-viewer in the toolbar. The dialog ends with a line such as
Browser graphics: WebGL2, ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Pro, Unspecified Version)
The first part is the WebGL version; the viewer needs WebGL2 for the outline pass of figure outlines, and works without it otherwise. The rest is the renderer. A name with SwiftShader, llvmpipe or Basic Render is a software renderer: the browser found no usable graphics card, and drawing runs on the CPU. Everything still works, at the speeds of the software rows below. A browser running remotely, in a virtual machine or with hardware acceleration switched off usually reports one of these.
What is measured¶
scripts/client_qualification.py opens the real viewer in Chromium and does
what a user does. A drag step is one mouse move, timed until the browser
has drawn the frame and the graphics card has finished it; a switch is
timed from the click until the new scene has arrived from the server and
been drawn. Every case runs at a 1280 × 800 window, a device pixel ratio of
1 and with the display’s refresh cap off, so the numbers are what the
machine can do rather than its refresh rate.
Case |
What it does |
|---|---|
|
Drag a 150-atom molecule in ball and stick |
|
Switch it to space filling |
|
Switch it back |
|
Drag it with atom labels on, which follow the camera on the graphics card |
|
Drag a water density on a 64³ grid, drawn as a translucent isosurface |
|
Drag it in the Publication look, with the outline pass on |
|
Switch the density to direct volume rendering |
|
Drag the volume |
The measured matrix¶
Median and 95th-percentile drag step in milliseconds, switches in seconds, Apple measurements from 2026-10-20 and Linux measurements from the v2.20.20 candidate on 2026-10-21:
Case |
Apple M4 Pro, Chrome 155, GPU |
Apple M4 Pro, Chromium 156, SwiftShader |
Linux x86-64, Chromium 156, SwiftShader |
|---|---|---|---|
|
1.2 / 1.5 ms |
10.3 / 11.0 ms |
35.0 / 42.5 ms |
|
0.35 s |
0.33 s |
0.45 s |
|
0.51 s |
0.47 s |
0.90 s |
|
2.0 / 2.4 ms |
33.9 / 35.0 ms |
48.1 / 54.4 ms |
|
1.7 / 2.0 ms |
13.7 / 14.9 ms |
54.4 / 67.4 ms |
|
3.1 / 3.4 ms |
18.9 / 20.0 ms |
59.4 / 65.3 ms |
|
0.36 s |
0.35 s |
0.48 s |
|
1.2 / 1.4 ms |
11.9 / 13.4 ms |
28.4 / 34.5 ms |
Environment |
Class |
Status |
|---|---|---|
Apple Silicon (M4 Pro), macOS, Chrome |
GPU |
Measured above; qualified |
Apple Silicon (M4 Pro), macOS, headless Chromium |
Software (SwiftShader) |
Measured above; qualified; a repeat run agreed within 2 % |
Linux x86-64, headless Chromium, the candidate pipeline |
Software (SwiftShader) |
Measured above; qualified on Linux 6.8 / Chromium 156. The |
Windows with a graphics card |
GPU |
Qualification deferred until requested by users; not a release gate and no PASS claimed |
The switches take the same time on both renderers because they are the server’s work: rebuilding the scene and sending it. A software renderer shares the CPU with everything else on the machine, so its numbers rise sharply under load; a run while a test suite used the same machine was several times slower.
The limits¶
scripts/client_thresholds.json holds the limits, per renderer class. A
drag is judged on its 95th-percentile step, a switch on its seconds:
Class |
Drags |
Direct volume rendering |
Labels |
Switches |
|---|---|---|---|---|
GPU |
16.7 ms (60 frames a second) |
33.4 ms (30 frames a second) |
16.7 ms |
10 s |
Software |
150 ms |
75 ms |
reported only |
2 s |
The GPU limits are the interactive bar every machine with a graphics card should meet. The software limits allow about twice the measured Linux cost: the slowest regular drag was 67.4 ms, volume was 34.5 ms and the slowest switch was 0.90 s. They catch regressions on a machine without a graphics card, where a frame rate is not a promise; labels there are reported only, because the extruded text of many labels is a lot of triangles for a CPU to draw, whatever the viewer does.
Release candidates run the qualification in the client-performance
pipeline job, on Linux with SwiftShader. From 3.0 on it gates the candidate;
on 2.x candidates it measures and reports. A run on a machine with a
graphics card is a manual check: run the script there, as below.
Measuring your machine¶
The script needs the viewer extras and Playwright:
pip install -e '.[viewer]' playwright
python -m playwright install chromium
python scripts/client_qualification.py --json my-machine.json
Add --channel chrome to use an installed Google Chrome instead of
Playwright’s Chromium; headless Chromium often falls back to SwiftShader
even where a graphics card exists, and the renderer line it prints first
says which one ran. --gate makes the exit status 1 when a case is over its
limit. To add your machine to the matrix, open an issue with the JSON file
and the output of vibe-viewer doctor.
Large structures¶
From 500 atoms the viewer draws atoms and bonds as instanced glyphs: one sphere and one cylinder mesh that the graphics card repeats for every atom and bond, in the browser as in headless capture. From 10 000 atoms the browser draws coarse glyphs while the camera moves and the full ones when it stops (Nanotubes and tube–metal interfaces).
docs/bench_nanotube_scale.py measures four carbon nanotube scenes: an
open (10,10) tube, a five-wall tube on a (5,5) core, a 19-tube bundle and
an aligned forest of three-wall tubes 5 nm apart. It times each from 10³ to
10⁶ atoms in the three front ends, every case in its own process with a
10-minute limit:
load: from opening the archive until the structure is drawn. In the browser this runs from starting the server, which binds after its first scene build, until vtk.js has drawn it;
orbit: one camera step. In the browser, one drag step until drawn (median / 95th percentile); in the terminal, one re-raster of a 160 × 48 braille pane;
capture: one image at the 1280 × 800 window. In the browser this is the screenshot export; headless, a PNG of an off-screen render. That render and its readback are the headless frame cost: an off-screen orbit step only queues work on the graphics card, so it is not reported.
Measured 2026-10-28 on an Apple M5 Max (macOS, installed Google Chrome,
ANGLE Metal), with the viewer from 3.0.x main before and after instancing.
Before instancing¶
Every sphere and bond cylinder was a separate mesh merged on the server, and building the bonds grew faster than the atom count:
Scene |
Atoms |
Headless load |
Browser load |
Browser orbit |
|---|---|---|---|---|
Tube |
1 000 |
1.9 s |
4.8 s |
0.6 / 0.9 ms |
Tube |
10 000 |
116 s |
156 s |
1.1 / 1.5 ms |
Multi-wall |
10 200 |
115 s |
190 s |
1.1 / 1.4 ms |
Bundle |
10 640 |
141 s |
180 s |
1.2 / 2.3 ms |
Forest |
10 800 |
176 s |
213 s |
1.2 / 1.6 ms |
The practical ceiling was about 10⁴ atoms: two to three and a half minutes to open. 10⁵ atoms was not attempted.
With instancing¶
Scene |
Atoms |
Headless load / capture |
Terminal load / orbit |
Browser load |
Browser orbit |
|---|---|---|---|---|---|
Tube |
1 000 |
1.7 s / 0.03 s |
0.2 s / 29 ms |
7.0 s |
2.4 / 5.8 ms |
Tube |
10 000 |
1.4 s / 0.2 s |
0.6 s / 24 ms |
4.6 s |
4.9 / 8.3 ms |
Tube |
100 000 |
5.4 s / 1.6 s |
4.2 s / 40 ms |
12 s |
2.4 / 8.6 ms |
Tube |
1 000 000 |
55 s / 2.4 s |
73 s / 815 ms |
93 s |
15 / 21 ms |
Multi-wall |
10 200 |
1.1 s / 0.3 s |
0.7 s / 148 ms |
4.5 s |
18 / 68 ms |
Multi-wall |
100 200 |
4.8 s / 4.6 s |
4.2 s / 141 ms |
11 s |
2.2 / 8.0 ms |
Multi-wall |
1 000 200 |
57 s / 1.6 s |
75 s / 958 ms |
107 s |
12 / 26 ms |
Bundle |
10 640 |
1.9 s / 0.2 s |
1.1 s / 172 ms |
7.4 s |
9.7 / 13 ms |
Bundle |
100 320 |
8.5 s / 1.5 s |
5.9 s / 473 ms |
11 s |
2.6 / 7.7 ms |
Bundle |
1 000 160 |
68 s / 3.0 s |
76 s / 979 ms |
129 s |
9.6 / 14 ms |
Forest |
10 800 |
1.8 s / 0.3 s |
1.0 s / 77 ms |
7.0 s |
10 / 14 ms |
Forest |
108 000 |
9.3 s / 1.6 s |
9.0 s / 105 ms |
22 s |
4.4 / 8.8 ms |
Forest |
1 008 000 |
86 s / 9.7 s |
81 s / 1019 ms |
132 s |
26 / 204 ms |
The ceilings this gives, on this machine:
Browser: every scene up to 10⁶ atoms opens and turns. A drag step stays under 30 ms at the median; the 10⁶-atom forest has slow outliers (95th percentile 204 ms). Opening 10⁶ atoms takes 1.5 to 2.2 minutes, three quarters of it the server’s scene build and transfer. Between 10⁴ and 10⁵ atoms, below the coarse-glyph threshold, the 95th percentile can reach 68 ms. The full-quality frame after a drag of a 2 × 10⁵-atom forest took about 1 s.
Headless: 10⁶ atoms load in about a minute and capture in 2 to 10 s.
Terminal: the braille rasterizer runs on the CPU and draws every atom. Turning stays under about 150 ms a step to 10⁵ atoms for a single tube and to about 10⁴ for denser scenes; at 10⁶ atoms each step takes about a second. It loads 10⁶ atoms in about 75 s.
The browser numbers depend on the graphics card; a software renderer is much slower at these sizes and has not been measured.
Synthetic trajectory diagnostic¶
Use --scenes trajectory --sizes 100000 --frontends browser --channel chrome
to measure twelve synthetic breathing frames of a five-wall tube. playback
reports six manual frame updates, including server inference, transfer and the
browser draw. It waits for the filmstrip before measuring. Coordinates are
synthetic and this is not a chemistry calculation.
On 2026-10-28, frozen commit 8d06e1a displayed all 100,200 atoms, inferred
bonds and four thumbnails in Chrome 156 on the M5 Max. The observed frame
median / p95 was 2,785 / 2,919 ms; browser load was 15.3 s. Other machine
workloads were active, so these are diagnostic observations, not controlled
before/after results or an interactive-rate claim. The reader still loads the
whole coordinate array; playback strides rendering work rather than streaming
archive bytes. Coordinate-editor pagination also prevents its 500,000 HTML
inputs from stalling playback at this size.
Vectorised loading diagnostic¶
Frozen commits 8d06e1a and 33ada85 were compared on the same synthetic
1,008,000-atom forest, in that order, on 2026-10-28 (M5 Max, Chrome 156,
ANGLE Metal). Other workloads were active: these observations are not a
controlled speedup claim.
Measurement |
Before |
After |
|---|---|---|
Server ready |
63.5 s |
61.0 s |
Browser loaded and drawn |
91.2 s |
84.4 s |
Browser drag median / p95 |
11.6 / 13.3 ms |
10.8 / 11.6 ms |
Profiled scene build, including read |
200.2 s |
124.0 s |
Profiled connectivity |
39.1 s |
23.4 s |
Profiled geometry after loading |
15.3 s |
2.6 s |
Profiling adds substantial overhead. Unchanged JSON-schema validation alone accounted for 122.3 / 83.3 s in those profiled runs, showing the environment variation and the remaining loading bottleneck. The changes vectorise replicated atom positions and element attributes, bond selection/length filtering, and cell-list neighbourhood distance calculations. The inference tolerance, periodic-image handling and resource limits are unchanged; no dependency or format change is involved.
Motion detail and at-rest prototype experiment¶
A frozen comparison of 33ada85 and 420ec89 on the same M5 Max / Chrome
156 setup lowered the motion-detail threshold from 100,000 to 10,000
instances. For the 10,200-atom multi-wall tube, median / p95 drag steps changed
from 16.0 / 65.9 ms to 3.8 / 13.9 ms. The lower threshold is retained.
An 8 × 6 sphere prototype at 100,000 atoms was also tried against the usual
12 × 8. On a 204,000-atom forest the verified full-detail restoration measured
23.4 / 22.9 ms before/after, with browser captures at 0.237 / 0.120 s. These
runs did not reproduce the earlier approximately one-second at-rest frame,
and concurrent workloads limit comparison. The restoration result does not
justify reducing at-rest quality, so the 12 × 8 prototype is retained at all
counts. rest_ms now waits for the LOD restoration counters before completing
the GPU work; it includes the animation-end scheduling delay.
Terminal point-sprite diagnostic¶
Frozen 33ada85 and 86cbb84, the same forest geometries at a 160 × 48
braille pane, ten orbit steps per case, 2026-10-28. Concurrent workloads were
active, so these are diagnostic observations. Loading includes unchanged
archive validation and varied independently of raster cost.
Forest atoms |
Before load / orbit median / p95 |
After load / orbit median / p95 |
|---|---|---|
108,000 |
6.0 s / 73.6 / 80.4 ms |
9.5 s / 50.2 / 51.0 ms |
1,008,000 |
96.3 s / 1,109 / 1,204 ms |
108.9 s / 638 / 703 ms |
From 10,000 atoms, projected radii of at most 0.75 pixel use depth-tested sprites. The rasterizer culls off-screen atoms and omits subpixel bonds covered by atom endpoints. Larger projected spheres and sticks-only views retain their existing raster paths. Tests cover depth ordering and zoomed-in shading, and the forest raster was visually inspected. The million-atom case remains below a smooth interactive rate.