使用GPU在Google Chrome上绘制HTML5 Canvas



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

代码非常简单,我有一个for循环,我在画布上绘制标记:

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循环,只绘制那些还没有绘制的点,使用整数坐标,等等。

之后,我有一些不错的结果,但是我的页面在谷歌浏览器上有点卡住了。然而,令我惊讶的是,在Firefox中,它的运行速度非常快,非常非常快。所以我对Google Chrome的性能标签进行了一些挖掘,我发现我的代码使用了很多CPU,而且很慢。

我还发现了这篇文章,它说Chrome使用一些启发式来确定它是使用CPU还是GPU来绘制画布。

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

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

简单地自己栅格化将使Chrome的性能立即增长。

要做到这一点,使用createImageBitmap()方法,这将创建一个ImageBitmap,浏览器将能够直接存储在GPU的内存。
Safari只是在最新版本的浏览器中公开了这个方法,所以你可能仍然想要使用polyfill。虽然在这种情况下,简单地在画布上绘图就足够了,但我做了这样一个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>