前言
这篇文章就带着大家简单过一下Android的GLSurfaceView
源码的一些主要的处理流程。
GLSurfaceView怎么用
在开始分析源码前,非常有必要先看看GLSurfaceView的基本使用方法:
mGLView= (GLSurfaceView) findViewById(R.id.gl_view);
mGLView.setEGLContextClientVersion(2);
//在setRenderer之前,可以调用以下方法来进行EGL设置
//mGLView.setEGLConfigChooser(); //颜色、深度、模板等等设置
//mGLView.setEGLWindowSurfaceFactory(); //窗口设置
//mGLView.setEGLContextFactory(); //EGLContext设置
//设置渲染器,渲染主要就是由渲染器来决定
mGLView.setRenderer(new GLSurfaceView.Renderer(){
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//todo surface被创建后需要做的处理
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//todo 渲染窗口大小发生改变的处理
}
@Override
public void onDrawFrame(GL10 gl) {
//todo 执行渲染工作
}
});
/*渲染方式,RENDERMODE_WHEN_DIRTY表示被动渲染,RENDERMODE_CONTINUOUSLY表示持续渲染*/
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
源码分析
入口方法
我们先从setRenderer(Renderer renderer)
这个入口开始。
它主要做了两件事:
1、检查环境和变量同步配置
//检测环境
checkRenderThreadState();
//同步配置项,如果没有设置取默认项(懒加载模式)
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
函数checkRenderThreadState()
检查了mRenderer是否已经存在,存在则抛出异常;换句话说,我们不能在同一个GLSurfaceView
调用多次setRenderer(Renderer renderer)
,会挂!
mEGLConfigChooser
、mEGLContextFactory
、mEGLWindowSurfaceFactory
是用户在setRenderer之前,可以调用相关方法来进行EGL设置,如果没有设置则采用默认实现。
mEGLConfigChooser
主要是指定了OpenGL ES一些颜色深度、缓存区深度的一些设定。
mEGLContextFactory
主要是提供了创建和销毁EGL Context上下文的一些处理。
mEGLWindowSurfaceFactory
主要是提供了创建和销毁EGLSurface的一些处理。
2、启动一个GL线程
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
GLThread
就是我们一直所说的GL线程,主要是用于与OpenGL ES环境进行交互的线程。
入参mThisWeakRef
是一个弱引用,指向了GLSurfaceView
本身。
GL线程
接下来我们需要分析一下GLThread
这个GL线程,跟进到GLThread
的run()
方法。
我们发现run()
里面有一个方法guardedRun()
,这个也就是GL线程的主要逻辑函数,由两个while(true)
循环组成。
public void run() {
try {
//最最最重要的逻辑
guardedRun();
}
catch (InterruptedException e) {}
finally {}
}
转进去查看guardedRun()
的代码。
我们先来查看初始化创建相关的代码。
在代码开头创建了一个EglHelper
,EglHelper
是一个封装了一些EGL通用操作的工具类。
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
首次循环,逻辑会走到这个位置:
if (! mHaveEglContext) {
if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else {
try {
//进行OpenGL ES环境初始化
mEglHelper.start();
} catch (RuntimeException t) {}
mHaveEglContext = true;
createEglContext = true;
}
}
流程很明显,将会调用EglHelper.start()
进行OpenGL ES环境的初始化。
然后将标示量createEglContext
设置为true。
查看一下EglHelper.start()
的实现:
public void start() {
//获取一个EGL实例
mEgl = (EGL10) EGLContext.getEGL();
//获取显示设备
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//检测EglDisplay是否正常
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
//初始化EGL的内部数据结构,返回EGL实现的主版本号和次版本号。
int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
//获取GLSurfaceView的引用
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view == null) {
mEglConfig = null;
mEglContext = null;
} else {
//看到这里,大家还记得前面setEGLConfigChooser()、setEGLContextFactory()这两个方法么
//就是让我们自己配置EGL参数或者自己创建EglContext的方法
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
mEglSurface = null;
}
如果我们没有调用setEGLContextFactory()
这个方法(除非特殊需求,一般来说也没有用到),那么GLSurfaceView会默认取DefaultContextFactory()
来代替。
在DefaultContextFactory
中有一个创建EglContext
的方法:
//最简单的创建EGLContext的方式,如果需要用到多线程共享一个OpenGL ES环境的话,需要自己实现这个方法处理。
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
EGL10.EGL_NONE };
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
mEGLContextClientVersion != 0 ? attrib_list : null);
}
OpenGL ES环境的初始化就完成了,但是我们知道应该还有一个EGL的Surface需要创建。
后续的代码就是处理 EGL的Surface创建 的业务:
//我们要记得这里将createEglSurface和createGlInterface都设置为true了
if (mHaveEglContext && !mHaveEglSurface) {
mHaveEglSurface = true;
createEglSurface = true;
createGlInterface = true;
sizeChanged = true;
}
//由上面代码可知 mHaveEglSurface == true
if (mHaveEglSurface) {
//mSizeChanged是在SurfaceView回调surfaceChanged会设置会true
//首次初始化mSizeChanged默认为true
if (mSizeChanged) {
sizeChanged = true;
//更新宽高
w = mWidth;
h = mHeight;
mWantRenderNotification = true;
createEglSurface = true;
mSizeChanged = false;
}
}
我们需要关注这个变量createEglSurface
,在下面有一段代码:
if (createEglSurface) {
//这里主要逻辑就是 mEglHelper.createSurface()
if (mEglHelper.createSurface()) {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
}
} else {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
mSurfaceIsBad = true;
}
continue;
}
createEglSurface = false;
}
代码又借助了EglHelper
类,调用了其的createSurface()
方法:
public boolean createSurface() {
//检测环境,由前面可知,这个都是有值的
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
//如果已经创建过EglSurface,这里先销毁掉呗
destroySurfaceImp();
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//调用mEGLWindowSurfaceFactory工程创建一个EglSurface
//如果我们没有指定,会默认触发DefaultWindowSurfaceFactory的逻辑
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
}
//检测创建是否成功
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
return false;
}
//将EglContext上下文加载到当前线程环境
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
return false;
}
return true;
}
可以看看DefaultWindowSurfaceFactory
的createWindowSurface()
是怎么创建EglSurface的:
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
EGLConfig config, Object nativeWindow) {
EGLSurface result = null;
try {
result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
} catch (IllegalArgumentException e) {
}
return result;
}
也就是说我们可以通过自己实现createWindowSurface()
方法实现我们需要的EGLSurface创建方式。
还有一个destroySurfaceImp()
,也顺便瞧一瞧呗:
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
//清理当前线程的EglContext上下文环境
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//我们需要销毁前面创建出来的EGLSurface
view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
DefaultWindowSurfaceFactory
的逻辑看完了。
回归上面创建流程,紧接着可以看到createGlInterface
这个标示量的代码逻辑块:
if (createGlInterface) {
gl = (GL10) mEglHelper.createGL();
createGlInterface = false;
}
继续跟进mEglHelper.createGL()
实现:
GL createGL() {
//获得OpenGL ES的编程接口
GL gl = mEglContext.getGL();
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//如果我们没调用setGLWrapper(GLWrapper glWrapper)这个接口,view.mGLWrapper == null
if (view.mGLWrapper != null) {
gl = view.mGLWrapper.wrap(gl);
}
return gl;
}
然后可以关注下createEglContext
这个标示量的代码块:
if (createEglContext) {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
} finally {
}
}
createEglContext = false;
}
这个回调到上层自定义的Renderer的onSurfaceCreated()
,说明GLSurfaceView
的GL环境已经准备完毕了。
顺便看看size变化的通知是怎么实现的:
if (sizeChanged) { //在前面我们可以指定当surfaceview回调宽高变更时,sizeChanged会为true
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onSurfaceChanged(gl, w, h);
} finally {}
}
sizeChanged = false;
}
这个回调到上层自定义的Renderer的onSurfaceChanged()
,标识GLSurfaceView
的宽高已经发生改变。
上面整个流程已经将GLSurfaceView的EGL环境准备完毕了,接下来我们看看是怎么触发渲染的。
渲染业务
还是在guardedRun()
这个函数中,有这么一段代码:
{
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onDrawFrame(gl);
} finally {}
}
}
int swapError = mEglHelper.swap();
如果逻辑能走到这里,就能触发到上层Renderer的onDrawFrame()
实现,也就是说就能开始OpenGL ES的绘制工作。
我们还记得开头说过,GLSurfaceView
有两种刷新模式:RENDERMODE_WHEN_DIRTY
和 RENDERMODE_CONTINUOUSLY
。
那么下面我们就来分析一下这两种是怎么进行工作的。
首先我们先看看RENDERMODE_CONTINUOUSLY
循环刷新模式。
先看看设置入口,嗯没啥重要逻辑。
public void setRenderMode(int renderMode) {
synchronized(sGLThreadManager) {
mRenderMode = renderMode;
}
}
搜索一下代码,我们发现mRenderMode在这个readyToDraw()
函数使用到:
private boolean readyToDraw() {
//需要非暂停状态、EGLSurface已经创建、并宽高不为0
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
//这个就需要仔细看,如果我们设置RENDERMODE_CONTINUOUSLY的话 这条件是成立的!
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
接着我们看看哪里用到这个readyToDraw()
函数,在guardedRun()
中:
while (true) {
if (readyToDraw()) {
if (! mHaveEglContext) {
//......
}
if (mHaveEglContext && !mHaveEglSurface) {
//......
}
if (mHaveEglSurface) {
//......
//触发一次后重置为false。
mRequestRender = false;
//......
break;
}
}
//......
sGLThreadManager.wait();
}
//......
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onDrawFrame(gl);
} finally {}
}
可以得知,如果readyToDraw()
返回true的话,逻辑会走到break退出当前while循环。
自然而然会调用到我们上层OpenGL ES的渲染实现。
如果mRenderMode == RENDERMODE_CONTINUOUSLY
的话,那么readyToDraw()
在成功初始化后就是true。
也就是在子线程运行时间里会不断while循环调用view.mRenderer.onDrawFrame()
这个回调。
那么当mRenderMode
的值为RENDERMODE_WHEN_DIRTY
时,由于mRequestRender
默认为false。
那么readyToDraw()
返回了false,然后逻辑走到了sGLThreadManager.wait()
导致线程阻塞。
很明显,我们需要看看mRequestRender这个变量被赋值的位置,也就是函数requestRender()
中:
public void requestRender() {
synchronized(sGLThreadManager) {
//重点这里,将mRequestRender设置为true,也就是说接下来readyToDraw()返回true
mRequestRender = true;
//通知guardedRun()里面的sGLThreadManager.wait()解除阻塞继续运行
sGLThreadManager.notifyAll();
}
}
可以得出结论,在RenderMode
的值为RENDERMODE_WHEN_DIRTY
时,我们调用一次requestRender()
的话,
相对应的就能触发一次onDrawFrame()
进行OpenGL的渲染。
关于初始化和渲染触发的源码流程分析,就是上面这些了,剩下来的是一些小功能的源码简单解释。
暂停与恢复
一般来说,GLSurfaceView需要跟随Activity或者Fragment的生命周期调用对应的
onPause()
和onResume()
方法,代码最终会调用到GLThead
类的相关方法:
请求暂停相关:
public void onPause() {
synchronized (sGLThreadManager) {
//将要暂停的状态标示量
mRequestPaused = true;
//通知GL线程解除阻塞
sGLThreadManager.notifyAll();
//这里是为了保证onPause()调用完毕后,GL线程已经是Paused状态了
while ((! mExited) && (! mPaused)) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
解除暂停相关:
public void onResume() {
synchronized (sGLThreadManager) {
//解除暂停的状态标示量
mRequestPaused = false;
//顺便请求重绘一次GL
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
//这里是为了保证onResume()调用完毕后,GL线程已经是Resume状态了
while ((! mExited) && mPaused && (!mRenderComplete)) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
我们看看这个状态是怎么工作的,还是在guardedRun()
这个函数里面:
boolean pausing = false;
//如果mPaused与mRequestPaused,肯定是上层修改了mRequestPaused
if (mPaused != mRequestPaused) {
pausing = mRequestPaused;
mPaused = mRequestPaused;
sGLThreadManager.notifyAll();
}
if (pausing && mHaveEglSurface) {
//销毁了EGL Surface,并将mHaveEglSurface设置false
stopEglSurfaceLocked();
}
if (pausing && mHaveEglContext) {
//销毁了EGL Context,并将mHaveEglContext设置false
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view == null ?
false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause) {
stopEglContextLocked();
}
}
顺便看看释放了什么东西:
stopEglSurfaceLocked()
最终会回调到destroySurfaceImp()
函数。
private void destroySurfaceImp() {
//将当前线程跟EGL环境解除绑定
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//调用EGL Surface的销毁逻辑
view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
stopEglContextLocked()
最终会回调到EglHelper
的finish()
函数。
public void finish() {
if (mEglContext != null) {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//调用到Context销毁的工厂方法
view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
}
mEglContext = null;
}
if (mEglDisplay != null) {
//销毁掉EglDisplay
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
接着,我们怎么知道暂停状态,不然逻辑会跑到渲染回调那边,但是会在哪里阻塞住呢?看回到readyToDraw()
:
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
因为mPaused
的值是true,所以这里返回false,由上面可知逻辑将会阻塞住等待唤醒。
关于onResume()
就简单了,就是一次重新的EGL环境初始化过程,这里不做详细分析。
同步与控制
关于GLThreadManager
这个类,其主要提供的是线程同步控制的功能,因为在GLSurfaceView里面存在两个线程,分别是GL线程和调用线程。
所以我们需要引入synchronized
来保证逻辑的一致性,涉及状态量变动例如mShouldExit、mRequestPaused、mRequestRender必须用synchronized
。
还有一个主要是提供wait
和notifyAll
的功能,进行逻辑阻塞等待唤醒。
外部调用GL线程
由于操作OpenGL环境只能通过GL线程,所以GLSurfaceView帮我们提供了queueEvent(Runnable r)
这个方法:
public void queueEvent(Runnable r) {
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
如果我们queueEvent了一个Runnable,也就会在guardedRun()
里面触发到这个逻辑:
while(true) {
//......
if (! mEventQueue.isEmpty()) {
event = mEventQueue.remove(0);
break;
}
//......
}
if (event != null) {
event.run();
event = null;
continue;
}
也就是在GL线程回调了我们传入的Runnable的run方法。
释放销毁
最后我们来简单看一下怎么销毁释放整个GLSurfaceView,我们可以定位到onDetachedFromWindow()
,其会将mShouldExit设置为true。
然后返回guardedRun()
可以看到进行return退出线程逻辑:
if (mShouldExit) {
return;
}
但是我们需要关注到这块逻辑:
finally {
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
这里将EGL相关的Context、Surface这些进行释放,并且退出了GL线程的循环。
结语
GLSurfaceView
不仅帮我们简化了OpenGL ES的使用流程,而且帮我们提供了一个使用EGL的开发范例,了解下源码的流程对于我们开发GL还是大有裨益的。
End!