2011-01-23 18 views
21

Tengo un juego de C++ corriendo a través de JNI en Android. La velocidad de fotogramas varía de aproximadamente 20-45 fps debido a la complejidad de la escena. Cualquier cosa por encima de 30 fps es una tontería para el juego; solo quema la batería. Me gustaría limitar la velocidad de cuadros a 30 fps.¿Cómo limitar la velocidad de cuadros al usar GLSurfaceView.RENDERMODE_CONTINUOUSLY de Android?

  • pude cambiar a RENDERMODE_WHEN_DIRTY, y utilizar un temporizador o ScheduledThreadPoolExecutor a requestRender(). Pero eso agrega un lío completo de partes móviles adicionales que pueden funcionar o no de manera consistente y correcta.
  • Intenté inyectar Thread.sleep() cuando las cosas se están ejecutando rápidamente, pero esto no parece funcionar en absoluto para valores de tiempo pequeños. Y de todos modos puede estar respaldando eventos en la cola, sin detenerse.

¿Hay un método "capFramerate()" escondido en la API? ¿Alguna forma confiable de hacer esto?

Respuesta

38

La solución de Mark es casi buena, pero no del todo correcta. El problema es que el intercambio en sí requiere una cantidad de tiempo considerable (especialmente si el controlador de video está almacenando en caché las instrucciones). Por lo tanto, debe tener esto en cuenta o finalizará con una velocidad de fotogramas inferior a la deseada. Así que la cosa debe ser:

algún lugar al inicio (como el constructor):

startTime = System.currentTimeMillis(); 

entonces en el bucle render:

public void onDrawFrame(GL10 gl) 
{  
    endTime = System.currentTimeMillis(); 
    dt = endTime - startTime; 
    if (dt < 33) 
     Thread.Sleep(33 - dt); 
    startTime = System.currentTimeMillis(); 

    UpdateGame(dt); 
    RenderGame(gl); 
} 

De esta manera se tendrá en cuenta el tiempo se necesita intercambiar los buffers y el tiempo para dibujar el marco.

+0

Nice Fili! Estoy un poco avergonzado de haberlo echado de menos, pero tu enfoque funciona bien. Gracias. – grinliz

+4

Con este código, no tiene un FPS constante. En mi juego, yo oscilar entre 30 y 20 fps. El método real es una llamada de EGL eglSwapInterval pero no está implementado en dispositivos Android. – Ellis

+0

@Ellis: es extraño, he usado este código en juegos comerciales y tengo un FPS constante. También asegúrese de que cuando mida FPS, debe hacer una media de los últimos 5-10 cuadros, por ejemplo, para eliminar cualquier irregularidad (a veces el teléfono realiza algunas tareas en segundo plano, como recibir cosas, consultar correo, etc.). – Fili

8

Al usar GLSurfaceView, realiza el dibujo en el OnDrawFrame de su Renderer que se maneja en un subproceso separado por GLSurfaceView. Simplemente asegúrese de que cada llamada a onDrawFrame tome (1000/[cuadros]) milisegundos, en su caso algo así como 33 ms.

Para hacer esto: (en su onDrawFrame)

  1. medir el tiempo actual antes de empezar a dibujar usando System.currentTimeMillis (Digamos que es horaInicio)
  2. Realizar el dibujo
  3. Medir el tiempo de nuevo (Digamos que es endTime)
  4. delta T = endTime - Startime
  5. si < delta T 33, el sueño (33-delta T)

Eso es todo.

+0

Wow! ¡Esto es hermoso! – Lumis

+1

Entendido. Pero, ¿cómo puedo controlar cuándo está sucediendo glSwap? Android está llamando onDrawFrame para mí. No estoy al tanto de ningún control para ajustar cuando eso ocurra. (Aunque espero que estén allí.) RENDERMODE_WHEN_DIRTY deshabilita el comportamiento, pero introduce una nueva complejidad de temporización. En cualquiera de los modos, Android llama al glSwap después de onDrawFrame, por lo que llamar a OnDrawFrame no coordina con la actualización del video. (Y de hecho crea un caos de visualización de video. Lo he intentado.) Y como onDrawFrame es abstracto vacío, obviamente no hay ningún comportamiento de clase superior en el que confiar. – grinliz

+0

No necesita controlarlo. GLSurfaceView maneja eso por ti. Y no tiene que llamar a DrawFrame usted mismo. Su pregunta fue cómo controla y limita la velocidad de cuadros, y la solución que sugerí hace exactamente eso. ¿Qué más te estás perdiendo? – Lior

-3

También puede tratar de reducir la prioridad del hilo de onSurfaceCreated():

Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE); 
+1

¿Cómo logra eso lo que pide el OP, que es bajar la velocidad de cuadro a una cantidad controlada (pero solo cuando es más alto de lo deseado)? En mi humilde opinión, lo único que hará su sugerencia es disminuir IMPEDICTABLY la velocidad de cuadros. Es probable que el resultado sea ERRATICO, ya que hace que el proceso sea más interrumpible. – ToolmakerSteve

+0

Es probable que esto no haga absolutamente nada en los dispositivos con CPU modernas multi-core. La prioridad del hilo afecta a la tarea que elige el planificador del kernel. Si la cantidad de subprocesos ejecutables es <= la cantidad de núcleos de CPU activos, todos los subprocesos ejecutables se ejecutarán independientemente de la prioridad. –

0

solución de Fili está fallando para algunas personas, por lo que sospecho que está durmiendo hasta inmediatamente después de la siguiente VSYNC en lugar de inmediatamente antes. También creo que mover la suspensión hasta el final de la función daría mejores resultados, porque allí puede rellenar el cuadro actual antes de la próxima vsync, en lugar de intentar compensar el anterior. Thread.sleep() es inexacto, pero afortunadamente solo necesitamos que sea preciso para el período vsync más cercano de 1/60 s. El código de LWJGL tyrondis publicó un enlace que parece demasiado complicado para esta situación, probablemente está diseñado para cuando vsync está deshabilitado o no disponible, lo que no debería ser el caso en el contexto de esta pregunta.

me gustaría probar algo como esto:

private long lastTick = System.currentTimeMillis(); 

public void onDrawFrame(GL10 gl) 
{ 
    UpdateGame(dt); 
    RenderGame(gl); 

    // Subtract 10 from the desired period of 33ms to make generous 
    // allowance for overhead and inaccuracy; vsync will take up the slack 
    long nextTick = lastTick + 23; 
    long now; 
    while ((now = System.currentTimeMillis()) < nextTick) 
     Thread.sleep(nextTick - now); 
    lastTick = now; 
} 
Cuestiones relacionadas