I'm using a canvas to draw a marker (in SVG) hundreds (sometimes thousands) of times. The size of the canvas is 300x300 pixels and the SVG is 18x25 pixels.
The code is quite straigth forward, I have a for loop where I draw the markers on the 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);
};
I have already put in place some optimizations: like the for loop, only draw those points which are not already drawn, use integer coordinates, etc.
After that, I have some good results, but my page it gets a little bit stuck on Google Chrome. Nonetheless, to my surprise, in Firefox it goes fast as hell, like, really really fast. So I made some digging with the performance tab of Google Chrome and I found that my code was using a lot of CPU and that's slow.
I also found this article where it says that Chrome uses some heuristics to determine if it uses a CPU or a GPU to draw the canvas.
So, my question is, how do I force the use of GPU on Chrome? Is there any flag I can set or something similar? Do you any other way to speed that the drawing process?
CodePudding user response:
The problem is that apparently Chrome keeps SVG images in the CPU, and rasterizes it at every new call to drawImage()
.
Simply rasterizing it yourself will make Chrome's performances grow instantly.
To do that, use the createImageBitmap()
method, which will create an ImageBitmap that the browser will be able to store directly in the GPU's memory.
Safari just did expose this method in the newest version of their browser, so you may still want to use a polyfill for it. While in this case, simply drawing on a canvas would be enough, I made such a polyfill which does include a few features that most browsers don't support yet.
(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>