0%

如何开启渲染线程并为其构建EGL环境

如我们在OpenGL离屏渲染一文中所述,实现离屏渲染必须采用SurfaceView+自己创建渲染线程的技术路线。本文我们来了解一下如何为SurfaceView创建一个渲染线程,并在该线程中构建EGL环境。

开启线程

在Android应用程序中创建线程是非常简单的事儿,但在什么时刻创建是比较讲究的。最佳的创建时机我们在OpenGL离屏渲染中也介绍过,即在surfaceCreated()函数被调用的时刻最为合适,因为surfaceCreated()函数被调用时说明SurfaceView中的Surface已经被成功创建,此刻开启线程就可以直接操作该Surface了。

为了surfaceCreated()函数能够被成功调用,首先需要让App的Activty类实现SurfaceHolder.Callback类中的接口,然后在Activity中的onCreate(…)方法中将Activity设置为SurfaceView的回调对象,这样当SurfaceView中的Surface创建好后,就会回调Activity的surfaceCreated()方法,代码如下:

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
...
//实现SurfaceHolder.Callback接口
public class MyActivity extends Activity implements SurfaceHolder.Callback, ... {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//创建SurfaceView对象
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
//将Activity设置为SurfaceView的回调对象
sv.getHolder().addCallback(this);
...
}
//实现surfaceCreated方法
@Override
public void surfaceCreated(SurfaceHolder holder) {
...
//创建线程对象
mRenderThread = new RenderThread(...);
//启动线程
mRenderThread.start();
...
}
}
...

在上面代码的surfaceCreated()方法中,首先创建了一个RenderThread对象,紧接着调用了该对象的start()方法启了一个新线程。

渲染线程的实现

下面我们来看一下渲染线程是如何实现的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static class RenderThread extends Thread {
...
@Override
public void run() {
Looper.prepare();
//用于处理主线程发来的消息
mHandler = new RenderHandler(this);
//构造EGL环境
mEglCore = new EglCore(...);
...
Looper.loop();
}
...
}

通过上面的代码我们可以知道,RenderThread继承自Thread,并且重载了run()方法。在当我们调用RenderThread的start()方法时,它就是开启一个新的线程,并在新的线程中运行RenderThread的run()方法。

在run()方法中,首先创建了RenderHandler对象,该对象用于处理主线程发来的消息;之后创建EglCore对象,在其构造函数中会创建EGL环境;最后执行Looper.loop()等待消息。

构建EGL环境

下面我们就来分析一下,在EglCore的构造函数中是如何建立EGL环境的,代码如下:

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
...
public final class EglCore {
...
public EglCore(EGLContext sharedContext, int flags) {
...
//获得EGLDisplay对象,并加载OpenGLES库
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
//初始化OpenGLES库,并获得OpenGLES的版本
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}

// Try to get a GLES3 context, if requested.
if ((flags & FLAG_TRY_GLES3) != 0) {
//Log.d(TAG, "Trying GLES 3");
EGLConfig config = getConfig(flags, 3);
if (config != null) {
int[] attrib3_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
EGL14.EGL_NONE
};
//构建OpenGLES3.0的EGL上下文
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
attrib3_list, 0);

if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
//Log.d(TAG, "Got GLES 3 config");
mEGLConfig = config;
mEGLContext = context;
mGlVersion = 3;
}
}
}
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
//Log.d(TAG, "Trying GLES 2");
EGLConfig config = getConfig(flags, 2);
if (config == null) {
throw new RuntimeException("Unable to find a suitable EGLConfig");
}
int[] attrib2_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
//构建OpenGLES2.0的EGL上下文
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
attrib2_list, 0);
checkEglError("eglCreateContext");
mEGLConfig = config;
mEGLContext = context;
mGlVersion = 2;
}
}
...
}

在EglCore的构造函数中,首先调用**EGL14.eglGetDisplay(…)方法加载OpenGLES库,之后调用EGL14.eglInitialize(…)**方法初始化OpenGLES库,紧接着根据用户的要求创建OpenGLES上下文。如果用户要求创建OpenGLES3.0上下文且OpenGLES库支持到3.0那么就建立OpenGLES3.0上下文,否则建立OpenGLES2.0上下文。

至此,我们就将渲染线程的EGL环境构建好了。

小结

本篇文章我向你详细介绍了在Android系统下如何自己启动一个渲染线程,如何在渲染线程中构建EGL环境。实际上,其实现与GLSurfaceView的实现是很类似的,我们完全可以参考GLSurfaceView的源码来实现自己的渲染线程。

另外一点,渲染线程的创建时机是比较有讲究的,一般是在SurfaceView中的Surface创建好后再开启渲染线程,这样可以让我们的代码更简洁、丝滑!

参考资料

系统玩转OpenGL+AI,实现各种酷炫视频特效

欢迎关注我的其它发布渠道