WebGL response context loss solution

Table of Contents

Response context lost

How to respond to context loss

context events

Sample program (RotatingTriangle_contextLost.js)


Response context lost

WebGL uses the computer’s graphics hardware, and these resources are managed by the operating system and shared by multiple applications, including browsers. In some special circumstances, such as another program taking over the graphics hardware, or the operating system going to sleep, the browser loses the right to use these resources, resulting in the loss of data stored in the hardware. In this case, the WebGL drawing context is lost. For example, if you are running a WebGL program on a laptop or smartphone, as shown below (left), and then put it to sleep, typically an error message will appear in the browser’s console. When you wake up your computer or mobile phone again, the operating system does return to the state before hibernation, but the WebGL program running in the browser is gone, as shown in the figure below (right). The background color of the web page is white, so the browser is blank.

For example, when you run the RotatingTriangle program and put the computer to sleep, the console may display:

WebGL error CONTEXT_LOST_WEBGL in uniformMatrix4fv([object WebGLUniformLocation, false, [object Float32Array]]

This message indicates that the browser is calling the gl.uniform-Matrix4fv() function and an error occurred before the system entered sleep state or after waking up. The exact content of this message depends on what the program was doing when the entry context was lost. This section explains how to deal with the problem of context loss.

How to respond to context loss

As mentioned before, in some cases context may be lost. In fact, WebGL provides two events to represent this situation, Context loss event (webglcontextlost) and Context recovery event (webglcontextrestored). As shown in Table 10.4.

Context event

When the context event is lost, the rendering context object gl obtained by the getWebGLContext() function becomes invalid, and all previous operations on gl, such as creating buffer objects and texture objects, initializing shaders, setting background colors, etc., All failed. After the browser resets the WebGL system, the context recovery event is triggered. At this time, we need to complete the above steps again. Variables saved in JavaScript will not be affected and can be used as usual.

Before studying the sample code, we need to use the addEventListener() function of to register the response functions for context loss events and context restoration events. You should still remember that before we directly registered the mouse event response function through the onmousedown attribute of the element, but did not support a special attribute to register the response function for the context event, so addEventListener() must be used function.

Sample program (RotatingTriangle_contextLost.js)

We built a sample program RotatingTriangle_contextLost that modifies RotatingTriangle so that it handles context loss events, as shown above. The code for the program is shown below.

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\\
' +
  'uniform mat4 u_ModelMatrix;\\
' +
  'void main() {\\
' +
  ' gl_Position = u_ModelMatrix * a_Position;\\
' +
  '}\\
';
var FSHADER_SOURCE =
  'void main() {\\
' +
  ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\\
' +
  '}\\
';

function main() {
  var canvas = document.getElementById('webgl'); // Get the canvas element
  //Register event response functions to handle context loss and recovery events
  canvas.addEventListener('webglcontextlost', contextLost, false);
  canvas.addEventListener('webglcontextrestored', function(ev) { start(canvas); }, false);
  start(canvas); // Start the process related to WebGL
}

var ANGLE_STEP = 45.0;
var g_currentAngle = 0.0; // Change from local variable to global variable
var g_requestID; // return value of requestAnimationFrame() function
function start(canvas) {
  // Get the WebGL rendering context
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl); // Write the positions of vertices to a vertex shader
  gl.clearColor(0.0, 0.0, 0.0, 1.0); // Specify the color for clearing <canvas>
  var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
  var modelMatrix = new Matrix4(); // Create a model matrix
  var tick = function() { // Start drawing
    g_currentAngle = animate(g_currentAngle); // Update current rotation angle
    draw(gl, n, g_currentAngle, modelMatrix, u_ModelMatrix); // Draw the triangle
    g_requestID = requestAnimationFrame(tick, canvas); // Reregister this Function again
  };
  tick();
}

function contextLost(ev) { //Context loss event response function
  cancelAnimationFrame(g_requestID); // Stop animation
  ev.preventDefault(); // Prevent default behavior
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array ([
    0.0, 0.5, -0.5, -0.5, 0.5, -0.5
  ]);
  var n = 3; // The number of vertices
  var vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Position);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  return n;
}

function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) {
  modelMatrix.setRotate(currentAngle, 0, 0, 1);
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLES, 0, n);
}

var g_last = Date.now();
function animate(angle) {
  var now = Date.now();
  var elapsed = now - g_last;
  g_last = now;
  var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
  return newAngle %= 360;
}

The process of handling context loss has nothing to do with the shader and happens in the main() function. The main() function in this example is very simple: first register the context loss and context recovery event response functions (lines 15 and 16) respectively, and then call the start() method (line 17), and it’s over.

The start() function performs most of the logic of the main() function in RotatingTriangle.js (line 23). This function should be called again when the context is lost and restored. In order to handle the reinitialization of the WebGL program when the context is restored, the start() function has two important changes.

First, the program stores the current angle of the triangle in the global variable g_currentAngle instead of a local variable (line 21), so that when the context is restored, the angle can be obtained from it to draw the triangle. Secondly, in order to stop the animation after the context is lost (that is, to stop calling the tick() function repeatedly), the program also saves the return value of the requestAnimationFrame() function in the global variable g_requestID (line 22).

Let’s look at the context event response function. The context loss event response function contextLost() has only two lines, stopping calling the function that generates the animation to ensure that no redraw is attempted before the context is restored (line 40), and preventing the browser’s default handling behavior for the event (line 41 ). The browser’s default handling behavior for context loss events is to no longer trigger the context recovery event. In this example, the event needs to be triggered, so we need to prevent the browser’s default behavior.

The context recovery event response function is simple, directly calling the start() function to reset the WebGL system, so we define it as an anonymous function (line 16).

Note that when a context loss event is triggered, the browser will always display the following line of warning in the console:

WARNING: WebGL content on the page might have caused the graphics card to reset

By responding to context loss events, WebGL programs can run normally even when context is lost.