Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





¿Cómo controlar los FPS?

Iniciado por JL10, 29 de Agosto de 2006, 08:39:32 PM

« anterior - próximo »

JL10

Me estoy basando en el código de Hoffman, Mitchell  y Preetham  para controlar los Frames Per Second (FPS) de mi animación, con objeto de variarlo a voluntad a un valor determinado.

Funciona bien cuando los fps-deseados son bajos, entre 5 y 30, pero a partir de 35 veo que se reducen los fps obtenidos y tienden a un valor fijo, aproximadamente 60 fps para cualquier pfs-deseado superior a 70.

Estoy probando con una animación muy simple, un cubo en alambre, para entender el mecanismo. Pero no me funciona nada bien. Si comento el Sleep() consigo más de 1000 fps, por lo que no es problema de mi código ni de la tarjeta aceleradora, más bien creo que el Sleep no funciona bien para este propósito.

¿Conocéis otra manera de controlar los fps de un renderizado?/* (c) 2002 Nathaniel Hoffman, Kenneth J. Mitchell and Arcot J. Preetham */

//-----------------------------------------------------------------------------
// Name: CAppForm::Render()
// Desc: Called once per frame, the call is the entry point for 3d
//       rendering. This function sets up render states, clears the
//       viewport, and renders the scene.
//-----------------------------------------------------------------------------
HRESULT CAppForm::Render()
{
    DXUtil_Timer( TIMER_GETELAPSEDTIME  ); // Start timer.

    m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                        0x000000ff, 1.0f, 0L );      // Clear the viewport

    if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) // Begin the scene
    {    
         m_pScene->Render();                         // TODO: render world
         m_pd3dDevice->EndScene();               // End the scene.
    }

    FLOAT fTimePerFrame = DXUtil_Timer(TIMER_GETELAPSEDTIME) * 1000;  // End timer

    float fDesiredFPS = 50;
    float fDesiredTimePerFrame = 1000.0/fDesiredFPS; // in ms

    if (fTimePerFrame < fDesiredTimePerFrame)
         Sleep(fDesiredTimePerFrame-fTimePerFrame);
 
   return S_OK;
}

(La función DXUtil_Timer se basa en las QueryPerformanceFrequency y QueryPerformanceCounter)
Gracias

FANatiko

Parece ser que el timer de Windows tiene una precision de 10-15 msecs, asi que no seria una locura suponer que el Sleep como minimo duerme ese intervalo y por tanto te rebajando a los FPS si toca esperarse menos de eso.

Lo que podrias hacer es controlar manualmente el tiempo tu en vez de llamar a Sleep. Algo como...


float fDesiredFPS = 50;
float fDesiredTimePerFrame = 1000.0/fDesiredFPS; // in ms

// hay que inicializar en algun lado fElapsedTime (yo dejaria un valor alto, 1 sec, asi te aseguras que renderize la primera vez.
m_fElapsedTime += DXUtil_Timer( TIMER_GETELAPSEDTIME  ) * 1000;

if( m_fElapsedTime > fDesiredTimePerFrame )
{
    m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                        0x000000ff, 1.0f, 0L );      // Clear the viewport

    if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) // Begin the scene
    {    
         m_pScene->Render();                         // TODO: render world
         m_pd3dDevice->EndScene();               // End the scene.
    }

    fElapsedTime = 0.0f; // reiniciamos
}
   
return S_OK;

zupervaca

El bucle principal de mis apps con la libreria dib es este:

/// <summary>Bucle principal</summary>
void MainLoop()
{
// Indicar ahora el ultimo tiempo antes de comenzar el bucle principal
this->lastTime = dib::System::Time::GetCurrent<float>();
// Procesar escena
IState* currentState;
while( this->window.IsCreated() && (currentState = (IState*)this->State.GetState()) != NULL )
{
// Actualizar solo cuando sea necesario (60fps)
float current = dib::System::Time::GetCurrent<float>();
// Esperar hasta que sea necesario comenzar a procesar la escena
while( current - this->lastTime < (1000.0f/60.0f) )
{
current = dib::System::Time::GetCurrent<float>();
};
// Actualizar la escena tantas veces sea necesario
while( current - this->lastTime > (1000.0f/60.0f) )
{
this->lastTime += (1000.0f/60.0f);
// Evento de actualizar acciones
this->input.Update();
currentState->OnUpdateActions();
// Evento para actualizar la escena
currentState->OnUpdateScene();
}
// Actualizar todo el audio
this->master.Update();
// Renderizar la escena
this->render.Begin();
currentState->OnRenderScene();
this->render.End();
this->render.Update();
// Procesar todos los mensajes pendientes de la ventana
this->window.UpdateAll();
// Comprobar si las dimensiones de la ventana han o no cambiado
int dummy, width, height;
this->window.GetPosition( &dummy, &dummy, &width, &height );
if( width != this->widthWindow || height != this->heightWindow )
{
// Ajustar los viewports
this->ForceUpdateViewports( width, height );
}
// Seleccionar el nuevo estado si es necesario
this->State.Update();
}
// Quitar el estado actual
this->State.SetState( NULL );
this->State.Update();
}

Se que muchos viendo el codigo no entenderan el motivo de comprobar las dimensiones de la ventana de esta manera en vez de capturar el evento de la ventana, pero al ser multisistemadeventanas lo tengo asi por si algun sistema no va con mensajes, por ejemplo el X11 me ha dado algun que otro problema extraño en linux con el sistema de eventos.
Como se ve en el codigo hago el sistema de esperar para comenzar la sincronizacion si le sobra tiempo, si le falta tiempo repite el proceso de la escena tantas veces sea necesario y luego ya por ultimo renderiza la escena una sola vez, de esta manera el juego se sincroniza siempre a 60fps.

PD: Decir que este bucle es una capa mas de la libreria y en algunas aplicaciones lo he usado y en otras no.

[EX3]

Este tema ya lo pregunte yo hace un mes asi y encontre un metodo muy sencillo que funciona a las mil maravillas para controlar los fps en el bucle principal de renderizado. Echa un vistazo a este hilo.

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

JL10

Las soluciones que proponéis (FANatiko y zupervaca) consumen CPU. Mantienen muy bien los FPS a los FPS deseados, pero se está haciendo un uso innecesario de los recursos de la máquina que podrían estar disponibles para otros procesos. Se trata de mantener unos FPS deseados sin necesidad de tener la CPU al 100%.

La solución del Sleep, en teoría es correcta, pero desplanifica nuestro programa y parece ser que tiene sus inconvenientes. A bajos FPS funciona muy bien y la CPU está muy, pero que muy baja. ¡Este es el efecto que persigo!. Pero a lo poco que subo los FPS deseados se satura los FPS obtenidos, la CPU sube algo, pero no paso de ahí.

¿O, a lo mejor debo de olvidarme de este planteamiento y sólo pensar en los FPS deseados independiente de la carga que ello ocasione a la CPU? :?

(Ahora voy a leer el hilo que nos propone [EX3])

En el hilo, Sante propone los QueryPerformance y reconoce la deficiencia del Sleep():
CitarEl típico sleep (tiempoQueFalte) tampoco suele ser una buena idea, al menos en PC. La razón es que sleep no garantiza que se espere el tiempo indicado, sino al menos ese tiempo. Esto es, que si haces un sleep (10), eso puede esperar 10 mseg, o 15, o 17...

En mi opinión la mejor solución es un bucle de espera que esté continuamente preguntando si ya ha transcurrido el tiempo, basado en un contador de alta precisión. Y para evitar bloquear la CPU, se mete un sleep (0), que permite al SO seguir ejecutando sus tareas.
¿[EX3], has probado lo propuesto por "Guille" con su clase Stopwatch?http://elguille.info/NET/vs2005/como/stopwatch_clase_calcular_tiempos.htm ¿resolvería mi problema? ¿Qué experiencia tienes?.
Gracias

marcode

Yo también estoy en contra del uso de sleep, como se ha comentado solo hay que controlar el tiempo transcurrido entre cada renderizado para saber si hay que actualizar.  El resto del tiempo se puede dedicar a otras funciones o al resto de aplicaciones.

Con QueryPerformanceCounter tienes bastante precisión, por ejemplo si quieres 50 fps, no se debe actualizar la imagen hasta que transcurran 1000/50= 20 milésimas.

Para evitar desajustes de precisión mejor no poner el contador a cero cada vez, si no obtener las diferencias en 20 ms, 40, 60... (suponiendo que se quieren 50 fps)

Esto en teoría, luego en la práctica o por las circustancias puede no servir.
size=9]afortunadamente siempre ha habido alguien dispuesto a reinventar la rueda, de lo contrario seguiríamos usando un disco de piedra con un agujero.[/size]

[EX3]

Cita de: "JL10"¿[EX3], has probado lo propuesto por "Guille" con su clase Stopwatch?http://elguille.info/NET/vs2005/como/stopwatch_clase_calcular_tiempos.htm ¿resolvería mi problema? ¿Qué experiencia tienes?.
Pues ni se me habia ocurrido mirar en la pagina del Guille sobre el tema (no pense encontrar informacion sobre controles de tiempos) pero le echare un vistazo ya que estoy portando la dx_lib32 a .NET y me vendra bien ver otros metodos. Yo en principio tenia en mente implementar el mismo metodo que habia implementado en VB 6.0, al que te referia con mi enlace, ya que funciona a las mil maravillas en las pruebas que hice y no es un codigo muy pesado para la CPU o al menos a mi no me lo parece.

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

zupervaca

Citar¿O, a lo mejor debo de olvidarme de este planteamiento y sólo pensar en los FPS deseados independiente de la carga que ello ocasione a la CPU?
Efectivamente te debes de olvidar de la carga del cpu ya que lo normal en un juego es que se la coma siempre, solo tienes que mirar la cpu que come cualquier juego comercial, incluso muchos juegos en windows (como el dungeon siege de microsoft) tienes la opcion de dar mas prioridad al juego que a los demas procesos que se esten ejecutando.
Un juego es el egoismo puro :lol:

bnl

Ex3 dijo
CitarPues ni se me habia ocurrido mirar en la pagina del Guille sobre el tema

Y eso que te puse ese mismo link en el otro post  :D

El pdf al que haces referencia en el otro post ya no esta en la web. ¿Lo tienes por ahi a mano?
Mi web: http://www.brausoft.com/
No sabían que era imposible, así que lo hicieron.

fiero

Cita de: "marcode"Yo también estoy en contra del uso de sleep, como se ha comentado solo hay que controlar el tiempo transcurrido entre cada renderizado para saber si hay que actualizar.  El resto del tiempo se puede dedicar a otras funciones o al resto de aplicaciones.

Sleep en Windows hace eso precisamente, "duerme" el hilo de ejecución actual y pasa a ejecutar otras aplicaciones u otros hilos del mismo programa. Cuando se ejecuta un Sleep(x) el sistema deja la ejecución del programa en ese punto y "pasa" de volver a darle tiempo de CPU durante x milisegundos. El Sleep es útil también para cambiar de hilo de ejecución, si tienes un programa con varios hilos. Definitivamente es la instrucción perfecta para no dejar al S.O. colgado.

La única pega de usar Sleep para controlar los frames es que los tiempos pueden ser imprecisos. El tiempo que el hilo permanece "dormido" puede ser mayor o igual que x, debido a que el sistema puede estar ejecutando otros procesos y tardar un poco más en volver a darle tiempo a tu programa,... o puede ser que se encuentre con uno de esos programas que no usan Sleep y esté quitando tiempo a tu aplicación  :)

un saludo
www.videopanoramas.com Videopanoramas 3D player

marcode

Sí, a eso me refería, al uso de sleep para controlar los frames.
size=9]afortunadamente siempre ha habido alguien dispuesto a reinventar la rueda, de lo contrario seguiríamos usando un disco de piedra con un agujero.[/size]

gdl

Encontré esta página donde da un método para hacer Sleep()s con precisión de microsegundos. El truco es usar un sleep para acercarse al milisegundo más próximo y luego esperar un ratito en un bucle hasta el microsegundo elegido.

JL10

Muchas gracias gdl por tu aportación con la página, es un artículo realmente muy interesante y apropiado. Me lo voy a leer, no, mejor me lo voy a estudiar con calma, porque creo que es la solución de mi problema. Luego os comento.

Parece que Sante ya nos estaba vaticinando la solución, pero yo no le llegaba a entender bien: :shock:
CitarY para evitar bloquear la CPU, se mete un sleep (0), que permite al SO seguir ejecutando sus tareas.
El artículo va por ahí... explica las deficiencias del sleep y como sacar partido de ello.


(Ahora un poco de humor. :lol:  Buscando por ahí, he encontrado un foro donde intentan dar con el significado de FPS. No tiene desperdicio. Si no fueran por estos ratos...)     http://forums.vugames.com/thread.jspa?threadID=8239&tstart=0
Gracias

[EX3]

Cita de: "bnl"Ex3 dijo
CitarPues ni se me habia ocurrido mirar en la pagina del Guille sobre el tema
Y eso que te puse ese mismo link en el otro post  :D
Juas! cierto, acabo de releer el otro hilo xD El tema es que ese metodo era para .NET y en ese hilo buscaba la forma de hacerlo en VB 6.0, asi que quizas no me hubiera servido de mucho me temo. Ahora que le estoy metiendo mano a C# si le echare un ojo ya que puede serme util :)

Cita de: "bnl"El pdf al que haces referencia en el otro post ya no esta en la web. ¿Lo tienes por ahi a mano?
Cierto, ya no esta el archivo :? El tema es que no descargue el pdf, solo guarde el extracto del codigo para el control de tiempos :(

Salu2...
José Miguel Sánchez Fernández
.NET Developer | Game Programmer | Unity Developer

Blog | Game Portfolio | LinkedIn | Twitter | Itch.io | Gamejolt

JL10

¡¡FUNCIONA!!

He terminado de estudiar el artículo de gdl y lo he aplicado a mis programas, y además de funcionar a la primera el resultado es muy satisfactorio.

Con ello he conseguido mis dos objetivos:

                a) Controlar los FPS, y

                b) No saturar la CPU innecesariamente.

(Si lo queréis aplicar, hacer caso de todos los detalles que sugiere el autor del artículo).

Gracias "gdl"
Gracias






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.