ApiDemosを調べる2 - 個別API GLES20Activity

graphics -> OpenGL ES -> OpenGL ES 2.0

で、GLES20Activityを実行します。

public class GLES20Activity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLSurfaceView = new GLSurfaceView(this);
        if (detectOpenGLES20()) {
            // Tell the surface view we want to create an OpenGL ES 2.0-compatible
            // context, and set an OpenGL ES 2.0-compatible renderer.
            mGLSurfaceView.setEGLContextClientVersion(2);
            mGLSurfaceView.setRenderer(new GLES20TriangleRenderer(this));
        } else {
            // Set an OpenGL ES 1.x-compatible renderer. In a real application
            // this renderer might approximate the same output as the 2.0 renderer.
            mGLSurfaceView.setRenderer(new TriangleRenderer(this));
        }
        setContentView(mGLSurfaceView);
    }

    private boolean detectOpenGLES20() {
        ActivityManager am =
            (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo info = am.getDeviceConfigurationInfo();
        return (info.reqGlEsVersion >= 0x20000);
    }

    @Override
    protected void onResume() {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onPause();
        mGLSurfaceView.onPause();
    }

    private GLSurfaceView mGLSurfaceView;
}

onPause()とonResume()は割愛します。
(Pauseはほかのアプリにフォーカスが移ったときに呼び出される。
 その時の状態を保存したりする。
 Resumeはほかのアプリからまた戻ってきたときに呼び出される。
 以前保存した状態を呼び出したりする。)

onCreate()が呼ばれる。

次にGLSurfaceViewクラスのインスタンスを生成する。

detectOpenGLES20はシステムがGLES2.0に対応しているかどうかを確認する。
自分はエミュレータ上で実行したところ、非対応になってしまった。

(GLES2.0に対応している場合には、EGLContextのバージョンを2に設定してから)
setRendererでRendererを設定する。

setRendererの内部実装は以下のとおり(Android SDKだけではアクセスできないのでソースが必要)

    public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (mEGLConfigChooser == null) {
            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (mEGLContextFactory == null) {
            mEGLContextFactory = new DefaultContextFactory();
        }
        if (mEGLWindowSurfaceFactory == null) {
            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        mRenderer = renderer;
        mGLThread = new GLThread(renderer);
        mGLThread.start();
    }

checkRenderThreadStateはすでにmGLThreadが生成されていないかを
確認するもの。

その後、GLSurfaceViewクラスに必要なインスタンス等生成していく。
描画用のThreadを別途生成している。

RendererはGLSurfaceViewの中にinterfaceが書かれている。

    public interface Renderer {
        /**
         * Called when the surface is created or recreated.
         * 
         */
        void onSurfaceCreated(GL10 gl, EGLConfig config);

        /**
         * Called when the surface changed size.
         * @param gl the GL interface. Use <code>instanceof</code> to
         * test if the interface supports GL11 or higher interfaces.
         * @param width
         * @param height
         */
        void onSurfaceChanged(GL10 gl, int width, int height);

        /**
         * Called to draw the current frame.

         * @param gl the GL interface. Use <code>instanceof</code> to
         * test if the interface supports GL11 or higher interfaces.
         */
        void onDrawFrame(GL10 gl);
    }

onSurfaceCreatedは起動時に呼ばれる。
GLの初期化をしたり、画面サイズを設定したりする。
今回は、TriangleRenderer.javaの中で以下の記述がなされている。

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        gl.glDisable(GL10.GL_DITHER);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

        gl.glClearColor(.5f, .5f, .5f, 1);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glEnable(GL10.GL_TEXTURE_2D);


        int[] textures = new int[1];
        gl.glGenTextures(1, textures, 0);
        mTextureID = textures[0];
        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
                GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_LINEAR);

        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_CLAMP_TO_EDGE);

        gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
                GL10.GL_REPLACE);

        InputStream is = mContext.getResources()
                .openRawResource(R.raw.robot);
        Bitmap bitmap;
        try {
            bitmap = BitmapFactory.decodeStream(is);
        } finally {
            try {
                is.close();
            } catch(IOException e) {
                // Ignore.
            }
        }

        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
    }

GLの初期設定と、Textureの準備を行っている。
リソースのR.raw.robotをbitmapにして、それを
GLUtils.texImage2D()でTextureに設定している。
(ちなみに、R.raw.の中にリソースを配置するとDPI(画素密度)によらず、
常にオリジナルの画像を使用してくれるらしい)


onSurfaceChangedは画面サイズが変わった、たとえば回転などのときに呼ばれる。
画面サイズを設定したり、GLの再設定をしたりする。
初回にも上記onSurfaceCreated()の後に呼ばれてくる。
今回は、以下の処理を行っている。

    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);

        float ratio = (float) w / h;
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);

    }

視点(Viewportと射影行列)に関する設定。
Viewportは画面の幅と高さ。Frustum(視錐台)で見える範囲を設定。
2Dの場合はPerspectiveをOrtho設定とする。遠くも近くも同じ大きさになる。


onDrawFrameが毎回呼ばれる処理。
ここで描画する。
今回は、以下の処理を行っている。

    public void onDrawFrame(GL10 gl) {
        gl.glDisable(GL10.GL_DITHER);
        gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
                GL10.GL_MODULATE);

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();

        GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        gl.glActiveTexture(GL10.GL_TEXTURE0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                GL10.GL_REPEAT);
        gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                GL10.GL_REPEAT);

        long time = SystemClock.uptimeMillis() % 4000L;
        float angle = 0.090f * ((int) time);

        gl.glRotatef(angle, 0, 0, 1.0f);

        mTriangle.draw(gl);
    }

画面をクリアしてからモデルビュー行列に対して設定しています。
特にここでは、システムから時間を取得して回転角度を更新させています。
最後のdrawは以下のとおり。

    public void draw(GL10 gl) {
        gl.glFrontFace(GL10.GL_CCW);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexBuffer);
        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
                GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    }

頂点配列(mFVertexBuffer。事前に三角形の座標を格納している)と
それに対応するテクスチャ座標配列(mTexBuffer)を設定してDrawElementsで
描画しています。ここではドロイド君がテクスチャとして貼られた
三角形が表示されます。

これはGLES1.1の場合の処理です。
GLES2.0の場合はシェーダプログラムが必要になります。


onDraw()では描画のみで、画面の更新はしない。
画面の更新はEGLが行う。

GLSurfaceView.javaのなかにEGLHelperクラスがあり、その中に
swap()関数がある。

        public boolean swap() {
            if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {

                int error = mEgl.eglGetError();
                switch(error) {
                   ....
                }
            }
            return true;
        }

というわけで、ただ単にeglSwapBuffers()を呼び出しているだけです。