When to dispose: How to completely clean up a Three.js scene

Continuing the discussion from API Documentation:

If we are completely done wanting to have a Three.js scene exist in an application (f.e., suppose we have a single page application where the page was rendering a Three.js scene, and we’ve switched to another view and we no longer need to render any Three.js scene), how do we properly and completely clean the scene up so that no traces of it left behind in memory?

Here’s some things I can think of:

  1. Call dispose() on all geometries
  2. Call dispose() on all materials

What else? What’s the comprehensive list of things to do to completely remove any and all Three.js rendering from a page (and even THREE itself if it is no longer needed)?

4 Likes

I think the best way to do it is to let the javascript garbage collector handle this for you.
All you have to do is make sure there are no references left to your scene object and any objects inside it.

If you have some class in your application that holds references to materials, for example, you’ll have to remove them by simply calling delete this.materials['my_mateiral_name_or_id']. The same thing applies to anything that is in your scene that might live elsewhere in your application.

I think this is essentially what dispose() does for you internally.

Lastly, forcing a context loss on the webgl context and getting rid of the renderer afterward will ensure you’ll be freeing up GPU resources, and allowing you to initialize another renderer afterward without resource usage being stacked up.

See the discussion on GH too:

And also these examples: https://threejs.org/examples/?q=memory

4 Likes

textures also need to be disposed

@Harold Simply losing rederences I don’t think always works. For example, if you keep a reference to a renderer in order to use it later to render a different scene, not having called dispose() will have leaked geometries/materials/textures/etc into the GPU, right?

As for losing a reference to everything (f.e. Three, canvases, gl contexts, all meshes/geometries/materials/etc) will that successfully clean things up without having to call dispose() on anything? @pailhead @looeee?

Anything that is an object that no longer holds a reference anywhere, will get cleaned up by the GC.
A texture is also an object (THREE.Texture), that holds a reference to either Image, Canvas or ImageData or any other kind of image (or video) source.

Nullifying these references (setting the property that holds the reference to null or undefined), makes the dereferenced object eligible for garbage collection. There is no way to “force” this in any other way.

This is effectively what a dispose() function should do.

As for the renderer, calling dispose() should force a “context loss” that will free up the GL context (and therefore free up GPU resources) once the GC kicks in.

Psuedo dispose() example (not to be taken literally):

this.dispose = function () {
    // Iterate through all properties of this object.
    Object.keys(this).forEach((key) => {
        // Recursively call dispose() if possible.
        if (typeof this[key].dispose === 'function') {
            this[key].dispose();
        }
        // Remove any reference.
        this[key] = null;
    });
}

Of course, you shouldn’t do it like this, but it simply illustrates how a dispose function could work.

If an object holds any reference to another object that is still being used (e.g. a DOM element that is still present), it isn’t eligible for garbage collection and therefore will never be cleaned up.

1 Like

So, if I lose all references to all Three.js objects, and also the canvas (remove it from DOM), then I suppose I should be okay (I’m guessing losing a reference to canvas plus all Three obects cleans up the GPU too). I’m guessing in that case I don’t need to call any dispose functions, and that dispose is useful only if we’re keeping the canvas and the renderer, and just want to change the scene to show other stuff and to unshow some other stuff (f.e. switching views in the scene).

@trusktr it’s probably better to test that rather than guess.

3 Likes

Testing takes time. I figured someone might know. The docs don’t go into detail on that (yet), and reading source will take some time. I reading little by little… :blush:

1 Like

Dealing with scene cleanup is one of the grey box areas of three.js currently. There isn’t really good documentation and it does take a lot of time to test.

I’m not saying that it should be you that does this testing… But it would be great if somebody wrote a comprehensive guide and added it to the docs.

5 Likes

Alright, the following is what I have so far, and I’m doing it to see if there’s any difference in memory use compared to not doing it (my guess is there won’t be and the Garbage Collector will work great, but just in case). Am I missing anything?

console.log('dispose renderer!')
renderer.dispose()

scene.traverse(object => {
	if (!object.isMesh) return
	
	console.log('dispose geometry!')
	object.geometry.dispose()

	if (object.material.isMaterial) {
		cleanMaterial(object.material)
	} else {
		// an array of materials
		for (const material of object.material) cleanMaterial(material)
	}
})

const cleanMaterial = material => {
	console.log('dispose material!')
	material.dispose()

	// dispose textures
	for (const key of Object.keys(material)) {
		const value = material[key]
		if (value && typeof value === 'object' && 'minFilter' in value) {
			console.log('dispose texture!')
			value.dispose()
		}
	}
}

6 Likes

Hi, I have tested your code, it doesnt do anything, unfortunately. Have you found a way to clean up the GPU memory?

Thanks.

First, welcome to the forums.

What do you mean by “it doesn’t do anything”? What do you want it to do? If you want more help, you need to post more detail.

Also, I have more cleanup helper code utilities here: lume/src/utils/three.ts at a16fc59473e11ac53e7fa67e1d3cb7e060fe1d72 · lume/lume · GitHub

For example, disposeObject(someObject) disposes every mesh/geometry/material in the tree contained in someObject.

3 Likes

Hello together. Thanks for all input here. I try to find out what’s the basic proper way today to free ressources of a not used 3d-file in a three.js scene. Let’s say add only one simple model to the scene how it is explained here. Is it possible to say the code from trusktr three post above is common sense today? I mean this dialogue here seems to be the latest discussion of this topic but older posts looks like if they handle this situation in another way with a more extensive code… I first aksed this on stackoverflow.

Yes, the approach presented here is still valid. Instead of using a scene, you can also use an arbitrary 3D object.

Keep in mind that calling renderer.dispose() is only necessary if you want to clean up the entire three.js scene (and want to stop rendering).

4 Likes

Hi! Will these code be a part of THREE? It would be pretty continent if the function is a part of scene or group object!

1 Like

i’ve try this, but it gives me an error like this

so much of that errors, why is that?

@naonao_naonvl No idea, because we can’t see your code. Did you try to dispose something, and then use it?

The error stack trace in your screenshot is not from the dispose functions, those errors are happening in Three.js code because something is null. If it has to do with the dispose functions, then my blind guess is that disposing stuff makes something null (in a newer version of Three???) and then you are trying to use it?

That’s random guess. I could be completely wrong. The only way you can get good help is posting live code examples.

1 Like

i figure it out,i found a way to remove everything from scene is setting scene to null i found that on stackoverflow that is why autoUpdate can’t be read. I remove that and just using your code above,thanks

Here is the code that works for me, adjust your variable accordingly. for scene and renderer.

// dispose off all the 3D scene elements
    const cleanMaterial = material => {
      material.dispose()
      // dispose textures
      for (const key of Object.keys(material)) {
        const value = material[key]
        if (value && typeof value === 'object' && 'minFilter' in value) {
          value.dispose()
        }
      }
    }

    this.scene.traverse(object => {
      if (!object.isMesh) return

      object.geometry.dispose()

      if (object.material.isMaterial) {
        cleanMaterial(object.material)
      } else {
        // an array of materials
        for (const material of object.material) cleanMaterial(material)
      }
    })

    this.renderer.dispose()
    this.scene.clear()
    this.renderer.forceContextLoss()