Рассчитать FPS в Canvas, используя requestAnimationFrame
Как я могу рассчитать FPS игрового приложения холста? Я видел несколько примеров, но ни один из них не использует requestAnimationFrame, и я не уверен, как применять свои решения там. Это мой код:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element){
window.setTimeout(function(){
callback(+new Date);
}, 1000 / 60);
};
})();
(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) requestAnimFrame(gameLoop);
}
gameLoop();
}(this, this.document))
canvas{
border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="200"></canvas>
Ответы
Ответ 1
Не используйте new Date()
Этот API имеет несколько недостатков и полезен только для получения текущей даты + времени. Не для измерения временных интервалов.
Date-API использует внутренние часы операционной системы, которые постоянно обновляются и синхронизируются с серверами времени NTP. Это означает, что скорость/частота этих часов иногда бывают быстрее и иногда медленнее фактического времени - и, следовательно, не пригодны для измерения длительностей и кадров.
Если кто-то изменяет системное время (вручную или из-за летнего времени), вы могли бы по крайней мере увидеть проблему, если один кадр внезапно понадобился час. Или отрицательное время. Но если системные часы на 20% быстрее синхронизируются с мировым временем, это практически невозможно обнаружить.
Кроме того, Date-API очень неточен - часто намного меньше 1 мс. Это делает его особенно бесполезным для измерений частоты кадров, когда один кадр 60 Гц требует ~ 17 мс.
API-интерфейс Performance специально разработан для таких случаев использования и может быть эквивалентен 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
Ответ 2
Вы можете отслеживать последний запрос 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/
Ответ 3
Chrome имеет встроенный счетчик кадров в секунду: https://developer.chrome.com/devtools/docs/rendering-settings
![enter image description here]()
Просто откройте консоль разработчика (F12), откройте ящик (Esc) и добавьте вкладку "Рендеринг".
Здесь вы можете активировать оверлей FPS-Meter, чтобы увидеть текущую частоту кадров (включая хороший график), а также потребление памяти графическим процессором.
Кросс-браузерное решение: аналогичное наложение можно получить с помощью библиотеки JavaScript stat.js: https://github.com/mrdoob/stats.js/
![enter image description here]()
Он также обеспечивает хорошее наложение на частоту кадров (включая график) и очень прост в использовании.
При сравнении результатов из stats.js и инструментов разработчика Chrome оба показывают одинаковые измерения. Таким образом, вы можете доверять этой библиотеке, чтобы действительно делать правильные вещи.
Ответ 4
У меня другой подход, потому что если вы вычислите 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
Ответ 5
Просто проверьте разницу во времени между обратными вызовами AFR. AFR уже передает время в качестве аргумента для обратного вызова. Я обновил вашу скрипту, чтобы показать ее: http://jsfiddle.net/WCKhH/1/
Ответ 6
Просто доказательство концепции. Очень простой код. Все, что мы делаем, это установить наши кадры в секунду и интервалы между каждым фреймом. В функции рисования мы вычитаем наше последнее время выполнения кадров с текущего времени, чтобы проверить, прошло ли время, прошедшее с момента последнего кадра, больше нашего интервала (который основан на 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/
Ответ 7
Здесь другое решение:
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 измеряется за последнюю секунду, поэтому число не будет скачкообразно перемещаться, особенно для приложений с одиночными длинными кадрами.
Я написал об этом решении более подробно на моем сайте.
Ответ 8
На самом деле ни один из ответов не был достаточным для меня. Вот лучшее решение, которое:
- Используйте performance.now()
- Вычисляет фактическое среднее число кадров в секунду
- Среднее в секунду и десятичные разряды настраиваются
Код:
// 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
Ответ 9
Мне не хватало реализации, позволяющей настроить размер выборки для усредненного значения 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>