2014-04-21 106 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%的收益。