0

Tl;DR:我的渲染时间出奇地慢,使用下面的代码 - 任何想法为什么会这样或关于如何提高性能的建议将不胜感激。

我正在开发一个需要支持多个渲染后端(例如SVGCanvas)的应用程序,但是我遇到了一些在渲染到 Canvas 时没有预料到的严重性能问题。

对,所以我的代码是这样构建的,这样每个后端都实现了一个类似 Canvas 的绘图 API,这样无论使用什么后端,渲染语法都保持不变。我的代码正在渲染定义为 SVG 路径(最初来自 SVG 字体文件)的“字形”。问题是画布渲染速度非常慢 - 几乎与具有大量DOM 交互的 SVG 一样慢。

我希望渲染时间允许动画帧速率至少为 30 FPS,但现在,使用 Canvas 时,单个帧大约需要 50-70 毫秒(Chrome),使用 SVG 时需要 80-90 毫秒,这充其量会导致〜20 FPS。我现在正在渲染 30 个字形,平均数量约为 24.5 个绘图命令。

我的问题是:有没有更有效的方法来进行这种渲染或任何方法来获得更好的性能,因为我对这种方法的低效率感到震惊(即使我缓存了字形!)。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能够以 30 fps 的速度渲染包含数千条命令的路径。在 Fabric 中,我使用类似的方法将 SVG 路径数据解析为命令数组,然后调用相应的上下文方法。

还要考虑 Fabric识别更多命令(您的示例缺少大多数绝对命令 - Q、C、S 等)

在这个动画示例中,您可以看到大约 4000 到 5000 条路径的渲染性能不错。只有当它上升到 ~10000 时,您才会开始看到减速(此时 FPS 计数器已失效,所以我只谈论感知性能)。

其他可能影响性能的因素——画布大小、页面上可能存在的其他元素、动画循环。另外,您在哪个硬件/平台上看到性能不佳?

于 2012-06-15T15:45:28.477 回答