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;
}
SurfaceView
的 SurfaceHolder
爲 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 };
這個過程如下:
- 獲得
EGLDisplay
對象。 - 初始化
EGLDisplay
對象。 - 選擇
EGLConfig
。 - 基於
SurfaceHolder
創建 Windows Surface。 - 創建 EGL context。
- 啓用前面創建的 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.