TextureViewで画面描画 ― 2016/05/10
画面描画に TextureViewを使用した Androidアプリのサンプルです。
・メインの処理は native
・描画は OpenGL ES
・フレーム毎の処理
という、ゲーム作成を前提とした仕様となっています。
プロジェクト一式は、こちらから。
今更な感もありますが、今まで GLSurfaceViewを使用していたのを TextureViewに変えてみました。
APIレベル14(Android4.0)未満では、TextureViewが使えないので SurfaceViewを使用するようになっています。
BaseActivity.java
package sys; import android.app.KeyguardManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.AssetManager; import android.graphics.SurfaceTexture; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import android.view.WindowManager; import android.widget.FrameLayout; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; /******************** アクティビティ ********************/ public class BaseActivity extends FragmentActivity implements Runnable { static { System.loadLibrary("native"); } private final static int NATIVE_PRIORITY = android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE; // nativeスレッド優先度 public final static int KEY_BACK = 1; // バックキー public final static int KEY_YES = 2; // ダイアログ用 public final static int KEY_NO = 3; private final static int PHASE_RUN = 0; // 実行中 private final static int PHASE_INIT = 1; // 初期化 private final static int PHASE_CONTINUE = 2; // 再開 private final static int PHASE_STOP = 3; // 中断 private final static int PHASE_FINISH = 4; // 終了 protected FrameLayout base_layout; // ベースレイアウト protected BaseView base_view; // ビュー private int phase; // 実行段階 protected int screen_width, screen_height; // 画面の大きさ private final Object sync_native = new Object(); private ScheduledExecutorService executor; // 定期実行管理 private ScheduledFuture<?> future; private long time0, time1; private int frame_rate; // フレームレート private short[] touch_status = new short[5*3]; // タッチパネル状態 protected int key_status = 0; // キー入力状態 public native int initNative(boolean _init, AssetManager _mgr); // native部初期化 public native void setScreenNative(int _w, int _h); // native部画面サイズ設定 public native void quitNative(); // native部終了 public native void pauseNative(); // native部一時停止 public native boolean updateNative(boolean draw, short _touch[], int _key); // native部稼働 /********** 開始 **********/ @Override protected void onCreate(Bundle _savedInstanceState) { onCreate2(_savedInstanceState, null, null); } protected void onCreate2(Bundle _savedInstanceState, FrameLayout _base) { onCreate2(_savedInstanceState, _base, null); } protected void onCreate2(Bundle _savedInstanceState, FrameLayout _base, int[] _attribs) { super.onCreate(_savedInstanceState); phase = (_savedInstanceState == null) ? PHASE_INIT : PHASE_CONTINUE; if ( _attribs == null ) { _attribs = (new int[] { // デフォルト 画面アトリビュート EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_STENCIL_SIZE, 0, EGL10.EGL_NONE }); } base_view = new BaseView((Build.VERSION.SDK_INT < 14), _attribs); // ベースビュー if ( _base == null ) { base_layout = base_view; setContentView(base_view); } else { base_layout = _base; base_layout.addView(base_view, 0); } } @Override protected void onStart() { super.onStart(); SoundManager.init(); // サウンド管理初期化 } /********** 終了 **********/ @Override public void finish() { phase = PHASE_FINISH; super.finish(); } @Override protected void onDestroy() { if ( phase == PHASE_FINISH ) { quitNative(); // native部終了 } super.onDestroy(); } @Override protected void onStop() { super.onStop(); SoundManager.quit(); // サウンド管理終了 } /********** 再開 **********/ @Override protected void onResume() { super.onResume(); executor = Executors.newSingleThreadScheduledExecutor(); // 描画スレッド管理 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // スリープ禁止 for (int i = 0; i < 5; i++) { // タッチパネル状態クリア touch_status[i*3] = 0; } if ( ((KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode() ) { receiver = new UnLockReceiver(); registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_PRESENT)); // スクリーンロック解除待ち } else { start(); } } private UnLockReceiver receiver = null; // スクリーンロック解除検知用 private class UnLockReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { unregisterReceiver(receiver); // レシーバー登録を解除 receiver = null; start(); } } /************** 一時停止 **************/ @Override synchronized protected void onPause() { super.onPause(); if ( phase == PHASE_RUN ) { phase = PHASE_STOP; } if ( future != null ) { // 定期実行停止 future.cancel(false); future = null; } try { executor.submit(new Runnable() { @Override public void run() { base_view.quitGL(); // OpenGL終了 } }).get(); } catch (InterruptedException | ExecutionException e) {} executor.shutdown(); executor = null; if ( phase >= PHASE_STOP ) { pauseNative(); // native部一時停止 } if ( receiver != null ) { unregisterReceiver(receiver); // レシーバー登録を解除 receiver = null; } getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // スリープ禁止解除 } /******************************** 開始 戻り値 フレームレート ********************************/ synchronized public void start() { if ( (executor == null) || (base_view.native_window == null) ) { return; } try { executor.submit(new Runnable() { @Override public void run() { android.os.Process.setThreadPriority(NATIVE_PRIORITY); base_view.initGL() ; // OpenGL初期化 synchronized (sync_native) { frame_rate = initNative((phase == PHASE_INIT), getAssets()); // native部初期化 } phase = PHASE_RUN; time0 = System.currentTimeMillis(); time1 = 0; } }).get(); } catch (InterruptedException | ExecutionException e) {} future = executor.scheduleAtFixedRate(this, 0, 1000/frame_rate, TimeUnit.MILLISECONDS); // 定期実行開始 } /********* 稼働 **********/ @Override public void run() { if ( phase == PHASE_RUN ) { long _t = System.currentTimeMillis(); int _loop; time1 += (_t - time0)*frame_rate; if ( time1 > 4*1000 - 1 ) { time1 = 4*1000 - 1; } time0 = _t; _loop = (int)time1/1000; if ( _loop > 0 ) { time1 -= _loop*1000; base_view.swap(); synchronized (sync_native) { for (; (_loop > 0) && (phase == PHASE_RUN); _loop--) { int _key = key_status; key_status = 0; if ( !updateNative((_loop == 1), touch_status, _key) ) { // native部稼働 finish(); break; } } } } } } /******************** 画面サイズ設定 ********************/ public void set_screen(int _width, int _height) { screen_width = _width; screen_height = _height; synchronized (sync_native) { setScreenNative(_width, _height); } } /******************** タッチイベント ********************/ public boolean onTouchEvent(final MotionEvent event) { if ( phase != PHASE_RUN ) { return false; } int _action = event.getAction(); int _index, _id; switch ( _action & MotionEvent.ACTION_MASK ) { case MotionEvent.ACTION_DOWN : case MotionEvent.ACTION_POINTER_DOWN : _index = (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; _id = event.getPointerId(_index)*3; if ( _id < 5*3 ) { touch_status[_id + 1] = (short)event.getX(_index); // X座標 touch_status[_id + 2] = (short)event.getY(_index); // Y座標 touch_status[_id + 0] = 1; // タッチ中 } break; case MotionEvent.ACTION_MOVE : for (_index = 0; _index < event.getPointerCount(); _index++) { _id = event.getPointerId(_index)*3; if ( _id < 5*3 ) { touch_status[_id + 1] = (short)event.getX(_index); // X座標 touch_status[_id + 2] = (short)event.getY(_index); // Y座標 touch_status[_id + 0] = 1; // タッチ中 } } break; case MotionEvent.ACTION_UP : case MotionEvent.ACTION_POINTER_UP : _index = (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; _id = event.getPointerId(_index)*3; if ( _id < 5*3 ) { touch_status[_id + 0] = 0; // 非タッチ } break; } return true; } /******************** バックキー入力 ********************/ @Override public void onBackPressed() { key_status = KEY_BACK; } /************ ビュー ************/ class BaseView extends FrameLayout { public EGL10 mEgl; public EGLDisplay mEglDisplay; public EGLContext mEglContext; public EGLSurface mEglSurface; public Object native_window; private int[] config_attribs; private static final int EGL_OPENGL_ES2_BIT = 4; private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; /************************************************ コンストラクタ 引数 _kind = true :SurfaceView false:TextureView _attribs = 画面アトリビュート ************************************************/ public BaseView(boolean _kind, int[] _attribs) { super(getApplication()); config_attribs = _attribs; if ( _kind ) { // SurfaceView SurfaceView _view = new SurfaceView(getApplication()); _view.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) {} @Override synchronized public void surfaceDestroyed(SurfaceHolder holder) { native_window = null; } @Override public void surfaceChanged(SurfaceHolder _holder, int format, int _width, int _height) { set_screen(_width, _height); set_surface(_holder); } }); addView(_view); } else { // TextureView TextureView _view = new TextureView(getApplication()); _view.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture _surface, int _width, int _height) { set_screen(_width, _height); set_surface(_surface); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int _width, int _height) { set_screen(_width, _height); } @Override synchronized public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { native_window = null; return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }); addView(_view); } } /********** 開始 **********/ synchronized private void set_surface(Object _window) { if ( native_window == null ) { native_window = _window; start(); } } /********** 稼働 **********/ private void swap() { if ( !mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext) ) { throw new RuntimeException("eglMakeCurrent failed"); } mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); } /****************** OpenGL初期化 ******************/ private void initGL() { mEgl = (EGL10)EGLContext.getEGL(); mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if ( mEglDisplay == EGL10.EGL_NO_DISPLAY ) { throw new RuntimeException("eglGetDisplay failed"); } if ( !mEgl.eglInitialize(mEglDisplay, new int[2]) ) { throw new RuntimeException("eglInitialize failed"); } if ( (search_config(config_attribs) == null) && (search_config(new int[] {EGL10.EGL_NONE}) == null) ) { throw new RuntimeException("eglCreateWindowSurface failed"); } mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); } private EGLConfig search_config(final int[] attribs) { int[] num_config = new int[1]; mEgl.eglChooseConfig(mEglDisplay, attribs, null, 0, num_config); if ( num_config[0] <= 0 ) { return null; } EGLConfig[] configs = new EGLConfig[num_config[0]]; mEgl.eglChooseConfig(mEglDisplay, attribs, configs, num_config[0], num_config); final int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; for (EGLConfig config : configs) { mEglContext = mEgl.eglCreateContext(mEglDisplay, config, EGL10.EGL_NO_CONTEXT, attrib_list); // Context作成 if ( (mEglContext != null) && (mEglContext != EGL10.EGL_NO_CONTEXT) ) { mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, config, native_window, null); // Surface作成 if ( (mEglSurface != null) && (mEglSurface != EGL10.EGL_NO_SURFACE) ) { return config; } mEgl.eglDestroyContext(mEglDisplay, mEglContext); } } return null; } /**************** OpenGL終了 ****************/ private void quitGL() { if ( mEglContext != EGL10.EGL_NO_CONTEXT ) { mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); mEgl.eglDestroyContext(mEglDisplay, mEglContext); mEglContext = EGL10.EGL_NO_CONTEXT; } if ( mEglSurface != EGL10.EGL_NO_SURFACE ) { mEgl.eglDestroySurface(mEglDisplay, mEglSurface); mEglSurface = EGL10.EGL_NO_SURFACE; } if ( mEglDisplay != EGL10.EGL_NO_DISPLAY ) { mEgl.eglTerminate(mEglDisplay); mEglDisplay = EGL10.EGL_NO_DISPLAY; } } } } /***************** End of File ***************************************************/
シンプルな処理なので、ライフサイクル通りに順を追っていけば何をやっているかはわかると思いますが、一応少しだけ説明。
・executor
定期実行を管理するための ScheduledExecutorServiceです。
また、そのスレッドが OpenGLの描画スレッドとなっています。
自分がそんなに速さを必要とするアプリを作っていないこともあって、計算用のスレッドを分けたりはしていません。
・フレームレートに合わせた処理
scheduleAtFixedRateで実行する処理の間隔とフレームレートにはズレが出てきますので、時刻を見て処理回数を変えて合わせています(268行~)。
ついでに、ある程度の処理落ちにも対応できます。
・SurfaceViewと TextureView
393行からの Viewとそのコールバックの作成で、SurfaceViewと TextureViewの違いを吸収しています。
こちらは、同様に描画を変更した「ひっくり返しパズル かえすがえす」のプロジェクトです。
さすがに、これだけの変更でバージョンアップリリースはしていませんが。
そんなわけで稼働実績は大して無いので、上記の内容は参考程度に。
まんがタイムファミリー 7月号 ― 2016/05/19
軍神ちゃんとよばないで
器の大きさを見せる信玄と、なぜか大物に見える謙信。
その分、勘助が割を食ってる。
「逃げ弾正」って、ホームの「のちの真田幸村である」でも言ってたな。
寺島さんは悟っている
いつも寺や坊さんのネタを盛り込んでいるが、「お坊さん便」の類にも言及してきた。
無難な見解だったけど。
ひかり!出発進行
最終回だけど、前半はいつもの鉄道ネタ。
タイトルの「出発進行」できれいに締めようとしたら、やはり鉄道用語で否定される有様。
生まれてくる赤ちゃんは、最後に顔も見せずに出てきただけだったか。
かしこみかしこみ
三度笠も奏衣様にとっては「懐かしい」となるか
寺島さんは悟っている(1) |
最近のコメント