Calculer des images par seconde dans un jeu

Quel est un bon algorithme pour calculer des images par seconde dans un jeu? Je veux le montrer sous forme de chiffre dans le coin de l’écran. Si je regarde juste le temps qu’il a fallu pour rendre la dernière image, le nombre change trop rapidement.

Points bonus si votre réponse met à jour chaque image et ne converge pas différemment lorsque la fréquence d’images augmente ou diminue.

Vous avez besoin d’une moyenne lissée, le plus simple est de prendre la réponse actuelle (le temps de dessiner la dernière image) et de la combiner avec la réponse précédente.

// eg. float smoothing = 0.9; // larger=more smoothing measurement = (measurement * smoothing) + (current * (1.0-smoothing)) 

En ajustant le ratio de 0,9 / 0,1, vous pouvez changer la «constante de temps» – c’est la rapidité avec laquelle le nombre répond aux changements. Une fraction plus importante en faveur de l’ancienne réponse donne un changement plus lent, une fraction importante en faveur de la nouvelle réponse donne une valeur plus rapide. Évidemment, les deux facteurs doivent en append un!

C’est ce que j’ai utilisé dans de nombreux jeux.

 #define MAXSAMPLES 100 int tickindex=0; int ticksum=0; int ticklist[MAXSAMPLES]; /* need to zero out the ticklist array before starting */ /* average will ramp up until the buffer is full */ /* returns average ticks per frame over the MAXSAMPLES last frames */ double CalcAverageTick(int newtick) { ticksum-=ticklist[tickindex]; /* subtract value falling off */ ticksum+=newtick; /* add new value */ ticklist[tickindex]=newtick; /* save new value so it can be subtracted later */ if(++tickindex==MAXSAMPLES) /* inc buffer index */ tickindex=0; /* return average */ return((double)ticksum/MAXSAMPLES); } 

Bien, certainement

 frames / sec = 1 / (sec / frame) 

Mais, comme vous le faites remarquer, le temps de rendu d’une seule image est très variable et, du sharepoint vue de l’interface utilisateur, la mise à jour de la valeur fps à la cadence n’est pas utilisable (sauf si le nombre est très stable).

Ce que vous voulez, c’est probablement une moyenne mobile ou une sorte de compteur de binning / reset.

Par exemple, vous pouvez gérer une structure de données de queue qui contenait les temps de rendu pour chacune des 30, 60, 100 ou dernières images (vous pouvez même la concevoir pour que la limite soit ajustable à l’exécution). Pour déterminer une approximation fps correcte, vous pouvez déterminer le nombre d’images par seconde moyen de tous les temps de rendu dans la queue:

 fps = # of rendering times in queue / total rendering time 

Lorsque vous avez fini de rendre une nouvelle image, vous mettez en queue une nouvelle heure de rendu et vous retirez la queue d’un ancien temps de rendu. Alternativement, vous ne pouvez mettre en queue que lorsque le total des temps de rendu dépasse une valeur prédéfinie (par exemple 1 seconde). Vous pouvez conserver la “dernière valeur du fps” et un dernier horodatage mis à jour pour que vous puissiez déclencher la mise à jour du chiffre, si vous le souhaitez. Bien qu’avec une moyenne mobile si vous avez un formatage cohérent, l’impression de la moyenne instantanée “fps” sur chaque image serait probablement correcte.

Une autre méthode serait d’avoir un compteur de réinitialisation. Conservez un horodatage précis (milliseconde), un compteur de trames et une valeur fps. Lorsque vous avez fini de rendre un cadre, incrémentez le compteur. Lorsque le compteur atteint une limite prédéfinie (par exemple, 100 images) ou lorsque le temps écoulé depuis l’horodatage a dépassé une valeur prédéfinie (par exemple 1 seconde), calculez le nombre d’images par seconde:

 fps = # frames / (current time - start time) 

Remettez ensuite le compteur à 0 et définissez l’horodatage sur l’heure actuelle.

Incrémentez un compteur chaque fois que vous restituez un écran et effacez ce compteur pendant un intervalle de temps pendant lequel vous souhaitez mesurer le débit d’images.

C’est à dire. Toutes les 3 secondes, obtenez le compteur / 3 puis effacez le compteur.

Il y a au moins deux façons de le faire:


Le premier est celui que les autres ont mentionné ici avant moi. Je pense que c’est la manière la plus simple et la plus préférée. Vous venez de garder une trace de

  • cn: compteur du nombre d’images que vous avez rendu
  • time_start: le temps écoulé depuis que vous avez commencé à compter
  • time_now: l’heure actuelle

Calculer le fps dans ce cas est aussi simple que d’évaluer cette formule:

  • FPS = cn / (time_now – time_start).

Ensuite, il y a le moyen génial que vous aimeriez peut-être utiliser un jour:

Disons que vous avez des frameworks «i» à considérer. Je vais utiliser cette notation: f [0], f [1], …, f [i-1] pour décrire combien de temps il a fallu pour rendre l’image 0, image 1, …, image (i-1 ) respectivement.

 Example where i = 3 |f[0] |f[1] |f[2] | +----------+-------------+-------+------> time 

Ensuite, la définition mathématique de fps après i frames serait

 (1) fps[i] = i / (f[0] + ... + f[i-1]) 

Et la même formule mais en considérant uniquement les images i-1.

 (2) fps[i-1] = (i-1) / (f[0] + ... + f[i-2]) 

Maintenant, l’astuce consiste à modifier le côté droit de la formule (1) de manière à ce qu’il contienne le côté droit de la formule (2) et à le remplacer par son côté gauche.

Comme ça (vous devriez le voir plus clairement si vous l’écrivez sur un papier):

 fps[i] = i / (f[0] + ... + f[i-1]) = i / ((f[0] + ... + f[i-2]) + f[i-1]) = (i/(i-1)) / ((f[0] + ... + f[i-2])/(i-1) + f[i-1]/(i-1)) = (i/(i-1)) / (1/fps[i-1] + f[i-1]/(i-1)) = ... = (i*fps[i-1]) / (f[i-1] * fps[i-1] + i - 1) 

Donc, selon cette formule (mes compétences en calcul sont un peu rouillées), pour calculer les nouveaux fps, vous devez connaître le fps de l’image précédente, la durée de rendu de la dernière image et le nombre d’images que vous avez rendu.

Cela pourrait être excessif pour la plupart des gens, c’est pourquoi je ne l’avais pas posté lorsque je l’ai mis en œuvre. Mais c’est très robuste et flexible.

Il stocke une queue avec les dernières images, ce qui lui permet de calculer avec précision une valeur FPS moyenne bien meilleure que la seule prise en compte de la dernière image.

Cela vous permet également d’ignorer une image, si vous faites quelque chose qui, selon vous, va fausser artificiellement le temps de cette image.

Il vous permet également de modifier le nombre d’images à stocker dans la queue pendant son exécution. Vous pouvez ainsi tester à la volée la meilleure valeur pour vous.

 // Number of past frames to use for FPS smooth calculation - because // Unity's smoothedDeltaTime, well - it kinda sucks private int frameTimesSize = 60; // A Queue is the perfect data structure for the smoothed FPS task; // new values in, old values out private Queue frameTimes; // Not really needed, but used for faster updating then processing // the entire queue every frame private float __frameTimesSum = 0; // Flag to ignore the next frame when performing a heavy one-time operation // (like changing resolution) private bool _fpsIgnoreNextFrame = false; //============================================================================= // Call this after doing a heavy operation that will screw up with FPS calculation void FPSIgnoreNextFrame() { this._fpsIgnoreNextFrame = true; } //============================================================================= // Smoothed FPS counter updating void Update() { if (this._fpsIgnoreNextFrame) { this._fpsIgnoreNextFrame = false; return; } // While looping here allows the frameTimesSize member to be changed dinamically while (this.frameTimes.Count >= this.frameTimesSize) { this.__frameTimesSum -= this.frameTimes.Dequeue(); } while (this.frameTimes.Count < this.frameTimesSize) { this.__frameTimesSum += Time.deltaTime; this.frameTimes.Enqueue(Time.deltaTime); } } //============================================================================= // Public function to get smoothed FPS values public int GetSmoothedFPS() { return (int)(this.frameTimesSize / this.__frameTimesSum * Time.timeScale); } 

Bonnes réponses ici. La façon dont vous le mettez en œuvre dépend de vos besoins. Je préfère la moyenne en cours moi-même “time = time * 0.9 + last_frame * 0.1” par le gars ci-dessus.

Cependant, personnellement, je préfère peser plus lourdement ma moyenne vers les nouvelles données, car dans un jeu, ce sont les SPIKES qui sont les plus difficiles à écraser et donc les plus intéressants pour moi. Donc, je voudrais utiliser quelque chose de plus comme un split .7 \ .3 fera apparaître un pic beaucoup plus rapidement (même si son effet disparaîtra plus rapidement de l’écran .. voir ci-dessous)

Si vous vous concentrez sur le temps de rendu, alors la partition .9.1 fonctionne assez bien b / c, elle a tendance à être plus fluide. Bien que les pics de gameplay / IA / physique soient beaucoup plus préoccupants, cela signifie que ce qui rend généralement votre jeu instable (ce qui est souvent pire qu’une faible fréquence d’images en supposant que nous ne plongions pas en dessous de 20 fps)

Donc, ce que je ferais, c’est aussi append quelque chose comme ceci:

 #define ONE_OVER_FPS (1.0f/60.0f) static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS; if(time > g_SpikeGuardBreakpoint) DoInternalBreakpoint() 

(remplissez 3.0f avec la magnitude que vous trouvez être un pic inacceptable) Cela vous permettra de trouver et donc de résoudre les problèmes de FPS à la fin du cadre où ils se produisent.

Vous pouvez conserver un compteur, l’incrémenter après chaque rendu, puis réinitialiser le compteur lorsque vous êtes sur une nouvelle seconde (en stockant la valeur précédente en tant que nombre d’images restantes de la dernière seconde)

Comment je le fais!

 boolean run = false; int ticks = 0; long tickstart; int fps; public void loop() { if(this.ticks==0) { this.tickstart = System.currentTimeMillis(); } this.ticks++; this.fps = (int)this.ticks / (System.currentTimeMillis()-this.tickstart); } 

En quelques mots, une horloge de tick suit les tics. Si c’est la première fois, il prend l’heure actuelle et la met en ‘tickstart’. Après le premier tick, la variable ‘fps’ est égale au nombre de ticks de la durée du tick divisé par le temps moins le temps du premier tick.

Fps est un entier, donc “(int)”.

JavaScript:

 // Set the end and start times var start = (new Date).getTime(), end, FPS; /* ... * the loop/block your want to watch * ... */ end = (new Date).getTime(); // since the times are by millisecond, use 1000 (1000ms = 1s) // then multiply the result by (MaxFPS / 1000) // FPS = (1000 - (end - start)) * (MaxFPS / 1000) FPS = Math.round((1000 - (end - start)) * (60 / 1000)); 

Mettre le compteur à zéro. Chaque fois que vous dessinez une image, incrémentez le compteur. Après chaque seconde, imprimez le compteur. faire mousser, rincer, répéter. Si vous voulez un crédit supplémentaire, gardez un compteur courant et divisez par le nombre total de secondes pour une moyenne en cours.

stocker une heure de début et incrémenter votre compteur de fram une fois par boucle? toutes les quelques secondes, vous pouvez simplement imprimer framecount / (Now – starttime) et ensuite les réinitialiser.

edit: oops. double ninja

Dans le pseudo-code (de type c ++), ces deux-là sont ceux que j’ai utilisés dans les applications de traitement d’images indussortingelles qui devaient traiter des images provenant d’un ensemble de caméras déclenchées de l’extérieur. Les variations du “frame rate” avaient une source différente (production plus lente ou plus rapide sur la bande) mais le problème est le même. (Je suppose que vous avez un simple appel timer.peek () qui vous donne quelque chose comme le n ° de msec (nsec?) Depuis le début de l’application ou le dernier appel)

Solution 1: rapide mais pas mis à jour à chaque image

 do while (1) { ProcessImage(frame) if (frame.framenumber%poll_interval==0) { new_time=timer.peek() framerate=poll_interval/(new_time - last_time) last_time=new_time } } 

Solution 2: mise à jour à chaque image, nécessite davantage de mémoire et de processeur

 do while (1) { ProcessImage(frame) new_time=timer.peek() delta=new_time - last_time last_time = new_time total_time += delta delta_history.push(delta) framerate= delta_history.length() / total_time while (delta_history.length() > avg_interval) { oldest_delta = delta_history.pop() total_time -= oldest_delta } } 
 qx.Class.define('FpsCounter', { extend: qx.core.Object ,properties: { } ,events: { } ,construct: function(){ this.base(arguments); this.restart(); } ,statics: { } ,members: { restart: function(){ this.__frames = []; } ,addFrame: function(){ this.__frames.push(new Date()); } ,getFps: function(averageFrames){ debugger; if(!averageFrames){ averageFrames = 2; } var time = 0; var l = this.__frames.length; var i = averageFrames; while(i > 0){ if(l - i - 1 >= 0){ time += this.__frames[l - i] - this.__frames[l - i - 1]; } i--; } var fps = averageFrames / time * 1000; return fps; } } }); 

Voici comment je le fais (en Java):

 private static long ONE_SECOND = 1000000L * 1000L; //1 second is 1000ms which is 1000000ns LinkedList frames = new LinkedList<>(); //List of frames within 1 second public int calcFPS(){ long time = System.nanoTime(); //Current time in nano seconds frames.add(time); //Add this frame to the list while(true){ long f = frames.getFirst(); //Look at the first element in frames if(time - f > ONE_SECOND){ //If it was more than 1 second ago frames.remove(); //Remove it from the list of frames } else break; /*If it was within 1 second we know that all other frames in the list * are also within 1 second */ } return frames.size(); //Return the size of the list } 

Un système beaucoup mieux que d’utiliser un grand nombre d’anciens frameworks est de faire quelque chose comme ceci:

 new_fps = old_fps * 0.99 + new_fps * 0.01 

Cette méthode utilise beaucoup moins de mémoire, nécessite beaucoup moins de code et accorde plus d’importance aux récents cadrages que les anciens modèles tout en atténuant les effets des changements soudains de la cadence.

Voici un exemple complet, utilisant Python (mais facilement adapté à n’importe quel langage). Il utilise l’équation de lissage dans la réponse de Martin, donc presque pas de mémoire, et j’ai choisi des valeurs qui fonctionnaient pour moi (n’hésitez pas à jouer avec les constantes pour vous adapter à votre cas d’utilisation).

 import time SMOOTHING_FACTOR = 0.99 MAX_FPS = 10000 avg_fps = -1 last_tick = time.time() while True: #  current_tick = time.time() # Ensure we don't get crazy large frame rates, by capping to MAX_FPS current_fps = 1.0 / max(current_tick - last_tick, 1.0/MAX_FPS) last_tick = current_tick if avg_fps < 0: avg_fps = current_fps else: avg_fps = (avg_fps * SMOOTHING_FACTOR) + (current_fps * (1-SMOOTHING_FACTOR)) print(avg_fps)