2014-04-21 65 views
3

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

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

我现在有encoutered两个性能问题:

  • iOS上的Safari浏览器(在iPad上空气测试):绘图函数被调用在31 fps的,但屏幕上的画布渲染laggy(视觉,我会说这是在10fps的最大更新,再加上0.5秒它不更新的话)
  • iOS上的Chrome浏览器的一些间隔:性能是可怕的,因为我得到2.9 fps的

在一个桌面mac,我得到一个稳定的表现:55fps与firefo铬x和45 fps的

所以,我有两个问题

  • 如何将迫使画布被刷新iOS上的Safari浏览器(更快,才能有一个真正的每秒30帧的渲染,或者可能是一个小更低)?
  • 如何优化性能?我错过了可能的优化吗?

请参考下面的代码:它是一个单独的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> 
+0

不要忘记在myDraw中声明x,y作为变量。您可以缓存canvas.width和canvas.height以避免DOM访问,缓存pos.x和pos.y,并为(&0xFF)交易(%255),并缓存Math.abs(对此不太确定)。所有这些都不会对我担心的帧速率做出巨大改变。 – GameAlchemist

+0

此外,更重要的是:只需创建一个您不断修改的imageData。并通过使用一个标志来绘制requestAnimationFrame。不要使用输入,而要使用fillText。所有这些fps在几个浏览器中都能提高2到5个。 http://jsbin.com/saruzoqo/2/ – GameAlchemist

+0

不错,你的改变使得fps在safari/iOS上从30上升到60 fps,而在chrome/iOs上从5上升到10 fps(这是iPad上的空气) –

回答

8

的Rq:
- 避免泄漏的全球和申报的x,y为myDraw瓦尔。
的建议:
- 缓存canvas.width和canvas.height避免访问DOM,
- 缓存pos.x和pos.y
- 贸易(%255)(&为0xFF)
- 缓存数学.abs
- 只需创建一个您不断修改的imageData(释放gc)。
- 在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 VS 20毫秒)时,Chrome的15毫秒以内(116(!)到100),和Safari需要的7,而不是20 ! (mac操作系统)

我没有调查很多,但它似乎事实本身并没有创建/复制每个重绘帐户的imageData超过60%的收益。