2008-09-17 14 views
97

¿Qué es un buen algoritmo para calcular cuadros por segundo en un juego? Quiero mostrarlo como un número en la esquina de la pantalla. Si solo miro cuánto tardó en renderizar el último fotograma, el número cambia demasiado rápido.Cálculo de cuadros por segundo en un juego

Puntos de bonificación si su respuesta actualiza cada fotograma y no converge de manera diferente cuando la velocidad de fotogramas aumenta o disminuye.

Respuesta

88

Necesita un promedio suavizado, la manera más fácil es tomar la respuesta actual (el momento de dibujar el último cuadro) y combinarla con la respuesta anterior.

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

Mediante el ajuste de la relación de 0,9/0,1 puede cambiar la 'constante de tiempo' - que es la rapidez con que el número responde a los cambios. Una fracción más grande a favor de la vieja respuesta da un cambio más lento y más suave, una gran fracción a favor de la nueva respuesta da un valor de cambio más rápido. ¡Obviamente los dos factores deben sumarse a uno!

+13

Luego de tonto-impermeabilidad y tidyness, usted probablemente querrá algo así como flotador weightRatio = 0,1; y time = time * (1.0 - weightRatio) + last_frame * weightRatio – korona

+1

Suena bien y simple en principio, pero en realidad el suavizado de este enfoque es apenas perceptible. No es bueno. – Petrucio

+1

@Petrucio si el suavizado es demasiado bajo, solo suba la constante de tiempo (pesoRatio = 0.05, 0.02, 0.01 ...) –

11

Incremente un contador cada vez que represente una pantalla y borre ese contador durante un intervalo de tiempo sobre el que desea medir la velocidad de fotogramas.

Ie. Cada 3 segundos, obtenga contador/3 y luego borre el contador.

+0

+1 Aunque esto solo le dará un nuevo valor en intervalos, esto es fácil de entender y no requiere matrices ni adivinar valores, y es científicamente correcto. – opatut

+1

Pero luego tienes un contador de FPS solo actualizado cada 3 segundos ... – tigrou

0

Ajuste el contador a cero. Cada vez que dibuja un cuadro, incremente el contador. Después de cada segundo imprima el contador. enjabonar, enjuagar, repetir. Si desea crédito adicional, mantenga un contador en funcionamiento y divida por la cantidad total de segundos para un promedio continuo.

1

Se podría mantener un contador, se incrementará después de cada cuadro se representa, a continuación, reiniciar el contador cuando se está en un nuevo segundo (almacenar el valor anterior como # del último segundo de cuadros prestados)

0

tienda de un comienzo tiempo e incrementar su framecounter una vez por ciclo cada pocos segundos puede imprimir framecount/(Now - starttime) y luego reiniciarlos.

editar: oops. Doble ninja'ed

22

Bueno, sin duda

frames/sec = 1/(sec/frame) 

Pero, como usted señala, hay una gran cantidad de variación en el tiempo que se necesita para hacer un único fotograma, y ​​desde una perspectiva de interfaz de usuario la actualización de los fps el valor a la velocidad de cuadro no se puede usar en absoluto (a menos que el número sea muy estable).

Lo que desea es probablemente una media móvil o algún tipo de contador de agrupamiento/restablecimiento.

Por ejemplo, puede mantener una estructura de datos de cola que contenga los tiempos de renderización para cada uno de los últimos 30, 60, 100 o marcos que tenga usted (incluso podría diseñarlo para que el límite sea ajustable en funcionamiento -hora). Para determinar un FPS decente aproximación puede determinar los fps promedio de todos los tiempos de renderización en la cola:

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

Cuando termine la prestación de un nuevo marco que encolar un nuevo tiempo de presentación y dequeue un viejo tiempo de renderizado. De forma alternativa, podría dequear solo cuando el total de los tiempos de renderización excediera algún valor preestablecido (por ejemplo, 1 segundo). Puede mantener el "último valor de fps" y una última marca de tiempo actualizada para que pueda activar cuándo actualizar la figura de fps, si así lo desea. Aunque con una media móvil, si tiene un formato coherente, la impresión del fps "instantáneo promedio" en cada fotograma probablemente sea correcta.

Otro método sería tener un contador de restablecimiento. Mantenga una marca de tiempo precisa (milisegundos), un contador de cuadros y un valor de fps. Cuando termine de renderizar un cuadro, incremente el contador. Cuando el contador realiza un límite pre-establecido (por ejemplo, 100 marcos) o cuando el tiempo desde la marca de tiempo ha pasado un cierto valor preestablecido (por ejemplo, 1 seg), calcular el fps:

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

continuación, restablecer el contador a 0 y establecer la marca de tiempo a la hora actual.

42

Esto es lo que he usado en muchos juegos.

#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

Me gusta mucho este enfoque. ¿Algún motivo específico por el que establece MAXSAMPLES en 100? – Zolomon

+1

MAXSAMPLES aquí es el número de valores que se promedian para obtener un valor para fps. –

+6

Es la media móvil simple (SMA) – KindDragon

0

en (C++ similares) pseudocódigo estos dos son lo que he usado en aplicaciones industriales de procesamiento de imágenes que tenían que procesar imágenes a partir de un conjunto de cámara de disparo externo. Las variaciones en "velocidad de cuadros" tenían una fuente diferente (producción más lenta o más rápida en el cinturón) pero el problema es el mismo. (Supongo que tiene una llamada timer.peek sencilla() que le da algo así como el nr de milisegundos (ns) desde el inicio de la aplicación o la última llamada?)

Solución 1: rápido pero no actualizado cada cuadro

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

Solución 2: actualiza cada marco, requiere más memoria y 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

buenas respuestas aquí. La forma de implementarlo depende de lo que necesita. Prefiero el promedio corriente por mi parte "time = time * 0.9 + last_frame * 0.1" por el tipo anterior.

Sin embargo, personalmente me gusta ponderar mi promedio más fuertemente hacia datos más nuevos porque en un juego son los SPIKES los más difíciles de aplastar y, por lo tanto, los que más me interesan. Así que usaría algo más como una división .7 \ .3 hará que un pico aparezca mucho más rápido (aunque su efecto también caerá fuera de la pantalla más rápido ... ver abajo)

Si su atención se centra en RENDERING time , entonces la división .9.1 funciona bastante bien b/c, tiende a ser más suave. A pesar de que el juego/AI/spikes de física son mucho más preocupantes ya que ESO generalmente hará que tu juego parezca entrecortado (que a menudo es peor que una tasa de cuadros baja asumiendo que no estamos sumergiendo por debajo de 20 fps)

lo que haría es también añadir algo como esto:

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

(3.0f rellenar con cualquier magnitud a encontrar a ser un pico inaceptable) esto le permitirá encontrar y así resolver FPS emite el final de el marco en el que suceden

+0

Me gusta el cálculo promedio 'time = time * 0.9 + last_frame * 0.1' que hace que la pantalla cambie sin problemas. –

9

Hay al menos dos maneras de hacerlo:


La primera es aquella que otros han mencionado aquí antes que yo. Creo que es la forma más simple y preferida.Que acaba de realizar un seguimiento de

  • cn: contador del número de fotogramas si ha interpretado
  • TIME_START: el tiempo desde que ha comenzado a contar
  • TIME_NOW: la hora actual

Calcular el fps en este caso es tan simple como evaluar esta fórmula:

  • FPS = cn/(time_now - time_start).

Luego está la manera fresca súper que le gustaría utilizar algunos días:

Digamos que usted tiene 'i' marcos a considerar. Usaré esta notación: f [0], f [1], ..., f [i-1] para describir el tiempo que tardó en renderizar el fotograma 0, fotograma 1, ..., fotograma (i-1) respectivamente.

Example where i = 3 

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

Entonces, definición matemática de fps después de que los marcos sería

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

Y la misma fórmula, pero sólo teniendo en cuenta i-1 fotogramas.

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

Ahora el truco aquí es modificar el lado derecho de la fórmula (1) de tal manera que contendrá el lado derecho de la fórmula (2) y sustituirlo por su lado izquierdo.

Al igual que (debería ver con más claridad si se escribe en un papel):

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) 

Así que de acuerdo a esta fórmula (mis matemáticas derivadas de habilidad son un poco oxidado sin embargo), para calcular el nuevo fps necesita conocer los fps del fotograma anterior, la duración que tardó en representar el último fotograma y la cantidad de fotogramas que ha procesado.

+0

+1 para el segundo método. Me imagino que sería bueno para un cálculo súper preciso: 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

¡Cómo lo hago!

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 palabras, un tic-tac rastrea las señales. Si es la primera vez, toma la hora actual y la pone en 'tickstart'. Después del primer tic, hace que la variable "fps" sea igual a la cantidad de tics del reloj de tic dividido por el tiempo menos el tiempo del primer tic.

Fps es un número entero, por lo tanto "(int)".

+0

No recomendaría a nadie. Si se divide el número total de ticks por el número total de segundos, el FPS se aproxima a un límite matemático, donde básicamente se establece en 2-3 valores después de un tiempo prolongado y muestra resultados inexactos. –

0

Así es como lo hago (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

Esto podría ser excesiva para la mayoría de la gente, es por eso que no había publicado cuando lo puesto en práctica. Pero es muy robusto y flexible.

Almacena una cola con los últimos tiempos de fotogramas, por lo que puede calcular con precisión un valor promedio de FPS mucho mejor que solo teniendo en cuenta el último fotograma.

También le permite ignorar un fotograma, si está haciendo algo que sabe que va a estropear artificialmente el tiempo de ese fotograma.

También le permite cambiar el número de fotogramas para almacenar en la cola mientras se ejecuta, por lo que puede probarlo sobre la marcha cuál es el mejor valor para usted.

// 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 sistema mucho mejor que usar una gran variedad de viejos tasa de fotogramas es simplemente hacer algo como esto:

new_fps = old_fps * 0.99 + new_fps * 0.01 

Este método utiliza mucha menos memoria, requiere mucho menos código, y da más importancia en las tasas de fotogramas recientes que los cuadros de fotogramas anteriores al mismo tiempo suavizar los efectos de los cambios bruscos de velocidad de fotogramas.

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

Aquí hay un ejemplo completo, usando Python (pero fácilmente adaptado a cualquier idioma). Utiliza la ecuación de suavizado en la respuesta de Martin, por lo que casi no sobrecarga la memoria, y elegí los valores que funcionaron para mí (puede jugar con las constantes para adaptarse a su caso de 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) 
Cuestiones relacionadas