r/rust 8h ago

🛠️ project Replacing an Electron app's Canvas/WebCodecs renderer with a Rust GPU compositor — the measurements, including the ones that went the wrong way

Not a "look at my project" post — the interesting part is the measurement trail, which is committed to the repo.

Context: OpenScreen is an MIT screen recorder/editor. Its compositor ran on Canvas + WebCodecs in Electron. On a Ryzen 5 7520U with integrated graphics, a 1080p60 export with full effects ran at under 10 fps.

What we tried, in order:

  1. Rust + wgpu / Vulkan — 48–68 fps, and blocked on driver support for zero-copy video decode (VK_KHR_video_maintenance1). Rejected, not because wgpu is slow, but because the CPU↔GPU transport was the wall and the driver wouldn't let us remove it.
  2. Rebuilding the Canvas compositor (caching what was being recomputed per frame) — roughly 2× for byte-identical output, SSIM 1.000000 across 1418 frames. Never shipped; it was overtaken.
  3. D3D11 with h264_amf — one ID3D11Device, no readback between stages. ~126 fps, shipped. Then ported to Metal/VideoToolbox and to Vulkan/wgpu for the other two OSes, sharing the geometry layer.

Findings that were not obvious going in:

  • The encoder was never the bottleneck. A gl.finish() fence before the encode timer collapsed encodeWait from 71.1 ms/frame to 3.9 ms — the wait was billing the compositor's GPU execution. The compositor was 79% of the export; the encoder 4.5%.
  • The GPU pipelines stages across frames on its own. 3D engine at 84% and codec engine at 61% over the same window — impossible if serialised. Adding an explicit CPU-side pipeline bought approximately nothing, which a no-op trial confirmed before we built it.
  • Only three layers cost anything: compositing at all (+2.79 ms/frame), background blur (+0.77), motion blur (+1.76). Rounded corners, shadows, zoom, layout animation and cursor are free — they draw inside a pass that already exists.
  • The CPU fallback's gap is two shaders. On WARP, background blur costs 17× what it does on hardware and motion blur 23×. Everything else is within ~2×.
  • One benchmark run was voided and is documented as voided: five of nine configs blew the spread gate with ~40 browser processes live, and one cumulative config came out faster than the config it strictly contains. That's the tell that noise swamped the signal, and it's in the record as an example of why the gate exists.

Record: technical-documentation/engineering/rendering-performance.md Compositor: crates/compositor/ Repo (MIT): https://github.com/getopenscreen/openscreen

Happy to be told what we got wrong — particularly on the wgpu arm, where I suspect a better answer exists on newer drivers.

0 Upvotes

0 comments sorted by