TL;DR
描画をまったく行わない場合でも、Android デバイスの OpenGL ES レンダリング スレッドで 60Hz の更新レートを維持することは不可能に思えます。謎のスパイクが頻繁に発生し (一番下のコードで示されています)、その理由や方法を突き止めようと私が行ったすべての努力が行き詰まりにつながっています。カスタム レンダリング スレッドを使用したより複雑な例でのタイミングでは、eglSwapBuffers() が原因であることが一貫して示され、頻繁に 17ms ~ 32ms を超えています。ヘルプ?
詳細
私たちのプロジェクトのレンダリング要件は、画面の片側から反対側へ固定された高速で水平にスムーズにスクロールする要素であるため、これは特に厄介です。いわばプラットフォームゲーム。60Hz から頻繁に低下すると、時間ベースの動きがある場合とない場合の両方で、顕著なポッピングとランチングが発生します。スクロール速度が速いため、30Hz でのレンダリングはオプションではありません。これはデザインの交渉の余地のない部分です。
私たちのプロジェクトは、互換性を最大化するために Java ベースであり、OpenGL ES 2.0 を使用しています。API 7-8 デバイスでの OpenGL ES 2.0 レンダリングと API 7 デバイスでの ETC1 サポートの NDK についてのみ説明します。それと以下のテストコードの両方で、ログ出力と私の制御を超えた自動スレッドを除いて、割り当て/GC イベントがないことを確認しました。
ストック Android クラスを使用し、NDK を使用しない単一のファイルで問題を再現しました。以下のコードは、Eclipse で作成された新しい Android プロジェクトに貼り付けることができ、API レベル 8 以上を選択している限り、ほとんどそのまま使用できます。
このテストは、さまざまな GPU と OS バージョンのさまざまなデバイスで再現されています。
- ギャラクシー タブ 10.1 (アンドロイド 3.1)
- ネクサス S (アンドロイド 2.3.4)
- ギャラクシー S II (アンドロイド 2.3.3)
- Xperiaプレイ(アンドロイド2.3.2)
- ドロイド インクレディブル (Android 2.2)
- Galaxy S (Android 2.1-update1) (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();
}