2010-01-14 10 views
6

Tengo algunas animaciones ejecutándose en una ventana de contexto de OpenGL, así que necesito volver a dibujarlo constantemente. Así que se me ocurrió el siguiente código:.NET ¿Volver a dibujar a 60 FPS?

void InitializeRedrawTimer() 
    { 
     var timer = new Timer(); 
     timer.Interval = 1000/60; 
     timer.Tick += new EventHandler(timer_Tick); 
     timer.Start(); 
    } 

    void timer_Tick(object sender, EventArgs e) 
    { 
     glWin.Draw(); 
    } 

Eso solo me da 40 FPS. Sin embargo, si configuro el intervalo en 1 ms, puedo lograr 60. Entonces, ¿dónde fueron los otros 20 ms? ¿Eso solo se debe a la mala precisión del temporizador o qué? ¿Qué pasa si quiero que mi programa se ejecute lo más rápido posible? ¿Hay alguna forma de llamar continuamente al func de dibujo?

Respuesta

7

Puede intentar implementar un bucle de juego.

http://blogs.msdn.com/tmiller/archive/2005/05/05/415008.aspx

El bucle básico (ligeramente modificado a partir de su versión original y la versión en el nuevo SDK para facilitar la lectura):

public void MainLoop() 
{ 
     // Hook the application’s idle event 
     System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle); 
     System.Windows.Forms.Application.Run(myForm); 
}  

private void OnApplicationIdle(object sender, EventArgs e) 
{ 
    while (AppStillIdle) 
    { 
     // Render a frame during idle time (no messages are waiting) 
     UpdateEnvironment(); 
     Render3DEnvironment(); 
    } 
} 

private bool AppStillIdle 
{ 
    get 
    { 
     NativeMethods.Message msg; 
     return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); 
    } 
} 

//And the declarations for those two native methods members:   
[StructLayout(LayoutKind.Sequential)] 
public struct Message 
{ 
    public IntPtr hWnd; 
    public WindowMessage msg; 
    public IntPtr wParam; 
    public IntPtr lParam; 
    public uint time; 
    public System.Drawing.Point p; 
} 

[System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously 
[DllImport(“User32.dll”, CharSet=CharSet.Auto)] 
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags); 

simple, elegante y eficaz . Sin asignaciones adicionales, sin colecciones adicionales, simplemente funciona .. El evento inactivo se dispara cuando no hay mensajes en la cola, y luego el manejador sigue dando vueltas continuamente hasta que aparece un mensaje, en cuyo caso se detiene .. Una vez que todos los mensajes son manejado, el evento inactivo se dispara de nuevo, y el proceso comienza de nuevo.

Este enlace describe uno que utiliza el evento Inactivo de la aplicación. Puede ser de uso. Simplemente puede realizar una pequeña prueba de tiempo o dormir para reducir la velocidad a su fps requerido. Intente utilizar la clase System.Diagnostics.StopWatch para obtener el temporizador más preciso.

Espero que esto ayude un poco.

+0

¡Bonito! Usar 'Application.Idle' parece mucho más elegante que un temporizador de 1 ms. Tendré que probar este 'StopWatch' mañana también. ¡Gracias! ** Editar: ** En realidad, mentí. Cuando está cambiando el tamaño o moviendo la ventana, parece detenerse al ralentí y, por lo tanto, detiene el redibujado. Esto básicamente reduce la prioridad del dibujo. Tal vez me quede con un temporizador para imponer una tasa de redibujado mínima ... no hay razón por la que no pueda combinar ambos métodos. – mpen

+3

Además, StopWatch solo sirve para cronometrar cosas, no para eventos de devolución de llamada. – mpen

+3

Puede intentar iniciar su temporizador cuando el ralentí termine, y luego detenerlo/volver a comprobarlo cuando el evento inactivo se dispare. Puedes acercarte mucho de esta manera. – Nanook

2

El Windows Forms Timer tiene, en última instancia, una resolución limitada a la resolución del reloj del sistema estándar de su computadora. Esto varía de una computadora a otra, pero generalmente es de entre 1 ms y 20 ms.

En WinForms, cada vez que solicite un intervalo de tiempo en el código, Windows solo garantiza que su temporizador se llamará a menudo y luego el número de milisegundos especificado. No garantiza que se llame a su temporizador exactamente en los milisegundos que especifique.

Esto se debe a que Windows es un sistema operativo de tiempo compartido, no en tiempo real. Se programarán otros procesos y subprocesos para que se ejecuten al mismo tiempo, por lo que el hilo se verá obligado a esperar con regularidad el uso del procesador. Por lo tanto, no debe escribir el código del temporizador WinForms que se basa en intervalos exactos o retrasos de milisegundos.

En su situación, establecer el intervalo en 1 ms es realmente decirle a Windows que active este temporizador tan a menudo como sea posible. Como ha visto, esto es mucho menos frecuente que 1 ms (60 fps = aproximadamente 17 ms).

Si necesita un temporizador de resolución más preciso y de mayor resolución, la API de Windows proporciona temporizadores de alta resolución/rendimiento, si su hardware lo admite, consulte How to use the high resolution timer. Una desventaja de esto es que el uso de la CPU de su aplicación se incrementará para soportar un mayor rendimiento.

En general, creo que es mejor implementar un bucle de juego independiente de hardware/sistema. De esta forma, la animación percibida de su escena parece constante independientemente de la velocidad de fotogramas real lograda.Para obtener más información, these articles brinda una buena información.

3

Dependiendo de lo que esté haciendo, eche un vistazo a WPF y su soporte de animación, puede ser una mejor solución escribir sus propias amimaciones en WinForms.

También considere usar DirectX, ya que ahora tiene un contenedor .NET y es muy rápido, sin embargo, es más difícil usar WPF o WinForms.

Para llamar continuamente su función draw:

  • Haz el dibujo en el método repaint
  • añadir al final del método repaint hacer un BeginInvoke
  • En el delegado que pasó a BeginInvoke, invalida el control que está dibujando

Sin embargo, a sus usuarios puede no gustarles que maten a su computadora!

Application.Idle es también otra buena opción, sin embargo me gusta el hecho de que las ventanas ralentizarán la velocidad a la que enviarán los mensajes de WmPaint cuando sea necesario.

+0

También descubrí que invalidar inmediatamente después del dibujo produce velocidades de cuadros mucho más altas que el método del temporizador (65 fps fue el valor más alto con el temporizador, por alguna razón). –

-1

he notado en el código de ejemplo dado en la línea:

timer.Interval = 1000/60; 

Se utiliza la operación de división de enteros, por lo que el resultado se redondeará a un entero y no será precisamente 16.6666ms, pero 17ms . Por lo tanto, el temporizador hace tics más grandes que la actualización de la pantalla.

+0

La división entera trunca, no las rondas. 1000/60 es por lo tanto 16 ([prueba] (https://repl.it/GGNW/0)). Debería obtener ~ 62.5 FPS si el temporizador fuera preciso. – mpen

+0

Oh. Me he olvidado de eso Pero de todos modos, si son 16 ms, los tics no están sincronizados con el monitor. – Dennis

+1

Eso depende del monitor. Estoy bastante seguro de que esto no es un problema con gsync/freesync, pero independientemente, estoy bastante seguro de que 'timer.Interval' no es la respuesta aquí. – mpen

Cuestiones relacionadas