Javascript 使用 requestAnimationFrame 在 Canvas 中计算 FPS

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8279729/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 05:24:42  来源:igfitidea点击:

Calculate FPS in Canvas using requestAnimationFrame

javascriptcanvasframe-raterequestanimationframe

提问by Enrique Moreno Tent

How could I calculate the FPS of a canvas game application? I've seen some examples, but none of them use requestAnimationFrame, and im not sure how to apply their solutions there. This is my code:

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

By the way, is there any library I could add to surpervise performance?

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

采纳答案by maja

Do not use new Date()

不使用 new Date()

This API has several flaws and is only useful for getting the current date + time. Not for measuring timespans.

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

The Date-API uses the operating system's internal clock, which is constantly updated and synchronized with NTP time servers. This means, that the speed / frequency of this clock is sometimes faster and sometimes slower than the actual time - and therefore not useable for measuring durations and framerates.

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

If someone changes the system time (either manually or due to DST), you could at least see the problem if a single frame suddenly needed an hour. Or a negative time. But if the system clock ticks 20% faster to synchronize with world-time, it is practically impossible to detect.

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

Also, the Date-API is very imprecise - often much less than 1ms. This makes it especially useless for framerate measurements, where one 60Hz frame needs ~17ms.

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

Instead, use performance.now()

相反,使用 performance.now()

The Performance API has been specificly made for such use cases and can be used equivalently to new Date(). Just take one of the other answers and replace new Date()with performance.now(), and you are ready to go.

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

Sources:

资料来源:

Also unlike Date.now(), the values returned by Performance.now() always increase at a constant rate, independent of the system clock (which might be adjusted manually or skewed by software like NTP). Otherwise, performance.timing.navigationStart + performance.now() will be approximately equal to Date.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://developer.mozilla.org/en-US/docs/Web/API/Performance/now

And for windows:

对于窗户:

[The time service] adjusts the local clock rate to allow it to converge toward the correct time. If the time difference between the local clock and the [accurate time sample] is too large to correct by adjusting the local clock rate, the time service sets the local clock to the correct time.

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

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

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

回答by Justin Thomas

You could keep track of the last time requestAnimFrame was called.

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

http://jsfiddle.net/vZP3u/

回答by maja

Chromehas a built-in fps counter: https://developer.chrome.com/devtools/docs/rendering-settings

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

enter image description here

在此处输入图片说明

Just open the dev-console (F12), open the drawer (Esc), and add the "Rendering" tab.

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

Here, you can activate the FPS-Meter overlay to see the current framerate (incl. a nice graph), as well as GPU memory consumption.

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

Cross-browser solution:You can get a similar overlay with the JavaScript library stat.js: https://github.com/mrdoob/stats.js/

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

enter image description here

在此处输入图片说明

It also provides a nice overlay for the framerate (incl. graph) and is very easy to use.

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

When comparing the results from stats.js and the chrome dev tools, both show the exact same measurements. So you can trust that library to actually do the correct thing.

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

回答by kernel

I have a different approach, because if you calculate the the FPS you'll get this flickering when returning the number. I decided to count every Frame and return it once a second

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

提琴手

回答by Daniel Imms

Here's another solution:

这是另一个解决方案:

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();

This improves on some of the others in the following ways:

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

  • performance.now()is used over Date.now()for increased precision (as covered in this answer)
  • FPS is measured over the last second so the number won't jump around so erratically, particularly for applications that have single long frames.
  • performance.now()用于在Date.now()增加精度(如覆盖在此答案
  • FPS 是在最后一秒测量的,因此数字不会如此不规律地跳跃,特别是对于具有单个长帧的应用程序。

I wrote about this solution in more detail on my website.

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

回答by Mick

Actually none of the answers were sufficient for me. Here is a better solution which:

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

  • Use's performance.now()
  • Calculates the actual averagefps per second
  • Average per second and decimal places are configurable
  • 使用的 performance.now()
  • 计算每秒的实际平均fps
  • 每秒平均值和小数位可配置

Code:

代码:

// 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

JSFiddle

回答by colxi

I was missing an implementation that allows to customize the size of the sample for the averaged FPS value. Here is mine , it has the following features :

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

  • Accurate: performance.now() based
  • Stabilized: Returned FPS value is an averaged value ( fps.value | fps.tick() )
  • Configurable: FPS samples array size can be customized ( fps.samplesSize )
  • Efficient: Rotatory array for collecting samples (avoids array resizing)
  • 准确:基于 performance.now()
  • 稳定:返回的 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>

回答by Gerben

Just check the difference in time between the AFR-callbacks. AFR already passes the time as an argument to the callback. I updated your fiddle to show it: http://jsfiddle.net/WCKhH/1/

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

回答by yckart

Just a proof of concept. Very simple code. All we do is set our frames per second and intervals between each frame. In the drawing function we deduct our last frame's execution time from the current time to check whether the time elapsed since the last frame is more than our interval (which is based on the fps) or not. If the condition evaluates to true, we set the time for our current frame which is going to be the “last frame execution time” in the next drawing call.

只是一个概念证明。非常简单的代码。我们所做的就是设置每秒的帧数和每帧之间的间隔。在绘图函数中,我们从当前时间中减去上一帧的执行时间,以检查自上一帧以来经过的时间是否大于我们的间隔(基于 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));
};

Usage:

用法:

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

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

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

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