使用 requestAnimationFrame 在 Canvas 中计算 FPS

IT技术 javascript html5-canvas requestanimationframe
2021-02-27 16:54:07

如何计算画布游戏应用程序的 FPS?我看过一些例子,但没有一个使用 requestAnimationFrame,我不知道如何在那里应用他们的解决方案。这是我的代码:

(function(window, document, undefined){

    var canvas       = document.getElementById("mycanvas"),
        context      = canvas.getContext("2d"),
        width        = canvas.width,
        height       = canvas.height,
        fps          = 0,
        game_running = true,
        show_fps     = true;

    function showFPS(){
        context.fillStyle = "Black";
        context.font      = "normal 16pt Arial";

        context.fillText(fps + " fps", 10, 26);
    }
    function gameLoop(){

        //Clear screen
        context.clearRect(0, 0, width, height);

        if (show_fps) showFPS();

        if (game_running) requestAnimationFrame(gameLoop);

    }
    
    gameLoop();

}(this, this.document))
canvas{
    border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="150"></canvas>

顺便说一句,有没有我可以添加的库来监视性能?

6个回答

不使用 new Date()

这个 API 有几个缺陷,仅用于获取当前日期 + 时间。不用于测量时间跨度。

Date-API 使用操作系统的内部时钟,该时钟不断更新并与 NTP 时间服务器同步。这意味着,该时钟的速度/频率有时比实际时间快,有时慢 - 因此不能用于测量持续时间和帧率。

如果有人更改了系统时间(手动或由于 DST),如果单个帧突然需要一个小时,您至少可以看到问题。或者负时间。但是,如果系统时钟与世界时间同步的速度快 20%,则几乎无法检测到。

此外,Date-API 非常不精确 - 通常远小于 1 毫秒。这使得它对于帧率测量尤其无用,其中一帧 60Hz 帧需要约 17 毫秒。

相反,使用 performance.now()

Performance API 专为此类用例而设计,可等效于new Date(). 只需选择其他答案之一并替换new Date()performance.now(),您就可以开始了。

资料来源:

同样与 Date.now() 不同的是,Performance.now() 返回的值始终以恒定速率增加,独立于系统时钟(可能手动调整或由 NTP 等软件倾斜)。否则,performance.timing.navigationStart + performance.now() 将大约等于 Date.now()。

https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

对于窗户:

[时间服务] 调整本地时钟频率,使其向正确的时间收敛。如果本地时钟与【准确时间样本】之间的时差过大无法通过调整本地时钟速率进行校正,则时间服务会将本地时钟设置为正确的时间。

https://technet.microsoft.com/en-us/library/cc773013(v=ws.10).aspx

Chrome有一个内置的 fps 计数器:https : //developer.chrome.com/devtools/docs/rendering-settings

在此处输入图片说明

只需打开开发控制台 ( F12),打开抽屉 ( Esc),然后添加“渲染”选项卡。

在这里,您可以激活 FPS-Meter 叠加层以查看当前帧速率(包括漂亮的图表)以及 GPU 内存消耗。

跨浏览器解决方案: 您可以使用 JavaScript 库 stat.js 获得类似的覆盖:https : //github.com/mrdoob/stats.js/

在此处输入图片说明

它还为帧率(包括图形)提供了很好的叠加,并且非常易于使用。

比较 stats.js 和 chrome 开发工具的结果时,两者都显示完全相同的测量结果。因此,您可以相信该库实际上会做正确的事情。

您可以跟踪上次调用 requestAnimFrame 的时间。

var lastCalledTime;
var fps;

function requestAnimFrame() {

  if(!lastCalledTime) {
     lastCalledTime = Date.now();
     fps = 0;
     return;
  }
  delta = (Date.now() - lastCalledTime)/1000;
  lastCalledTime = Date.now();
  fps = 1/delta;
} 

http://jsfiddle.net/vZP3u/

一; getTime 以毫秒为单位返回时间,因此您应该使用 1000/delta。第二; 你永远不会更新 lastCalledTime。第三; requestAnimationFrame 发送一个 date 对象作为回调的时间的第一个参数,所以不需要创建新的 Date 对象。
2021-05-01 16:54:07
不要使用Date()它非常不精确(通常小于 1 毫秒),如果操作系统当前正在将其时钟与 NTP 计时器服务器同步(这种情况一直发生),它甚至可能返回错误的数据。也不要忘记时区和所有这些东西。相反,请使用 Performance API:developer.mozilla.org/en-US/docs/Web/API/Performance/now——它正是为这个用例而开发的。
2021-05-14 16:54:07
答案用于包括两者Date.now()new Date().getTime()这令人困惑。我编辑了Date.now()在两个地方使用的答案,因为它们返回相同的东西
2021-05-17 16:54:07
同意@maja,不要使用日期,但甚至不要使用 performance.now。requestAnimationFrame 将一个 highResTimestamp 传递给它的回调。
2021-05-19 16:54:07
我强烈建议只使用github.com/mrdoob/stats.js(它甚至有书签版本)
2021-05-20 16:54:07

这是另一个解决方案:

var times = [];
var fps;

function refreshLoop() {
  window.requestAnimationFrame(function() {
    const now = performance.now();
    while (times.length > 0 && times[0] <= now - 1000) {
      times.shift();
    }
    times.push(now);
    fps = times.length;
    refreshLoop();
  });
}

refreshLoop();

这通过以下方式改进了其他一些:

  • performance.now()用于在Date.now()增加精度(如覆盖在此答案
  • FPS 是在最后一秒测量的,因此数字不会如此不规律地跳跃,特别是对于具有单个长帧的应用程序。

在我的网站上更详细地描述了这个解决方案

Array.shift() 删除数组中的第一个元素。这需要重新索引数组中的所有元素。在每个动画帧循环中使用 Array.shift() 并不是一个性能友好的解决方案。
2021-04-24 16:54:07
小阵列上的单次移位大约每秒 60 次可能不会引起注意。
2021-05-18 16:54:07
这给出了实际的 fps
2021-05-20 16:54:07

我有一个不同的方法,因为如果你计算 FPS,你会在返回数字时看到这个闪烁。我决定计算每一帧并每秒返回一次

window.countFPS = (function () {
  var lastLoop = (new Date()).getMilliseconds();
  var count = 1;
  var fps = 0;

  return function () {
    var currentLoop = (new Date()).getMilliseconds();
    if (lastLoop > currentLoop) {
      fps = count;
      count = 1;
    } else {
      count += 1;
    }
    lastLoop = currentLoop;
    return fps;
  };
}());

requestAnimationFrame(function () {
  console.log(countFPS());
});

提琴手

自己将代码复制粘贴到 jsfiddle 有那么难吗?这就是你想要的jsfiddle.net/krnlde/u1fL1cs5
2021-04-25 16:54:07