如何完全清理 three.js 中的上下文和 canvas

How to fully clean up the context and canvas in three.js

我们有一个也在 iPad 上运行的应用程序。使用 three.js r100.

它有一个 "main" 和几个 "popups",每个都有自己的 canvas、场景和渲染器。 "main" 也有一个场景等总是显示。

为避免内存问题,我们在弹出窗口打开时创建所有对象,并在弹出窗口关闭时清理。 但是在 iPad 上,webinfo 仍然显示 canvasses 关闭的弹出窗口。

在 opening/closing 几个弹出窗口之后,我们收到有关上下文过多的错误 ("There are too many active WebGL contexts on this page, the oldest context will be lost.")。 第一个丢失的上下文是 "main" 场景。之后,系统会尝试释放 "popup" 上下文。显示第二个错误:"WebGL: INVALID_OPERATION: loseContext: context already lost"。这似乎合乎逻辑,因为我们在关闭弹出窗口时执行了 forceContextLoss()。

在弹出窗口关闭时我们:

我怀疑 canvas 阻止上下文被清理,但也许我错过了什么? 那么,如何才能完全去除弹窗的上下文呢?

谢谢,威廉

不确定这是一个直接的答案,但我认为你的运气会更好

(a) 使用单个上下文和剪刀测试来模拟多个画布(推荐)

参见 techniques like this

(b) 使用 virtual webgl context 在单个上下文之上模拟多个上下文。

你实际上只有 1 个上下文而其他上下文是虚拟的

据我所知,无法强制浏览器释放上下文。即使强制上下文丢失也不能保证摆脱 WebGLRenderingContext 对象,事实上它明确不会。当您遇到上下文丢失事件时,即使在恢复后您仍会继续使用相同的上下文对象。

因此,无法保证浏览器不会在创建第 9 个上下文(或任何限制)后立即删除最旧的上下文。唯一的保证通常是在创建新上下文时只有旧上下文会丢失。

是最近最少使用的上下文还是最旧的上下文,或者资源最少的上下文还是没有更多引用的上下文取决于浏览器。浏览器确实没有简单的方法知道要释放哪些上下文。

下面是创建和删除上下文的快速测试。当在 Chrome 桌面

上创建第 17 个上下文时,最旧的上下文丢失

'use strict';

/* global THREE */

function makeScene(canvas, color = 0x44aa88, timeout = 0) {
  
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  const material = new THREE.MeshPhongMaterial({color});

  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  let requestId;
  function render(time) {
    time *= 0.001;  // convert time to seconds

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestId = requestAnimationFrame(render);
  }
  requestId = requestAnimationFrame(render);
  
  if (timeout) {
    setTimeout(() => {
      cancelAnimationFrame(requestId);
      canvas.parentElement.removeChild(canvas);
      // manually free all three objects that hold GPU resoucres
      geometry.dispose();
      material.dispose();
      renderer.dispose();
    }, timeout);
  }
}

makeScene(document.querySelector('#c'));

let count = 0;
setInterval(() => {
  console.log(++count);
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  makeScene(canvas, Math.random() * 0xFFFFFF | 0, 500);
}, 1000);
<canvas id="c"></canvas>
  
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>

这是使用 virtual-webgl 进行的相同测试

'use strict';

/* global THREE */

function makeScene(canvas, color = 0x44aa88, timeout = 0) {
  
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  const material = new THREE.MeshPhongMaterial({color});

  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  let requestId;
  function render(time) {
    time *= 0.001;  // convert time to seconds

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestId = requestAnimationFrame(render);
  }
  requestId = requestAnimationFrame(render);
  
  if (timeout) {
    setTimeout(() => {
      cancelAnimationFrame(requestId);
      // take the canvas out of the dom
      canvas.parentElement.removeChild(canvas);
      // manually free all three objects that hold GPU resoures
      geometry.dispose();
      material.dispose();
      // hold on to the context incase the rendered forgets it
      const gl = renderer.context;
      // dispose the rendered in case it has any GPU resources
      renderer.dispose();
      // dispose the virutal context
      gl.dispose(); // added by virtual-webgl
    }, timeout);
  }
}

makeScene(document.querySelector('#c'));

let count = 0;
setInterval(() => {
  console.log(++count);
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  makeScene(canvas, Math.random() * 0xFFFFFF | 0, 500);
}, 1000);
<canvas id="c"></canvas>
<script src="https://greggman.github.io/virtual-webgl/src/virtual-webgl.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>