2011-08-29 16 views
14

TL; DRAndroid EGL/OpenGL ES Frame Rate balbuzie

Anche quando si fa alcun disegno in tutto, sembra impossibile mantenere una velocità di aggiornamento 60Hz su un thread di rendering OpenGL ES su un dispositivo Android. Frequentemente spuntano punte misteriose (dimostrate nel codice in fondo) e ogni sforzo che ho fatto per capire perché o come ha portato a un vicolo cieco. Il tempismo in esempi più complicati con un thread di rendering personalizzato ha mostrato in modo coerente che eglSwapBuffers() è il colpevole, che frequenta spesso oltre i 17ms-32ms. Aiuto?

Dettagli

Ciò è particolarmente schiacciante perché i requisiti di rendering per il nostro progetto è elementi di schermo allineati uniformemente scorrimento orizzontalmente alla, alto tasso fisso di velocità da un lato dello schermo all'altro. In altre parole, un gioco platform. Le frequenti cadute da 60 Hz provocano notevoli scoppi e sobbalzi, sia con che senza movimento basato sul tempo. Il rendering a 30 Hz non è un'opzione a causa dell'alta velocità di scorrimento, che è una parte non negoziabile del progetto.

Il nostro progetto è basato su Java per massimizzare la compatibilità e utilizza OpenGL ES 2.0. Scendiamo solo nel NDK per il rendering OpenGL ES 2.0 su dispositivi API 7-8 e supporto ETC1 su dispositivi API 7. In entrambi e il codice di test indicato di seguito, non ho verificato nessun allocazione/eventi GC ad eccezione della stampa dei log e dei thread automatici al di fuori del mio controllo.

Ho ricreato il problema in un singolo file che utilizza le classi Android standard e nessun NDK. Il codice sottostante può essere incollato in un nuovo progetto Android creato in Eclipse e dovrebbe funzionare praticamente subito dopo aver scelto l'API di livello 8 o superiore.

Il test è stato riprodotto su una varietà di dispositivi con una gamma di versioni GPU e 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) (quando dr opping requisiti API fino al livello 7)

Esempio di output (raccolte da meno di 1 secondo di tempo di esecuzione):

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 

Sto inseguendo questo per un po 'e ho circa colpire un mattone parete. Se una correzione non è disponibile, almeno una spiegazione sul perché questo accada e consigli su come questo è stato superato in progetti con requisiti simili sarebbe molto apprezzato.

Esempio Codice

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

orologio uscita logcat per i messaggi GC, che è una vita fatto-of-se si sta costruendo in Java. le versioni più recenti di Android hanno un GC concorrente, ma è difficile evitare un GC completo occasionale e le pause di accompagnamento. dovresti essere soddisfatto di 40 fps e cercare il GC simultaneo. –

risposta

0

Lei non è vincolante il framerate da soli. Quindi è vincolato dalla CPU.

Ciò significa che verrà eseguito velocemente quando la CPU non ha nulla da fare e più lentamente quando fa altro.

Altre cose sono in esecuzione sul telefono, che a volte richiede tempo alla CPU dal gioco. Garbage Collector farà anche questo, anche se non congelerà il tuo gioco come faceva (dato che ora viene eseguito in un thread separato)

Si vedrebbe ciò accada su qualsiasi sistema operativo multiprogrammazione che era in uso normale. Non è solo colpa di Java.

Il mio suggerimento: associare il framerate ad un valore inferiore se è richiesto un bitrate costante. Suggerimento: utilizzare un mix di Thread.sleep() e l'attesa (mentre il ciclo è occupato), poiché il sonno potrebbe andare oltre il tempo di attesa richiesto.

3

Non sicuro se questa è la risposta, ma si noti che la chiamata a eglSwapBuffers() blocca per almeno 16 ms, anche se non c'è nulla da disegnare.

L'esecuzione della logica di gioco in un thread separato potrebbe recuperare un po 'di tempo.

Dai un'occhiata al post sul blog del gioco di piattaforme open source Relica Island. La logica di gioco è pesante, eppure il framrate è scorrevole a causa della pipeline degli autori/soluzione a doppio buffer.

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

Problemi correlati