Performance and plots
gui.perf() adds a Performance folder, in place of stats.js. It's a plot each of frame time, CPU time, GPU time and memory, each on a scale of its own, so a tenth of a millisecond of GPU time reads as clearly as a 16 ms frame. Each plot's name, readout and note are written over its graph, and each has every plot's controls (below). A note is numbers only. Where a time comes from, or why there's none, is in the plot's tooltip.
- frame: frame time, measured from animation frames.
- Its note: frames a second, the 1% lows (the slowest 1% of recent frames), and long frames (over 50 ms in the last 10 seconds) when there are any.
- A dashed line marks the display's frame budget, its refresh rate as the frames show it.
- cpu: CPU time of your frame, once you time one with
perf.begin('frame')/perf.end('frame')(stats.js's MS panel).- The browser's clock is coarse: Chrome's steps by 0.1 ms, 5 µs on a cross-origin-isolated page (frames.md). So the readout is the mean over a quarter second.
- With two sections or more timed, each has its own line, and its time is in the note, most first. With one, the note gives its share of the frame.
- gpu: GPU time, beside CPU's, its note the same.
- memory: the JS heap where the browser tells it (Chromium), and the GPU memory the app has allocated, with the frame scope on the context. It's sampled four times a second, so its graph covers a minute and a leak's slope shows.
- three.js (with its extension):
renderer.info's draw calls, triangles, points, lines, textures, geometries and programs, open. - frame scope (given a context, or with three.js's extension): the frame scope of your context, folded away. It's attached the first time you open it, and it records only while it's on screen, so folded away it costs nothing; the compact meters fold it away too. The palette's Frame scope opens it here. Where your app attached a scope to the context itself, the folder isn't offered, and the palette opens your app's scope where it is.
perf.offerScope(target)offers one of another target (aGPUDevice, say).
GPU time is measured given a context (gui.perf({ gl })), through EXT_disjoint_timer_query_webgl2:
- Each pass your sections draw (a draw target written in one go) is timed from its first draw or clear.
- Each pass's time is as it typically takes now: the low quarter of its last 15 readings, as the frame scope's are (frames.md). Timed whole, a section would also count the GPU idle while your code prepares its first draw, and the GPU waiting for the browser: on Chrome, often milliseconds more than its work.
- A result spoiled by a disjoint event (a GPU clock change) is dropped.
- With the frame scope open on the context, the GPU time is the scope's own.
const perf = gui.perf({ gl });
perf.begin('shadows'); drawShadows(); perf.end('shadows'); // or perf.time('shadows', drawShadows)
gui.perf({ folder: 'Stats' }) puts the folder elsewhere. perf.stats holds the readouts (fps, 1% low, long frames, frame ms, cpu ms, gpu ms, heap MB) for your own use. The plots are perf.plot, perf.cpuPlot, perf.gpuPlot and perf.memory (Performance/frame, /cpu, /gpu and /memory, which an agent's read_plot reads).
Compact meters. perf.compact(), or the palette's Performance meters, shows the gpu, cpu and memory plots small, side by side, in a window of their own, about 300 × 50 px, with no title bar. They're read by hovering alone, their readouts without notes; the full folder has their controls and notes.
Drag the window by any part of it but its buttons. A double-click, or its expand button, turns it into the Performance folder, where it is; the folder window's compact button turns it back. Where it was left is kept with the layout. The meters are the folder's window in its compact form, so while they're open the folder isn't in the panel too; closing them puts it back there.
const perf = gui.perf({ gl, compact: true }); // start as the meters, top-left ('bottom-right'…)
perf.compact('top-right'); // or later; perf.expand() for the folder
CPU and GPU time are the frame section's, or else every named section's together. A section inside another one counts in both, on the CPU and the GPU; one inside another of its own name is part of it. devui's own drawing on your context (texture views) isn't counted, nor are passes while your app times its own work with a timer query (they can't nest): each of those counts as it typically takes. With the three.js extension, renderer.render is a section (render, its calls summed per frame). Code that uses stats.js can alias it to @haxiomic/devui/compat/stats, which drives gui.perf() instead (the engine demo does this).
Where timer queries can't time a pass (on Chrome on macOS: see the frame scope, under GPU time), no queries are made: their sum would be many frames' worth, and even one around the frame reads up to twice its time. The gpu plot is the whole frame's time from a fence instead, from its first GPU work until the GPU finished it, without stalling anything. It's good to about 2 ms and counts JavaScript the GPU may have been idle through; its tooltip says so. The frame scope times each pass there, over frames.
Plots can be hovered, paused, measured and zoomed:
- Hover for a crosshair, the values under it, and how long before the newest sample they were.
- Click to pause; the plot keeps recording underneath. Click again to resume. The button over its top-left corner does the same (bottom-left on an overlaid plot): it shows while you point at the plot, and stays while it's paused.
- Drag across a range for its min, max, mean and p95.
- Zoom and pan as in every graph of devui's (see gestures): Ctrl or ⌘ + the wheel, or a pinch, zooms about the pointer, by 1.25 a mouse's notch (a plain scroll still scrolls the panel). While it plays, it zooms about the newest sample, which stays at the right edge. Shift + the wheel, a sideways scroll or a drag with the middle button pans back through what it holds, pausing it. Double-click, or fit over its corner while it's zoomed, to see it all again; focused, + and − zoom and 0 fits. Zoomed, its scale fits what's shown.
- On touch, press to read a value and drag sideways to measure; two fingers pinch and pan.
- The plot's menu has a log scale and Copy data as CSV, which copies what's on screen (the measured range, if there is one).
Vector and colour monitors (gui.monitor(body, 'velocity', { view: 'graph' })) plot one series per component, in the usual x/y/z colours.
A plot's options, as the Performance folder's use them:
overlay: true: its name, readout and note are written over its graph rather than above it, for a plot read at a glance. The readout is its first series alone, the mean of its last quarter second, so it holds still; hovering reads the samples under the pointer.half: true: half a row wide. Plots that are next to each other and half wide sit side by side.guide: value: a dashed line across it at that value, in its range (plot.guidechanges it).colors: CSS colours, or the theme's by name ('--series-3').plot.setNote(text): a line by its name, for what its readout leaves out.
const p = gui.plot('step ms', { unit: 'ms', min: 0, overlay: true, half: true, guide: 16.7, colors: ['--series-2'] });
p.setNote('physics and AI');