浏览器对活跃的WebGL上下文数量有限制。超过限制,浏览器将开始卸载旧的上下文。我的理解是每个域名都有限制以及总体最大值。
两个问题:
- 是否有方法确定您离限制有多近,即有多少活动的WebGL上下文以及有多少可用的上下文?
- 我在这里找到了一些有关的信息,但还没有能够确定每个浏览器(每个域和最大)的限制是什么。 Chrome、Firefox、Safari、Edge和IE 11的限制是什么?
浏览器对活跃的WebGL上下文数量有限制。超过限制,浏览器将开始卸载旧的上下文。我的理解是每个域名都有限制以及总体最大值。
两个问题:
浏览器支持的上下文数量是没有可靠的方法来知道的,即使你知道了,它也可能会在明天改变或者根据浏览器运行的机器或浏览器自身的启发式条件进行更改,例如如果vram较低,则允许较少的上下文。或者,如果最新的上下文使用了太多资源,它会尝试通过失去其他上下文来释放空间。
我的个人经验法则是浏览器至少支持8个上下文。这是我构建网站时的假设。
您可以理解为什么存在限制。WebGL应用程序使用大量资源。也许不是所有的游戏,但游戏特别容易使用大量vram,并且该vram不像常规ram一样容易虚拟化,特别是由于要在浏览器本身中显示结果,因此所有结果都必须达到同一进程。因此,由于它们可能没有被虚拟化并且可以使用如此多的资源,浏览器需要限制同时创建的上下文数量以释放资源用于用户访问的最新页面。
有很多技巧可以使用单个上下文在整个网页上显示很多东西,这些技巧在您链接到的Q&A中得到了涵盖或引用。
您可以像这样计数:
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个上下文。Pixel 2 XL上的Android上的Chrome 81报告了8个,在同一设备上的Firefox报告了9个。但正如上面所说,所有这些数字在不同条件下明天甚至今天可能会发生变化。
无论是Chrome还是Firefox和Safari,浏览器的限制似乎都是每个域限制,但在Firefox和Safari中是每个页面限制。您可以在 此处 进行测试。打开两个窗口,在一个窗口中创建16个上下文,然后在另一个窗口中创建一个上下文。在Chrome中,当第二个窗口创建一个更多的上下文时,您将看到第一个窗口失去一个上下文。在Firefox和Safari中,每个窗口都有自己的限制。
我还发现其他行为中有很多变化。在Chrome上,如果我在一个窗口中创建了16个上下文,在另一个窗口中创建了一个上下文,我希望看到第一个窗口失去一个上下文,但是当我关闭第二个窗口时,我不会看到那个失去的上下文恢复到第一个窗口中。我不知道是否有任何触发器可以将上下文恢复。
在Firefox中,使用上面链接的代码,在同一窗口中创建第17个上下文时,它会进入无限循环。它会失去第一个上下文,该上下文注册以便恢复,火狐立即恢复它,这会导致另一个上下文丢失,反复如此。这似乎使其在Firefox中无法使用。
尝试另一个示例,在这个示例中我没有保留画布的引用,这意味着它们可以被垃圾回收,我在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无法控制操作系统。例如,在Windows中,如果任何一个应用程序在GPU上执行某些操作需要太长时间,操作系统本身会重置GPU,这会有效地丢失所有应用程序(而不仅仅是您的浏览器)的上下文。所有应用程序都无法阻止这种情况发生,因此浏览器只能将此信息传递给您的网页。同样,在Windows中,您可以在无需重新启动计算机的情况下启用/禁用GPU。这是另一种情况,浏览器无法控制,只能告诉您上下文已丢失。