2011-08-29 89 views
14

TL; DR的Android EGL/OpenGL ES的幀速率口吃

即使沒有做圖時可言,這似乎是不可能維持在Android設備上上的OpenGL ES渲染線程60Hz的刷新率。經常出現神祕的尖峯(在底部的代碼中顯示),並且我爲了弄清楚爲什麼或如何導致死路一條而做出的每一個努力。使用自定義渲染線程定時更復雜的示例一直表明eglSwapBuffers()是罪魁禍首,經常在17ms-32ms以上。幫幫我?

更多詳細信息

這是特別確鑿因爲我們的項目的呈現要求是屏幕對齊元件順暢地在固定的,高速的其他速率從屏幕的一側水平滾動。換句話說,一個平臺遊戲。從60Hz頻繁下降導致明顯的爆裂和l,,無論是否有基於時間的移動。由於滾動速度很高,因此30Hz下的渲染不是一種選擇,這是設計中不可談判的部分。

我們的項目是基於Java的,以最大限度地提高兼容性並使用OpenGL ES 2.0。我們只能深入到API 7-8設備上的OpenGL ES 2.0渲染和API 7設備上的ETC1支持。在它和下面給出的測試代碼中,除了我的控制之外的日誌打印和自動線程,我沒有驗證分配/ GC事件。

我已經在使用庫存Android類和NDK的單個文件中重新創建了該問題。下面的代碼可以粘貼到在Eclipse中創建的新Android項目中,只要您選擇API級別8或更高版本,即可使用即開即用的代碼。

測試已被再現的各種設備與一系列GPU和OS版本:

  • 銀河標籤10.1(3.1的Android)
  • 的Nexus S(的Android 2.3.4)
  • 星系S II(的Android 2.3.3)
  • XPERIA播放(2.3.2的Android)
  • Droid難以置信(的Android 2.2)
  • 銀河S(的Android 2.1-UPDATE1)(當博士opping API的需求下降到7級)

樣本輸出(從下的運行時間1秒)收集:

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 

我一直在追逐這一個了一段時間,有關於打磚壁。如果修復程序不可用,那麼至少應該解釋爲什麼會發生這種情況,以及如何在具有類似要求的項目中克服這些問題的建議將不勝感激。

示例代碼

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

手錶logcat的輸出GC的消息,這是一個事實的生活,如果你在Java中構建它。較新版本的Android具有併發GC,但很難避免偶爾發生的全面GC和伴隨的暫停。你應該對40 fps感到滿意,並爭取併發GC。 –

回答

0

你自己都沒有約束力的幀率。所以它受CPU限制。

這意味着當CPU無所事事時它會運行得很快,而當它做其他事情時會變慢。

其他東西正在您的手機上運行,​​有時會從您的遊戲中獲取CPU時間。垃圾收集器也會這樣做,雖然不像以前那樣凍結你的遊戲(因爲它現在運行在一個單獨的線程中)

你會在任何正常使用的多程序操作系統上看到這種情況。這不僅僅是Java的錯。

我的建議:如果需要恆定比特率,則將幀速率綁定到較低的值。 提示:使用Thread.sleep()和busy waiting(while循環)的混合,因爲睡眠可能會超過所需的等待時間。

3

不確定這是否是答案,但請注意,對eglSwapBuffers()的調用至少需要16 ms,即使沒有任何內容可以繪製。

在單獨的線程中運行遊戲邏輯可能會贏回一些時間。

查看開源平臺遊戲Relica Island的博文。遊戲邏輯很重,但由於作者管線/雙緩衝解決方案,該框架非常流暢。

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