Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGLRenderLists: Fix the issue that memory for disposed objects is not released #12464

Closed
wants to merge 7 commits into from

Conversation

takahirox
Copy link
Collaborator

This PR fixes #12447

Let me know if there's any special reasons that renderItems wasn't cleared in init()

@aardgoose
Copy link
Contributor

Hi,
RenderItems isn't cleared in init() because it acts as a cache of RenderItem objects rather than having repeated allocations for each frame (init is called every frame). This patch will cause excessive heap churn and GC.

@aardgoose
Copy link
Contributor

aardgoose commented Oct 22, 2017

I came across this issue hence renderLists is exposed by the renderer so you can do:

renderer.renderLists.dispose();

to drop all the references to objects, materials etc after removing a set of objects or finishing with a camera / scene combination. Not an elegant solution but it works, and allows the reductions in per frame heap allocs introduced by renderLists to be maintained.

@takahirox
Copy link
Collaborator Author

takahirox commented Oct 23, 2017

You mean, array.length = 0 allocates new memory?

And I don't think renderer.renderLists.dispose(); is a good idea. I don't want users to be aware of renderer.renderLists for object disposal.

@takahirox
Copy link
Collaborator Author

takahirox commented Oct 23, 2017

Ah, you meant allocating new memory here if we clear renderItems, don't you.

https://github.com/takahirox/three.js/blob/871abf5cd090a67c5373bbdbf67f4dd7532d77ba/src/renderers/webgl/WebGLRenderLists.js#L73-L82

OK, I need to update not to allocate new memory each frame...

@takahirox
Copy link
Collaborator Author

Updated

@aardgoose
Copy link
Contributor

Correct, that's the allocation the render lists caches.

Don't know what the performance hit is of clearing all the references, in most cases, with an unchanged number of render items between frames you could probably avoid most of this clearing by using renderListIndex as a high water mark from the previous frame and clear from renderListIndex to renderList.length - 1 : That is, the RenderItems that haven't been overwritten in the previous frame.

I think this would in the common case avoid most of the overhead?

The renderLists.dispose() function is the only answer in some cases where a camera/scene combination won't be reused, but yes it is a very blunt object and a bit ugly except in special cases ( I required it after a rtt pass.

@takahirox
Copy link
Collaborator Author

takahirox commented Oct 23, 2017

Updated.

I think having WebGLRenderList.finish() again like r86 and clearing references in it would be more straightforward.

@mrdoob Let me know if you don't wanna have WebGLRenderList.finish() again.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jan 4, 2018

We should merge this PR or a similar solution. There are scenarios where WebGLRenderList effectively avoids garbage collection, see https://jsfiddle.net/v6bbxxa1/

Although 90 of the 100 objects are removed, the internal renderItems array of WebGLRenderList still has 100 entries with references to obsolete geometries. Something like WebGLRenderList.finish() would free this stuff.

@mrdoob
Copy link
Owner

mrdoob commented Mar 3, 2018

This is how the code used to be before. I changed it because, depending on the scene, it would be creating and deleting objects all the time.

Would using WeakMaps help here?

@aardgoose
Copy link
Contributor

@mrdoob
You sadly couldn't replace the list with a WeakMap. You can't iterate them which is the price of weak references + keys must be objects, so the string 'hash' wouldn't work.

@mrdoob mrdoob modified the milestones: r91, r92 Mar 14, 2018
@anzorb
Copy link

anzorb commented Apr 12, 2018

Our application suffers from this in production and we are seeing GPU process crashes due to a memory leak. Running renderer.renderLists.dispose() makes the profile look clean again. How far is this PR from being complete/released and what do you recommend doing in the meantime? When is a good time to run renderer.renderLists.dispose()? Thanks!

EDIT: looks like updating to r91 has resolved this issue. I'll continue to monitor.
EDIT: looks like there were still Arrays left behind, that are referencing Geometries that were disposed. Calling renderer.renderLists.dispose() after bulk removal of meshes looks to be the solution for now.

@mrdoob mrdoob modified the milestones: r92, r93 Apr 18, 2018
@mrdoob mrdoob modified the milestones: r93, r94 May 22, 2018
@mrdoob mrdoob removed this from the r94 milestone Jun 27, 2018
@aardgoose
Copy link
Contributor

in WebGLRenderList.sort() you could do

renderItems.length = transparent.length + opaque.length; to limit the cache to the current amount used.

Its the separation of cache between (scene, camera ) pairs that gives the bulk of the benefit here I think rather than caching withing a (scene, camera) pair, and would handle this case without setting arbitrary limits.

@sgaoae
Copy link

sgaoae commented Apr 11, 2019

What about clear all of the renderItems after each render?

function clearRenderItem (renderItem) {
    renderItem.id = undefined;
    renderItem.object = undefined;
    renderItem.geometry = undefined;
    renderItem.material = undefined;
    renderItem.program = undefined;
    renderItem.groupOrder = undefined;
    renderItem.renderOrder = undefined;
    renderItem.z = undefined;
    renderItem.group = undefined;
}
renderList = renderer.renderLists.get(scene, camera)
for (let i = 0; i < renderList.renderItems.length; ++i) {
    clearRenderItem(renderList.renderItems[i])
}
// or clear opaque and transparent array instead?
for (let i = 0; i < renderList.opaque.length; ++i) {
    clearRenderItem(renderList.opaque[i])
}
for (let i = 0; i < renderList.transparent.length; ++i) {
    clearRenderItem(renderList.transparent[i])
}

Sorry, never mind. I didn't check all of the commits. This pr will solve the problem.

@Mugen87
Copy link
Collaborator

Mugen87 commented Apr 11, 2019

@sgaoae I think the PR implemented this approach but in a slightly different way. Clearing the internal renderItems is sufficient since the same items of this array are in the opaque and transparent render lists.

@aardgoose
Copy link
Contributor

@sgaoae if you have a case where the object count in a scene reduces significantly as you propose you can always use the brute force approach which removes all the references, if this PR is not adopted.

renderer.renderLists.dispose();

@endel
Copy link
Contributor

endel commented Jul 10, 2019

Just stumbled upon this issue by searching the web for "renderItems memory leak", as my app rebuilds the scene quite often, the suggestion @aardgoose made (#12464 (comment)) seems perfect for me so far.

@mrdoob mrdoob modified the milestones: r107, r108 Jul 30, 2019
@viazmin
Copy link

viazmin commented Aug 6, 2019

I'm using a lot of large BufferGeometries and manually removing all the attributes from them before disposing, frees up most of the memory (r104).

for (const key in geometry.attributes) {
    geometry.removeAttribute(key);
}
geometry.setIndex([]);
geometry.dispose();

@mrdoob mrdoob modified the milestones: r108, r109 Aug 27, 2019
@mrdoob mrdoob modified the milestones: r109, r110 Sep 25, 2019
@mrdoob mrdoob modified the milestones: r110, r111 Oct 30, 2019
@mrdoob mrdoob modified the milestones: r111, r112 Nov 27, 2019
@mrdoob mrdoob modified the milestones: r112, r113 Dec 23, 2019
@Mugen87
Copy link
Collaborator

Mugen87 commented Jan 17, 2020

Closing in favor of #18411.

@Mugen87 Mugen87 closed this Jan 17, 2020
@Mugen87 Mugen87 removed this from the r113 milestone Jan 17, 2020
@takahirox takahirox deleted the FixWebGLRenderLists branch January 17, 2020 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory not released after dispose
10 participants