WebGL memory management puzzlers

webgl_unload_test.html (6.8 KB)

Hi all-

I have been struggling with graphics memory not being freed in an application I’m working on… and to that end, I wrote a test app (attached here) to try to reproduce the problem in it’s simplest form. It’s based on the FBX loader example. Click LOAD to load the model, UNLOAD to dispose all the resources and make sure things are being freed up. Uninterestingly, the model I used was the Jeep1.FBX file included with the assimp distro (not attached) and the texture I changed from JPG to an up-sampled (2048x2048x4), uncompressed TGA that weighs in at 16MB.

Anyway, what I’m seeing is just as weird as the leaking memory issue, but in the other direction…

I click LOAD to load the model with ~7MB of memory displaying as used in the stats tracker… it spikes to ~40MB once everything is resident. Totally expected. Then it drops back to ~7MB after < 30 seconds!!! I didn’t unload the model and it’s still rendering.

So my first question is: what’s actually being displayed? It doesn’t seem like it’s showing the real amount of video memory used because the texture would exceed what’s indicated all by itself. Has anybody else seen this happen?

Second, does anyone have a recommended method for debugging memory issues like this? I’ve tried using the info.memory.textures | geometries… meh. The console collapses messages of the same type into clumps, too. Veeeery helpful.
My background is C++ with OpenGL and DirectX and I’d be using memory breakpoints well before now… but JS is… fun. The issue I’m having with my real project (this test frees memory ok, but the real app does not) feels like some kind of reference counted problem where I’m freeing things during a render (even though I’ve attempted to stop that) and so the system thinks they’re still being used and then never release. Asynchronicity. Hard to tell.

Thanks in advance for any pearls of wisdom.
-Keelan

Are you making sure you clean things up? See here:

Basically make sure you clean up any objects that you are done with. Whatever you discover, it would be great if you add your findings to those discussions so it may help others too.

If you see in the code I sent, I am disposing everything… which I also do in my real app. If you try this test though, you will see that even without unloading anything, the memory consumption reported appears to return to a baseline after a little while… making me not trust it a whole lot.

I have checked out your posts already in trying to solve my issues, but I do appreciate you including links to them… they’re the reason I knew not to rely on garbage collection for WebGL objects.

Cheers!

The memory panel in the stats widget displays the estimated JS heap memory using performance.memory but I can’t say for certain how accurate that API is, either (see here). As far as I know there’s no way to get the amount of vram used other than estimating memory size by measuring the buffer attribute arrays. The memory spike you see is likely due to memory allocated during the loading process that is subsequently cleaned up by the garbage collector.

You can use the performance tab in Chrome to profile the page which will show when the garbage collector runs and when memory is allocated in some charts. You can also trigger the GC manually from there.

WebGL objects can still get GC’d so if you lose a handle to a geometry it can still get cleaned up correctly but it’s always best to call dispose because you can’t always guarantee when, if, or what the GC will collect when it runs.

Good information; thanks for sharing!

I guess it’s js memory that I’m somehow keeping references to that are inflating my footprint. Is there a way in js to see an “address” that I could trace back to a creation?

Thanks again for the help!

I guess it’s js memory that I’m somehow keeping references to that are inflating my footprint.

Precisely tracking memory can be a difficult game in JS. The 30+ MB that stick around after loading won’t be immediately cleaned up once all references are released. Once all references are released they can be cleaned up at some point in the future but it’s not really designed to be predictable when. It very well could not be the case that that memory is from variables you’re keeping references to.

Is there a way in js to see an “address” that I could trace back to a creation?

Look into the “memory” tab in Chrome. I’m less familiar with it but you can take heap snapshots to see what’s taking up memory. Though I’m not sure if it will list or label items that could be garbage collected but haven’t been yet.

Yep, once an object is no longer referenced (i.e. not stored in a variable or property), then the engine can collect it. This can take up to a few seconds depending on the engine’s heuristics. This is not an immediate thing like in C++, where as soon as an object is out of scope (f.e. a function completes execution and nothing else references the object) it will be immediately destructed (i.e. collected).

Your best friend here will be the Performance tab of devtools. After you record an app for a while and then stop, the blue line in the graph will show you memory use. You’ll see in the flame chart when GC runs (f.e. search for “Major GC” in the search bar) and at a point like that you should see the blue line go immediately downward be the amount of allocated memory went down.

If you see that even after GCs happen, the blue line is not going down all the way to a value as what it was before (i.e. overall it keeps growing), then there may be a leak somewhere.

Most likely the problem is from not disposing something. But who knows: Three.js hasn’t been perfect either (no lib is), and it has leaked things before, or it had parts that were not obvious how to clean up (the reason for starting those threads I linked). Here’s an example of something Three.js wasn’t cleaning up..

Next, if something seems to be leaking based on what you see in the Performance tab, you can take a Heap snapshot of the application after it has ran for a while in the Memory tab, and you’ll be able to see in the snapshot all objects in the heap. If you see something still referenced that you thought you got rid of a long time ago, then it has leaked.

This will be useful in pinpointing if there’s an issue in your app or in Three.js.

@keelanstuart By the way, your HTML file is missing a bunch of stuff.

If you’d like someone to investigate your code, you should either upload a zip containing all the needed files, or better yet just make a live example on https://codepen.io. Here’s an example:

(Just paste the link on its own line, and the forums will automatically embed the pen.)

There are other free options too, like

Sorry for the late reply. The only thing it should be missing is the fbx / texture content… I made it so it could be placed in three.js examples. That said, I will definitely give codepen a try the next time I post.

As for the ORIGINAL issue I was having (not the rabbit hole I got into)…

It may have been the THREE.Cache soaking up more and more memory.

Not everyone clones Three.js and uses the example folder. It would be better to use codepen or similar so people will see a working example off the bat. Do you mind posting an example? If so, I would be curious to know what it happening, and add it to the other thread regarding how to clean things up.

1 Like