How to Destroy a WebGL Context Properly

Managing GPU memory is crucial in web applications, yet WebGL lacks a single, native method to tear down an entire rendering context at once. This guide explains how to properly dispose of a WebGL context, manually free allocated GPU assets—including buffers, textures, and shaders—and trigger explicit context loss using standard WebGL extensions to prevent memory leaks and performance degradation.

1. Release Allocated GPU Resources

Relying solely on the browser's JavaScript garbage collector is not enough to clear GPU memory immediately. Before destroying the context, you must explicitly iterate through and delete all created WebGL resources.

  • Buffers: Call gl.deleteBuffer(buffer) for every vertex or index buffer created.
  • Textures: Call gl.deleteTexture(texture) to free texture VRAM.
  • Renderbuffers and Framebuffers: Call gl.deleteRenderbuffer(renderbuffer) and gl.deleteFramebuffer(framebuffer).
  • Shaders and Programs: Detach shaders using gl.detachShader(program, shader), delete individual shaders with gl.deleteShader(shader), and finally remove the program with gl.deleteProgram(program).

2. Force Context Loss Using the Extension

Because WebGL contexts are tied to the underlying graphics driver, the correct way to inform the browser that the context is dead is by using the WEBGL_lose_context extension.

Acquiring this extension allows you to programmatically trigger the webglcontextlost event and simulate a hardware-level drop:

const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
    loseContextExt.loseContext();
}

Calling loseContext() halts all rendering operations immediately and tells the browser to discard internal driver resources associated with that context.

3. Clear DOM Elements and JavaScript References

Once the context has been forced to lose its state, release all remaining JavaScript references to allow the garbage collector to reclaim CPU memory:

  1. Remove Event Listeners: Remove any resize, render, or context restoration listeners tied to the <canvas> element.
  2. Remove the Canvas: Remove the <canvas> element from the DOM using canvas.remove().
  3. Nullify References: Set your canvas element, context reference (gl), extension instances, and cached assets to null.
  4. Reset Dimensions: Optionally set canvas.width = 1 and canvas.height = 1 to shrink the canvas backing store before dereferencing.

Complete Cleanup Example

function destroyWebGLContext(gl, canvas, resources = {}) {
    // 1. Delete registered GPU resources
    if (resources.textures) resources.textures.forEach(t => gl.deleteTexture(t));
    if (resources.buffers) resources.buffers.forEach(b => gl.deleteBuffer(b));
    if (resources.framebuffers) resources.framebuffers.forEach(fb => gl.deleteFramebuffer(fb));
    if (resources.renderbuffers) resources.renderbuffers.forEach(rb => gl.deleteRenderbuffer(rb));
    
    if (resources.programs) {
        resources.programs.forEach(prog => {
            const shaders = gl.getAttachedShaders(prog);
            if (shaders) {
                shaders.forEach(shader => {
                    gl.detachShader(prog, shader);
                    gl.deleteShader(shader);
                });
            }
            gl.deleteProgram(prog);
        });
    }

    // 2. Force the context to be lost
    const loseContextExt = gl.getExtension('WEBGL_lose_context');
    if (loseContextExt) {
        loseContextExt.loseContext();
    }

    // 3. Clean up the DOM and shrink the drawing buffer
    if (canvas) {
        canvas.width = 1;
        canvas.height = 1;
        if (canvas.parentNode) {
            canvas.parentNode.removeChild(canvas);
        }
    }

    // 4. Clear all references
    return null;
}

Executing these steps in order ensures that both the CPU and GPU release all handles, preventing memory growth during dynamic canvas creation and destruction.