Hey everyone!
I have been tracking a memory leak in Cosmos Journeyer for about a week now where disposing planets would not release the RAM used.
I finally tracked it down to a large procedural texture that just does not want to die somehow Basically, calling dispose
on the procedural texture does not free the RAM. What I donât understand yet is why is the data stored in RAM while the procedural texture is used in GPU memory
Here is a repro of the bug (be careful it might crash your computer if you let it run for too long):
If anyone knows how to fix this I would be greatful, but that kinda looks like a bug to me
You should not use a texture if it has been disposed, it is undefined behavior. So, when you dispose of the texture, you should also set mat.diffuseTexture = null
:
1 Like
So in consequence, if we want to play it safe, whenever we
- dispose a texture,
- disopse a material with the dispose-texture paramter
- dispose a particle system (afaik auto-disposes its texture)
we need to iterate all materials and check all texture channels for leftover references?
Would it suffice to use onTextureRemovedObservable** in order to always prevent a memory leak? (**I mean if the observable notifies, iterate materials then)
1 Like
Yes, onTextureRemovedObservable
seems a good way to remove references to a texture that has just been removed.
2 Likes
Alright thatâs definitely an improvement.
However setting the texture to null is not something that can be done with a ShaderMaterial for example (setTexture
only accepts a texture and not null)
Also I still donât understand why the texture has any cpu ram footprint as it is stored on the GPU. Is there some cached texture data that can be flushed to optimize RAM usage?
You canât pass null
to setTexture
because passing a null texture to a shader is not allowed. If your shader doesnât use the texture anymore, you must update it, because the texture(procTex, vUV);
instruction is wrong/undefined behavior.
Thatâs probably because context lost management is enabled for your engine (it is the default). In that case, a number of resources (like textures) are cached, to be able to recreate them after a context has been lost/recreated. See Babylon.js docs
Alright disabling the feature indeed saves a lot of RAM nice
I also confused myself about the ShaderMaterial, sorry about that ^^
What I really meant is that in this PG, the ShaderMaterial
is disposed and so is the ProceduralTexture
(every second). However, waiting for about 30s on my machine, the cpu ram usage starts to rise (even with doNotHandleContextLost
set to true) and then It crashes:
WebGL: CONTEXT_LOST_WEBGL: loseContext: context lost
thinEngine.ts:2817 Uncaught (in promise) Error: Unable to create texture
at t._createTexture (thinEngine.ts:2817:19)
at t._createHardwareTexture (thinEngine.ts:2825:46)
at new t (internalTexture.ts:309:44)
at t._createInternalTexture (thinEngine.ts:2877:25)
at t.createRenderTargetTexture (engine.renderTarget.ts:74:73)
at t._createRtWrapper (proceduralTexture.ts:228:48)
at new t (proceduralTexture.ts:202:32)
at t.createProceduralTexture (nodeMaterial.ts:1294:35)
at <anonymous>:63:52
t._createTexture @ thinEngine.ts:2817
t._createHardwareTexture @ thinEngine.ts:2825
t @ internalTexture.ts:309
t._createInternalTexture @ thinEngine.ts:2877
(anonymous) @ engine.renderTarget.ts:74
(anonymous) @ proceduralTexture.ts:228
t @ proceduralTexture.ts:202
(anonymous) @ nodeMaterial.ts:1294
(anonymous) @ VM41:63
Promise.then
createTex @ VM41:62
(anonymous) @ VM41:80
setTimeout
(anonymous) @ VM41:78
(anonymous) @ proceduralTexture.ts:338
(anonymous) @ effect.ts:579
e.notifyObservers @ observable.ts:393
e._onRenderingStateCompiled @ effect.ts:743
onRenderingStateCompiled @ effect.ts:790
(anonymous) @ effect.functions.ts:308
(anonymous) @ thinEngine.functions.ts:351
f @ thinEngine.functions.ts:249
t._finalizePipelineContext @ thinEngine.ts:2067
t._isRenderingStateCompiled @ thinEngine.ts:2127
get @ webGLPipelineContext.ts:37
e._isReadyInternal @ effect.ts:447
e._checkIsReady @ effect.ts:591
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
setTimeout
e._checkIsReady @ effect.ts:603
(anonymous) @ effect.ts:604
When running nvidia-smi
, I can see the VRAM usage increase until overflow while I would expect it to be stable over time if the texture was released from memory every second
It looks like it could be a problem with the browser⌠I added some debug code and could check that each time we dispose a texture, we do execute context.deleteTexture
for that texture, which should free the GPU memory, or at least mark the memory as being âreclamableâ: itâs not required that the browser immediately releases the memory.
Also, I saw with nvidia-smi that once I reach my max GPU memory, refreshes still work for 5 or 6s, meaning the browser is able to reuse GPU memory under-the-hood: the used memory stays at max during all that time, proving (I think) that GPU memory is not really freed by the browser, but that it is able to recycle the released textures. But at some point it is not able to do it anymore for some reasonsâŚ
Note that it works in WebGPU, memory does not raise with each refresh.
It looks quite like Memory Leak in Screenshots - #19 by Evgeni_Popov, but I donât know what the final outcome of this case was.
1 Like
Okay, so I guess the solution for WebGL is to avoid doing large allocations and release in quick succession to let the browser take its time to give it back to the GPU.
I wanted to use WebGPU eventually so I wonât have to worry about it!
Thanks again for your time and knowledge