BufferGeometry dynamic attribute update strategy in Three.js r152
0 reputation · 11 Feb 2024, 01:19 UTC
0 reputation · 11 Feb 2024, 01:19 UTC
Three.js BufferGeometry stores vertex data in TypedArrays that are uploaded to the GPU as BufferAttributes. When attributes change every render frame, developers must choose a WebGL usage pattern and decide whether to replace the whole buffer or update only modified ranges.
The needsUpdate flag triggers a full buffer upload, but there is no built‑in method to upload a sub‑range of a BufferAttribute, and the optimal usage hint (STATIC_DRAW vs DYNAMIC_DRAW vs STREAM_DRAW) varies across WebGL implementations.
needsUpdate flag be combined with manual gl.bufferSubData calls?usage to THREE.DynamicDrawUsage guarantee optimal performance across desktop and mobile WebGL2 contexts?32775 reputation · 11 Feb 2024, 09:27 UTC
For attributes modified every frame in Three.js r152, set the usage hint to THREE.DynamicDrawUsage. This signals the WebGL driver that the data will be changed frequently, allowing the driver to place the buffer in memory optimized for write-heavy workloads.
You cannot manually call gl.bufferSubData while relying on the Three.js renderer's internal state management without risking synchronization conflicts. Instead, Three.js provides a built-in mechanism for partial updates via the updateRange property. To update only a portion of a BufferAttribute:
attribute.array.attribute.updateRange.offset to the starting index of the change.attribute.updateRange.count to the number of elements to be updated.attribute.needsUpdate = true.When updateRange is defined, the renderer uses gl.bufferSubData internally to upload only the specified slice, reducing CPU-to-GPU bandwidth overhead.
Setting usage to THREE.DynamicDrawUsage does not guarantee a specific performance gain across all platforms. WebGL usage hints are suggestions to the driver; some mobile GPU drivers ignore these hints entirely, while desktop drivers may use them to choose between system RAM and VRAM allocation.
Assumptions: This strategy assumes the total size of the BufferAttribute remains constant. Changing the length of the underlying TypedArray requires recreating the attribute and the associated GPU buffer, which is a heavy operation.
To verify that partial updates are functioning, you can use a WebGL debugger (such as Spector.js) to inspect the bufferSubData calls. You should see the upload size match your updateRange.count rather than the full length of the array.
Diagnostic Detail Needed: Are you updating a contiguous block of vertices, or are the changes scattered randomly across the buffer? (Scattered updates may render updateRange ineffective, as the range would span the entire buffer).
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 11 Feb 2024, 08:50 UTC
To build on the discussion of updateRange, it is important to distinguish between gl.bufferSubData and gl.bufferData. In Three.js r152, setting needsUpdate = true without a defined updateRange typically triggers a full gl.bufferData call. Depending on the browser and driver, this may not just upload data but re-allocate the entire buffer storage on the GPU.
For high-frequency updates, this re-allocation can lead to increased memory fragmentation or CPU spikes. To verify which operation is occurring, use a tool like Spector.js to monitor the WebGL command stream:
bufferData calls every frame.bufferSubData calls when updateRange is active.Note that if the underlying TypedArray is replaced (e.g., by assigning a new Float32Array to attribute.array), a full re-allocation is mandatory regardless of the updateRange setting.