Renderer Profiler
OpenPencil includes a CanvasKit renderer profiler for debugging frame time, GPU timing, draw calls, cache behavior, and expensive render phases.
Enable the HUD
In the browser app, open the menu and choose:
View → ProfilerThe app toggles store.toggleProfiler(), which maps to editor.renderer.profiler.toggle().
The HUD is drawn directly on the Skia canvas so it measures the same rendering path as the document. It is not a DOM overlay.
HUD metrics
The profiler HUD shows:
- FPS / frame time — smoothed frame cadence.
- CPU — JavaScript/WASM render time for the frame.
- GPU — latest available
EXT_disjoint_timer_query_webgl2result when the browser exposes it. - Nodes / culled nodes — total visible scene work and viewport culling count.
- Draws — WebGL draw calls counted through the instrumented context.
- Cache — whether the scene picture cache was reused.
- Phases — timings for renderer phases such as scene draw, picture replay/record, volatile overlays, section labels, selection, rulers, and flush.
- Frame graph — rolling frame history with 60 fps / 30 fps / slow thresholds and GPU bars when available.
GPU timing is asynchronous. The value shown is the latest completed GPU query, not necessarily the current frame.
Implementation locations
Core profiler code lives in:
packages/core/src/profiler/Main entry points:
render-profiler.ts—RenderProfilerfacade used bySkiaRenderer.frame/stats.ts— rolling frame statistics.gpu-timer.ts— WebGL timer query wrapper.draw-call-counter.ts— WebGL draw-call instrumentation.phase-timer.ts— phase timing and User Timing integration.hud-renderer.ts— canvas HUD rendering.frame/capture.tsandspeedscope-export.ts— detailed capture and Speedscope export.
Renderer integration lives under:
packages/core/src/canvas/renderer*.ts
packages/core/src/canvas/renderer/App wiring lives in:
src/app/editor/profiler/index.ts
src/app/shell/menu/schema.ts
src/app/shell/menu/app-menu.tsProgrammatic use
From app/editor code:
store.toggleProfiler()From a renderer instance:
renderer.profiler.toggle()
renderer.profiler.beginCapture()
// render one or more frames
const capture = renderer.profiler.endCapture()
const speedscopeJson = renderer.profiler.exportSpeedscope()
renderer.profiler.downloadSpeedscope()Detailed captures are for targeted debugging. Keep the normal HUD path lightweight and avoid enabling expensive capture work unless a user or developer explicitly asks for it.
Notes
- The profiler is designed to be safe when disabled: no timing calls or allocations should be added to hot paths unless
profiler.enabled/profiler.capturingis active. - GPU timing depends on browser and hardware support for
EXT_disjoint_timer_query_webgl2. - If GPU timing is unavailable, the HUD still reports CPU time, draw calls, phases, node counts, and cache status.