【问题标题】:Why is this canvas code so slow?为什么这个画布代码这么慢?
【发布时间】:2014-05-02 12:06:31
【问题描述】:

我有一个简短的代码,可以在这些轨道上绘制圆(轨道)点(卫星)。卫星围绕轨道运行。事实上代码不是我的,但我被要求解决问题。

根据 chrome 和 firefox 中的分析器,函数 drawSatellite 占用 50%-100% cpu,我想知道为什么。

画布与您的窗口一样大 (1920x1080)。大约有160个轨道(随着时间的增加页面在线)。

这是drawSatellite

OrbitBackground.prototype.drawSatellite = function(ctx, satellite) {
  ctx.fillStyle = satellite.satellite.fill;
  ctx.beginPath();

  if (++satellite.satellite.angularPosition == 360)
    satellite.satellite.angularPosition = 0;

  // 1 FPS = 60 calls => 180 / 6 (6-times faster @ 60 FPS) = 30
  var radians = satellite.satellite.angularPosition * Math.PI / 30 / satellite.rps;

  if (satellite.backward)
    radians = -radians;

  ctx.arc(
      satellite.satellite.x + satellite.orbit.radius * Math.cos(radians),
      satellite.satellite.y + satellite.orbit.radius * Math.sin(radians),
      satellite.satellite.radius,
      0,
      Math.PI*2,
      true
  );
  ctx.closePath();
  ctx.fill();
};

调用它的函数:

OrbitBackground.prototype.drawFrame = function() {
  if (this.running)
    requestAnimationFrame(this.drawFrame.bind(this));

  this.dynamicStageCtx.clearRect(0, 0, this.pageWidth, this.pageHeight);

  for (var i=0; i < this.orbits.length; i++) {
    this.drawSatellite(this.dynamicStageCtx, this.orbits[i]);
  }
};

【问题讨论】:

  • 我认为问题可能是,我们将画布用作 2D 上下文,并且我们使用 javascript 函数而不是着色器,因此操作可能太繁重,因为 cpu 无法调用所有这些 javascript 函数每一帧?但这只是一个理论......
  • 您的 FPS 是否太低?高 CPU 消耗并不总是意味着代码很慢或不是最优的。您应该关心的主要事情是您的绘图方法运行速度快于 (1000/targetFPS) 毫秒
  • 在 chrome 中 fps 还可以,在 firefox 中 fps 非常低,有时浏览器会死机。在嵌入式系统上,fps 超低。
  • 1) 缓存:satellite.satellite可以缓存避免6次访问,satellite.orbit.radius也可以缓存。 2) 什么是填充物?使填充正常化对表演有好处:创建填充后,执行 ctx.fillStyle = myFill;然后 myFill = ctx.fillStyle;所以填充是完全正确的格式。 3)尝试使用fillRect,看看画圆是否不是瓶颈。 4) 请注意,FF 和 Ch 都有画布调试/分析工具。
  • 嗯,我尝试了 2 个样本。 this one seems to run faster in FF than ChromeThis one is faster in Chrome than FF 至少在我的机器上

标签: javascript canvas webgl


【解决方案1】:

你正在这样做:

Loop:
    set fill style
    begin path
    make path
    end path
    fill

这样做会更快:

set fill style (just once, before loop)
begin path (just one path, with loop-number of subpaths)
Loop:
    moveTo (start of subpath)
    make path
    close path
fill (just once, after loop)

但这要求每个卫星的填充样式都相同。如果只有几种颜色,您可以尝试按颜色将它们捆绑在一起。

还要注意计算余弦和正弦很慢(所有三角函数和平方根调用都很慢),如果你能避免使用它们,你会更好。

画布的大小(像素数)也很重要。考虑将画布设为一半大小或四分之一大小(960x540 或 480x270)并使用 CSS 将其放大。

【讨论】:

  • 谢谢你的好建议。画布不能小,因为它应该是页面的背景,所以它取决于客户端屏幕。确实,我的颜色很少,所以我会尝试将它们捆绑在一起。
  • 画布可以更小,然后用 CSS 放大到两倍或三倍的大小,但这会损失保真度
【解决方案2】:

可能有问题:

我在您的 ctx.arc 命令之前没有看到 ctx.beginPath。

如果没有 ctx.beginPath,您之前的所有弧线都将与您当前的弧线一起重绘。

一个小优化

将 Math.PI*2 分配给一个变量,因为它经常被使用

var PI2=Math.PI*2;

如何消除代码中最慢的部分(Math.cos 和 Math.sin)。

由于您的节点处于重复轨道中,您可以预先计算所有未翻译的 [x,y] 以获得完整的轨道。

var statellite.orbitTrig=[];

for(var i=0;i<360;i++){
    var radians=PI2/360*i;
    var x=satellite.orbit.radius * Math.cos(radians)
    var y=satellite.orbit.radius * Math.sin(radians)
    satellite.orbitTrig.push({x:x,y:y});
}

然后您可以在动画循环中参考预先计算的值。

var trig=satellite.orbitTrig[satellite.satellite.angularPosition];
var x=satellite.satellite.x + trig.x;
var y=satellite.satellite.y + trig.y;
ctx.beginPath();
ctx.arc(x,y,satellite.satellite.radius,0,PI2);
ctx.closePath();
ctx.fill();

【讨论】:

    猜你喜欢
    • 2017-01-10
    • 2011-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 2011-04-24
    • 1970-01-01
    • 2010-10-19
    相关资源
    最近更新 更多