OpenGLの機能を使うまで

プロジェクト基本構成

androidOpenGLでプログラムを作る際は、基本的に以下の3つのクラスが必要になります。
・アプリケーションを管理するActivityクラス
・画面を管理するViewクラス
OpenGLで絵を描く処理をするThreadクラス


上記のクラスに絵を描くためのThreadクラスが含まれていますが、
これはOpenGLを使う際はViewクラスにSurfaceViewというクラスを使う必要があり、
このクラスはonDrawメソッドが呼ばれず、画面への描画処理を自身で作る必要があるためです。
これからOpenGL ESを覚えようというところで、さらにマルチスレッドプログラミングの知識が必要になり非常に厄介ですが、
ここではマルチスレッドの影響を最小限にして、複雑化しないようにしていきたいと思います。

OpenGLを使用するまでの最小実装プログラム

まずOpenGLの機能を使うまでの部分と片付けの部分に絞って解説していきます。
以下のソースは上で解説している3つのクラスに分けて作成しています。実行すると白い背景と画面右上1/4を水色にした画面が表示されます。
公式サイトのサンプルを参考にしていますが、とりあえずわかりにくい部分は次回以降に回します。


●GLActivity.java
ViewクラスをsetContentViewでセットしているだけです。
===================================================================================

package jp.kambayashi.gl;

import android.app.Activity;
import android.os.Bundle;

public class GLActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new GLView(this));
    }
}

===================================================================================


●GLView.java
ウィンドウの初期化が終わった際のOpenGL描画スレッドの作成と、ウィンドウ破棄の際の終了処理をしています。
===================================================================================

package jp.kambayashi.gl;

import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;

public class GLView extends SurfaceView implements Callback {
	//描画管理スレッド
	private GLThread mGLThread;
	
	/**
	 * コンストラクタ
	 * @param context	アプリケーションコンテキスト
	 */
	public GLView( GLActivity context ) {
		super( context );
		
		//コールバックインターフェース設定
		getHolder().addCallback(this);
		
		//OpenGL ESを使う際はサーフェイスタイプGPUにする
		getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
	}
		
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		//画面サイズ変更処理
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		//GLThreadを動かす
		mGLThread  = new GLThread(this);
		mGLThread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		//GLThreadに終了要求を出す
		mGLThread.RequestExitAndWait();
	}
}

===================================================================================


●GLThread.java
OpenGL ESの初期化と、描画できるまでのみに絞っています。
本来はもっとエラーに関する処理がありますが、省略しています。
===================================================================================

package jp.kambayashi.gl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

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;
import javax.microedition.khronos.opengles.GL10;

import android.util.Log;

public class GLThread extends Thread {
	//オブジェクト識別子
	private String mName;
	//画面管理オブジェクト
	private GLView mView;
	//実行完了フラグ
	private boolean mDone;
	//描画ヘルパー
	private Renderer mRenderer;

	//ESレンダリングコンテキスト
	private EGLContext mEglContext;
	//ESディスプレイコネクション
	private EGLDisplay mEglDisplay;
	//ESサーフェイス
	private EGLSurface mEglSurface;
	//ESコンフィグ
	private EGLConfig mEglConfig;
	
	/**
	 * コンストラクタ
	 * @param view	
	 */
	public GLThread(GLView view){
		mView = view;
		mName = "GLThread";
		mDone = false;
		mRenderer = new Renderer();
	}

	/**
	 * スレッド処理スタート
	 */
	@Override
	public void run() {
		//OpenGL ESの準備
		if( !initGLES() ){
			Log.e(mName, "OpenGL ES初期化失敗");
			mDone = true;
		}
			
		//終了要求がでるまで繰り返し
		GL10 gl = (GL10)mEglContext.getGL();
		while(!mDone){
			//描画
			drawFrame(gl);
		}
		
		//OpenGL ESの片付け
		endGLES();
	}
	
	/**
	 * 描画
	 * 
	 * @param gl	OpenGL操作ハンドル
	 */
	private void drawFrame( GL10 gl ){
		//ビューポート設定
        gl.glViewport(0,0,mView.getWidth(),mView.getHeight());
        
		//背景クリア
		gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		
		//矩形描画
		mRenderer.draw(gl);
		
		//画面に出力するバッファの切り替え
		EGL10 egl = (EGL10)EGLContext.getEGL();
		egl.eglSwapBuffers(mEglDisplay, mEglSurface);
	}
	
	/**
	 * OpenGL ES初期化
	 * 
	 * 
	 * @return	正常終了なら真、エラーなら偽
	 */
	private boolean initGLES(){
		//GL ES操作モジュール取得 
		EGL10 egl = (EGL10)EGLContext.getEGL();
		
		{
			//ディスプレイコネクション作成
			mEglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
			if( mEglDisplay == EGL10.EGL_NO_DISPLAY ){
				Log.e(mName, "ディスプレイコネクション作成失敗");
				return false;
			}
			
			//ディスプレイコネクション初期化
			int[] version = new int[2];
			if( !egl.eglInitialize(mEglDisplay, version) ){
				Log.e(mName, "ディスプレイコネクション初期化失敗");
				return false;
			}
		}
		
		{
			//コンフィグ設定
			int[] configSpec = {
				EGL10.EGL_ALPHA_SIZE, 8,	//アルファチャンネル:8ビット
				/*
					これを使うと実機で動かないのでカット
				EGL10.EGL_RED_SIZE, 8,		//赤要素:8ビット
				EGL10.EGL_GREEN_SIZE, 8,	//緑要素:8ビット
				EGL10.EGL_BLUE_SIZE, 8,		//青要素:8ビット
				*/
				EGL10.EGL_DEPTH_SIZE, 16,	//深度バッファ:16ビット
				EGL10.EGL_NONE				//終端にはEGL_NONEを入れる
			};
			EGLConfig[] configs = new EGLConfig[1];
			int[] numConfigs = new int[1];
			if( !egl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, numConfigs) ){
				Log.e(mName, "コンフィグ設定失敗");
				return false;
			}
			mEglConfig = configs[0];
		}
		
		{
			//レンダリングコンテキスト作成
			mEglContext = 
				egl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null);
			if( mEglContext == EGL10.EGL_NO_CONTEXT ){
				Log.e(mName, "レンダリングコンテキスト作成失敗");
				return false;
			}
		}
		
		{
			//サーフェイス作成(あとで分けるので別メソッド)
			if( !createSurface() ){
				Log.e(mName, "サーフェイス作成失敗");
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * サーフェイス作成
	 * 
	 * サーフェイスを作成して、レンダリングコンテキストと結びつける
	 * 
	 * @return	正常終了なら真、エラーなら偽
	 */
	private boolean createSurface(){
		EGL10 egl = (EGL10)EGLContext.getEGL();
		
		{
			//サーフェイス作成
			mEglSurface = 
				egl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mView.getHolder(), null);
			if( mEglSurface == EGL10.EGL_NO_SURFACE ){
				Log.e(mName, "サーフェイス作成失敗");
				return false;
			}
		}
		
		{
			//サーフェイスとレンダリングコンテキスト結びつけ
			if( !egl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext) ){
				Log.e(mName, "レンダリングコンテキストとの結びつけ失敗");
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * OpenGL ES片付け
	 */
	private void endGLES(){
		EGL10 egl = (EGL10)EGLContext.getEGL();
		
		//サーフェイス破棄
		if( mEglSurface != null){
			//レンダリングコンテキストとの結びつけは解除
			egl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
			
			egl.eglDestroySurface(mEglDisplay, mEglSurface);
			mEglSurface = null;
		}
		
		//レンダリングコンテキスト破棄
		if( mEglContext != null ){
			egl.eglDestroyContext(mEglDisplay, mEglContext);
			mEglContext = null;
		}
		
		//ディスプレイコネクション破棄
		if( mEglDisplay != null){
			egl.eglTerminate(mEglDisplay);
			mEglDisplay = null;
		}
	}
	
	/**
	 * スレッド終了要求
	 * 
	 * スレッドに終了要求を出して、停止するのを待つ
	 */
	public void RequestExitAndWait(){
		synchronized (this) {
			//終了要求を出す
			mDone = true;
		}
		
		try{
			//スレッド終了を待つ
			join();
		}
		catch( InterruptedException ex ){
			Thread.currentThread().interrupt();
		}
	}
	
	/**
	 * 描画補助クラス
	 */
	class Renderer{
		private FloatBuffer buffer;
		
		/**
		 * コンストラクタ
		 */
		public Renderer(){
			//頂点数x頂点構成要素数x4バイトのサイズのバッファ作成
			ByteBuffer vb = ByteBuffer.allocateDirect(4 * 3 * 4);
			vb.order(ByteOrder.nativeOrder());	//ビッグエンディアンかリトルエンディアンにあわせてくれる
			buffer = vb.asFloatBuffer();
		
			//頂点データで頂点バッファ作成
			float[] vertices = {
					0.0f, 0.0f, 0.1f,
					1.0f, 0.0f, 0.1f,
					0.0f, 1.0f, 0.1f,
					1.0f, 1.0f, 0.1f,
			};
			buffer.put(vertices);
			buffer.position(0);
		}
		
		/**
		 * 描画
		 * @param gl	GL操作ハンドル
		 */
		public void draw( GL10 gl ){
			//ポリゴンカラー設定
			gl.glColor4f(0.8f, 0.8f, 1.0f, 1.0f);
			//頂点バッファ機能ON
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			//頂点バッファ設定
			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, buffer);
			//描画
			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
		}
	}
}

===================================================================================

まとめ

大分簡略化しましたが、それでも入門というには長いコードになってしまいました。
それでもマルチスレッドはあまり意識しなくていいように出来たと思います。
今後のサンプルはこのコードのGLThreadクラスを修正しながら作っていきます。