使用 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>
我使用 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>