キャンバスゲームアプリケーションの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により)変更した場合、単一のフレームが突然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
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;
}
Chromeには組み込みのfpsカウンターがあります: https://developer.chrome.com/devtools/docs/rendering-settings =
Dev-console(F12)、引き出しを開きます(Esc)、[レンダリング]タブを追加します。
ここで、FPS-Meterオーバーレイをアクティブにして、現在のフレームレート(ニースグラフを含む)とGPUメモリ消費量を確認できます。
クロスブラウザソリューション:JavaScriptライブラリstat.jsで同様のオーバーレイを取得できます: https://github.com/mrdoob /stats.js/
また、フレームレート(グラフを含む)にNiceオーバーレイを提供し、非常に使いやすいです。
Stats.jsとchrome devツールからの結果を比較すると、どちらもまったく同じ測定値を示します。そのライブラリが実際に正しいことを行うと信頼できます。
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());
});
別のソリューションを次に示します。
_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()
よりも使用されます( この回答で説明されているように )このソリューションについて詳しく説明しました 私のWebサイトで 。
単なる概念実証。非常にシンプルなコード。 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);
AFRコールバック間の時間差を確認してください。 AFRはすでにコールバックへの引数として時間を渡します。表示するためにフィドルを更新しました: http://jsfiddle.net/WCKhH/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();
平均FPS値に合わせてサンプルのサイズをカスタマイズできる実装がありませんでした。これは私のもので、次の機能があります:
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>