Why We Rebuilt Our BIM Rendering Core with WebGPU
We started with Three.js and WebGL. Once we had models rendering in the browser, we worked through the usual optimizations: loading in chunks, reducing detail at a distance, batching draws, and skipping occluded geometry. As that work progressed, changes began to affect one another. Drawing less required more checks. Keeping the display stable meant dealing with visibility decisions that changed…
The team began by using Three.js and WebGL to render BIM models in the browser. They optimized rendering through techniques like loading models in chunks, reducing detail at a distance, batching draws, and skipping occluded geometry. However, these optimizations introduced complexities as changes often affected one another. For instance, reducing the number of drawn elements required more visibility checks, leading to a cycle of fixes and re-checks.
After trying various optimizations without success, the team decided to rebuild their rendering core using WebGPU. They encountered challenges along the way, such as inconsistencies in visibility decisions and the limitations of WebGL in handling certain optimizations. For example, elements marked invisible were still being drawn because the data used for display couldn't independently determine which element to remove.
The team traced this issue back to a mismatch in granularity between the visibility logic and the data used for display. This revealed a limitation in the old implementation's data organization and decision-making process. They found that relying solely on WebGL to address these issues would not resolve the underlying problems.
To address these challenges, the team broke down the work in each frame into several categories, such as traversing the scene, updating state, preparing drawing commands, handling mouse input, and managing the application UI. They realized that even rendering a single room didn't guarantee that the work required to decide what to draw was cheap.
WebGPU provided the team with more flexibility in organizing work. Its compute and drawing interfaces allowed for the processing of batches of work in parallel on the GPU. This could reduce CPU work per object and minimize round trips between the CPU and GPU. However, moving work to the GPU comes with its own costs, such as preparing and transferring data and ensuring tasks are executed in the right order.
The team also considered replacing only the renderer while retaining their existing data preparation methods. However, they ultimately decided to rebuild both components to ensure that the new graphics API wouldn't inherit the inefficiencies of the old implementation. This included reconsidering which data should be stored on the GPU versus the CPU and identifying work that could be avoided earlier in the process.
For instance, when dealing with shared geometry, like a thousand identical chairs, storing complete copies of the geometry for each chair would be inefficient. Instead, instancing allows these chairs to share geometry while maintaining their individual positions. However, even with instancing, a smaller file doesn't eliminate the pixels covered by these objects or the occlusion between them.
Batching combines many objects into a single draw call, which can reduce submission work. However, if a batch spans several rooms, viewing one room might still cause geometry from other rooms to be submitted. BIM's requirement for individual elements to be independently usable, such as selecting, hiding, and inspecting valves, also poses challenges for large batch sizes.
Culling, or removing objects that are not visible, is another optimization technique. Establishing that elements are hidden requires view frustum checks, spatial hierarchies, and occlusion information. However, each approach has its costs and works best in different scenarios. Directly checking for occlusion for every object can be costly. A balance must be struck between accuracy and performance, as drawing a little extra can be preferable to hiding elements.
Ultimately, the team found that moving work to the GPU could reduce the overall render time and improve performance. While fewer draw calls can lead to less submission work, it doesn't account for the full rendering process. By leveraging WebGPU's capabilities, the team aimed to achieve a more efficient rendering pipeline, balancing the trade-offs between draw calls, GPU utilization, and the need for accurate visibility checks.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.