Systems & graphics engineeringDrawing 25.1 of 25Part 1 of 2
The engine
A frame costs what is on screen and nothing else. Culling, instanced draws, no webview, and the three ceilings that are still there.
Specification / as recorded
Status
Essentially done, 2026
Role
Solo designer and engineer
Built with
Rust, wgpu, Loro CRDT, SQLite, 18 crates
Runs on
macOS and Windows, no webview
Reference board
a real 596-widget Miro import
Sheet 02 of 04/Rule
Cost what is on screen, nothing else
Everything in the codebase is downstream of that sentence.
An R-tree spatial index answers "what is visible" before anything else is touched, so viewport queries and hit tests cost time proportional to the items in view, not the items that exist. Visible quads go to the GPU in one instanced draw rather than one draw per widget. Rounded rectangles, borders and shadows are signed distance fields evaluated in a shader, so they are exact at any zoom and never get tessellated. There is no garbage collector, which matters because JS canvas apps stutter during collection precisely while you are panning.
World coordinates are f64 and rebased to camera-relative f32 every frame. That sounds fussy until you notice the reference board is 41,282 by 17,515 pixels, and f32 alone starts losing precision around ten million.
You can watch the rule work. On a synthetic 100,000-item board at a zoom I would actually use, the overlay reads 233 of 100,000 visible, 33 draw calls, 59 MB resident. Pull back to 4 percent and it reads 87,687 of 100,000, because now everything really is on screen and there is nothing left to cull. Those two pictures are the honest version of the claim.
PlatePL-01
TitleCull working zoom
ViewDetail
A synthetic 100,000-item board at a zoom you would actually work at: the HUD reads 233 of 100000 visible, 33 draw calls, 59 MB resident.
PlatePL-02
TitleSame board pulled back to 4 percent
ViewDetail
Where the culling has nothing left to remove: 87,687 items on screen, and the frame budget goes with them.
Sheet 03 of 04/Native
No webview, decided first
An early draft put the renderer inside a Tauri webview and compiled the engine to WASM. I reversed it, and I wrote down why, because it is the kind of decision that gets re-proposed.
Tauri closed WebGPU flag support as "not planned", so there is no supported way to influence the embedded webview's GPU behaviour. WKWebView was frame-capped at 60fps until macOS 26, which put the target physically out of reach inside the host I had chosen. WebView2 can quietly fall back to software rendering. And WASM costs recur daily: no threads without cross-origin isolation, no system fonts, and every decoded image crossing native to IPC to JS to WASM to GPU with at least two copies on the way.
Native wgpu talks to Metal and DX12 directly. The same reasoning made me skip the standard egui-wgpu bridge for the interface: it pins an older wgpu than the renderer needs, and two wgpu versions cannot share a device. Adopting it would gate every future graphics upgrade on somebody else's release schedule, which is the webview problem again in miniature. So the render crate draws the menus and panels itself. A few hundred lines bought permanent version independence.
The door I did leave open: winit and wgpu also compile to wasm32 with WebGPU, and in a real browser rather than an embedded webview, WebGPU works properly. A web viewer later reuses the renderer as it stands.
PlatePL-03
TitleMenus
ViewDetail
The menus are drawn by the renderer itself rather than by an off-the-shelf bridge, over a board carrying imported pen strokes Miro's API will not export.
Sheet 04 of 04/Limits
What it still does badly, and why I write it down
Frame cost scales with the whole board when the whole board genuinely is on screen. Text ends the quad batch once per item, and ink and connectors re-tessellate every frame, so a fully zoomed-out view still costs what it costs. The batching work that would fix it is the next real job.
Inserting into the document is O(n squared) in the CRDT. A thousand items open in 18 ms; a hundred thousand take about twenty seconds. A very large paste stalls.
Text formatting survives an edit but cannot be created by one. Live embeds like YouTube or Figma cannot render inside a GPU canvas, so they import as cards with a title, description and favicon and a button to open them in a browser.
There is also a rename I keep deferring, on purpose. The board file extension is load-bearing, and getting it wrong orphans every board on the machine.
The thing I did not expect to learn was how much value there is in separating what I measured from what I believed. My notes carry numbers with their conditions attached, and doing that has caught me more than once claiming a subsystem was fast when what I actually had was a fast case.
A frame budget that only holds at one zoom is a frame budget with an asterisk. The asterisk belongs in the note, and on this page.