【问题标题】:Javascript canvas buffer / slow performance on iOs Safari and chromeJavascript 画布缓冲区/iOs Safari 和 chrome 上的性能缓慢
【发布时间】:2014-06-05 19:43:58
【问题描述】:

我正在尝试通过将像素直接绘制到 imageData 缓冲区来更新 javascript 画布。基本上,我会在每次 mousemove / touchmove 事件后更新 imageData 缓冲区上的所有像素,并尝试获得最佳性能。

背景: 我正在开发一个基于 emscripten 的应用程序,其中画布上的绘图是由“本机”代码逐像素完全绘制的。我在这个问题中给出的示例是一个更简单的示例,我在其中复制了我的问题。

我目前遇到了两个性能问题:

  • 在 iOS safari 上(在 iPad air 上测试):绘图功能以 31 fps 调用,但屏幕上的画布渲染滞后(视觉上,我会说它以 10 fps 的最大值更新,加上一些间隔0.5秒根本不更新)
  • 在 iOS Chrome 上:性能很糟糕,因为我得到 2.9 fps

在桌面 Mac 上,我获得了稳定的性能:firefox 为 55 fps,chrome 为 45 fps

所以,我有两个问题

  • 如何强制在 iOs safari 上更快地刷新画布(以实现真正的 30 fps 渲染,或者可能会更低一些)?
  • 您将如何优化性能?我错过了可能的优化吗?

请参考下面的代码:这是一个重现我的问题的单个 html 文件。

我知道我可以使用 webworker,但由于我使用的是 emscripten,所以这不是最佳选择(每个 webworker 都从新的内存开始,我需要记录状态)。

见这里的代码(它是一个单独的 html 文件,js 是自包含的)。请在画布内移动鼠标以查看计算的 fps。

<canvas width=800 height=600 id="canvas"> </canvas>

<script>


//Disable scroll : usefull for tablets where touch events
//will scroll the page
function DisableScroll()
{
  window.addEventListener("touchmove", function(event) {
    if (!event.target.classList.contains('scrollable')) {
      // no more scrolling
      event.preventDefault();
    }
  }, false);
}

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();


window.countFPS = (function () 
{
  var nbSamples = 20; //number of samples before giving a fps
  var counter = 0;
  var fps = 0;
  var timeStart = new Date().getTime();
  return function()
  {
    counter++;
    if (counter == nbSamples)
    {
      var timeEnd = new Date().getTime();
      var delaySeconds = (timeEnd - timeStart) / 1000;
      fps = 1 / delaySeconds * nbSamples;

      counter = 0;
      timeStart = timeEnd;
    }
    return fps.toFixed(2);
  }
}());


function getMousePos(canvas, evt) {
  var rect = canvas.getBoundingClientRect();
  return {
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top
  };
}
function getTouchPos(canvas, evt) {
  var rect = canvas.getBoundingClientRect();
  return {
    x: evt.targetTouches[0].clientX - rect.left,
    y: evt.targetTouches[0].clientY - rect.top
  };
}


DisableScroll();

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
var canvasData = "empty";

function myDraw(pos)
{
  canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var binaryData = canvasData.data;

  var idx = 0;
  for (y = 0; y < canvas.height; y++)
  {
    for (x = 0; x < canvas.width; x++)
    {
      //Red
      binaryData[idx ++] = x % 255;
      //Green : add a little animation on the green channel
      //var dist = Math.sqrt( (pos.x - x) * (pos.x - x) + (pos.y - y) * (pos.y - y));
      var dist = Math.abs(pos.x - x) + Math.abs(pos.y - y);
      var g = 255 - dist;
      if ( g < 0 )
        g = 0;
      binaryData[idx++] = g;
      //Blue
      binaryData[idx ++] = y % 255;
      //Alpha
      binaryData[idx ++] =  255;
    }
  }

  ctx.putImageData(canvasData, 0, 0);
}



var OnLoad = function()
{
  myDraw({x:0, y:0});
}


//
// Mouse & touch callbacks
//
function CanvasMouseMove(pos)
{
  myDraw(pos);
  var elem = document.getElementById("fps");
  elem.value = window.countFPS();

}
canvas.addEventListener("touchmove", function(e){ CanvasMouseMove( getTouchPos(canvas, e)); } , false);
canvas.addEventListener("mousemove", function(e){ CanvasMouseMove( getMousePos(canvas, e) ); });


</script>

<body onload=OnLoad()>
<br/>
FPS<input type=text id="fps" />&nbsp;&nbsp;&nbsp;
</body>

【问题讨论】:

  • 不要忘记在 myDraw 中将 x,y 声明为 vars。您可以缓存 canvas.width 和 canvas.height 以避免 DOM 访问,缓存 pos.x 和 pos.y,并用 (% 255) 换取 (& 0xFF),并缓存 Math.abs(这个不太确定)。我担心所有这些都不会对帧速率产生巨大变化。
  • 另外,更重要的是:只需创建一个您不断修改的 imageData。并通过使用标志在 requestAnimationFrame 上绘制。不要使用输入,而是使用 fillText。有了这一切,fps 在一些浏览器中提高了 2 到 5。 jsbin.com/saruzoqo/2
  • 不错,您的更改使 safari / iOS 上的 fps 从 30 fps 上升到 60 fps,在 chrome / iOs 上从 5 fps 上升到 10(这是在 iPad 上播放的)
  • 我真的很喜欢你关于存储静态 imageData 的建议。它解决了 iOS safari 上的延迟问题,并在 iOS Chrome 下加速了很多。如果您对声誉积分感兴趣,请发表您的评论作为答案,我会接受!
  • 我知道我迟到了,但"use strict"; 可以大幅提升性能。

标签: javascript ios html canvas emscripten


【解决方案1】:

Rq :
- 避免泄漏全局并将 x,y 声明为 myDraw 中的变量。
建议:
- 缓存 canvas.width 和 canvas.height 以避免 DOM 访问,
- 缓存 pos.x 和 pos.y
- 将 (% 255) 换成 (& 0xFF)
- 缓存 Math.abs
- 只需创建一个您不断修改的 imageData(减轻 g.c.)。
- 在 requestAnimationFrame 上绘制(否则您可能需要等待帧绘制)。
- 缓存画布的边界矩形(及其顶部/左侧值)。

jsbin 在这里:

http://jsbin.com/saruzoqo/4/

您可以使用 2 个按钮切换旧/新。

看起来像

var staticCanvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);

function myDraw2(pos) {
    canvasData = staticCanvasData;
    var binaryData = canvasData.data;
    var cw = canvas.width,
        ch = canvas.height;
    var posX = pos.x,
        posY = pos.y;
    var idx = 0;
    var abs = Math.abs;
    for (var y = 0; y < ch; y++) {
        var yDiff = abs(posY - y) ;
        for (var x = 0; x < cw; x++) {
            //Red
            binaryData[idx++] = x & 0xFF;
            //Green : add a little animation on the green channel
            //var dist = Math.sqrt( (pos.x - x) * (pos.x - x) + (pos.y - y) * (pos.y - y));
            var dist = abs(posX - x) + yDiff;
            var g = 255 - dist;
     //       if (g < 0) g = 0; // useless array is clamped
            binaryData[idx++] = g;
            //Blue
            binaryData[idx++] = y & 0xFF;
            //Alpha
            binaryData[idx++] = 255;
        }
    }
    ctx.putImageData(canvasData, 0, 0);
}

结果相当不错,FF 需要一半时间(10 对 20 毫秒)时间,Chrome 少 15 毫秒(116(!)到 100),safari 需要 7 而不是 20 ! (Mac 操作系统)

我没有进行太多调查,但似乎仅在每次重绘时不创建/复制 imageData 这一事实就占了收益的 60% 以上。

【讨论】:

    猜你喜欢
    • 2015-12-14
    • 2012-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多