使用 GPU 在 HTML5 Canvas 上绘制 Google Chrome

Use GPU to draw on HTML5 Canvas on Google Chrome

我使用 canvas 绘制标记(在 SVG 中)数百次(有时数千次)。 canvas 的大小为 300x300 像素,SVG 为 18x25 像素。

代码非常直截了当,我有一个 for 循环,我在 canvas:

上绘制标记
   drawNewTile = (canvas, points) => {
        const drawn = {};
        const context = canvas.getContext('2d');
        if (points.length === 0) return;
        for (let i = points.length; i -= 1;) {
            const [x, y] = points[i];
            if (!drawn[`${x}:${y}`]) {
                drawn[`${x}:${y}`] = true;
                this.drawMarker(context, x, y);
            }
        }
    };

    drawMarker = (context, x, y) => {
        const x_ = Math.floor(x - this.MARKER_WIDTH / 2 + this.MAX_DIMENSION_OF_MARKER);
        const y_ = Math.floor(y - this.MARKER_HEIGHT + this.MAX_DIMENSION_OF_MARKER);
        context.drawImage(this.marker, x_, y_, this.MARKER_WIDTH, this.MARKER_HEIGHT);
    }; 

我已经进行了一些优化:比如for循环,只绘制那些还没有绘制的点,使用整数坐标等

在那之后,我有一些不错的结果,但是我的页面有点卡在 Google Chrome 上。尽管如此,令我惊讶的是,在 Firefox 中它运行得非常快,非常非常快。所以我对 Google Chrome 的性能选项卡进行了一些挖掘,我发现我的代码使用了很多 CPU 并且速度很慢。

我还发现了这个 article,它说 Chrome 使用一些启发式方法来确定它是使用 CPU 还是 GPU 来绘制 canvas。

那么,我的问题是,如何在 Chrome 上强制使用 GPU?有没有我可以设置的标志或类似的东西?您还有其他加快绘图过程的方法吗?

问题是显然 Chrome 将 SVG 图像保留在 CPU 中,并在每次新调用 drawImage() 时对其进行栅格化。

自己简单地光栅化它会使Chrome的性能立即提高。

为此,请使用 createImageBitmap() 方法,该方法将创建浏览器能够直接存储在 GPU 内存中的 ImageBitmap。
Safari 只是在其最新版本的浏览器中公开了此方法,因此您可能仍想为其使用 polyfill。在这种情况下,简单地利用 canvas 就足够了,I made such a polyfill 确实包含了一些大多数浏览器尚不支持的功能。

(async () => {
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  const select = document.querySelector("select");
  const html_img = new Image();
  const svg_str = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 25" width="18" height="25">
    <circle cx="9" cy="9" r="6"/>
  </svg>`;
  const svg_blob = new Blob([svg_str], {
    type: "image/svg+xml"
  });

  ctx.font = "20px sans-serif";
  ctx.fillStyle = "red";

  html_img.src = URL.createObjectURL(svg_blob);
  const sources = {
    html_img,
    bitmap: await createImageBitmap(svg_blob)
  };
  const times = [];
  await html_img.decode();

  anim();

  function anim() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let y = 0; y < canvas.width; y += 10) {
      for (let x = 0; x < canvas.width; x += 5) {
        ctx.drawImage(sources[select.value], x, y);
      }
    }
    requestAnimationFrame(anim);

    // ultra rough FPS counter
    const now = performance.now();
    while (times.length > 0 && times[0] <= now - 1000) {
      times.shift();
    }
    times.push(now);
    fps = times.length;
    ctx.fillText(fps + "FPS", 30, 30);
  }
})();
<!-- createImageBitmap polyfill for old browsers --> <script src="https://cdn.jsdelivr.net/gh/Kaiido/createImageBitmap/dist/createImageBitmap.js"></script>
source: <select>
  <option value="bitmap">ImageBitmap</option>
  <option value="html_img">HTMLImage</option>
</select><br>
<canvas width="300" height="300"></canvas>