Reputation: 714
Tl;DR: I'm having a surprisingly slow rendering time, using the below code - Any idea why that might be or suggestions on how to improve the performance would be deeply appreciated.
I'm developing an application that needs to support multiple rendering backends (e.g. SVG
and Canvas
), but I'm having some demi-serious performance problems that I didn't expect when rendering to Canvas.
Right, so my code is built up, so that each backend implements a Canvas-like drawing API, so that the rendering syntax remains the same no matter what backend is being used. My code is rendering "Glyphs" which are defined as SVG paths (originally from a SVG font file). The problem is that the canvas rendering is surprisingly slow - Almost as slow as the SVG which has a lot of DOM interaction.
I would like to have a rendering time that allowed to have an animation framerate of at least 30 FPS, but right now, a singly frame takes about 50-70ms (Chrome) with Canvas, and 80-90ms with SVG which at the best would result in ~20 FPS. I'm right now rendering 30 Glyphs with an average count of ~24.5 drawing commands.
My question is: Is there a more efficient way to do this sort of rendering or any way to achieve better performance, because I'm stunned by the inefficiency of this method (even though I cache the glyphs!). A Glyph
is an object which on initialization decodes the SVG path string into a (what I would think) faster notation, which makes the path an array of arrays. For example:
[['M', 201, 203.5551],['s', 15.2, 13.254, 15.3, 18.5, 22.3, 50.118], ...]
My CanvasRenderingContext2D#renderGlyph
method is defined as something like, where the glyph.path
object (array) is defined as above:
canvas.renderGlyph = function canvasRenderGlyph(name, x, y, nocache) {
if (!(name instanceof Glyph) && !font.glyphs[name]) {
return console.log('Unsupported Glyph: ' + name, 'warn');
}
x = x * scale;
y = y * scale;
var glyph, path, c, startx, starty, px, py, controlpx, controlpy;
if (typeof name === 'string' && name in glyphCache && !nocache) {
glyph = glyphCache[name];
} else {
glyph = (name instanceof Glyph) ? name : new Glyph(font.glyphs[name]);
glyph.scale(scale * font.scale.x, scale * font.scale.y);
if (typeof name === 'string') {
glyphCache[name] = glyph;
}
}
path = glyph.path;
startx = x;
starty = y;
px = 0;
py = 0;
this.beginPath();
for (var i = 0, length = path.length; i < length; i++) {
c = path[i];
switch (c[0]) {
case 'M':
px = c[1];
py = c[2];
this.moveTo(startx + px, starty + py);
break;
case 'l':
px += c[1];
py += c[2];
this.lineTo(startx + px, starty + py);
break;
case 'h':
px += c[1];
this.lineTo(startx + px, starty + py);
break;
case 'v':
py += c[1];
this.lineTo(startx + px, starty + py);
break;
case 'q':
controlpx = px + c[1];
controlpy = py + c[2];
px += c[3];
py += c[4];
this.quadraticCurveTo(
startx + controlpx, starty + controlpy, startx + px, starty + py);
break;
case 't':
controlpx = px + (px - controlpx);
controlpy = py + (py - controlpy);
px += c[1];
py += c[2];
this.quadraticCurveTo(
startx + controlpx, starty + controlpy, startx + px, starty + py);
break;
case 'c':
controlpx = px + c[3];
controlpy = py + c[4];
this.bezierCurveTo(
startx + px + c[1], starty + py + c[2], startx + controlpx, starty + controlpy, startx + px + c[5], starty + py + c[6]);
px += c[5];
py += c[6];
break;
case 's':
this.bezierCurveTo(
startx + controlpx, starty + controlpy, startx + px + c[1], starty + py + c[2], startx + px + c[3], starty + py + c[4]);
px += c[3];
py += c[4];
controlpx = px + c[1];
controlpy = py + c[2];
break;
case 'z':
this.closePath();
break;
default:
if (c[0].match(/[a-z]/i)) {
console.log('Unsupported path command: ' + cname, name, 'warn');
}
break;
}
}
this.fillStyle = self.settings.fillcolor;
this.fill();
};
Upvotes: 0
Views: 2477
Reputation: 39208
There's definitely something off about this. The performance should not be so poor rendering glyphs of ~24 command complexity.
Fabric.js, for example, is capable of rendering paths with thousands commands at 30fps. In Fabric, I'm using similar approach of parsing SVG path data into an array of commands and then invoking corresponding context methods.
Consider also that Fabric recognizes more commands (your example is missing most of the absolute ones — Q, C, S, etc.)
In this animation example, you can see a decent performance rendering ~4000, ~5000 paths. Only when it goes up to ~10000, you start to see slowdowns (the FPS counter is busted at the moment, so I'm only talking about perceived performance).
Other things that could be affecting your performance — canvas size, possibly presence of other elements on the page, something about animation loop. Also, which hardware/platform are you seeing poor performance on?
Upvotes: 1