Tl;DR: 以下のコードを使用すると、レンダリング時間が驚くほど遅くなります。パフォーマンスを改善する方法についてのアイデアや提案をいただければ幸いです。
SVG
複数のレンダリング バックエンド (および など)をサポートする必要があるアプリケーションを開発していますがCanvas
、Canvas にレンダリングするときに予期していなかった深刻なパフォーマンスの問題がいくつかあります。
そうです、私のコードは、各バックエンドが Canvas のような描画 API を実装するように構築されているため、どのバックエンドが使用されていてもレンダリング構文は同じままです。私のコードは、SVG パス (元は SVG フォント ファイルから) として定義されている「グリフ」をレンダリングしています。問題は、canvas のレンダリングが驚くほど遅いことです。DOM とのやり取りが多いSVG とほぼ同じくらい遅いです。
少なくとも 30 FPS のアニメーション フレームレートを可能にするレンダリング時間を設定したいと考えていますが、現時点では、1 つのフレームに Canvas で約 50 ~ 70 ミリ秒 (Chrome)、SVG で 80 ~ 90 ミリ秒かかります。 〜 20 FPS になります。私は現在、平均 24.5 回の描画コマンドで 30 個のグリフをレンダリングしています。
私の質問は、この種のレンダリングを行うためのより効率的な方法、またはパフォーマンスを向上させるための方法はありますか?というのは、この方法の非効率性に唖然としているからです (たとえグリフをキャッシュしても!)。AGlyph
は、初期化時に SVG パス文字列を (私が思うに) より高速な表記にデコードし、パスを配列の配列にするオブジェクトです。例えば:
[['M', 201, 203.5551],['s', 15.2, 13.254, 15.3, 18.5, 22.3, 50.118], ...]
私のCanvasRenderingContext2D#renderGlyph
メソッドは、glyph.path
オブジェクト (配列) が上記のように定義されているようなものとして定義されています。
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();
};