The pitch for a browser viewer is obvious: share a link, the recipient sees the recording, no install. The problem is that the desktop viewer is a GPU application, and for years "in the browser" meant giving that up.
One backend, two targets
The renderer is written against wgpu, which targets Metal, Vulkan and D3D12 natively
and WebGPU in the browser. In principle the same code compiles to both. In practice "in principle"
did a lot of work in that sentence.
What genuinely was shared: shaders, the instanced point pipeline, the chunk culling, the camera maths. Roughly 90% of rendering code has no idea which target it is on.
Where it broke
Threads. The desktop viewer decodes on a thread pool. In the browser that means web
workers plus SharedArrayBuffer, which requires cross-origin isolation headers, which
means a page that embeds the viewer has to set them. We support both, but the single-threaded
fallback is meaningfully slower on video-heavy recordings.
Memory. A 32-bit wasm heap caps out around 4 GB, and in practice you want to stay well under. The desktop viewer memory-maps a recording and lets the OS page it. That is not available, so the wasm build streams chunks and maintains a bounded cache with eviction.
File access. No mmap, no random-access reads over a file handle. Everything is
HTTP range requests, which turned out to be a blessing: the chunk scheduler we built for the browser
was good enough that we moved it to the desktop Hub client too.
#[cfg(target_arch = "wasm32")] type Reader = HttpRangeReader; // bounded cache, evicts under pressure #[cfg(not(target_arch = "wasm32"))] type Reader = MmapReader; // let the OS page it
Numbers
The wasm bundle is about 11 MB gzipped, which is large for a web page and small for a 3D application. First paint on a cached bundle is under a second. Rendering throughput is roughly 70% of native on the same machine — the gap is mostly the memory ceiling forcing smaller working sets.
Would we do it again
Yes, and earlier. The constraints the browser imposed — bounded memory, streaming reads, no assumption of threads — made the desktop viewer better. The chunk scheduler that made Hub playback fast exists because the browser forced us to build it.