Request for Adding Canvas WebGL Context Cleanup Method

In the Babylon.js framework, when utilizing the getContext function on an HTMLCanvas element, it creates a new rendering context object, such as 2d, webgl, webgl2, webgpu, or experimental-webgl, to facilitate 3D rendering. This context creation is essential for the engine to operate effectively, and it occurs during the setup of the private _gl property within the ThinEngine class, which serves as the foundation for the Engine.

However, there’s an issue we’ve identified: the webgl context created in this process is never released, even when the engine is disposed of. Consequently, for applications that rely on creating and destroying multiple engines, not necessarily in the order of their creation, various canvas contexts may linger in the browser for each engine instance. Over time, this could lead to an overflow of webgl contexts, with the browser eventually discarding the oldest context in its creation history. Depending on the application, this behavior might deviate from the expected workflow and pose challenges for debugging, particularly for developers less familiar with the HTML Canvas API.

Our proposal is to address this issue by exposing a public method on the ThinEngine class for explicitly releasing the current canvas WebGL context, if it exists. This enhancement would empower developers to efficiently manage memory and perform a straightforward cleanup of the context as needed.

We have considered incorporating this context cleanup method as part of the dispose function’s internal calls. However, this approach may not align with the preferred workflow for some applications that might have manually created the context before initializing the engine. Making it a private method within dispose could also introduce breaking changes for existing users.

Hence, our preferred solution is to introduce a separate public method, as mentioned earlier. This approach maintains flexibility for those who may want to persist manually created contexts while providing a clean solution for memory management.

We believe that this feature would not only benefit the application we are currently working on but also be a valuable addition for the wider Babylon.js community. Therefore, we are interested in creating a Pull Request (PR) to implement this feature, as it has the potential to streamline context management and enhance the Babylon.js experience for developers.

Your feedback and insights on this proposal are highly appreciated, and we look forward to collaborating on this enhancement.

Thank you for considering this feature request.

How would you release the context? I can’t see any method such as destroy / dispose / release on the WebGL context (WebGLRenderingContext - Web APIs | MDN).

The garbage collector will automatically get rid of the context after you dispose the engine, so you should normally not encounter this situation. If this happens, it probably means you have a problem in your program somewhere (maybe something is retaining the gl context, preventing the browser to release the ressources?).

There’s the WELGL_lose_context extension. The garbage collector doesn’t seem to automatically do context loss, I guess probably because it can be lost and restored across several events like switching browser tabs etc. But when I test it with a React setup:

  • Has a list of products models shown in 3D space on a canvas
  • Makes 3D preview as a modal when a product model is clicked with a seperate canvas (new engine and scene).
  • Dispose engine and scene each time product preview is closed.

Now much as this is a simplistic description of the setup, what happens is when I preview about 15 products regardless of if it’s same product or not i.e (create and dispose new engines) then the 16th preview would lead to an overflow which would clear out the original setup that shows the product lists after a browser forceful loss of context to the oldest created one, which in this case is the product list setup.

I’m not sure how this extension will help disposing a canvas? This extension is mainly used for debugging purpose, to simulate losing and restoring a context.

I think you have a problem in your program where the engine is either not disposed, or something else is retaining the gl context, preventing the browser to reclaim it. Are you sure the variable you store a reference to the engine you create also goes out of scope when you dispose the engine? Try to set it to null / undefined to be sure there’s no dangling references to it.

This won t help as @Evgeni_Popov mentioned. The extension is only here to help you troubleshoot context loss internally and ensures your app is dealing with it correclty.

The only way is to dispose the engine, and remove the canvas element from the dom. Then the GC will kick in whenever seems appropriate so as long as you create/dispose frequency is not to high, it should be all good. Otherwise, anyway, I would advise to debounce those as it is a pretty expensive process and even better, recycle recycle recycle :slight_smile:

Laurent Hrybyk GIF - Find & Share on GIPHY

2 Likes

@sebavan @Evgeni_Popov I appreciate that this was written that way in the spec and can also be interpreted incorrectly to mean this is a debugging tool, however if you can look at the Khronos spec of this extension here WebGL WEBGL_lose_context Khronos Ratified Extension Specification you’ll find this under the looseContext around the new functions section.

Implementations should destroy the underlying graphics context and all graphics resources when this method is called. This is the recommended mechanism for applications to programmatically halt their use of the WebGL API.

This is indeed the implementation of this and I can say, my program might have references that I just haven’t debugged hard enough to get rid of, that I can’t really say for sure, after all it’s a pretty complex setup. But this solves the issue I mentioned earlier for my setup, and that is proof that this doesn’t just simulate a context loss but it’s the programmatic way of actually doing it.

I agree that this specification says this is the recommended way to get rid of WebGL resources.

However, the engine class constructor takes the canvas as an input parameter, it doesn’t create the canvas itself. So, in my opinion, it’s the responsibility of the external code to get rid of the canvas, just as it created it.

What we could do is add a parameter to the options object passed to the constructor (something like loseContextOnDispose), but that would be false by default to keep backward compatibility.

cc @sebavan for his opinion.

It’s true about not creating the canvas internally, hence why I was almost suggesting a completely separate public method to dispose of the context, but I like your idea even better for having optional parameter as this then will probably be way easier for the developers using the Engine class without knowing too much about the Canvas API.

It is true that the canvas is not created by the Engine, but also using the getContext method creates a new context if none exist, and I would imagine most use cases of the engine don’t have a preexisting drawing context, just a canvas DOM element. So arguably it is the engine in most cases that is creating this drawing context and it is the role of the engine to lose this context too.

Could we add an extra parameter on dispose instead ?

1 Like

Actually, adding a flag to the dispose function is a bit annoying, because it would be specific to WebGL and would have no meaning for WebGPU / Native… In this regard, adding an option to the construction object is less intrusive because we can already have specific options depending on the engine…

What do you think?

2 Likes

Let’s discuss it on the PR:

1 Like

that’s what I do in react-babylonjs. if an instance is passed to the framework then I had no way to know if the intention was that the caller wanted to manage the lifecycle. I added a property disposeOnUnmount to indicate that they wanted to hand over disposal responsibility. I would think of it as a constructor argument.

1 Like