Hey,
why is dispose() method needed? I see now I have memory leaks if I ramp up the number of objects, but if I keep no other references to the object, why won’t garbage collector get rid of it?
Hey,
why is dispose() method needed? I see now I have memory leaks if I ramp up the number of objects, but if I keep no other references to the object, why won’t garbage collector get rid of it?
For BufferGeometry
, the dispose
method triggers an event, which calls onGeometryDispose
. This method ensures the attribute
references are removed from the object (freed for garbage collection). It also does bookkeeping, tracking the number of geometries handled by three.js (line 57).
For Material
objects, the dispose
method triggers an event, which follows a chain of methods. It first calls onMaterialDispose
, which calls deallocateMaterial
, which finally calls releaseMaterialProgramReference
(all in three.js/src/renderers/WebGLRenderer.js
).
In deallocateMaterial
it removes the material reference from the WebGLProperties
.
In releaseMaterialProgramReference
, it un-sets the material’s program
(line 561), then releases the program from the program cache (line 565).
TL;DR: Even though your Mesh
may get garbage collected, that does not mean that the low-level supporting objects were also released, because some of those low-level objects are referenced in other parts of three.js which aren’t obvious on the surface. Calling dispose
ensures these low-level objects are properly freed from internal caches, allowing them to be fully garbage-collected.
(All code in reference to three.js r94.)
Ah ok, I came from C/C++ to Javascript and I was relieved no more deallocation for me, didn’t know functions could create variables that won’t get collected. Great, thanks!
I don’t know if that’s conceptually correct. We may already be on the same page, but I’ll offer an example just in case there’s some misunderstanding.
class Position {
constructor( x, y, z ) {
this.x = x
this.y = y
this.z = z
}
}
class PointCache {
constructor() {
this.points = []
}
add( point ) {
this.points.push( point )
}
remove( point ) {
let idx = this.points.indexOf( point )
if (idx > -1) {
this.points.splice( idx, 1 )
}
}
}
class Example {
constructor( point, cache ) {
this.point = point
this.cache = cache
cache.add( point )
}
dispose() {
this.point = undefined
}
}
// Instantiate the objects
let pt = new Point( 1, 2, 3 )
let c = new PointCache()
let ex = new Example( pt, c )
// Setting these to undefined doesn't release the objects to garbage collection
// because other objects still hold references to them
pt = undefined
c = undefined
// Calling dispose on the Example to prepare it and its sub-objects for garbage collection
ex.dispose()
ex = undefined
// The Example is now ready to be collected, but...
…the above code is leaky. PointCache
will retain a reference to pt
, even though the other references to pt
were set to undefined
. Therefore, the Point
represented by pt
will never get garbage collected.
But if we change Example.dispose
like this…
dispose() {
this.cache.remove( this.point )
this.point = undefined
}
…then the PointCache
will discard its reference to the Point
. When dispose
finishes, all references to the Point
have been nullified, so the Point
is freed for garbage collection.
It also removes stuff from the GPU.
Maybe I didn’t look at the code closely enough, but I didn’t see where it was doing that. Could you link to where it tells the GPU to discard buffers?
Edit: Nevermind, I found it. The attributes.remove
call ends up calling gl.deleteBuffer
, and materials end up calling gl.deleteProgram
.
I actually thought that you only need it for that. Even when you call foo.dispose()
you still have foo. You have to do foo = undefined
to release it.
The webgl stuff though on the other hand has absolutely no idea what you’re doing with your garbage collection in javascript, probably doesn’t know it exists. So if you release something that holds pointers to your webgl objects… you cant release those objects, you dont know how to access them.
At least until you close the tab, and the entire context gets discarded.
Yeah, managing JavaScript object references can be a pain, but I can definitely see why the primary purpose of dispose
is to deallocate GPU resources.