Questions on threejs LoD memory impact

Hi,
I have a question on memory usage of LoD module in threejs. I have a huge scene where I added several lod instances with just the low details at loading time. When I zoom in, if the camera gets closer to a lod, I asynchronously download the higher detail level and add them to the LoD.

There comes the issue, when I zoom out, I guess the memory is still allocated for the higher level of the LoD. Is there a way to “dispose” the geometry/material associated to the high level of LoD ?

I’m not sure what you mean here. Are you talking about CPU or GPU memory? Do you want to keep the high-resolution levels in the LOD object and only free the underlying data?

In any event, the following guide should explain how to dispose of various three.js entities like materials and geometries. Calling .dispose() on your geometry and material objects will free the GPU geometry buffer data and shader programs. However, as long as you have references to objects of THREE.BufferGeoemtry and THREE.Material, they will stay in memory and not GC collected.

https://threejs.org/docs/index.html#manual/en/introduction/How-to-dispose-of-objects

Thanks for your feedback.
I’m talking about CPU memory (altough I’m not sure what happens with GPU memory when the higher level is not displayed).
Correct me If I’m wrong, lod.addLevel(mesh) adds a reference to the mesh, so calling .dispose() on the geometry/material will have no effect, right ?
Would it make sense to have a lod.removeLevel(obj, dist) function ?

It has an effect but primarily on GPU memory.

I’m not sure I understand the workflow in which this method would be useful. When the user zooms in again, you also need the respective high-resolution LOD levels. It’s definitely no good approach to download, parse and GPU-upload these resources every time when this happens.

I’m not sure I understand the workflow in which this method would be useful. When the user zooms in again, you also need the respective high-resolution LOD levels. It’s definitely no good approach to download, parse and GPU-upload these resources every time when this happens.

I have a scene which is composed of 12 GB high resolutions meshes, where the average size of a mesh is 200mb. I created low/medium resolution meshes with an average size of 1/10 mb. I have one lod instance per mesh. If I keep the high resolution meshes in CPU memory, the memory usage rapidly hits 2 GB. This is why I’m looking for a way to offload CPU memory of these high resolution meshes when the camera is far from the lod.

what you probably want is a loading cache and a custom LOD system. You can write your own loading cache or look for an NPM module, a custom LOD system can probably be built on top of what three.js already offers.

I think I can try to rely on the caching system of the webbrowser to avoid downloading again the most detailed tiles. The LoD mecanism provided by threejs works perfectly for my usecase, but it’s just lacking an API to remove a specific level in a given LoD.

I found out that lod.remove(mesh) works fine for what I wanted to do.

If you you think that’s the best way for you. I’m sure you understand your use-case better than I :slight_smile:

When running out of memory is a real concern, you don’t really have any choice but to remove things from memory, so I think your solution makes sense.

Just out of curiosity: Do you manage to render a lower LOD in the zoomed state, before the high-res model is loaded?

I’d use a LRU cache to reference the chunks. Dispose the oldest when the cache is full. For a cache miss, re-download the chunk.

Chunks may not have same RAM occupancy in some applications. The cache limit is no longer the number of chunks, but the total RAM cost of the chunks.

2 Likes

LRU as in “Least Recently Used”, if I am not mistaken. :+1:

1 Like

Just out of curiosity: Do you manage to render a lower LOD in the zoomed state, before the high-res model is loaded?

Yes, no issue on that side.

In the end, my solution:

lodinstance.remove(high_level_mesh);
high_level_mesh.geometry.dispose();
lodinstance.update();

is not working properly, I see that CPU memory is still allocated …

After digging halfway into the source code, I think geometry.dispose() does not remove the data in the geometry object, but removes the GPU-side data. I may be wrong, though. Should be easy to check… Update: Checked with a simple SphereBufferGeometry. The data is not removed when I call dispose().

This was also mentioned by @Mugen87 above:

You can explicitly remove the attributes from the geometry, or simply “lose” all references to it. I guess removing the attributes explicitly is safer, and possibly faster.

I was hoping that calling lod.remove(mesh) would remove all reference to the mesh, but as you said, no luck here.

Thanks for you help, I’m going to try what you suggest.

You could try:

highResMesh.geometry.dispose();
lod.remove(highResMesh); //Update: Not this. See my next comment.
highResMesh = null; //removes another reference
highResMesh.geometry.dispose();
lod.remove(highResMesh);
highResMesh = null; //removes another reference

it’s doing some weard stuff. Before lod.remove() call, lod children number is 2 (high and low), and after it’s only one, so from the lod perspective, it seems ok.

But if I print the renderer.info with:
console.log(renderer.info);

I see that both geometries/material/texture are still present.

I finally managed to offload CPU memory with an ugly hack:

highResMesh.geometry.dispose();
lod.remove(highResMesh);
highResMesh = null; //removes another reference
lod.levels.shift();
lod.levels[0].object.visible = true;

The mesh were still referenced in the levels attributes of the LOD.

This confirms my opinion that a specific API is needed (at least in my usecase).

1 Like

Hm, why do you call lod.remove(highResMesh)? This implies that highResMesh is a child of lod. I thought it was really a level. But there is no method lod.removeLevel yet. Object3D.remove “works” (fails without complaining) when called with an object which is not a child…

You’re right, it’s useless to call lod.remove() !