Calcolo dei fotogrammi al secondo in un gioco


110

Qual è un buon algoritmo per calcolare i fotogrammi al secondo in un gioco? Voglio mostrarlo come un numero nell'angolo dello schermo. Se guardo solo il tempo impiegato per il rendering dell'ultimo fotogramma, il numero cambia troppo velocemente.

Punti bonus se la tua risposta aggiorna ogni fotogramma e non converge in modo diverso quando la frequenza dei fotogrammi aumenta o diminuisce.

Risposte:


100

Hai bisogno di una media levigata, il modo più semplice è prendere la risposta corrente (il tempo per disegnare l'ultimo fotogramma) e combinarla con la risposta precedente.

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

Regolando il rapporto 0,9 / 0,1 è possibile modificare la "costante di tempo", ovvero la velocità con cui il numero risponde ai cambiamenti. Una frazione maggiore a favore della vecchia risposta fornisce un cambiamento più lento e fluido, una frazione grande a favore della nuova risposta fornisce un valore che cambia più rapidamente. Ovviamente i due fattori devono sommarsi a uno!


14
Quindi per sicurezza e ordine, probabilmente vorresti qualcosa come float weightRatio = 0.1; and time = time * (1.0 - weightRatio) + last_frame * weightRatio
korona

2
Sembra buono e semplice in linea di principio, ma in realtà l'approccio smussato è appena percettibile. Non buono.
Petrucio

1
@ Petrucio se il livellamento è troppo basso, aumenta la costante di tempo (weightRatio = 0,05, 0,02, 0,01 ...)
John Dvorak

8
@ Petrucio: last_framenon significa (o almeno non dovrebbe significare) la durata del frame precedente; dovrebbe significare il valore di timequello che hai calcolato per l'ultimo fotogramma. In questo modo, verranno inclusi tutti i frame precedenti, con i frame più recenti pesati maggiormente.
j_random_hacker

1
Il nome della variabile "last_frame" è fuorviante. "current_frame" sarebbe più descrittivo. È anche importante sapere che la variabile "time" nell'esempio deve essere globale (cioè mantenere il suo valore su tutti i frame) e idealmente un numero in virgola mobile. Contiene il valore medio / aggregato e viene aggiornato su ogni frame. Più alto è il rapporto, più tempo ci vorrà per assestare la variabile "tempo" verso un valore (o allontanarla da esso).
jox

52

Questo è quello che ho usato in molti giochi.

#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);
}

Mi piace molto questo approccio. Qualche motivo specifico per cui hai impostato MAXSAMPLES su 100?
Zolomon

1
MAXSAMPLES qui è il numero di valori mediati per ottenere un valore per fps.
Cory Gross il

8
È una media mobile semplice (SMA)
KindDragon

Perfetto grazie! L'ho modificato nel mio gioco in modo che la funzione tick sia nulla e un'altra funzione restituisca l'FPS, quindi posso eseguire quella principale ogni tick, anche se il codice di rendering non ha FPS mostrato.
TheJosh

2
Si prega di utilizzare modulo e non un if. tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.

25

Beh, certamente

frames / sec = 1 / (sec / frame)

Ma, come fai notare, ci sono molte variazioni nel tempo necessario per il rendering di un singolo fotogramma e, dal punto di vista dell'interfaccia utente, l'aggiornamento del valore fps al frame rate non è affatto utilizzabile (a meno che il numero non sia molto stabile).

Quello che vuoi è probabilmente una media mobile o una sorta di contatore di binning / reset.

Ad esempio, è possibile mantenere una struttura dei dati della coda che contenga i tempi di rendering per ciascuno degli ultimi 30, 60, 100 o what-have-you frame (si potrebbe persino progettare in modo che il limite fosse regolabile in fase di esecuzione). Per determinare un'approssimazione di fps decente è possibile determinare gli fps medi da tutti i tempi di rendering nella coda:

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

Al termine del rendering di un nuovo fotogramma, si accoda un nuovo tempo di rendering e si rimuove dalla coda un tempo di rendering precedente. In alternativa, è possibile rimuovere la coda solo quando il totale dei tempi di rendering supera un valore preimpostato (ad esempio 1 sec). È possibile mantenere "il valore dell'ultimo fps" e un timestamp dell'ultimo aggiornamento in modo da poter attivare quando aggiornare la cifra degli fps, se lo si desidera. Anche se con una media mobile se hai una formattazione coerente, la stampa della "media istantanea" fps su ogni fotogramma sarebbe probabilmente ok.

Un altro metodo potrebbe essere quello di avere un contatore di ripristino. Mantieni un timestamp preciso (millisecondo), un contatore di fotogrammi e un valore fps. Al termine del rendering di un fotogramma, incrementa il contatore. Quando il contatore raggiunge un limite preimpostato (es. 100 frame) o quando il tempo trascorso dal timestamp ha superato un valore preimpostato (es. 1 sec), calcola l'fps:

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

Quindi reimposta il contatore a 0 e imposta il timestamp sull'ora corrente.


12

Incrementa un contatore ogni volta che esegui il rendering di uno schermo e azzera quel contatore per un certo intervallo di tempo in cui desideri misurare il frame-rate.

Vale a dire. Ogni 3 secondi, ottieni counter / 3 e poi azzera il counter.


+1 Anche se questo ti darà solo un nuovo valore a intervalli, è facile da capire e non richiede né array né valori di indovinazione ed è scientificamente corretto.
opatut

10

Ci sono almeno due modi per farlo:


Il primo è quello che altri hanno menzionato qui prima di me. Penso che sia il modo più semplice e preferito. Devi solo tenerne traccia

  • cn: contatore di quanti fotogrammi hai renderizzato
  • time_start: il tempo trascorso da quando hai iniziato a contare
  • time_now: l'ora corrente

Calcolare gli fps in questo caso è semplice come valutare questa formula:

  • FPS = cn / (time_now - time_start).

Poi c'è il modo super cool che potresti voler usare un giorno:

Diciamo che devi considerare i frame "i". Userò questa notazione: f [0], f [1], ..., f [i-1] per descrivere il tempo impiegato per il rendering del fotogramma 0, fotogramma 1, ..., fotogramma (i-1 ) rispettivamente.

Example where i = 3

|f[0]      |f[1]         |f[2]   |
+----------+-------------+-------+------> time

Quindi, la definizione matematica di fps dopo i frame sarebbe

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

E la stessa formula ma considerando solo i frame i-1.

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

Ora il trucco qui è modificare il lato destro della formula (1) in modo tale che conterrà il lato destro della formula (2) e sostituirlo con il lato sinistro.

In questo modo (dovresti vederlo più chiaramente se lo scrivi su un foglio):

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)

Quindi, secondo questa formula (le mie capacità di derivazione matematica sono un po 'arrugginite però), per calcolare i nuovi fps devi conoscere gli fps del fotogramma precedente, la durata necessaria per il rendering dell'ultimo fotogramma e il numero di fotogrammi che hai reso.


1
+1 per il secondo metodo. Immagino che sarebbe buono per un calcolo super preciso: 3
zeboidlund

5

Questo potrebbe essere eccessivo per la maggior parte delle persone, ecco perché non l'avevo pubblicato quando l'ho implementato. Ma è molto robusto e flessibile.

Memorizza una coda con i tempi dell'ultimo fotogramma, quindi può calcolare con precisione un valore FPS medio molto meglio che prendere in considerazione solo l'ultimo fotogramma.

Ti permette anche di ignorare un frame, se stai facendo qualcosa che sai rovinerà artificialmente il tempo di quel frame.

Ti consente inoltre di modificare il numero di frame da memorizzare nella coda durante l'esecuzione, in modo da poter testare al volo qual è il valore migliore per te.

// 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);
}

2

Buone risposte qui. Il modo in cui lo implementate dipende da cosa vi serve. Preferisco quello medio corrente me stesso "time = time * 0.9 + last_frame * 0.1" dal ragazzo sopra.

tuttavia, personalmente mi piace pesare maggiormente la mia media sui dati più recenti perché in un gioco sono i PICCHI ad essere i più difficili da schiacciare e quindi di maggior interesse per me. Quindi userei qualcosa di più simile a una divisione .7 \ .3 farà apparire un picco molto più velocemente (anche se il suo effetto scenderà dallo schermo più velocemente .. vedi sotto)

Se il tuo focus è sul tempo di RENDERING, allora la divisione .9.1 funziona abbastanza bene ma tende ad essere più liscia. Anche se per il gameplay / AI / i picchi di fisica sono molto più preoccupanti in quanto QUESTO di solito è ciò che rende il tuo gioco mosso (che è spesso peggio di un frame rate basso supponendo che non stiamo scendendo sotto i 20 fps)

Quindi, quello che vorrei fare è aggiungere anche qualcosa del genere:

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

(inserisci 3.0f con qualunque grandezza tu ritenga essere un picco inaccettabile) Questo ti permetterà di trovare e quindi risolvere i problemi di FPS alla fine del frame che si verificano.


Mi piace il time = time * 0.9 + last_frame * 0.1calcolo della media che fa cambiare il display senza problemi.
Fabien Quatravaux

2

Un sistema molto migliore rispetto all'utilizzo di una vasta gamma di vecchi framerate consiste nel fare qualcosa del genere:

new_fps = old_fps * 0.99 + new_fps * 0.01

Questo metodo utilizza molta meno memoria, richiede molto meno codice e attribuisce maggiore importanza ai framerate recenti rispetto ai vecchi framerate pur attenuando gli effetti di improvvisi cambiamenti di framerate.


1

È possibile mantenere un contatore, incrementarlo dopo che ogni fotogramma è stato renderizzato, quindi resettare il contatore quando ci si trova su un nuovo secondo (memorizzando il valore precedente come numero di fotogrammi renderizzati dell'ultimo secondo)


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

1

Ecco un esempio completo, utilizzando Python (ma facilmente adattabile a qualsiasi linguaggio). Utilizza l'equazione di smussamento nella risposta di Martin, quindi quasi nessun sovraccarico di memoria, e ho scelto valori che funzionavano per me (sentiti libero di giocare con le costanti per adattarti al tuo caso d'uso).

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

Imposta il contatore a zero. Ogni volta che disegnate un fotogramma, incrementate il contatore. Dopo ogni secondo stampare il contatore. schiuma, risciacquare, ripetere. Se desideri credito extra, tieni un contatore corrente e dividi per il numero totale di secondi per ottenere una media corrente.


0

Nello pseudocodice (simile a C ++) questi due sono ciò che ho usato nelle applicazioni di elaborazione delle immagini industriali che dovevano elaborare le immagini da una serie di telecamere attivate esternamente. Le variazioni nel "frame rate" avevano una fonte diversa (produzione più lenta o più veloce sulla cintura) ma il problema è lo stesso. (Presumo che tu abbia una semplice chiamata timer.peek () che ti dà qualcosa come il nr di msec (nsec?) Dall'avvio dell'applicazione o dall'ultima chiamata)

Soluzione 1: veloce ma non aggiornato ogni frame

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

Soluzione 2: aggiornato ogni frame, richiede più memoria e 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
   }
} 

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

});

0

Come lo faccio!

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

In parole, un ticchettio tiene traccia dei battiti. Se è la prima volta, prende l'ora corrente e la inserisce in "tickstart". Dopo il primo tick, rende la variabile 'fps' uguale al numero di tick del tick clock diviso per il tempo meno il tempo del primo tick.

Fps è un numero intero, quindi "(int)".


1
Non lo consiglierei a nessuno. Dividendo il numero totale di tick per il numero totale di secondi, l'FPS si avvicina a qualcosa di simile a un limite matematico, in cui fondamentalmente si stabilizza su 2-3 valori dopo molto tempo e visualizza risultati imprecisi.
Kartik Chugh

0

Ecco come lo faccio (in 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

In Typescript, utilizzo questo algoritmo per calcolare le medie di framerate e frametime:

let getTime = () => {
    return new Date().getTime();
} 

let frames: any[] = [];
let previousTime = getTime();
let framerate:number = 0;
let frametime:number = 0;

let updateStats = (samples:number=60) => {
    samples = Math.max(samples, 1) >> 0;

    if (frames.length === samples) {
        let currentTime: number = getTime() - previousTime;

        frametime = currentTime / samples;
        framerate = 1000 * samples / currentTime;

        previousTime = getTime();

        frames = [];
    }

    frames.push(1);
}

utilizzo:

statsUpdate();

// Print
stats.innerHTML = Math.round(framerate) + ' FPS ' + frametime.toFixed(2) + ' ms';

Suggerimento: se i campioni sono 1, il risultato è framerate e frametime in tempo reale.


0

Questo si basa sulla risposta di KPexEA e fornisce la media mobile semplice. Riordinato e convertito in TypeScript per un facile copia e incolla:

Dichiarazione variabile:

fpsObject = {
  maxSamples: 100,
  tickIndex: 0,
  tickSum: 0,
  tickList: []
}

Funzione:

calculateFps(currentFps: number): number {
  this.fpsObject.tickSum -= this.fpsObject.tickList[this.fpsObject.tickIndex] || 0
  this.fpsObject.tickSum += currentFps
  this.fpsObject.tickList[this.fpsObject.tickIndex] = currentFps
  if (++this.fpsObject.tickIndex === this.fpsObject.maxSamples) this.fpsObject.tickIndex = 0
  const smoothedFps = this.fpsObject.tickSum / this.fpsObject.maxSamples
  return Math.floor(smoothedFps)
}

Utilizzo (può variare nella tua app):

this.fps = this.calculateFps(this.ticker.FPS)

-1

memorizzare un orario di inizio e incrementare il framecounter una volta per ciclo? ogni pochi secondi potresti semplicemente stampare framecount / (Now - starttime) e quindi reinizializzarli.

modifica: oops. doppio ninja'ed

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.