2008-09-17 3 views
97

Qu'est-ce qu'un bon algorithme pour calculer des images par seconde dans un jeu? Je veux montrer comme un nombre dans le coin de l'écran. Si je regarde juste combien de temps il a fallu pour rendre la dernière image, le nombre change trop vite.Calcul d'images par seconde dans un jeu

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.

Répondre

88

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. En ajustant le rapport 0,9/0,1, vous pouvez modifier la «constante de temps», c'est-à-dire la rapidité avec laquelle le nombre répond aux changements. Une plus grande fraction 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 changeante plus rapide. Évidemment, les deux facteurs doivent ajouter à un!

+13

Alors pour fou-proofness et tidyness, vous voudrez probablement quelque chose comme flotteur = 0,1 rapport pondéral; et time = time * (1.0 - weightRatio) + last_frame * weightRatio – korona

+1

Sonne bien et simple en principe, mais en réalité le lissage de cette approche est à peine perceptible. Pas bien. – Petrucio

+1

@Petrucio si le lissage est trop bas, augmentez simplement la constante de temps (weightRatio = 0.05, 0.02, 0.01 ...) –

11

Incrémenter un compteur chaque fois que vous affichez un écran et effacez ce compteur pendant un certain intervalle de temps pendant lequel vous voulez mesurer la fréquence d'images.

Ie. Toutes les 3 secondes, obtenez le compteur/3 et effacez le compteur.

+0

+1 Bien que cela ne vous donne qu'une nouvelle valeur dans les intervalles, cela est facile à comprendre et ne nécessite ni tableaux, ni devinettes et est scientifiquement correct. – opatut

+1

Mais alors vous avez un compteur FPS seulement rafraichi toutes les 3 secondes ... – tigrou

0

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 en cours et divisez par le nombre total de secondes pour une moyenne courante.

1

Vous pouvez garder un compteur, incrémenter après chaque trame est rendu, puis remettre le compteur lorsque vous êtes sur une nouvelle seconde (mémorisation de la valeur précédente comme la dernière seconde de la Nombre de cadres rendus)

0

magasin un début temps et incrémenter votre framecounter une fois par boucle? toutes les quelques secondes, vous pouvez simplement imprimer framecount/(Now - starttime), puis les réinitialiser.

éditer: oops. double ninja'ed

22

Eh bien, certainement

frames/sec = 1/(sec/frame) 

Mais, comme vous le soulignez, il y a beaucoup de variation dans le temps qu'il faut pour rendre un cadre unique, et du point de vue de l'interface utilisateur mise à jour du fps valeur à la fréquence d'images n'est pas utilisable du tout (sauf si le nombre est très stable).

Ce que vous voulez est probablement une moyenne mobile ou une sorte de compteur binning/reset. Par exemple, vous pouvez maintenir une structure de données de file d'attente qui contient les temps de rendu pour chacun des 30, 60, 100 derniers trames (vous pouvez même le concevoir de façon à ce que la limite soit ajustable à l'exécution). -temps). Pour déterminer l'approximation d'un fps décent, vous pouvez déterminer les fps moyenne de tous les temps de rendu dans la file d'attente:

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

Lorsque vous avez fini de rendre une nouvelle image que vous ENQUEUE un nouveau temps de rendu et DEQUEUE un vieux temps de rendu. Alternativement, vous ne pouvez supprimer la file d'attente que lorsque le total des temps de rendu dépasse une certaine valeur prédéfinie (par exemple 1 seconde). Vous pouvez conserver la "dernière valeur fps" et un dernier horodatage mis à jour afin de pouvoir déclencher la mise à jour de la figure fps, si vous le désirez. 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 OK.

Une autre méthode serait d'avoir un compteur de remise à zéro. Conservez un horodatage précis (milliseconde), un compteur d'images et une valeur fps. Lorsque vous avez terminé le rendu d'une image, 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 passé une valeur prédéfinie (par exemple 1 sec), calculer le fps:

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

remis à zéro le compteur à 0 et définissez l'horodatage sur l'heure actuelle.

42

C'est ce que je l'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); 
} 
+0

J'aime beaucoup cette approche. Une raison spécifique pour laquelle vous définissez MAXSAMPLES sur 100? – Zolomon

+1

MAXSAMPLES Voici le nombre de valeurs moyennées pour obtenir une valeur pour fps. –

+6

C'est simple moyenne mobile (SMA) – KindDragon

0

Dans (C++ comme) pseudocode ces deux sont ce que j'utilisés dans des applications de traitement d'images industrielles qui ont dû traiter des images à partir d'un ensemble de caméra externe déclenché de. Les variations du "taux de trame" ont une source différente (production plus lente ou plus rapide sur la courroie) 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 (nanosecondes) depuis l'application démarrer ou le dernier appel?)

Solution 1: rapide mais pas mis à jour toutes les images

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 plus de mémoire et CPU

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 
    } 
} 
2

bonnes réponses ici. La façon dont vous l'appliquez dépend de ce dont vous avez besoin. Je préfère la moyenne courante moi-même "time = time * 0.9 + last_frame * 0.1" par le type ci-dessus.

mais je tiens personnellement à poids ma moyenne plus fortement vers des données plus récentes, car dans un jeu, il est qui sont les POINTES plus difficiles à squash et donc du plus grand intérêt pour moi. Donc, je voudrais utiliser quelque chose de plus comme un .7 \ .3 split fera un pic apparaître beaucoup plus vite (même si son effet tombera hors écran plus vite aussi bien .. voir ci-dessous)

Si vous vous concentrez sur RENDERING temps , alors le split .9.1 fonctionne plutôt bien car il a tendance à être plus lisse. Les pics de gameplay/AI/physique sont beaucoup plus préoccupants car CELA sera généralement ce qui rendra votre jeu agité (ce qui est souvent pire qu'un taux de trame bas en supposant que nous ne descendons pas en dessous de 20 fps)

ce que je ferais est aussi ajouter 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() 

(remplissage en 3.0f avec tout l'ampleur que vous trouvez être un pic inacceptable) cela vous permettra de trouver et donc résoudre FPS délivre la fin de le cadre ils arrivent.

+0

J'aime le calcul de la moyenne 'time = time * 0.9 + last_frame * 0.1' qui fait changer l'affichage en douceur. –

9

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


La première est celle que les autres ont dit avant moi. Je pense que c'est la façon la plus simple et plus pratique.Vous venez de garder une trace de

  • cn: compteur de nombre d'images que vous avez rendiez
  • TIME_START: le temps écoulé depuis que vous avez commencé à compter
  • TIME_NOW: l'heure actuelle

Le calcul du fps dans ce cas est aussi simple que l'évaluation de cette formule:

  • FPS = cn/(temps_temps - temps_démarrage).

Ensuite, il y a la façon cool uber que vous aimeriez utiliser un jour:

Disons que vous avez « i » cadres à prendre en compte. Je vais utiliser cette notation: f [0], f [1], ..., f [i-1] pour décrire le temps qu'il a fallu pour rendre le cadre 0, le cadre 1, ..., le cadre (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 images serait

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

Et la même formule, mais en ne considérant que les cadres i-1.

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

maintenant est ici l'astuce de modifier le côté droit de la formule (1) de telle sorte qu'il contiendra le côté droit de la formule (2) et le substituer à son côté gauche.

Comme tant (vous devriez le voir plus clairement si vous é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) 

Ainsi, selon cette formule (mes calculs provenant compétences sont un peu rouillé cependant), pour calculer la nouvelle fps vous avez besoin de connaître le fps de l'image précédente, la durée qu'il a fallu pour rendre la dernière image et le nombre d'images que vous avez rendu.

+0

+1 pour la deuxième méthode. J'imagine que ce serait bon pour un calcul précis: 3 – zeboidlund

0
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; 
     } 
    } 

}); 
1

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 mots, une horloge à cocher suit les graduations. Si c'est la première fois, cela prend l'heure actuelle et la met dans 'tickstart'. Après la première coche, la variable 'fps' est égale au nombre de ticks de l'horloge graduée divisé par le temps moins le temps de la première coche. Fps est un nombre entier, d'où «(int)».

+0

Je ne le recommanderais à personne. En divisant le nombre total de ticks par le nombre total de secondes, le FPS approche quelque chose comme une limite mathématique, où il se règle fondamentalement sur 2-3 valeurs après un long moment, et affiche des résultats inexacts. –

0

Voilà comment je le fais (en Java):

private static long ONE_SECOND = 1000000L * 1000L; //1 second is 1000ms which is 1000000ns 

LinkedList<Long> 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 
} 
5

Cela pourrait être surpuissant pour la plupart des gens, c'est pourquoi je ne l'avais pas posté quand j'implémenté. Mais c'est très robuste et flexible.

Il stocke une file d'attente avec les dernières heures de trame, de sorte qu'il peut calculer avec précision une valeur FPS moyenne beaucoup mieux que de simplement prendre en compte la dernière trame.

Il vous permet également d'ignorer une image, si vous faites quelque chose que vous savez va bousiller artificiellement le temps de cette image.

Il vous permet également de modifier le nombre d'images à stocker dans la file d'attente pendant son exécution, afin que vous puissiez tester à la volée quelle est 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<float> 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); 
} 
0

Un bien meilleur système que d'utiliser un large éventail de vieux framerate 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 sur les framerate récents que les anciens framerate tout en lissant les effets des changements brusques de framerate.

1

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)); 
0

Voici un exemple complet, en utilisant Python (mais facilement adapté à toute langue). Il utilise l'équation de lissage dans la réponse de Martin, donc presque pas de surcharge de mémoire, et j'ai choisi des valeurs qui ont fonctionné pour moi (n'hésitez pas à jouer avec les constantes pour s'adapter à votre cas d'utilisation).

import time 

SMOOTHING_FACTOR = 0.99 
MAX_FPS = 10000 
avg_fps = -1 
last_tick = time.time() 

while True: 
    # <Do your rendering work here...> 

    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) 
Questions connexes