web-dev-qa-db-fra.com

Calculer les images par seconde dans un jeu

Qu'est-ce qu'un bon algorithme pour calculer des images par seconde dans un jeu? Je veux le montrer sous forme de 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.

Des points bonus si votre réponse met à jour chaque image et ne converge pas différemment lorsque la cadence augmente ou diminue.

104
Tod

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 0,9/0,1, vous pouvez modifier la "constante de temps" - c’est la rapidité avec laquelle le nombre réagit aux changements. Une fraction plus importante en faveur de l'ancienne réponse donne un changement plus lent et plus lent, une fraction importante en faveur de la nouvelle réponse donne une valeur changeant plus rapidement. Évidemment, les deux facteurs doivent s’ajouter à un!

94
Martin Beckett

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);
}
45
KPexEA

Bien, certainement

frames / sec = 1 / (sec / frame)

Mais, comme vous l'avez souligné, le temps nécessaire au rendu d'une seule image varie énormément et, du point de vue de l'interface utilisateur, la mise à jour de la valeur en fps à la cadence n'est pas utilisable du tout (à moins que le nombre ne soit 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 conserver une structure de données de file d'attente contenant les temps de rendu pour chacune des 30, 60, 100 ou dernières images (vous pouvez même la concevoir de manière à ce que la limite soit réglable au moment de l'exécution). Pour déterminer une approximation décente en ips, vous pouvez déterminer la moyenne en ips de tous les temps de rendu de la file d'attente:

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

Lorsque vous avez fini de restituer une nouvelle image, vous mettez en file d'attente un nouveau temps de rendu et vous enlevez un ancien temps de rendu. Sinon, vous pouvez ne retirer de la file d'attente 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 en fps" et un dernier horodatage mis à jour afin de pouvoir déterminer quand mettre à jour la valeur en fps, si vous le souhaitez. Cependant, avec une moyenne mobile si le formatage est cohérent, il serait probablement correct d’imprimer la "moyenne instantanée" en images par seconde sur chaque image.

Une autre méthode serait d'avoir un compteur de réinitialisation. Conservez un horodatage précis (millisecondes), un compteur de trames et une valeur fps. Lorsque vous avez fini de restituer 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 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 réglez l'horodatage sur l'heure actuelle.

23
Wedge

Incrémentez un compteur chaque fois que vous rendez un écran et effacez-le pendant un intervalle de temps sur lequel vous souhaitez mesurer la cadence.

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

11
apandit

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


Le premier est celui que d’autres ont mentionné avant moi. Je pense que c'est le moyen le plus simple et préféré. Vous juste pour 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

Dans ce cas, calculer les images par seconde est aussi simple que d'évaluer cette formule:

  • FPS = cn/(time_now - time_start).

Ensuite, il y a la façon la plus cool d’utiliser un jour:

Supposons que vous ayez des cadres "i" à prendre en compte. Je vais utiliser cette notation: f [0], f [1], ..., f [i-1] pour décrire le temps nécessaire au rendu de 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 images serait

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

Et la même formule mais en ne considérant que 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 remplace par son côté gauche.

Comme si (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 toutefois un peu rouillées), pour calculer les nouveaux images par seconde, vous devez connaître les images par seconde de la trame précédente, la durée nécessaire au rendu de la dernière image et le nombre d'images rendu.

9
Peter Jankuliak

C'est peut-être exagéré 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 file d'attente avec les temps de la dernière image afin de pouvoir calculer avec précision une valeur moyenne de FPS bien mieux que de simplement prendre en compte la dernière image.

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

Il vous permet également de modifier le nombre de trames à stocker dans la file d’attente au fur et à mesure de son exécution, afin que vous puissiez le tester à la volée et déterminer le meilleur rapport qualité-prix 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);
}
5
Petrucio

Bonnes réponses ici. La façon dont vous la mettez en œuvre dépend de ce dont vous avez besoin. Je préfère la moyenne courante moi-même "temps = temps * 0.9 + last_frame * 0.1" par le gars ci-dessus.

cependant, j'aime personnellement peser davantage ma moyenne vers les nouvelles données car, dans un jeu, ce sont les SPIKES qui sont les plus difficiles à écraser et qui m'intéressent donc le plus. Donc, je voudrais utiliser quelque chose de plus semblable à un split .7\.3 qui fera apparaître une pointe beaucoup plus rapidement (bien que son effet disparaisse plus vite à l'écran aussi ... voir ci-dessous)

Si vous vous concentrez sur le temps de rendu, la division .9.1 fonctionne plutôt bien car elle a tendance à être plus fluide. Though pour le gameplay/l'intelligence artificielle/la physique sont beaucoup plus préoccupants, car c'est généralement ce qui rendra votre jeu chaotique (ce qui est souvent pire qu'un taux de trame bas si l'on ne passe pas à moins de 20 ips)

Donc, ce que je ferais, c'est aussi d'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()

(Complétez 3.0f avec la magnitude que vous jugez être une pointe inacceptable). Cela vous permettra de trouver et donc résoudre FPS émet la fin de la trame.

2
David Frenkel

Un système bien meilleur que d’utiliser un large éventail d’anciens programmes d’encodage consiste à 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 images précédentes plus récentes qu’aux images précédentes, tout en atténuant les effets des changements soudains d’image.

2
Barry Smith

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

1
Mike Stone

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));
1
Ephellon Dantzler

Voici un exemple complet, en utilisant Python (mais facilement adaptable à n’importe quel langage). Il utilise l’équation de lissage de la réponse de Martin, ce qui évite presque toute surcharge de mémoire, et j’ai choisi des valeurs qui fonctionnaient pour moi (sensation). libre de 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)
0
jd20

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 souhaitez un crédit supplémentaire, tenez un compteur en cours d'exécution et divisez-le par le nombre total de secondes d'une moyenne cumulée.

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

});
0
Totty.js

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 de ticks suit les ticks. Si c'est la première fois, il prend l'heure actuelle et le met en "tickstart". Après le premier tick, la variable 'fps' est égale au nombre de ticks de son horloge divisé par le temps moins le temps du premier tick.

Fps est un entier, d'où "(int)".

0
BottleFact

Dans le pseudo-code (de type c ++), ces deux éléments sont ceux que j’ai utilisés dans les applications de traitement d’image industrielles qui devaient traiter des images à partir d’un ensemble de caméras déclenchées de manière externe. Les variations de "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 nombre 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: mis à 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
   }
} 
0
jilles de wit

Voici 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
}
0
adventurerOK