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();
}
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. –