Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine number of active WebGL contexts?

Browsers impose limits on the number of active WebGL contexts. Exceed the limit and the browser will start to dump older contexts. My understanding is that there are limits per domain as well as an overall max.

Two questions:

  1. Is there a way to determine how close you are to the limits, i.e., how many active WebGL contexts there are and how many available?
  2. I've found scraps of info here and there, but haven't been able to nail down exactly what the limits are for each browser, both per-domain and max. What are the limits for Chrome, Firefox, Safari, Edge and IE 11?
like image 487
martinez314 Avatar asked Apr 17 '20 17:04

martinez314


People also ask

How many WebGL contexts?

Mobile browsers have a limit of eight WebGL contexts.

What is context in WebGL?

The WebGL context is a JavaScript object that stores the current state of the graphics library. WebGL is a state machine. That means that if you set a WebGL variable to a specific value, that value will not change until you change it. For example, you can set the color used to clear the canvas background once.

Why does WebGL lose context?

Another page running in the user's browser performs an operation using the GPU that takes too long, causing the browser to decide to reset the GPU in order to break the stall. This would cause every WebGL context to be lost throughout the entire browser.


1 Answers

There is no reliable way to know how many contexts a browser supports and even if you did know it could change tomorrow or it could change based on various conditions of the machine the browser is running on or the browser's own heuristics, like maybe if vram is low it allows less contexts. Or maybe if the newest context uses too many resources it tries to free up space by losing other contexts.

My personal rule of thumb is browsers support at least 8 contexts. That's what I've built my sites to assume.

You can probably understand why there's a limit. WebGL apps use tons of resources. Maybe not all of them but games in particular can easily use gigs of vram and that vram is not easily virutalized like regular ram is, especially since in order to display the results in the browser itself somehow the results all have to make it to the same process. So, since they are likely not virtualized and since they can use so many resources the browser needs to limit how many can be created at once to free up resources for the latest page the user visits.

There are plenty of techinques to use a single context to get display lots of things all over a webpage which are covered or referenced in the Q&A you linked to.

You can count though like this:

const canvases = [];
let done;

function createCanvas() {
  const canvas = document.createElement('canvas');
  canvas.addEventListener('webglcontextlost', () => {
    done = true;
    console.log('num contexts:', canvases.length - 1);
  });
  const gl = canvas.getContext('webgl');
  canvases.push(canvas);
}

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function main() {
  while (!done) {
    createCanvas();
    await wait();
  }
}

main();

On my Macbook Pro Chrome 80, Firefox 75, Safari 13.1, and Safari on an iPhone 11, all reported 16 contexts. Chrome 81 on Android on a Pixel 2 XL reported 8, Firefox on the same device reported 9. But like it says above, all those numbers could change tomorrow or even today under different conditions.


Follow Up

Whatever the browser's limit is seems to be per domain in Chrome but per page in Firefox and Safari. You can test this here. Open 2 windows. Create 16 contexts in one window, then create one more in another. In Chrome you'll see the first window lose a context as soon as the one more context is created in the 2nd window. In Firefox and Safari each window has it's own limit.

I also found lots of variation in other behavior. On Chrome I expected that if I created 16 contexts in one window, 1 in another I'd see one lost in the first window but that when I closed the 2nd window I'd see that one lost context restored in the first window. I don't. I don't know what if anything would trigger that context to get restored.

In Firefox with the code linked above, as soon as I create the 17th context in the same window it goes into an infinite loop. It loses the first context, that context registers to be restored, firefox restores it immediately, which loses another context, repeat. This seems like it makes it impossible to use in Firefox.

Trying another example where I don't keep a reference to the canvases, which means they can be garbage collected, I see in Firefox I never get a context lost event which makes some sense since I no longer have an effective reference to the context there's no reason to send a lost context event. Chrome on the other hand does still send the event which is also technically not wrong since I registered for the event so the event itself still has a reference and if I didn't want to know I should have unregistered the event.

Apparently this is an under specified and under tested part of the WebGL spec.

It looks like the only thing you can really do for a lost context is notify the user they got one and provide them a button to start over (create a new one or refresh the page)

const groups = [];
let done;

function createCanvas(parent, lostCallback) {
  const canvas = document.createElement('canvas');
  parent.appendChild(canvas);
  canvas.addEventListener('webglcontextlost', lostCallback);
  const gl = canvas.getContext('webgl');
  return {canvas, gl};
}

function createGroup() {
  const div = document.createElement('div');
  const group = {div};
  div.className = 'c';
  document.body.appendChild(div);
  
  function restore() {
    div.innerHTML = '';
    const {canvas, gl} = createCanvas(div, () => {
      done = true;
      group.gl = undefined;
      div.innerHTML = "context lost, click to restore";
      div.addEventListener('click', restore, {once: true});
    });
    group.gl = gl;
    group.canvas = canvas;
  }

  restore();  
  groups.push(group);
}

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function main() {
  while (!done) {
    createGroup();
    await wait();
  }
}

function render() {
  for (const {gl} of groups) {
    if (!gl) {
      continue;
    }
    gl.clearColor(Math.random() * 0.5 + 0.5, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
  }
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

main();
.c { 
  display: inline-block;
  border: 1px solid black;
  margin: 1px;
  font-size: xx-small;
}
canvas { 
  width: 100px;
  height: 10px;
  display: block;
}

Also note, the original point of WebGL losing the context is that WebGL has no control over the OS. In Windows for example, if any app does something on the GPU that takes too long the OS itself will reset the GPU which effectively loses the contexts for all apps, not just your browser. ALL APPS. There's nothing a browser can do to prevent that and so the browser just has to pass that info down to your webpage. Also in Windows, you can enable/disable GPUs without rebooting. That's another case where the browser has no control and just has to tell you the context was lost.

like image 116
gman Avatar answered Oct 08 '22 06:10

gman