在 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 获取)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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,并启用它。这通过如下的方法完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 做渲染了。

每次一个场景渲染完成,都需要通过交换缓冲区来显示渲染结果:

1
mEGL.eglSwapBuffers(mGLDisplay, mGLSurface);

整个渲染过程结束之后,还需要销毁前面创建的 EGL context:

1
2
3
4
5
6
7
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 图形驱动初始化
EGL Context 创建
Android 图形系统之图形缓冲区分配
Android 图形系统之gralloc

坚持原创技术分享,您的支持将鼓励我继续创作!