it-swarm.it

Calcolo dei fotogrammi al secondo in un gioco

Qual è un buon algoritmo per calcolare i frame al secondo in un gioco? Voglio mostrarlo come numero nell'angolo dello schermo. Se guardo solo quanto tempo ci vuole per rendere l'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.

104
Tod

Hai bisogno di una media uniforme, il modo più semplice è quello di 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 regolare, una grande frazione a favore della nuova risposta fornisce un valore che cambia più rapidamente. Ovviamente i due fattori devono aggiungere uno!

94
Martin Beckett

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

Beh, certamente

frames / sec = 1 / (sec / frame)

Ma, come hai sottolineato, 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 alla frequenza dei fotogrammi 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 di dati della coda che contiene i tempi di rendering per ciascuno degli ultimi 30, 60, 100 o frame what-have-you (è anche possibile progettarlo in modo che il limite sia regolabile in fase di esecuzione). Per determinare un'approssimazione fps decente è possibile determinare i fps medi da tutti i tempi di rendering in 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 elimina un vecchio tempo di rendering. In alternativa, è possibile eseguire il dequeue solo quando il totale dei tempi di rendering ha superato un valore preimpostato (ad es. 1 secondo). Puoi mantenere l '"ultimo valore fps" e un ultimo timestamp aggiornato in modo da poter attivare quando aggiornare la cifra fps, se lo desideri. Sebbene con una media mobile se si dispone di una formattazione coerente, la stampa della fps "media istantanea" su ciascun fotogramma sarebbe probabilmente ok.

Un altro metodo sarebbe quello di avere un contatore di reset. Mantenere un timestamp preciso (millisecondi), un contatore di frame e un valore fps. Al termine del rendering di una cornice, incrementare il contatore. Quando il contatore raggiunge un limite preimpostato (ad es. 100 fotogrammi) o quando il tempo trascorso dal timestamp ha superato un valore preimpostato (ad es. 1 secondo), calcolare i fps:

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

Quindi reimpostare il contatore su 0 e impostare il timestamp sull'ora corrente.

23
Wedge

Incrementa un contatore ogni volta che visualizzi una schermata e cancella quel contatore per un certo intervallo di tempo durante il quale vuoi misurare la frequenza dei fotogrammi.

Vale a dire. Ogni 3 secondi, ottieni il contatore/3 e quindi azzera il contatore.

11
apandit

Esistono 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 eseguito il rendering
  • time_start: il tempo 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 fantastico che potresti usare un giorno:

Supponiamo che tu abbia i frame 'i' da considerare. Userò questa notazione: f [0], f [1], ..., f [i-1] per descrivere il tempo impiegato per eseguire il rendering del frame 0, frame 1, ..., frame (i-1 ) rispettivamente.

Example where i = 3

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

Quindi, sarebbe la definizione matematica di fps dopo i frame

(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 da contenere 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 abilità matematiche derivate sono un po 'arrugginite), per calcolare i nuovi fps devi conoscere i fps del fotogramma precedente, la durata necessaria per il rendering dell'ultimo fotogramma e il numero di fotogrammi che hai reso.

9
Peter Jankuliak

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, in modo da poter calcolare con precisione un valore FPS medio molto meglio di prendere semplicemente in considerazione l'ultimo fotogramma.

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

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

Buone risposte qui. Il modo in cui lo implementi dipende da ciò di cui hai bisogno. Preferisco la media corrente da sola "time = time * 0.9 + last_frame * 0.1" del ragazzo sopra.

tuttavia, personalmente mi piace ponderare la mia media più pesantemente verso i dati più recenti perché in un gioco sono le SPIKES le più difficili da comprimere e quindi di maggior interesse per me. Quindi userei qualcosa di più simile a una suddivisione .7\.3 per far apparire uno spike molto più velocemente (anche se il suo effetto cadrà fuori dallo schermo più velocemente .. vedi sotto)

Se il tuo focus è sul tempo di RENDERING, allora la suddivisione .9.1 funziona piuttosto bene b/c e tende ad essere più regolare. Tuttavia, per i picchi di gameplay/AI/fisica sono molto più preoccupanti in quanto QUESTO di solito è ciò che rende il tuo gioco instabile (che è spesso peggiore di un frame rate basso supponendo che non stiamo scendendo sotto i 20 fps)

Quindi, ciò che vorrei fare è anche aggiungere 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()

(compila 3.0f con la grandezza che ritieni sia un picco inaccettabile) Questo ti permetterà di trovare e quindi risolvere FPS genera la fine del fotogramma in cui si verificano.

2
David Frenkel

Un sistema molto migliore rispetto all'utilizzo di una vasta gamma di vecchi framerate è semplicemente 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 uniformando gli effetti di improvvisi cambi di framerate.

2
Barry Smith

È possibile mantenere un contatore, incrementarlo dopo il rendering di ogni fotogramma, quindi reimpostare il contatore quando si è su un nuovo secondo (memorizzando il valore precedente come numero di fotogrammi dell'ultimo secondo reso)

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

Ecco un esempio completo, usando Python (ma facilmente adattabile a qualsiasi linguaggio). Utilizza l'equazione di smoothing nella risposta di Martin, quindi quasi nessun sovraccarico di memoria, e ho scelto valori che hanno funzionato per me (sentire libero di giocare con le costanti per adattarsi 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
jd20

Impostare il contatore su zero. Ogni volta che disegni un riquadro incrementa il contatore. Dopo ogni secondo, stampare il contatore. schiuma, risciacquo, ripetizione. Se desideri ulteriore credito, mantieni un contatore corrente e dividi per il numero totale di secondi per una media corrente.

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

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 segno di spunta tiene traccia delle zecche. Se è la prima volta, impiega l'ora corrente e la mette in "tickstart". Dopo il primo tick, la variabile 'fps' equivale al numero di tick del tick tick diviso per il tempo meno il tempo del primo tick.

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

0
BottleFact

Nello pseudocodice (c ++ like) questi due sono quelli che ho usato nelle applicazioni di elaborazione delle immagini industriali che dovevano elaborare le immagini da una serie di telecamere innescate esternamente. Le variazioni del "frame rate" avevano una fonte diversa (produzione più lenta o più veloce sulla cinghia) ma il problema è lo stesso. (Suppongo che tu abbia una semplice chiamata timer.peek () che ti dà qualcosa di simile al 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
jilles de wit

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
adventurerOK