在 Android 中使用 OpenGL 頂 原

Android 通過 OpenGL 包含了對高性能 2D 和 3D 圖形的支持,特別是 OpenGL ES API。OpenGL 是一個跨平臺的圖形 API,它爲 3D 圖形處理硬件規定了一個標準的軟件接口。OpenGL ES 是一種用於嵌入式設備的 OpenGL 規範。Android 支持多種版本的 OpenGL ES API:

  • OpenGL ES 1.0 和 1.1 - Android 1.0 及更高版本支持這個 API 規範。
  • OpenGL ES 2.0 - Android 2.0(API level 8)及更高版本支持這個 API 規範。
  • OpenGL ES 3.0 - Android 4.3(API level 18)及更高版本支持這個 API 規範。
  • OpenGL ES 3.1 - Android 5.0(API level 21)及更高版本支持這個 API 規範。

注意: 設備上對 OpenGL ES 3.0 API 的支持需要設備生產商提供這個圖形管線的實現。運行 Android 4.3 或更高版本的設備 可能不 支持 OpenGL ES 3.0 API。在運行時檢查支持何種版本的 OpenGL ES 的信息,請參考 Checking OpenGL ES Version

注意: Android framework 提供的特別的 API 與 J2ME JSR239 OpenGL ES API 類似,但不完全一致。如果你對 J2ME JSR239 比較熟悉,請注意其中的不同。

Android 同時通過它的 framework API 和 NDK 支持 OpenGL。這裏主要來看 Android framework 的接口。Android framework 中提供了多個接口讓我們可以通過 OpenGL ES 創建並管理圖形:GLSurfaceView,TextureView,SurfaceView 等等。如果是要在實際的應用中使用 OpenGL,則 GLSurfaceView 最好用。

爲了能夠更清晰地釐清,EGL 爲 OpenGL 渲染做環境準備的過程,以及 EGL contexts 的管理,這裏使用 SurfaceView。示例 OpenGL 應用程序代碼(完整代碼可以在 GitHub 獲取)如下:

package com.cloudgame.opengldemo;

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.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.opengl.GLDebugHelper;
import android.opengl.GLU;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class BasicGLActivity extends Activity {
    public static final String COLOR_OPTION_EXTRA = "COLORFUL";
    private boolean doColorful = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent starter = getIntent();
        doColorful = starter.getBooleanExtra(COLOR_OPTION_EXTRA, false);

        mAndroidSurface = new BasicGLSurfaceView(this);

        setContentView(mAndroidSurface);
    }

    private class BasicGLSurfaceView extends SurfaceView implements
            SurfaceHolder.Callback {
        SurfaceHolder mAndroidHolder;

         BasicGLSurfaceView(Context context) {
            super(context);
            mAndroidHolder = getHolder();
            mAndroidHolder.addCallback(this);
            mAndroidHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);

        }

        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
        }

        public void surfaceCreated(SurfaceHolder holder) {
            mGLThread = new BasicGLThread(this);
            
            mGLThread.start();
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
            if (mGLThread != null) {
                mGLThread.requestStop();
            }
        }
    }
    
    BasicGLThread mGLThread;
    private class BasicGLThread extends Thread {
        private static final String DEBUG_TAG = "BasicGLThread";
        SurfaceView sv;
        BasicGLThread(SurfaceView view) {
            sv = view;
        }
        
        private boolean mDone = false;
        public void run() {
            try {
                initEGL();
                initGL();
                
                TriangleSmallGLUT triangle = new TriangleSmallGLUT(3);
                mGL.glMatrixMode(GL10.GL_MODELVIEW);
                mGL.glLoadIdentity();
                GLU.gluLookAt(mGL, 0, 0, 10f, 0, 0, 0, 0, 1, 0f);
                mGL.glColor4f(1f, 0f, 0f, 1f);
                while (!mDone) {
                    mGL.glClear(GL10.GL_COLOR_BUFFER_BIT| GL10.GL_DEPTH_BUFFER_BIT);
                    mGL.glRotatef(1f, 0, 0, 1f);
                    
                    if (doColorful) {
                        triangle.drawColorful(mGL);
                    } else {
                        triangle.draw(mGL);
                    }
                                    
                    mEGL.eglSwapBuffers(mGLDisplay, mGLSurface);
                }
            } catch (Exception e) {
                Log.e(DEBUG_TAG, "GL Failure", e);
            } finally  {
                cleanupGL();
            }
        }
        
        public void requestStop() {
            mDone = true;
            try {
                join();
            } catch (InterruptedException e) {
                Log.e(DEBUG_TAG, "failed to stop gl thread", e);
            }
            
            cleanupGL();
        }
        
        private void cleanupGL() {
            mEGL.eglMakeCurrent(mGLDisplay, EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
            mEGL.eglDestroySurface(mGLDisplay, mGLSurface);
            mEGL.eglDestroyContext(mGLDisplay, mGLContext);
            mEGL.eglTerminate(mGLDisplay);

            Log.i(DEBUG_TAG, "GL Cleaned up");
        }
        
        public void initGL( ) {
            int width = sv.getWidth();
            int height = sv.getHeight();
            mGL.glViewport(0, 0, width, height);
            mGL.glMatrixMode(GL10.GL_PROJECTION);
            mGL.glLoadIdentity();
            float aspect = (float) width/height;
            GLU.gluPerspective(mGL, 45.0f, aspect, 1.0f, 30.0f);
             mGL.glClearColor(0.5f,0.5f,0.5f,1);
             
             // the only way to draw primitives with OpenGL ES
             mGL.glEnableClientState(GL10.GL_VERTEX_ARRAY);

            Log.i(DEBUG_TAG, "GL initialized");
        }
        
        public void initEGL() throws Exception {
            mEGL = (EGL10) GLDebugHelper.wrap(EGLContext.getEGL(),
                    GLDebugHelper.CONFIG_CHECK_GL_ERROR
                            | GLDebugHelper.CONFIG_CHECK_THREAD,  null);
            
            if (mEGL == null) {
                throw new Exception("Couldn't get EGL");
            }
            
            mGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            
            if (mGLDisplay == null) {
                throw new Exception("Couldn't get display for GL");
            }

            int[] curGLVersion = new int[2];
            mEGL.eglInitialize(mGLDisplay, curGLVersion);

            Log.i(DEBUG_TAG, "GL version = " + curGLVersion[0] + "."
                    + curGLVersion[1]);

            EGLConfig[] configs = new EGLConfig[1];
            int[] num_config = new int[1];
            mEGL.eglChooseConfig(mGLDisplay, mConfigSpec, configs, 1,
                    num_config);
            mGLConfig = configs[0];

            mGLSurface = mEGL.eglCreateWindowSurface(mGLDisplay, mGLConfig, sv
                    .getHolder(), null);
            
            if (mGLSurface == null) {
                throw new Exception("Couldn't create new surface");
            }

            mGLContext = mEGL.eglCreateContext(mGLDisplay, mGLConfig,
                    EGL10.EGL_NO_CONTEXT, null);
            
            if (mGLContext == null) {
                throw new Exception("Couldn't create new context");
            }


            if (!mEGL.eglMakeCurrent(mGLDisplay, mGLSurface, mGLSurface, mGLContext)) {
                throw new Exception("Failed to eglMakeCurrent");
            }
                    
            mGL = (GL10) GLDebugHelper.wrap(mGLContext.getGL(),
                    GLDebugHelper.CONFIG_CHECK_GL_ERROR
                            | GLDebugHelper.CONFIG_CHECK_THREAD
                            | GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES, null);
            
            if (mGL == null) {
                throw new Exception("Failed to get GL");
            }
                
        }
        
        // main OpenGL variables
        GL10 mGL;
        EGL10 mEGL;
        EGLDisplay mGLDisplay;
        EGLConfig mGLConfig;
        EGLSurface mGLSurface;
        EGLContext mGLContext;
        int[] mConfigSpec = { EGL10.EGL_RED_SIZE, 5, 
                EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5, 
                EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
    }
    
    @Override
    protected void onResume() {
	super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    SurfaceView mAndroidSurface;
}

SurfaceViewSurfaceHolder 爲 OpenGL 的渲染提供畫布,即 Surface,它決定圖像被實際渲染到什麼地方。在上面的代碼中,Activity 的整個 layout 中就只有一個 SurfaceView,它被用於獲取 SurfaceHolder

上面的代碼中,SurfaceView 的 Surface 創建好之後,起了一個單獨的線程,這個線程用於處理在該 Surface 上渲染的所有事情。

在能夠使用 OpenGL 渲染之前,首先需要爲渲染做環境上的準備,即創建 EGL context,並啓用它。這通過如下的方法完成:

        public void initEGL() throws Exception {
            mEGL = (EGL10) GLDebugHelper.wrap(EGLContext.getEGL(),
                    GLDebugHelper.CONFIG_CHECK_GL_ERROR
                            | GLDebugHelper.CONFIG_CHECK_THREAD,  null);
            
            if (mEGL == null) {
                throw new Exception("Couldn't get EGL");
            }
            
            mGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            
            if (mGLDisplay == null) {
                throw new Exception("Couldn't get display for GL");
            }

            int[] curGLVersion = new int[2];
            mEGL.eglInitialize(mGLDisplay, curGLVersion);

            Log.i(DEBUG_TAG, "GL version = " + curGLVersion[0] + "."
                    + curGLVersion[1]);

            EGLConfig[] configs = new EGLConfig[1];
            int[] num_config = new int[1];
            mEGL.eglChooseConfig(mGLDisplay, mConfigSpec, configs, 1,
                    num_config);
            mGLConfig = configs[0];

            mGLSurface = mEGL.eglCreateWindowSurface(mGLDisplay, mGLConfig, sv
                    .getHolder(), null);
            
            if (mGLSurface == null) {
                throw new Exception("Couldn't create new surface");
            }

            mGLContext = mEGL.eglCreateContext(mGLDisplay, mGLConfig,
                    EGL10.EGL_NO_CONTEXT, null);
            
            if (mGLContext == null) {
                throw new Exception("Couldn't create new context");
            }


            if (!mEGL.eglMakeCurrent(mGLDisplay, mGLSurface, mGLSurface, mGLContext)) {
                throw new Exception("Failed to eglMakeCurrent");
            }
                    
            mGL = (GL10) GLDebugHelper.wrap(mGLContext.getGL(),
                    GLDebugHelper.CONFIG_CHECK_GL_ERROR
                            | GLDebugHelper.CONFIG_CHECK_THREAD
                            | GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES, null);
            
            if (mGL == null) {
                throw new Exception("Failed to get GL");
            }
                
        }
        
        // main OpenGL variables
        GL10 mGL;
        EGL10 mEGL;
        EGLDisplay mGLDisplay;
        EGLConfig mGLConfig;
        EGLSurface mGLSurface;
        EGLContext mGLContext;
        int[] mConfigSpec = { EGL10.EGL_RED_SIZE, 5, 
                EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5, 
                EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };

這個過程如下:

  1. 獲得 EGLDisplay 對象。
  2. 初始化 EGLDisplay 對象。
  3. 選擇 EGLConfig
  4. 基於 SurfaceHolder 創建 Windows Surface。
  5. 創建 EGL context。
  6. 啓用前面創建的 EGL context。

隨後就可以使用 OpenGL 做渲染了。

每次一個場景渲染完成,都需要通過交換緩衝區來顯示渲染結果:

                    mEGL.eglSwapBuffers(mGLDisplay, mGLSurface);

整個渲染過程結束之後,還需要銷燬前面創建的 EGL context:

        private void cleanupGL() {
            mEGL.eglMakeCurrent(mGLDisplay, EGL10.EGL_NO_SURFACE,
                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
            mEGL.eglDestroySurface(mGLDisplay, mGLSurface);
            mEGL.eglDestroyContext(mGLDisplay, mGLContext);
            mEGL.eglTerminate(mGLDisplay);
        }

而 framework 的 GLSurfaceView 類提供了一些輔助類來管理 EGL contexts,線程間通信,以及與 Activity 生命週期的交互,僅此而已。大多數時候,GLSurfaceView 可以簡化我們對 OpenGL ES 的使用。

Done.

Android OpenGL 圖形系統分析系列文章

在 Android 中使用 OpenGL Android 圖形驅動初始化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章