Context Restore

Restoring Resources on contextrestored

After preventDefault(), the browser builds a new context behind the same gl object and fires webglcontextrestored. Keep all GPU setup in one init() that runs at startup and on every restore, and keep CPU-side sources (vertex arrays, decoded images) so nothing is fetched twice. This page loses the context at frame 10 and restores it 300 ms later:

A complete WEBGL_lose_context round trip on the cover wallHTML
<div style="display:flex;gap:14px">
  <canvas id="c" width="700" height="260" style="width:350px;height:130px"></canvas>
  <pre id="log" style="font-size:13px;margin:0"></pre></div>
<script src="webgl-helper.js"></script>
<script>
const canvas = document.getElementById('c'), wall = GLH.coverWall();   // CPU-side data kept
const log = (text) => { document.getElementById('log').textContent +=
  `${performance.now().toFixed(0).padStart(5)} ms  ${text}\n`; };
let gl, vao, frame = 0, request;
function init() {                                  // every GPU object is created here
  gl = canvas.getContext('webgl2');                // the same object after a restore
  const { attributes, uniforms } = GLH.load(gl, `#version 300 es
    in vec2 aPosition; in float aBook; uniform vec3 uColors[6]; out vec3 vColor;
    void main() { vColor = uColors[int(aBook)]; gl_Position = vec4(aPosition, 0, 1); }`,
  `#version 300 es
    precision mediump float; in vec3 vColor; out vec4 c;
    void main() { c = vec4(vColor, 1); }`);
  uniforms.uColors(GLH.covers.flatMap(GLH.rgb));
  vao = GLH.vao(gl, attributes, wall.vertices, [['aPosition', 2], ['aUV', 2], ['aBook', 1]],
    wall.indices);
}
function draw() {
  gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
  gl.bindVertexArray(vao); gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
  if (++frame === 10) { log('frame 10: loseContext()'); lose.loseContext(); }
  if (frame < 16) request = requestAnimationFrame(draw);
  else { log(`frame 16: getError() ${gl.getError()}`); window.__done = true; }
}
canvas.addEventListener('webglcontextlost', (event) => {
  event.preventDefault(); cancelAnimationFrame(request); log('webglcontextlost');
  setTimeout(() => { log('restoreContext()'); lose.restoreContext(); }, 300);
});
canvas.addEventListener('webglcontextrestored', () => {
  log('webglcontextrestored: init()'); init(); request = requestAnimationFrame(draw);
});
init();
const lose = gl.getExtension('WEBGL_lose_context');   // kept: it survives the restore
log('init(), drawing'); request = requestAnimationFrame(draw);
</script>
Browser output of Listing 3.139
Browser output of 139

Old objects stay dead: useProgram(old) gave INVALID_OPERATION, "object does not belong to this context". Without preventDefault(), restoreContext() failed with "context restoration not allowed". Three.js 30,815 and Babylon.js 89,603 restore their own resources; raw WebGL code needs its own init().

A complete WEBGL_lose_context round trip on the cover wall: lost at frame 10, restored 300 ms later, rebuilt by the same init()HTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  .row { display: flex; gap: 12px; max-width: 600px; align-items: flex-start; }
  canvas { width: 330px; height: 123px; flex: none; }
  pre { margin: 0; font: 11px monospace; }
</style>
<div class="row">
  <canvas id="c" width="660" height="246"></canvas>
  <pre id="log"></pre>
</div>
<script>
const canvas = document.getElementById('c');
const log = (text) => { document.getElementById('log').textContent += `${performance.now().toFixed(0).padStart(5)} ms  ${text}\n`; };
// CPU-side data survives a loss: keep it, so nothing has to be rebuilt from scratch.
const covers = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
const vertices = [], indices = [];
covers.forEach((hex, i) => {
  for (const [u, v] of [[0, 0], [1, 0], [1, 1], [0, 1]]) vertices.push(-0.94 + i * 0.32 + u * 0.26, -0.82 + v * 1.64, i);
  indices.push(i * 4, i * 4 + 1, i * 4 + 2, i * 4, i * 4 + 2, i * 4 + 3);
});
let gl, vao, frame = 0, request;
function init() {                                  // every GPU object is created here
  gl = canvas.getContext('webgl2');                // the same object after a restore
  const program = gl.createProgram();
  for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
    layout(location = 0) in vec2 aPosition; layout(location = 1) in float aBook; uniform vec3 uColors[6]; out vec3 vColor;
    void main() { vColor = uColors[int(aBook)]; gl_Position = vec4(aPosition, 0, 1); }`], [gl.FRAGMENT_SHADER, `#version 300 es
    precision mediump float; in vec3 vColor; out vec4 c; void main() { c = vec4(vColor, 1); }`]]) {
    const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); gl.attachShader(program, s);
  }
  gl.linkProgram(program); gl.useProgram(program);
  gl.uniform3fv(gl.getUniformLocation(program, 'uColors'), covers.flatMap((hex) => [1, 3, 5].map((k) => parseInt(hex.slice(k, k + 2), 16) / 255)));
  vao = gl.createVertexArray();
  gl.bindVertexArray(vao);
  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 12, 0); gl.enableVertexAttribArray(0);
  gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 12, 8); gl.enableVertexAttribArray(1);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
}
function draw() {
  gl.clearColor(0.93, 0.91, 0.87, 1); gl.clear(gl.COLOR_BUFFER_BIT);
  gl.bindVertexArray(vao); gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
  if (++frame === 10) { log('frame 10: loseContext()'); lose.loseContext(); }
  if (frame < 16) request = requestAnimationFrame(draw);
  else log(`frame 16: getError() ${gl.getError()}`);
}
canvas.addEventListener('webglcontextlost', (event) => {
  event.preventDefault(); cancelAnimationFrame(request); log('webglcontextlost');
  setTimeout(() => { log('restoreContext()'); lose.restoreContext(); }, 300);
});
canvas.addEventListener('webglcontextrestored', () => {
  log('webglcontextrestored: init()'); init(); request = requestAnimationFrame(draw);
});
init();
const lose = gl.getExtension('WEBGL_lose_context');   // kept: it survives the restore
log('init(), drawing');
request = requestAnimationFrame(draw);
</script>