【发布时间】: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" />
</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