0

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();
};
4

1 に答える 1

1

これには間違いなく何かがあります。パフォーマンスは、コマンドが最大24の複雑さのレンダリンググリフをそれほど悪くするべきではありません。

たとえば、 Fabric.jsは、30fpsで数千のコマンドを使用してパスをレンダリングできます。Fabricでは、SVGパスデータをコマンドの配列に解析してから、対応するコンテキストメソッドを呼び出すという同様のアプローチを使用しています。

また、Fabricがより多くのコマンドを認識することも考慮してください(例では、絶対的なコマンドのほとんどが欠落しています— Q、C、Sなど)。

このアニメーションの例では、〜4000、〜5000のパスをレンダリングする適切なパフォーマンスを確認できます。約10000に達すると、速度が低下し始めます(FPSカウンターは現在無効になっているため、知覚されるパフォーマンスについてのみ話します)。

パフォーマンスに影響を与える可能性のあるその他の事項—キャンバスのサイズ、ページ上の他の要素の存在、アニメーションループに関するもの。また、どのハードウェア/プラットフォームでパフォーマンスが低下していると思いますか?

于 2012-06-15T15:45:28.477 に答える