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