Loading image...
Keeping the UI Alive While the GPU Works

Keeping the UI Alive While the GPU Works

performanceweb-workersfrontend3dbrowserwomp
How I solved UI freezing at Womp by offloading heavy graphics computations to Web Workers — and what that taught me about the browser's single-threaded model.

It wasn't a problem until it was

When I joined Womp, the 3D scenes users were creating were relatively modest. The graphics engine handled them comfortably, operations were fast, and the browser had no trouble keeping everything responsive. Nobody was thinking about thread management because nobody needed to.

Then the graphics team got really good at their job.

Over time, Womp's rendering engine became significantly more capable. Scenes that used to be small grew into large, complex environments — detailed geometry, thousands of objects, intricate material definitions. The graphics team built tools that let users construct scenes that would have been impossible to run smoothly in a browser a year earlier.

This was a good problem to have. But it came with a side effect.

The freeze

Any operation that required heavy mathematical work on scene data — geometry exports, mesh analysis, collision calculations, certain transformation operations — now ran on data that was orders of magnitude larger than before. These weren't slow operations on a server. They were running synchronously on the browser's main thread.

The browser's main thread is responsible for everything visible to the user: rendering frames, responding to clicks, running animations, processing input. When a heavy computation occupies that thread, everything else waits. The UI freezes. Scroll stops. Buttons don't respond. On a large scene, some operations would lock up the interface for several seconds.

This was a product quality problem. Users were mid-workflow, trying to export or analyze their work, and the tool became temporarily unusable every time they triggered one of these operations. The underlying computation was correct. The result was right. But the experience felt broken.

Rethinking where work happens

The fix required separating two things that had always been bundled together: running the computation and keeping the UI alive.

Web Workers are the browser's mechanism for this. A worker runs in a completely separate thread from the main execution context. It has no access to the DOM, no ability to directly manipulate the UI — it just runs JavaScript in isolation. You send it data, it processes it, it sends back a result. The main thread never blocks.

I built a worker layer that sat between the graphics team's heavy computation code and the main application. When a graphics operation exceeded a complexity threshold — or was known to be expensive regardless of input size — it was routed to a worker instead of running inline. The main thread handed off the data, registered a callback for the result, and moved on. The UI remained fully interactive while the worker churned through the computation in the background.

When the worker finished, it posted the result back to the main thread, which applied it to the scene. From the user's perspective, they triggered an operation, the interface stayed responsive, and the result appeared when it was ready — rather than the interface locking up until the work was done.

The engineering details that mattered

The straightforward part was spinning up a worker and moving code into it. The harder parts were around data transfer and coordination.

Web Workers communicate via message passing, and by default that means copying data. For large geometry buffers — the kind you're dealing with in a complex 3D scene — copying is expensive. Serializing a large mesh, transferring it across thread boundaries, and deserializing it on the other side can take longer than the computation itself.

The solution was transferable objects. Certain buffer types in the browser — ArrayBuffers in particular — can be transferred rather than copied. The original thread hands ownership to the worker, the worker processes it and hands it back. Zero copying, near-zero transfer cost. Geometry data that would have been expensive to copy moved between threads essentially for free.

The second challenge was coordination. Not all operations can be fully offloaded — some need to read or write scene state that lives on the main thread. I built a message protocol between the worker and the main thread that allowed workers to request state reads, receive the data they needed, and continue processing. This kept the heavy computation in the worker while letting it stay in sync with scene state without requiring full data transfers upfront.

What changed after

The operations that used to freeze the UI became background tasks. Users could trigger a heavy export or analysis operation and continue working — moving the camera, editing objects, interacting with the tool — while the computation ran in parallel. The result would appear when it was ready without having ever interrupted their workflow.

For the graphics team, the worker layer became a consistent target for anything they knew would be expensive. Instead of thinking about thread management on a case-by-case basis, they had a clear pattern: expensive computation goes to the worker, result comes back asynchronously. The interface concern was separated from the computation concern.

Takeaways

The browser's single-threaded model is one of those constraints that's easy to ignore until you can't. For most web applications, it never matters — the computations are light enough that blocking the main thread for a few milliseconds is invisible. For a 3D creative tool running complex geometry at scale, it matters a lot.

The principle generalizes beyond workers. Any time you have a computation that competes with UI responsiveness, you have a choice: make the computation faster, or move it off the critical path. Sometimes you can do both. But understanding which operations belong on the main thread and which don't is the prerequisite to making either choice intelligently.

At Womp, the scenes got big enough that the choice became unavoidable. That constraint produced better software — a cleaner separation between computation and presentation, and a more responsive product for users working with complex scenes.

Contact

Talk to me about engineering, fast cars, or anything that gets the adrenaline going.

Loading image...
Contact

Theme

Accent color

Gray color

Appearance

Radius

Scaling

Panel background