Command Palette

Search for a command to run...

Hectal
Course outline
Topic 18.2Advanced ~20 min · step 2 of 7

Context Loss and Recovery

In one line

The GPU can take a canvas's WebGL context away while the page is open. three.js rebuilds its own objects when it comes back, but anything you drew once, or made with your own WebGL code, is empty until you rebuild it.

0/7 · 0%

Think of it like this

Someone wipes the whiteboard in your meeting room. The pens are still in your hand and the room is still yours, but everything you drew is gone, and a drawing you made only once must be drawn again. A lost context wipes the GPU's whiteboard.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

context loss
The browser takes back a canvas's connection to the GPU. Everything stored on the GPU for that canvas is gone.
preventDefault
A call on the event that says "I handle this myself". In webglcontextlost it asks the browser to bring the context back later.
render target
A texture a scene is drawn into instead of the screen. Its pixels live only in GPU memory.
WEBGL_lose_context
A browser extension for tests that loses and restores a context on purpose.

Step by step

01Draw, lose, restore

The first example draws an orange box (centre pixel 255,107,44), calls loseContext() and waits for the event. isContextLost() becomes true. renderer.render() while lost returns at once and does not throw.

02See the box come back by itself

After restoreContext() the next draw() shows 255,107,44 again with no code of ours: the mesh, material and program were rebuilt from JavaScript data.

03Paint a target once

In the second example a 64 × 64 render target is painted orange once and shown on a plane every frame. Before the loss the centre is 255,106,42. After restore, with no repair, it is 0,0,0: the target was reallocated but not repainted.

04Repaint on restore

paintTarget() in the restored handler brings back 255,106,42. The rule: for every thing you create once on the GPU, write down how to make it again.

Try it yourself

  1. 1

    Skip the repaint

    In the second example set REPAINT to false. The last line, after repainting: ..., disappears and the centre stays 0,0,0: nothing else tells you the target is empty.

  2. 2

    Make only 16

    In the third example change the 20 to 16 and contexts[19] to contexts[15]. No context is lost: it prints lost: 0 and an empty list.

Code & diagrams

Lose the GPU context and get it back New tab

The loss is simulated with WEBGL_lose_context. A real loss fires the same two events.

idle

Console

Run the scene to see its console.

Expected console output

1. drawing:       centre pixel 255,107,44 | lost: false
THREE.WebGLRenderer: Context Lost.
2. context lost:  lost: true | render() while lost: no error
THREE.WebGLRenderer: Context Restored.
3. restored:      lost: false | centre pixel 255,107,44
What three.js does not restore: a picture drawn once New tab

The centre pixel is read from the canvas. Set REPAINT to false to see the blank result stay.

idle

Console

Run the scene to see its console.

Expected console output

before the loss:   centre pixel 255,106,42
THREE.WebGLRenderer: Context Lost.
THREE.WebGLRenderer: Context Restored.
restored, no work: centre pixel 0,0,0
after repainting:  centre pixel 255,106,42
The 16-context limit New tab

Chromium's limit; other browsers differ. The devtools console also shows the browser's warning.

idle

Console

Run the scene to see its console.

Expected console output

contexts made: 20 | lost: 4 | the oldest ones: 0, 1, 2, 3
the newest (19) is alive: true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget preventDefault

On a plain canvas, listen for webglcontextlost without calling event.preventDefault(), then call restoreContext() 100 ms later.

terminal
$ Run (a plain canvas with the two listeners)
── what you'll see ──
lost event, defaultPrevented: false
restore called
prevent=false restored event: false | isContextLost: true
(with preventDefault: restored event: true | isContextLost: false)

Break #2

Reuse a raw WebGL buffer after the restore

On a raw WebGL 2 canvas (like Phase 14's glow overlay) keep the buffer you made before the loss and call gl.bindBuffer(gl.ARRAY_BUFFER, buffer) after webglcontextrestored.

terminal
$ Run, then read gl.getError()
── what you'll see ──
during the loss: getError -> 37442 (CONTEXT_LOST_WEBGL)
after the restore: bindBuffer(old buffer) -> getError: 1282 (INVALID_OPERATION)

Myth vs fact

Myth

three.js handles context loss completely.

Fact

It handles its own objects. Pictures painted once into render targets, environment maps made from a scene by PMREM (an environment loaded from an HDR file is rebuilt from its data), and any raw WebGL canvas you created are yours to rebuild.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Restoring is lazy: after initGLContext() nothing is uploaded until a mesh is drawn, so the first frame after a restore is slower. Expect a hitch.

  • ▸

    WebGPU has no webglcontextlost. A lost device reports through device.lost, a promise. The showroom's WebGPU path does not recover from it and says so in main.js; a production app on that path should show a message and restart.

Remember this

  1. 1

    The problem this solves: a laptop waking from sleep, a restarting graphics driver, or a browser reclaiming memory can end a context. The page stays open but the picture freezes or goes black, with no message.

  2. 2

    Two events. The browser fires webglcontextlost on the canvas, then, if it can, webglcontextrestored. In the lost handler you must call event.preventDefault(): that is how you tell the browser you want the context back. three.js's WebGLRenderer already does this and keeps a flag, so render() returns at once while the context is lost (see onContextLost and onContextRestore in WebGLRenderer.js).

  3. 3

    What three.js restores. On webglcontextrestored it calls initGLContext(), which makes new empty helper objects (properties, programs, textures, buffers). Meshes, geometries, materials and ordinary textures come back by themselves: the next render() uploads them again from the JavaScript objects that still hold the data. The first example draws, loses the context, and draws again.

  4. 4

    What you must restore. Anything whose contents exist only on the GPU. A render target (Topic 15.1) painted once, such as a baked reflection, a shadow or a PMREM environment made from a scene, has its memory back, empty. The second example paints a target once and shows the centre pixel go from 255,106,42 to 0,0,0 after restore. Your own WebGL contexts, like Phase 14's glow overlay, are separate canvases: three.js knows nothing about them.

  5. 5

    How to test it. Browsers offer the WEBGL_lose_context extension for exactly this test: gl.getExtension("WEBGL_lose_context").loseContext() and .restoreContext() simulate the event pair; renderer.forceContextLoss() does the same through three.js. Calling restoreContext() in the same instant as the lost event did nothing in our tests, so wait 100 ms first.

  6. 6

    The 16-context limit. Chromium keeps at most 16 live WebGL contexts per page. Making a 17th makes the browser lose the oldest, with a console warning: WARNING: Too many active WebGL contexts. Oldest context will be lost. The third example makes 20 and shows which survive. A page with many small 3D widgets must share one renderer or create canvases only while visible.

Explain it without notes

01

Which things does three.js bring back after webglcontextrestored, and which do you have to rebuild? Give two examples of each.

02

Why is calling event.preventDefault() in the lost handler important?

Practice

01

Write the two handlers for a page whose render loop is loop: stop it when the context is lost, start it when restored.

02

A scene paints a static reflection into target once at load. Add the code that keeps it correct after a restore.

Done when you can

  • You can lose and restore a context on purpose, with WEBGL_lose_context or forceContextLoss().

  • You can say what three.js rebuilds and what you must.

  • You always call preventDefault() in a lost handler.

  • You know the 16-context limit in Chromium and how to stay under it.