如我们在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
| ...
public class MyActivity extends Activity implements SurfaceHolder.Callback, ... { ... @Override protected void onCreate(Bundle savedInstanceState) { ... SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView); sv.getHolder().addCallback(this); ... } @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); 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) { ... 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]; if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { mEGLDisplay = null; throw new RuntimeException("unable to initialize EGL14"); }
if ((flags & FLAG_TRY_GLES3) != 0) { EGLConfig config = getConfig(flags, 3); if (config != null) { int[] attrib3_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE }; EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attrib3_list, 0);
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { mEGLConfig = config; mEGLContext = context; mGlVersion = 3; } } } if (mEGLContext == EGL14.EGL_NO_CONTEXT) { 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 }; 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,实现各种酷炫视频特效