HTML5画布性能-计算每秒循环/帧数

14

我知道之前有一些类似的问题,比如这个:Check FPS in JS?,虽然它在某种程度上起作用,我能够找出每个循环完成所需的时间。

但我想寻找的是更易读且可控的东西。我希望能够设置刷新率以使FPS计数器变慢,从而使它更容易读取,或者尽可能快地运行应用程序,这样我就可以将其用作某种速度表。

无论如何,这是我目前拥有的代码:

var lastLoop = new Date().getTime();

function updateStage()
{   
    clearCanvas();
    updateStageObjects();
    drawStageObjects();     

    var thisLoop = new Date().getTime(); 
    var fps = (thisLoop - lastLoop);

    $('#details').html(fps);

    lastLoop = thisLoop;
    iteration = setTimeout(updateStage, 1);
}
  1. 我设置setTimeout函数的速度为1毫秒是正确的吗?我想这样会使它尽可能地快速循环。

  2. 是否应该每隔100帧计数一次,找出运行100帧所需的毫秒数,然后进行计算以找出如果毫秒数为1000,则应完成多少帧?这个计算应该怎么做?

  3. 为了使结果更准确,我想我需要显示平均值,因为一个帧可能会有很大变化,该如何实现呢?

非常感谢任何提示。

谢谢。

5个回答

23
  1. 请注意,更新输出速度越快,您所测量的数值会受到更多影响。尽管很小,但我通常每秒更新一次或者更慢,除非有必要更快。

  2. 我喜欢在结果上应用低通滤波器,以便临时故障不会对数值产生太大影响。这比移动平均容易计算和编写,并且不会出现总体平均值的问题,其中您的“当前”读数会受到整个运行期间总体表现(例如启动时异常读数)的影响。

综合起来,这是我通常测量FPS的方法:

var fps = 0, now, lastUpdate = (new Date)*1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  if (now!=lastUpdate){
    fps += (thisFrameFPS - fps) / fpsFilter;
    lastUpdate = now;
  }

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 

嘿,谢谢你的回答,这一切似乎都很有道理。但出于某种原因,我的输出是NaN,我在这里粘贴了我修改后的代码,这样你就可以看到:http://jsfiddle.net/ksgSg/。 - Sabai
@Henryz 真尴尬,如果在第一次执行时now == lastUpdate,则会出现这种情况。我已经添加了一个保护措施。 (通常我在第一个drawFrame调用上有一个setTimeout,我想是这个原因。) - Phrogz
这似乎效果更好,但如果运行5-10秒钟后仍有时会导致NaN。我不确定原因,还没有完全弄清楚。 - Sabai
如果将fpsFilter设置为较低的数字(如10),则NaN问题不会发生。所以我已经让它工作到了那个程度。如果您有时间,可以给我一个快速解释,这是如何工作的:(fps + =(thisFrameFPS-fps)/ fpsFilter;)。我完全理解其余部分,只是无法弄清楚为什么会产生准确的结果。谢谢! - Sabai
@Henryz 感谢分享代码。即使使用您非常高的过滤器设置1000,我也从未看到它达到NaN。(请参见此处我的答案,以获取有关过滤器强度影响的更多详细信息;在1000时,需要700帧才能从初始fps 0移动到稳态fps的一半。)您在哪个操作系统/浏览器/版本上看到NaN? - Phrogz
显示剩余6条评论

3

Ive tried something out,

If you change the

lastUpdate = now

to

lastUpdate = now * 1 - 1;

您的 NaN 问题已解决!这也适用于定义 lastUpdate 的地方。可能是因为无法将日期转换为 Unix 时间戳。
新的结果将是:
var fps = 0, now, lastUpdate = (new Date)*1 - 1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  fps += (thisFrameFPS - fps) / fpsFilter;
  lastUpdate = now * 1 - 1;

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 

2
我已经采用了发布的解决方案并进行了改进。请查看此处 - http://jsfiddle.net/ync3S/
  1. 我通过使用Date.now()代替每次构建新的日期对象并尝试引用它来修复了NaN错误。这也避免了一些垃圾回收。
  2. 我稍微整理了一下变量和函数名称,并添加了一些额外的注释 - 这不是必要的,但很好有。
  3. 我包括了一些绘图代码进行测试。
  4. 我添加了fpsDesired作为引擎循环的测试变量。
  5. 我从fpsDesired开始计算fpsAverage,所以它不会从0开始计算实际FPS,而是从期望的FPS开始调整。
  6. 现在绘图会阻塞,以防它已经在绘制,这可以用于暂停和其他控制功能。
主要块如下:
var fpsFilter = 1; // the low pass filter to apply to the FPS average
var fpsDesired = 25; // your desired FPS, also works as a max
var fpsAverage = fpsDesired;
var timeCurrent, timeLast = Date.now();
var drawing = false;

function fpsUpdate() {
    fpsOutput.innerHTML = fpsAverage.toFixed(2);
}

function frameDraw() {
    if(drawing) { return; } else { drawing = true; }

    timeCurrent = Date.now();
    var fpsThisFrame = 1000 / (timeCurrent - timeLast);
    if(timeCurrent > timeLast) {
        fpsAverage += (fpsThisFrame - fpsAverage) / fpsFilter;
        timeLast = timeCurrent;
    }

    drawing = false;
}

setInterval(fpsUpdate, 1000);
fpsUpdate();

setInterval(frameDraw, 1000 / fpsDesired);
frameDraw();

我会尝试进行一些调整,看看能否想出更加顺畅的内容,因为这个帖子在谷歌搜索结果中排名靠前。

让我们作为一个团队共同探讨,我认为不使用第三方库总是很棒的,可以使代码对于任何人都具有可移植性 :)

-Platima


1

只需设置一个定时器,在每秒重置一次帧率计数器。

var fpsOut, fpsCount;

var draw = function () {

    fpsCount++;

    ..Draw To Canvas..


    ..Get the fps value: fpsOut

    requestAnimationFrame(draw);

};
setInterval(function () {

    fpsOut = fpsCount;
    fpsCount = 0;

}, 1000);

draw();

0
如果您想要实时更新,请考虑使其在实时中再次循环。为了减少对性能的影响,只更新受控变量,即FPS。您可以选择帧延迟,我将在此处提供,以备不时之需。只需复制、粘贴并根据您的需求调整代码即可。
请注意,单个帧持续时间为16.66毫秒。
setInterval(function(){var latencybase1 = parseFloat(new Date().getTime());
var latencybase2 = parseFloat(new Date().getTime());
var latency = latencybase2-latencybase1;
var fps = Math.round(1000/latency);

if (latency<16.66) 
{document.getElementById("FPS").innerHTML = fps+" 
FPS";}
else {document.getElementById("FPS").innerHTML = ""+fps+" FPS";}
document.getElementById("Latency").innerHTML = latency+" ms";}, 0);

我可能误读了,但似乎这段代码只测量了调用第二个 new Date().getTime()) 所需的时间,没有其他作用。此外,请注意,60Hz 不是唯一的刷新率,setInterval 在第 5 次迭代后有最小 4ms 的延迟,并且在一个渲染帧中多次设置 innerHTML 只会徒劳地消耗资源。 - Kaiido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接