Comment pourrais-je calculer le FPS d'une application de jeu canvas? J'ai vu quelques exemples, mais aucun d'eux n'utilise requestAnimationFrame, et je ne sais pas comment y appliquer leurs solutions. Voici mon code:
(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>
Soit dit en passant, existe-t-il une bibliothèque que je pourrais ajouter pour surveiller les performances?
new Date()
Cette API a plusieurs défauts et n'est utile que pour obtenir la date et l'heure actuelles. Pas pour mesurer les intervalles de temps.
L'API Date utilise l'horloge interne du système d'exploitation, qui est constamment mise à jour et synchronisée avec les serveurs de temps NTP. Cela signifie que la vitesse/fréquence de cette horloge est parfois plus rapide et parfois plus lente que la temps réel - et donc pas utilisable pour mesurer les durées et les framerates.
Si quelqu'un modifie l'heure du système (manuellement ou en raison de l'heure d'été), vous pourriez au moins voir le problème si une seule image nécessitait soudainement une heure. Ou un temps négatif. Mais si l'horloge système se synchronise 20% plus rapidement pour se synchroniser avec l'heure mondiale, elle est pratiquement impossible à détecter.
De plus, l'API Date est très imprécise - souvent bien inférieure à 1 ms. Cela le rend particulièrement inutile pour les mesures de fréquence d'images, où une trame de 60 Hz nécessite environ 17 ms.
performance.now()
L'API de performance a été spécialement conçue pour de tels cas d'utilisation et peut être utilisée de manière équivalente à new Date()
. Prenez simplement l'une des autres réponses et remplacez new Date()
par performance.now()
, et vous êtes prêt à partir.
Sources:
Contrairement à Date.now (), les valeurs renvoyées par Performance.now () augmentent toujours à un taux constant, indépendamment de l'horloge système (qui peut être ajustée manuellement ou biaisée par un logiciel comme NTP). Sinon, performance.timing.navigationStart + performance.now () sera approximativement égal à Date.now ().
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
Et pour les fenêtres:
[Le service de temps] ajuste la fréquence d'horloge locale pour lui permettre de converger vers l'heure correcte. Si la différence de temps entre l'horloge locale et [l'échantillon de temps précis] est trop grande pour être corrigée en ajustant la fréquence d'horloge locale, le service de temps règle l'horloge locale à l'heure correcte.
https://technet.Microsoft.com/en-us/library/cc773013 (v = ws.10) .aspx
Vous pouvez garder une trace de la dernière fois que requestAnimFrame a été appelé.
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 a un compteur fps intégré: https://developer.chrome.com/devtools/docs/rendering-settings =
Ouvrez simplement la console de développement (F12), ouvrez le tiroir (Esc) et ajoutez l'onglet "Rendu".
Ici, vous pouvez activer la superposition FPS-Meter pour voir le taux de rafraîchissement actuel (y compris un joli graphique), ainsi que la consommation de mémoire du GPU.
Solution multi-navigateur: Vous pouvez obtenir une superposition similaire avec la bibliothèque JavaScript stat.js: https://github.com/mrdoob /stats.js/
Il fournit également une superposition agréable pour le framerate (graphique inclus) et est très facile à utiliser.
Lorsque vous comparez les résultats de stats.js et des outils de développement chrome, les deux affichent exactement les mêmes mesures. Vous pouvez donc faire confiance à cette bibliothèque pour faire la bonne chose.
J'ai une approche différente, car si vous calculez le FPS, vous obtiendrez ce scintillement lors du retour du nombre. J'ai décidé de compter chaque image et de la retourner une fois par seconde
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());
});
Voici une autre 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();
Cela améliore certains des autres des manières suivantes:
performance.now()
est utilisé sur Date.now()
pour une précision accrue ( comme couvert dans cette réponse )J'ai écrit sur cette solution plus en détail sur mon site Web .
Juste une preuve de concept. Code très simple. Tout ce que nous faisons est de définir nos images par seconde et les intervalles entre chaque image. Dans la fonction de dessin, nous déduisons le temps d'exécution de notre dernière image de l'heure actuelle pour vérifier si le temps écoulé depuis la dernière image est supérieur à notre intervalle (qui est basé sur les fps) ou non. Si la condition est évaluée à vrai, nous définissons l'heure de notre image actuelle qui sera la "dernière heure d'exécution de l'image" dans le prochain appel de dessin.
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);
Vérifiez simplement la différence de temps entre les rappels AFR. AFR passe déjà l'heure en argument au rappel. J'ai mis à jour votre violon pour le montrer: http://jsfiddle.net/WCKhH/1/
En fait, aucune des réponses ne me suffisait. Voici une meilleure solution qui:
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();
Il me manquait une implémentation qui permet de personnaliser la taille de l'échantillon pour la valeur FPS moyenne. Voici le mien, il présente les caractéristiques suivantes:
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>