37

如何计算画布游戏应用程序的 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>

顺便说一句,我可以添加任何库来监督性能吗?

4

11 回答 11

35

不使用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

于 2017-07-17T11:38:57.913 回答
29

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 开发工具的结果时,两者都显示完全相同的测量值。因此,您可以相信该库实际上会做正确的事情。

于 2017-02-25T08:53:30.160 回答
25

您可以跟踪上次调用 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/

于 2011-11-26T17:20:05.843 回答
9

这是另一个解决方案:

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 是在最后一秒测量的,因此该数字不会如此不规律地跳跃,尤其是对于具有单个长帧的应用程序。

我在我的网站上更详细地介绍了这个解决方案。

于 2017-12-30T17:42:25.920 回答
8

我有一个不同的方法,因为如果你计算 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

于 2013-11-04T19:28:41.460 回答
7

我缺少一个允许为平均 FPS 值自定义样本大小的实现。这是我的,它有以下特点:

  • 准确:基于 performance.now()
  • Stabilized:返回的 FPS 值是平均值( fps.value | fps.tick() )
  • 可配置:可以自定义 FPS 样本数组大小( fps.samplesSize )
  • 高效:用于收集样本的旋转阵列(避免阵列大小调整)

const fps = {
    sampleSize : 60,    
    value : 0,
    _sample_ : [],
    _index_ : 0,
    _lastTick_: false,
    tick : function(){
        // if is first tick, just set tick timestamp and return
        if( !this._lastTick_ ){
            this._lastTick_ = performance.now();
            return 0;
        }
        // calculate necessary values to obtain current tick FPS
        let now = performance.now();
        let delta = (now - this._lastTick_)/1000;
        let fps = 1/delta;
        // add to fps samples, current tick fps value 
        this._sample_[ this._index_ ] = Math.round(fps);
        
        // iterate samples to obtain the average
        let average = 0;
        for(i=0; i<this._sample_.length; i++) average += this._sample_[ i ];

        average = Math.round( average / this._sample_.length);

        // set new FPS
        this.value = average;
        // store current timestamp
        this._lastTick_ = now;
        // increase sample index counter, and reset it
        // to 0 if exceded maximum sampleSize limit
        this._index_++;
        if( this._index_ === this.sampleSize) this._index_ = 0;
        return this.value;
    }
}


// *******************
// test time...
// *******************

function loop(){
    let fpsValue = fps.tick();
    window.fps.innerHTML = fpsValue;
    requestAnimationFrame( loop );
}
// set FPS calulation based in the last 120 loop cicles 
fps.sampleSize = 120;
// start loop
loop()
<div id="fps">--</div>

于 2019-04-12T03:41:19.443 回答
4

实际上,没有一个答案对我来说是足够的。这是一个更好的解决方案:

  • 使用的 performance.now()
  • 计算每秒的实际平均fps
  • 每秒平均值和小数位可配置

代码:

// Options
const outputEl         = document.getElementById('fps-output');
const decimalPlaces    = 2;
const updateEachSecond = 1;

// Cache values
const decimalPlacesRatio = Math.pow(10, decimalPlaces);
let timeMeasurements     = [];

// Final output
let fps = 0;

const tick = function() {
  timeMeasurements.push(performance.now());

  const msPassed = timeMeasurements[timeMeasurements.length - 1] - timeMeasurements[0];

  if (msPassed >= updateEachSecond * 1000) {
    fps = Math.round(timeMeasurements.length / msPassed * 1000 * decimalPlacesRatio) / decimalPlacesRatio;
    timeMeasurements = [];
  }

  outputEl.innerText = fps;

  requestAnimationFrame(() => {
    tick();
  });
}

tick();

JSFiddle

于 2018-10-09T18:27:36.683 回答
3

只需检查 AFR 回调之间的时间差。AFR 已经将时间作为参数传递给回调。我更新了你的小提琴来展示它:http: //jsfiddle.net/WCKhH/1/

于 2011-11-26T17:24:53.030 回答
3

只是一个概念证明。非常简单的代码。我们所做的就是设置每秒帧数和每帧之间的间隔。在绘图函数中,我们从当前时间中减去最后一帧的执行时间,以检查自上一帧以来经过的时间是否大于我们的间隔(基于 fps)。如果条件评估为真,我们为当前帧设置时间,这将是下一次绘图调用中的“最后一帧执行时间”。

var GameLoop = function(fn, fps){
    var now;
    var delta;
    var interval;
    var then = new Date().getTime();

    var frames;
    var oldtime = 0;

    return (function loop(time){
        requestAnimationFrame(loop);

        interval = 1000 / (this.fps || fps || 60);
        now = new Date().getTime();
        delta = now - then;

        if (delta > interval) {
            // update time stuffs
            then = now - (delta % interval);

            // calculate the frames per second
            frames = 1000 / (time - oldtime)
            oldtime = time;

            // call the fn
            // and pass current fps to it
            fn(frames);
        }
    }(0));
};

用法:

var set;
document.onclick = function(){
    set = true;
};

GameLoop(function(fps){
    if(set) this.fps = 30;
    console.log(fps);
}, 5);

http://jsfiddle.net/ARTsinn/rPAeN/

于 2013-04-06T13:32:33.127 回答
2

我的 fps 计算使用requestAnimationFrame()和它的回调函数匹配的时间戳参数。
请参阅https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFramehttps://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp

不需要new Date()or performance.now()

其余部分受到该线程中其他答案的极大启发,尤其是https://stackoverflow.com/a/48036361/4706651

var fps = 1;
var times = [];
var fpsLoop = function (timestamp) {
    while (times.length > 0 && times[0] <= timestamp - 1000) {
        times.shift();
    }
    times.push(timestamp);
    fps = times.length;
    console.log(fps);
    requestAnimationFrame(fpsLoop);
}

requestAnimationFrame(fpsLoop);

于 2020-09-30T16:15:20.767 回答
2

我与 Simple 一起使用的最佳方法是performance.now()
在函数上传递 TIMEgameLoop并计算 fps

fps = 1 / ( (performance.now() - LAST_FRAME_TIME) / 1000 );

(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,
        LAST_FRAME_TIME = 0;

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

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

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

        if (show_fps) showFPS();

        fps = 1 / ((performance.now() - LAST_FRAME_TIME) / 1000);

        LAST_FRAME_TIME = TIME /* remember the time of the rendered frame */

        if (game_running) requestAnimationFrame(gameLoop);

    }
    
    gameLoop();

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

于 2021-03-26T21:45:54.000 回答