algorithm - tiene - significado de frame rate
Cálculo de cuadros por segundo en un juego (17)
¿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.
Puntos de bonificación si su respuesta actualiza cada cuadro y no converge de manera diferente cuando la velocidad de cuadro aumenta o disminuye.
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));
¡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 ticks. 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)".
Aquí hay un ejemplo completo, usando Python (pero se adapta fácilmente 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)
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
}
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 más abajo)
Si tu enfoque está en RENDERING time, entonces la división .9.1 funciona muy bien b / c, tiende a ser más suave. A pesar de que el juego / IA / picos de física son mucho más preocupantes ya que ESO por lo general hará que tu juego se vea entrecortado (lo que a menudo es peor que una baja tasa de cuadros suponiendo que no estamos sumergidos por debajo de 20 fps)
Entonces, lo que haría es agregar 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()
(Completa 3.0f con la magnitud que consideres que es un pico inaceptable). Esto te permitirá encontrar y resolver los problemas de FPS al final del encuadre que suceden.
Bueno, ciertamente
frames / sec = 1 / (sec / frame)
Pero, como usted señala, hay una gran variación en el tiempo que lleva renderizar un solo cuadro, y desde la perspectiva de UI, la actualización del valor de fps a la velocidad de cuadro no se puede utilizar (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 en 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ñarlos para que el límite sea ajustable en tiempo de ejecución). Para determinar una aproximación de fps decente 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 termina de renderizar un nuevo fotograma, pone en cola un nuevo tiempo de renderizado y dequeue un tiempo de renderización anterior. Alternativamente, 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 alcanza un límite preestablecido (por ejemplo, 100 fotogramas) o cuando el tiempo transcurrido desde que la marca de hora ha pasado algún valor preestablecido (por ejemplo, 1 segundo), calcule los fps:
fps = # frames / (current time - start time)
Luego reinicie el contador a 0 y configure la marca de tiempo a la hora actual.
En pseudocódigo (c ++ like) estos dos son los que utilicé en aplicaciones industriales de procesamiento de imágenes que tenían que procesar imágenes desde un conjunto de cámaras disparadas externamente. 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 simple timer.peek () que le da algo así como el nr de msec (nsec?) 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: actualizado cada cuadro, 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
}
}
Establecer 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.
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);
}
Esto podría ser excesivo para la mayoría de las personas, es por eso que no lo publiqué cuando lo implementé. 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 tener en cuenta el último fotograma.
También le permite ignorar un fotograma, si está haciendo algo que sabe que va a arruinar artificialmente el tiempo de ese fotograma.
También le permite cambiar el número de cuadros para almacenar en la cola mientras se ejecuta, por lo que puede probar al vuelo 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);
}
Hay al menos dos formas de hacerlo:
El primero es el que otros han mencionado aquí antes que yo. Creo que es la forma más simple y preferida. Solo para hacer un seguimiento de
- cn: contador de cuántos cuadros ha renderizado
- time_start: el tiempo desde que comenzó 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 genial que te gustaría usar algún día:
Digamos que tienes ''i'' marcos para 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, la definición matemática de fps después de i frames sería
(1) fps[i] = i / (f[0] + ... + f[i-1])
Y la misma fórmula pero solo teniendo en cuenta los cuadros i-1.
(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 contenga el lado derecho de la fórmula (2) y la sustituya por su lado izquierdo.
De esta manera (debería verlo más claramente si lo 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)
De acuerdo con esta fórmula (mi habilidad para sacar matemáticas está un poco oxidada), para calcular los nuevos fps necesitas saber los fps del cuadro anterior, la duración que tomó para renderizar el último cuadro y la cantidad de fotogramas que tienes prestado
Incremente un contador cada vez que represente una pantalla y borre ese contador durante un intervalo de tiempo sobre el cual desea medir la velocidad de fotogramas.
Es decir. Cada 3 segundos, obtenga contador / 3 y luego borre el contador.
Necesita un promedio suavizado, la manera más fácil es tomar la respuesta actual (el momento para 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))
Al ajustar la relación 0.9 / 0.1 puede cambiar la ''constante de tiempo'', es decir, 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!
Puede mantener un contador, incrementarlo después de que se represente cada cuadro, luego reiniciar el contador cuando esté en un segundo nuevo (almacenando el valor anterior como el número de marcos del último segundo representados)
Un sistema mucho mejor que utilizar una gran variedad de velocidades de fotogramas antiguas es 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 le da más importancia a las tasas de fotogramas recientes que las velocidades de fotogramas antiguas, al tiempo que suaviza los efectos de los cambios bruscos de velocidad de fotogramas.
almacena una hora de inicio e incrementa tu framecounter una vez por ciclo? cada pocos segundos puede imprimir framecount / (Now - starttime) y luego reiniciarlos.
editar: oops. doble ninja
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;
}
}
});