BufferGeometry dispose() on shared geometry removes active mesh data
0 reputation · 03 Oct 2021, 04:25 UTC
0 reputation · 03 Oct 2021, 04:25 UTC
When multiple meshes reference a single BufferGeometry instance loaded via GLTFLoader, removing one mesh from the scene while others remain active creates ambiguity about when to call .dispose(). The geometry's GPU buffers are reference-counted only at the JavaScript level; Three.js does not track how many meshes currently use a geometry.
Calling geometry.dispose() immediately after removing a mesh releases the WebGLBuffer objects, causing remaining meshes to render as empty or trigger WebGL errors. Deferring disposal until all referencing meshes are removed requires manual reference tracking, which the loader does not provide.
.dispose() invalidates GPU buffers for all meshes sharing that geometryWhat pattern ensures GPU buffers are released exactly once after the last referencing mesh is removed? How can an application safely coordinate disposal across independently managed mesh lifecycles? Does Three.js expose any internal reference count or event hook for shared geometry cleanup?
34775 reputation · 03 Oct 2021, 14:13 UTC
Three.js does not keep an internal reference count for BufferGeometry objects. When multiple Meshs share the same geometry, calling geometry.dispose() will immediately delete the WebGL buffers, making all meshes that reference it empty. The only reliable way to release the GPU resources exactly once after the last mesh is removed is to implement manual reference tracking in your application.
Map (or a lightweight wrapper class) to count how many meshes currently reference each geometry.geometry.dispose() only when the counter reaches zero.const geometryRefs = new Map();
function addMesh(geometry, material, scene) {
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const count = geometryRefs.get(geometry) || 0;
geometryRefs.set(geometry, count + 1);
return mesh;
}
function removeMesh(mesh, scene) {
scene.remove(mesh);
const geometry = mesh.geometry;
const count = geometryRefs.get(geometry) - 1;
if (count <= 0) {
geometryRefs.delete(geometry);
geometry.dispose(); // safe – no other mesh uses it
} else {
geometryRefs.set(geometry, count);
}
mesh.geometry = null; // detach reference
mesh.material = null;
}
userData:
geometry.userData.refCount = (geometry.userData.refCount || 0) + 1;
This pattern guarantees that GPU buffers are freed exactly once, regardless of how many independent parts of your code add or remove meshes.
If different modules or components manage their own meshes, expose the addMesh and removeMesh helpers (or the wrapper class) as a shared service. Each module should call these helpers instead of manipulating the scene directly. This centralises reference counting and eliminates race conditions where a mesh is removed before the counter is updated.
When you need a mesh to have its own geometry lifetime (e.g., for animation or dynamic updates), clone the geometry:
const uniqueGeom = originalGeom.clone();
const mesh = new THREE.Mesh(uniqueGeom, material);
Cloning creates a new BufferGeometry instance with its own GPU buffers, so uniqueGeom.dispose() will not affect other meshes.
No. The library deliberately keeps geometry management simple: the JavaScript object is garbage‑collected, but the underlying WebGL buffers are not. The only official way to free those buffers is by calling dispose() yourself. If you need automatic cleanup, you must implement your own reference tracking or use a higher‑level framework that wraps Three.js.
geometry.dispose(), and observe that both meshes stop rendering.GL_INVALID_OPERATION.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.