Javascript 画布中SVG路径渲染的性能改进

Javascript 画布中SVG路径渲染的性能改进,javascript,performance,canvas,svg,Javascript,Performance,Canvas,Svg,Tl;DR:使用下面的代码,我的渲染时间出奇地慢——任何关于这可能的想法或关于如何提高性能的建议都将不胜感激 我正在开发一个需要支持多个渲染后端的应用程序(例如,SVG和Canvas),但我遇到了一些在渲染到Canvas时意想不到的半严重性能问题 好的,我的代码已经构建好了,这样每个后端都实现了一个类似画布的绘图API,这样无论使用什么后端,呈现语法都保持不变。我的代码正在呈现定义为SVG路径的“字形”(最初来自SVG字体文件)。问题在于,画布渲染速度惊人地慢——几乎与具有大量DOM交互的SVG

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

我正在开发一个需要支持多个渲染后端的应用程序(例如,
SVG
Canvas
),但我遇到了一些在渲染到Canvas时意想不到的半严重性能问题

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

我希望渲染时间允许动画帧速率至少为30 FPS,但现在,使用Canvas时,单个帧大约需要50-70毫秒(Chrome),使用SVG时需要80-90毫秒,这最多只能产生约20 FPS。我现在正在渲染30个glyph,平均计数约为24.5个绘图命令

我的问题是:有没有更有效的方法来进行这种渲染,或者有什么方法可以获得更好的性能,因为我对这种方法的低效性感到震惊(即使我缓存了glyph!)。
Glyph
是一个对象,它在初始化时将SVG路径字符串解码为(我认为)更快的符号,从而使路径成为数组数组。例如:

[['M', 201, 203.5551],['s', 15.2, 13.254, 15.3, 18.5, 22.3, 50.118], ...]
我的
canvasrendingcontext2d#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();
};
canvas.renderGlyph=函数canvasrrenderglyph(名称,x,y,nocache){
if(!(Glyph的名称实例)和&!font.glyphs[名称]){
返回console.log('Unsupported Glyph:'+name,'warn');
}
x=x*刻度;
y=y*刻度;
变量图示符,路径,c,startx,starty,px,py,controlpx,controlpy;
if(glyphCache&&!nocache中的typeof name==='string'&&name){
glyph=glyphCache[name];
}否则{
glyph=(glyph的名称实例)?名称:新glyph(font.glyphs[name]);
glyph.scale(scale*font.scale.x,scale*font.scale.y);
如果(名称的类型=='string'){
glyphCache[名称]=字形;
}
}
路径=glyph.path;
startx=x;
starty=y;
px=0;
py=0;
this.beginPath();
对于(变量i=0,长度=path.length;i
这肯定有点不对劲。性能不应如此之差,呈现~24个命令复杂度的图示符

例如,能够以30fps的速度使用1000命令渲染路径。在Fabric中,我使用类似的方法将SVG路径数据解析为命令数组,然后调用相应的上下文方法

还要考虑该结构(您的示例缺少绝大多数绝对值——Q、C、S等)

在这里,您可以看到一个相当不错的性能渲染~4000、~5000条路径。只有当它上升到~10000时,您才会看到速度变慢(FPS计数器此时被破坏,所以我只谈论感知性能)


其他可能会影响性能的因素-画布大小、页面上可能存在其他元素、动画循环等。另外,您认为哪个硬件/平台的性能较差?

如果您直接使用svgfont或truetype webfont,性能如何?和你的js实现相比,我很好奇。我还没试过。不过,我尝试过使用
ctx.drawImage
,它工作得很好,因为它是异步的,但速度太慢,不太适合动画(因为glyphs在准备好时渲染,这会导致一个明显的恼人问题),谢谢你的回答。缺少可识别的命令是一个深思熟虑的决定,因为字体没有使用大多数绝对命令。我在Chrome/Win7和Chrome/Arch上测试了这一点,它们都在合理的硬件上运行。我不知道画布的大小会影响渲染时间,但遗憾的是,我不认为这是造成它的原因——它最容易影响渲染时间