web-dev-qa-db-ja.com

requestAnimationFrameを使用してCanvasでFPSを計算する

キャンバスゲームアプリケーションの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>

ところで、パフォーマンスを監視するために追加できるライブラリはありますか?

31

new Date()を使用しないでください

このAPIにはいくつかの欠陥があり、現在の日付と時刻を取得する場合にのみ役立ちます。タイムスパンの測定用ではありません。

Date-APIは、常に更新され、NTPタイムサーバーと同期されるオペレーティングシステムの内部クロックを使用します。つまり、このクロックの速度/周波数は、実際の時間-したがって、継続時間とフレームレートの測定には使用できません。

誰かがシステム時間を(手動で、またはDSTにより)変更した場合、単一のフレームが突然1時間必要になった場合、少なくとも問題を確認できます。または負の時間。しかし、世界時計と同期するためにシステムクロックが20%速くなると、検出することは事実上不可能です。

また、Date-APIは非常に不正確で、多くの場合1ミリ秒未満です。これにより、1つの60Hzフレームが約17ms必要なフレームレート測定では特に役に立ちません。

代わりに、 performance.now() を使用してください

パフォーマンスAPIは、このようなユースケース専用に作成されており、new Date()と同等に使用できます。他の答えの1つを取り、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

Windowsの場合:

[タイムサービス]は、ローカルクロックレートを調整して、正しい時間に収束できるようにします。ローカルクロックと[正確な時間サンプル]の時間差が大きすぎてローカルクロックレートを調整して修正できない場合、タイムサービスはローカルクロックを正しい時間に設定します。

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

24
maja

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/

26
Justin Thomas

Chromeには組み込みのfpsカウンターがあります: https://developer.chrome.com/devtools/docs/rendering-settings =

enter image description here

Dev-console(F12)、引き出しを開きます(Esc)、[レンダリング]タブを追加します。

ここで、FPS-Meterオーバーレイをアクティブにして、現在のフレームレート(ニースグラフを含む)とGPUメモリ消費量を確認できます。

クロスブラウザソリューション:JavaScriptライブラリstat.jsで同様のオーバーレイを取得できます: https://github.com/mrdoob /stats.js/

enter image description here

また、フレームレート(グラフを含む)にNiceオーバーレイを提供し、非常に使いやすいです。

Stats.jsとchrome devツールからの結果を比較すると、どちらもまったく同じ測定値を示します。そのライブラリが実際に正しいことを行うと信頼できます。

17
maja

FPSを計算すると、数値を返すときにこのちらつきが発生するため、別のアプローチがあります。私はすべてのフレームを数えて1秒に1回返すことにしました

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

8
kernel

別のソリューションを次に示します。

_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は最後の1秒間で測定されるため、特に単一の長いフレームを持つアプリケーションの場合、数値がそれほど不規則にジャンプすることはありません。

このソリューションについて詳しく説明しました 私のWebサイトで

3
Daniel Imms

単なる概念実証。非常にシンプルなコード。 1秒あたりのフレームと各フレーム間の間隔を設定するだけです。描画関数では、最後のフレームの実行時間を現在の時間から差し引いて、最後のフレームからの経過時間が間隔(fpsに基づく)よりも長いかどうかを確認します。条件がtrueと評価された場合、現在のフレームの時間を設定します。これは、次の描画呼び出しの「最終フレーム実行時間」になります。

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/

3
yckart

AFRコールバック間の時間差を確認してください。 AFRはすでにコールバックへの引数として時間を渡します。表示するためにフィドルを更新しました: http://jsfiddle.net/WCKhH/1/

3
Gerben

実際、答えはどれも私にとって十分ではありませんでした。これはより良い解決策です:

  • 使用のperformance.now()
  • 1秒あたりの実際のaveragefpsを計算します
  • 1秒あたりの平均および小数点以下の桁数を構成可能

コード:

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

3
Mick

平均FPS値に合わせてサンプルのサイズをカスタマイズできる実装がありませんでした。これは私のもので、次の機能があります:

  • 正確: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>
1
colxi