如何确定活动的 WebGL 上下文的数量?

How to determine number of active WebGL contexts?

浏览器对活动的 WebGL 上下文的数量施加了限制。超出限制和。我的理解是每个域都有限制以及总体最大值。

两个问题:

  1. 有没有办法确定您离限制有多近,即有多少活跃的 WebGL 上下文以及有多少可用?
  2. 我在这里和那里发现了一些信息碎片,但无法准确确定每个浏览器的限制,包括每个域和最大。 Chrome、Firefox、Safari、Edge 和 IE 11 的限制是什么?

没有可靠的方法可以知道浏览器支持多少上下文,即使您知道它明天可能会改变,或者它可能会根据浏览器 运行 所在的机器的各种条件或浏览器自己的启发式算法,比如如果 vram 较低,它可能允许较少的上下文。或者,如果最新的上下文使用了太多资源,它会尝试通过丢失其他上下文来释放 space。

我个人的经验法则是浏览器至少支持 8 个上下文。这就是我建立我的网站所假设的。

你大概能理解为什么会有限制。 WebGL 应用程序使用大量资源。也许不是所有的游戏,尤其是游戏可以很容易地使用 gig 的 vram,而且 vram 不像普通的 ram 那样容易虚拟化,特别是因为为了在浏览器本身中显示结果,所有的结果都必须以相同的方式显示过程。因此,由于它们可能未被虚拟化,并且由于它们可以使用如此多的资源,因此浏览器需要限制一次可以创建的资源数量,以便为用户访问的最新页面释放资源。

有很多技术可以使用单个上下文在网页上显示很多内容,这些内容在您链接到的问答中涵盖或引用。

你可以这样数:

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();

在我的 Macbook Pro Chrome 80、Firefox 75、Safari 13.1 和 iPhone 11 上的 Safari,都报告了 16 个上下文。 Chrome Pixel 2 XL 上 Android 上的 81 报告 8,同一设备上的 Firefox 报告 9。但正如上面所说,所有这些数字明天甚至今天都可能在不同条件下发生变化。


跟进

无论浏览器的限制是什么,在 Chrome 中似乎是每个域,但在 Firefox 和 Safari 中是每个页面。你可以测试这个here。打开 2 windows。在一个 window 中创建 16 个上下文,然后在另一个中再创建一个。在 Chrome 中,您会看到第一个 window 在第二个 window 中创建了另一个上下文后立即丢失了一个上下文。在 Firefox 和 Safari 中,每个 window 都有自己的限制。

我还发现其他行为有很多差异。在 Chrome 上,我预计如果我在一个 window 中创建 16 个上下文,在另一个 window 中创建 1 个,我会看到第一个 window 中丢失了一个,但是当我关闭第二个 window 我会看到在第一个 window 中恢复了一个丢失的上下文。我不。我不知道是否有什么会触发该上下文恢复。

在使用上面链接的代码的 Firefox 中,只要我在同一个 window 中创建第 17 个上下文,它就会进入无限循环。它失去了第一个上下文,那个上下文注册要恢复,firefox 立即恢复它,失去另一个上下文,重复。这似乎使它无法在 Firefox 中使用。

尝试 another example where I don't keep a reference to the canvases,这意味着它们可以被垃圾收集,我在 Firefox 中看到我从来没有遇到上下文丢失事件,这在某种程度上是有道理的,因为我不再有效地引用上下文,所以没有理由发送丢失的上下文事件。另一方面,Chrome 仍然发送事件,这在技术上也没有错,因为我注册了该事件,所以该事件本身仍然有一个参考,如果我不想知道我应该取消注册该事件。

显然这是 WebGL 规范中指定和测试不足的部分。

对于丢失的上下文,您真正能做的唯一一件事就是通知用户他们得到了一个并为他们提供一个按钮来重新开始(创建一个新的或刷新页面)

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;
}

另请注意,WebGL 失去上下文的原点是 WebGL 无法控制 OS。例如,在 Windows 中,如果任何应用程序在 GPU 上执行某些操作花费的时间过长,则 OS 本身将重置 GPU,这实际上会丢失所有应用程序的上下文,而不仅仅是您的浏览器。所有应用程序。浏览器无法阻止这种情况,因此浏览器只需将该信息传递到您的网页即可。同样在 Windows 中,您可以 enable/disable GPU 而无需重新启动。这是另一种情况,浏览器无法控制,只能告诉你上下文丢失了。