Properly disposing of THREE.EffectComposer

I’ve got a multi-page application setup where renderers are created and destroyed when navigating to a different route. I’m using THREE.EffectComposer with a THREE.RenderPass and THREE.OutlinePass… but they don’t have dispose() methods which is becoming a huge problem.

Anyone know of a dependable way to cleanup these objects?

I think i figured it out. There was a geometry trapped in a closure that couldn’t be deallocated. It’s in examples/js/postprocessing/EffectComposer.js

THREE.Pass.FullScreenQuad = ( function () {

var camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
// THIS GEOMETRY
var geometry = new THREE.PlaneBufferGeometry( 2, 2 );

var FullScreenQuad = function ( material ) {

	this._mesh = new THREE.Mesh( geometry, material );

};

The fix is to move that geometry into FullScreenQuad(){} so that you can call dispose on it later. Somehow it was trapping a bunch of other objects (like WebGLRenderTarget) in memory.

2 Likes

I think it would be good to clarify this issue. I don’t think a geometry does prevent a render target from being disposed.

Yes I’m confused too. I’d love to figure out what’s going on. I tried to isolate the behaviour in a jsfiddle but couldn’t replicate it. The project is rather large, so i guess there are some other things going on.

The project (soon to be released) is here: http://labs.minutelabs.io/what-is-a-day

Every time i switch views, the canvas/renderer/scene/etc are disposed of and re-initialized. If i take away my “fix”, and don’t call …copyPass.fsQuad._mesh.geometry.dispose(), this is what I see in the profiler.

The blue bars seem to indicate that something huge is getting retained… memory usage increases by around 200MB every time i switch. Looks like the “Detached……” things are not being cleaned. I’m not sure why. Any idea what they are?

When i do apply my fix, and call dispose on the fsQuad geometry, this is what it looks like:

Memory leak gone….

So I’m not sure what’s going on. Maybe you have ideas?

Here’s a link to the heap timeline.

I believe I’ve run into a similar issue.

I’m using react-three-fiber and when a certain mesh gets added to the scene I instantiate an effectComposer and add an outlinePass + some shaderPasses. A few seconds later I remove the OutlinePostProcessing component (aka the effectComposer and all the passes). However, there are 2 textures that stick around in webGL memory. I’ve confirmed it’s the passes causing the increase in textures in memory.

I can’t figure out how to dispose of the textures being created by the passes. Anybody have a clue of how I could dispose properly?

I am experiencing the same issue as @Hayden. Any news on this?

Since r146, all built-in passes as well as EffectComposer have a dispose() method.

Thank you for the quick reply. But I think there is another issue here. As soon as a state update happens close to the effectsComposer, 2 more textures are added - per render. Calling dispose on the composer would only clear the last 2 textures, but not all the other that have accumulated. Also, this breaks the composer so that each time, a new one would need to be created.

I prepared a sandbox for this:

Um, what do you mean by state update? Besides, can you demonstrate the issue with plain three.js? For example by updating one of the post processing examples? three.js examples

If there is still an issue with unreleased memory, I would like to debug this in more detail.

I think this issue has something to do with React and not with plain Three. I opened an issue here: Memory leak on state update · Issue #173 · pmndrs/react-postprocessing · GitHub

A state update means that the component/class in which it happens will be run through again. React will then check which parts of the component need to change. In the codesandbox, the component that includes the effectComposer is (statefully) updated by clicking the button. But the update does not change the effectComposer → nothing should happen with it. However it seems that the effectComposer is recreated each time and looses the reference to it’s old version. But this is just a wild guess and for the react masters at poimandres to find out :wink:

I don’t know if there’s still an issue with unreleased memory - frankly, I’m afraid to even check it out - but “disposing” the composer and its passes won’t destroy the composer - same with a render target. They still exist, and you can check it in this simple fiddle: click on the canvas and press the D key to attempt removing them (comments are included).

So, after 4 years since the original question was asked … how to properly kill this thing (i.e. return to the exact same state as before creating it, structure and memory wise)? Is it enough to make the composer undefined after the disposing code, or should some other bits of code be added for a correct and complete clean up? :thinking:

When dispose() was called on the composer and all related passes, the GPU related memory should be released. When you additionally set the references in JS to undefined, the GC should clean up the remaining CPU-side stuff.

2 Likes

I see, it makes sense - thanks. I think that part of the question was why there is a need for additional calls to composer.removePass() on the passes (besides calling dispose() to handle the memory part) in order to remove the posprocessing effect as well - in other words why simply disposing isn’t enough to take care of everything:

for (var i = composer.passes.length - 1; i >= 0; i--)
{
  composer.passes[i].dispose();
  composer.removePass(composer.passes[i]);
};
rendertarget.dispose(); composer.dispose();
rendertarget = undefined; composer = undefined;

Although… now that I think of it, in some instances when cleaning up other sides of the ThreeJS ecosystem (e.g. meshes / geometries / materials), calls to similar purpose .removeFromParent() or .detach() are needed too, so maybe this also makes sense in the case of a composer.

EDIT: It seems to work without .removePass() as well, but only if setting the variables to undefined as the last step, so you were right: the dispose() of composer and its passes along with setting stuff to undefined is all it takes for a complete clean-up.