2011-08-29 14 views
14

TL; DRAndroid EGL/OpenGL ES Frame Rate El tartamudeo

Incluso cuando se hace ningún dibujo en absoluto, parece imposible mantener una tasa de actualización de 60 Hz en un subproceso de representación de OpenGL ES en un dispositivo Android. Con frecuencia aparecen picos misteriosos (demostrados en el código al final) y cada esfuerzo que he hecho para descubrir por qué o cómo ha llevado a un callejón sin salida. El tiempo en ejemplos más complicados con un hilo de renderizado personalizado ha demostrado consistentemente que eglSwapBuffers() es el culpable, llegando con frecuencia a más de 17ms-32ms. ¿Ayuda?

Más detalles

Esto es particularmente concluyente ya que los requisitos de representación para nuestro proyecto es elementos de la pantalla alineados sin problemas de desplazamiento horizontalmente a una, de alta tasa fija de velocidad desde un lado de la pantalla a la otra. En otras palabras, un juego de plataformas. Las frecuentes caídas desde 60Hz resultan en estallidos y sacudidas notables, tanto con o sin movimiento basado en el tiempo. La representación a 30Hz no es una opción debido a la alta velocidad de desplazamiento, que es una parte no negociable del diseño.

Nuestro proyecto está basado en Java para maximizar la compatibilidad y utiliza OpenGL ES 2.0. Solo nos sumergimos en el NDK para la representación de OpenGL ES 2.0 en dispositivos API 7-8 y soporte ETC1 en dispositivos API 7. Tanto en él como en el código de prueba que figura a continuación, he verificado que no haya asignaciones/eventos del GC, excepto la impresión del registro y los hilos automáticos que escapan a mi control.

He vuelto a crear el problema en un solo archivo que utiliza clases de stock de Android y no NDK. El siguiente código se puede pegar en un nuevo proyecto de Android creado en Eclipse y debería funcionar bastante bien siempre que elija API nivel 8 o superior.

La prueba se ha reproducido en una variedad de dispositivos con una gama de versiones GPU y OS:

  • Galaxy Tab 10.1 (Android 3.1)
  • Nexus S (Android 2.3.4)
  • Galaxy S II (Android 2.3.3)
  • XPERIA Play (Android 2.3.2)
  • Droid Incredible (Android 2.2)
  • Galaxy S (Android 2,1-Update1) (cuando dr opping requisitos de API hasta el nivel 7)

Salida de ejemplo (obtenida de menos de 1 segundo de tiempo de ejecución):

Spike: 0.017554 
Spike: 0.017767 
Spike: 0.018017 
Spike: 0.016855 
Spike: 0.016759 
Spike: 0.016669 
Spike: 0.024925 
Spike: 0.017083999 
Spike: 0.032984 
Spike: 0.026052998 
Spike: 0.017372 

que he estado persiguiendo éste por un tiempo y tengo de golpear a un ladrillo pared. Si no hay una solución disponible, al menos una explicación sobre por qué sucede esto y consejos sobre cómo se ha superado esto en proyectos con requisitos similares serían muy apreciados.

Ejemplo Código

package com.test.spikeglsurfview; 

import javax.microedition.khronos.egl.EGLConfig; 
import javax.microedition.khronos.opengles.GL10; 

import android.app.Activity; 
import android.opengl.GLSurfaceView; 
import android.os.Bundle; 
import android.util.Log; 
import android.view.LayoutInflater; 
import android.view.Window; 
import android.view.WindowManager; 
import android.widget.LinearLayout; 

/** 
* A simple Activity that demonstrates frequent frame rate dips from 60Hz, 
* even when doing no rendering at all. 
* 
* This class targets API level 8 and is meant to be drop-in compatible with a 
* fresh auto-generated Android project in Eclipse. 
* 
* This example uses stock Android classes whenever possible. 
* 
* @author Bill Roeske 
*/ 
public class SpikeActivity extends Activity 
{ 
    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 
     super.onCreate(savedInstanceState); 

     // Make the activity fill the screen. 
     requestWindowFeature(Window.FEATURE_NO_TITLE); 
     getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
           WindowManager.LayoutParams.FLAG_FULLSCREEN); 

     // Get a reference to the default layout. 
     final LayoutInflater factory = getLayoutInflater(); 
     final LinearLayout layout = (LinearLayout)factory.inflate(R.layout.main, null); 

     // Clear the layout to remove the default "Hello World" TextView. 
     layout.removeAllViews(); 

     // Create a GLSurfaceView and add it to the layout. 
     GLSurfaceView glView = new GLSurfaceView(getApplicationContext()); 
     layout.addView(glView); 

     // Configure the GLSurfaceView for OpenGL ES 2.0 rendering with the test renderer. 
     glView.setEGLContextClientVersion(2); 
     glView.setRenderer(new SpikeRenderer()); 

     // Apply the modified layout to this activity's UI. 
     setContentView(layout); 
    } 
} 

class SpikeRenderer implements GLSurfaceView.Renderer 
{ 
    @Override 
    public void onDrawFrame(GL10 gl) 
    { 
     // Update base time values. 
     final long timeCurrentNS = System.nanoTime(); 
     final long timeDeltaNS = timeCurrentNS - timePreviousNS; 
     timePreviousNS = timeCurrentNS; 

     // Determine time since last frame in seconds. 
     final float timeDeltaS = timeDeltaNS * 1.0e-9f; 

     // Print a notice if rendering falls behind 60Hz. 
     if(timeDeltaS > (1.0f/60.0f)) 
     { 
      Log.d("SpikeTest", "Spike: " + timeDeltaS); 
     } 

     /*// Clear the screen. 
     gl.glClear(GLES20.GL_COLOR_BUFFER_BIT);*/ 
    } 

    @Override 
    public void onSurfaceChanged(GL10 gl, int width, int height) 
    { 
    } 

    @Override 
    public void onSurfaceCreated(GL10 gl, EGLConfig config) 
    { 
     // Set clear color to purple. 
     gl.glClearColor(0.5f, 0.0f, 0.5f, 1.0f); 
    } 

    private long timePreviousNS = System.nanoTime(); 
} 
+0

reloj de salida Logcat para los mensajes de GC, que es un hecho de la vida, si usted está construyendo en Java. las versiones más nuevas de Android tienen GC concurrente, pero es difícil evitar un GC completo ocasional y las pausas de acompañamiento. deberías estar contento con 40 fps y esforzarte por GC concurrente. –

Respuesta

0

No son vinculantes para la tasa de fotogramas a sí mismo. Por lo tanto, está vinculado por la CPU.

Lo que significa que funcionará rápido cuando la CPU no tenga nada que hacer y más lento cuando esté haciendo otras cosas.

Otras cosas se ejecutan en su teléfono, lo que ocasionalmente toma tiempo de CPU de su juego. Garbage Collector también lo hará, aunque no congele su juego como solía hacerlo (ya que ahora se ejecuta en un hilo separado)

Vería esto en cualquier sistema operativo de multiprogramación que estuviera en uso normal. No es solo culpa de Java.

Mi sugerencia: vincula el framerate a un valor inferior si se requiere una tasa de bits constante. Sugerencia: utilice una mezcla de Thread.sleep() y busy waiting (while loop), ya que el sueño puede superar el tiempo de espera requerido.

3

No estoy seguro de si esta es la respuesta, pero tenga en cuenta que la llamada a eglSwapBuffers() bloques durante al menos 16 ms, incluso si no hay nada que dibujar.

Ejecutar la lógica del juego en un hilo separado podría recuperar algo de tiempo.

Echa un vistazo a la publicación del blog en el juego de plataformas de código abierto Relica Island. La lógica del juego es pesada, pero el framrate es suave debido a la solución de pipeline/doble buffer del autor.

http://replicaisland.blogspot.com/2009/10/rendering-with-two-threads.html

Cuestiones relacionadas