簡體   English   中英

使用requestAnimationFrame計算Canvas中的FPS

[英]Calculate FPS in Canvas using requestAnimationFrame

我如何計算 canvas 游戲應用程序的 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>

順便說一句,有沒有我可以添加的庫來監督性能?

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

這是另一個解決方案:

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 是在最后一秒測量的,因此數字不會如此不規律地跳躍,特別是對於具有單個長幀的應用程序。

在我的網站上更詳細地描述了這個解決方案。

我有一個不同的方法,因為如果你計算 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());
});

提琴手

我缺少一個允許為平均 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>

實際上,沒有一個答案對我來說是足夠的。 這是一個更好的解決方案:

  • 使用的 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

只是一個概念證明。 非常簡單的代碼。 我們所做的就是設置每秒的幀數和每幀之間的間隔。 在繪圖函數中,我們從當前時間中減去上一幀的執行時間,以檢查自上一幀以來經過的時間是否大於我們的間隔(基於 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/

只需檢查 AFR 回調之間的時間差即可。 AFR 已經將時間作為回調的參數傳遞。 我更新了你的小提琴來展示它: http : //jsfiddle.net/WCKhH/1/

我的 fps 計算使用requestAnimationFrame()及其回調函數的匹配時間戳參數。
請參閱https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFramehttps://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp

不需要new Date()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);

我與performance.now()使用的最佳方式
簡單我在gameLoop函數上傳遞 TIME 並計算 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>

我必須創建一個 function 來設置 animation 應該在哪個 fps 上運行,因為我有一個 240hz 的顯示器,我的屏幕上的動畫比其他屏幕上的動畫快得多,所以我的最終項目在其他顯示器上總是比較慢

function setFPSandRunAnimation(fps, cb) {
    let frameCount = 0;
    let fpsInterval, startTime, now, then, elapsed;

    runAnimating(fps);

    function runAnimating(fps) {
        fpsInterval = 1000 / fps;
        then = Date.now();
        startTime = then;
        animate();
    }

    function animate(timestamp) {
        requestAnimationFrame(animate);
        now = Date.now();
        elapsed = now - then;
        if (elapsed > fpsInterval) {
            then = now - (elapsed % fpsInterval);
            const sinceStart = now - startTime;
            const currentFps = Math.round(1000 / (sinceStart / ++frameCount) * 100) / 100;
            const elapsedTime = Math.round(sinceStart / 1000 * 100) / 100;
            cb(timestamp, currentFps, elapsedTime)
        }
    }
}

這是你如何使用它setFPSandRunAnimation(fpsSpeedYouWant, cbFunctionWhereYouGet timestamp, currentfps and elapsedTime)

在 cb function 內部,您可以運行任何您將在 animation function 中運行的代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM