OpenGL升级打怪之 GLSurfaceView源码分析
一、背景
Android对OpenGL这块封装是非常好的,也是非常隐蔽的,一般使用者直接使用GLSurfaceView即可达到需求。最近项目中将很多功能下层到c++层,这样必须对OpenGL 底层逻辑有所了解。Android虽然提供OpenGL 各个版本的So库,但是并没有对底层api做封装,所以如果是自己想用C++写OpenGL,最好的方式学习Android源码。
二、GLSurfaceView如何使用
在分析GLSurfaceView源码之前我们非常有必要介绍一下GLSurfaceView的使用方法:
surfaceView = findViewById(R.id.triangle_api_surfaceView)
surfaceView.setEGLContextClientVersion(3)
surfaceView.setRenderer(object : GLSurfaceView.Renderer {
/**
* Called when the surface is created or recreated.
*
*
* Called when the rendering thread
* starts and whenever the EGL context is lost. The EGL context will typically
* be lost when the Android device awakes after going to sleep.
*
*
* Since this method is called at the beginning of rendering, as well as
* every time the EGL context is lost, this method is a convenient place to put
* code to create resources that need to be created when the rendering
* starts, and that need to be recreated when the EGL context is lost.
* Textures are an example of a resource that you might want to create
* here.
*
*
* Note that when the EGL context is lost, all OpenGL resources associated
* with that context will be automatically deleted. You do not need to call
* the corresponding "glDelete" methods such as glDeleteTextures to
* manually delete these lost resources.
*
*
* @param gl the GL interface. Use `instanceof` to
* test if the interface supports GL11 or higher interfaces.
* @param config the EGLConfig of the created surface. Can be used
* to create matching pbuffers.
*/
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
TODO ("surface被创建后需要做的处理,包括顶点、纹理数据的处理")
}
/**
* Called when the surface changed size.
*
*
* Called after the surface is created and whenever
* the OpenGL ES surface size changes.
*
*
* Typically you will set your viewport here. If your camera
* is fixed then you could also set your projection matrix here:
* <pre class="prettyprint">
* void onSurfaceChanged(GL10 gl, int width, int height) {
* gl.glViewport(0, 0, width, height);
* // for a fixed camera, set the projection too
* float ratio = (float) width / height;
* gl.glMatrixMode(GL10.GL_PROJECTION);
* gl.glLoadIdentity();
* gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
* }
</pre> *
* @param gl the GL interface. Use `instanceof` to
* test if the interface supports GL11 or higher interfaces.
* @param width
* @param height
*/
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
TODO ("渲染窗口大小发生改变的处理,在视频播放是可以用来调整分辨率变化的视图窗口,或者3D动画中坐标系转换等")
}
/**
* Called to draw the current frame.
*
*
* This method is responsible for drawing the current frame.
*
*
* The implementation of this method typically looks like this:
* <pre class="prettyprint">
* void onDrawFrame(GL10 gl) {
* gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
* //... other gl calls to render the scene ...
* }
</pre> *
* @param gl the GL interface. Use `instanceof` to
* test if the interface supports GL11 or higher interfaces.
*/
override fun onDrawFrame(gl: GL10?) {
TODO("加载顶点、纹理数据并执行渲染工作")
}
})
surfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
可以看到使用GLSurfaceView非常简单,只要APP开发者实现以下三个方法:
- onSurfaceCreated(gl: GL10?, config: EGLConfig?)
- onSurfaceChanged(gl: GL10?, width: Int, height: Int)
- onDrawFrame(gl: GL10?)
这三个方法注释分别其作用,这里就不多赘述。下篇文章会写如何使用GLSurfaceView渲染。
这里需要注意的是:这三个方法都是在GL线程,OpenGL内部渲染线程。 OpenGL的核心逻辑都在GL线程中。
三、源码分析
入口方法
我们先从setRenderer入手
它主要做了两件事分别是:
1、检查环境和变量同步配置:
//检测环境
checkRenderThreadState();
//同步配置项,如果没有设置取默认项(懒加载模式)
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
checkRenderThreadState()
检查了GLThread是否存在,存在则会抛异常,也就是不能在同一个GLSurfaceView
多次调用setRenderer(Renderer renderer)
方法,会crash。则就限制了一个渲染业务只能在一个GLThread中,如果需要在同一个线程中做个渲染业务,则需要学习更高级的功能比如FBO,多目标渲染等。mEGLConfigChooser
、mEGLContextFactory
、mEGLWindowSurfaceFactory
是用户在setRenderer
之前,可以调用相关方法来进行EGL设置,如果没有设置则采用默认实现。
mEGLConfigChooser
用于指定OpenGL颜色、深度、模版等设置。
mEGLContextFactory
用于提供EGLContext创建和销毁的处理。
mEGLWindowSurfaceFactory
用于提供EGLSurface创建和销毁的处理。
2、启动一个GL线程:
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
这里就是鼎鼎大名的GL线程。主要是用于和OpenGL API环境的交互以及渲染的上下文切换和异常场景的处理。入参mThisWeakRef
是一个弱引用,指向了GLSurfaceView
本身。
GLThread线程 --- OpenGL的核心逻辑
我们先看下GLThread的实现,先看下run方法:
public void run() {
setName("GLThread " + getId());
if (LOG_THREADS) {
Log.i("GLThread", "starting tid=" + getId());
}
try {
guardedRun();
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
sGLThreadManager.threadExiting(this);
}
}
继续追踪代码到guardedRun()
,这个方法很长,是一个死循环,我们这里就把关键的几个方法列出来,重点说明下这些方法是做什么和其调用顺序
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
mWantRenderNotification = false;
try {
...
while (true) {
synchronized (sGLThreadManager) {
while (true) {
//用于暂停、推出等状态恢复
...
if (readyToDraw()) {
...
//创建EGLContext上下文
mEglHelper.start();
...
}
...
}
...
//创建EGLSurface,本质是申请一块内存
if (mEglHelper.createSurface()) {
...
}
...
//获取GL对象,包装OpenGL API环境,这里使用GL10
gl = (GL10) mEglHelper.createGL();
...
//回调外部Renderer对象的`onSurfaceCreated()`方法
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
if (sizeChanged) {
...
//回调外部Renderer对象的`onSurfaceChanged()`方法
view.mRenderer.onSurfaceChanged(gl, w, h);
...
}
...
//回调外部Renderer对象的`onDrawFrame()`方法
view.mRenderer.onDrawFrame(gl);
...
//Egl交互内存,opengl使用的双内存缓冲,一个进行显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示
int swapError = mEglHelper.swap();
...
}
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
在方法开头创建了一个EglHelper,EglHelper是一个封装了一些EGL通用操作的工具类。
1、 先看下readyToDraw()
方法:
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
mPaused
用于设置是否已暂停。mHasSurface
用于标记是否Surface是否已经创建,这里的Surface区别于EGLSurface,后面我会深入挖掘两者的差异。mSurfaceIsBad
用于标记EGLSurface是否可用,mWidth
和mHeight
则是Surface的宽和高。mRequestRender
标记用户是否请求刷新。mRenderMode
是指刷新模式,GLSurfaceView有两种刷新模式:
/**
* The renderer only renders
* when the surface is created, or when {@link #requestRender} is called.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
* @see #requestRender()
*/
public final static int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
*/
public final static int RENDERMODE_CONTINUOUSLY = 1;
RENDERMODE_WHEN_DIRTY
字面意思理解就是当画面脏模式,这种模式只有在Surface创建和当Renderer调用时才会渲染
RENDERMODE_CONTINUOUSLY
字面理解是持续模式,这种模式会一直持续不断渲染
回到readyToDraw()
,当不在暂停状态,切换Surface和EGLSurface都是正常状态时,既可以渲染。
2、那接下来就会执行mEglHelper.start()
看下其内部代码:
public void start() {
if (LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
EGLContext是用来链接渲染和视图窗口的上下文,上述代码的目的就是生成mEglContext
。我们可以看下mEGLContextFactory
的createContext()
实现, 如果外部没有设置mEGLContextFactory
,则其会使用默认DefaultContextFactory
,我们可以看下DefaultContextFactory
的实现
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);
}
最终调到egl
的eglCreateContext()
方法,这是EGL10的的API,如果继续追代码,发现最终会调到Native的API。后面有机会搞份Android系统源码可以看下Framework层实现。
3、 接着看下mEglHelper.createSurface()
代码
public boolean createSurface() {
if (LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
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");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
createSurface()主要做两件事:
a) 生成EGLSurface
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
同理,如果没有设置mEGLWindowSurfaceFactory
,默认使用DefaultWindowSurfaceFactory
看下其实现,调用到eglCreateWindowSurface()
方法
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) {
// This exception indicates that the surface flinger surface
// is not valid. This can happen if the surface flinger surface has
// been torn down, but the application has not yet been
// notified via SurfaceHolder.Callback.surfaceDestroyed.
// In theory the application should be notified first,
// but in practice sometimes it is not. See b/4588890
Log.e(TAG, "eglCreateWindowSurface", e);
}
return result;
}
b) 将EGLSurface和前面生成的EGLContext进行绑定
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
这一步执行完后,所有的渲染前环境准备已经完毕。接下来主角Renderer开始干活了。
接下来是Renderer三步走:
//回调外部Renderer对象的`onSurfaceCreated()`方法
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
...
if (sizeChanged) {
...
//回调外部Renderer对象的`onSurfaceChanged()`方法
view.mRenderer.onSurfaceChanged(gl, w, h);
...
}
...
//回调外部Renderer对象的`onDrawFrame()`方法
view.mRenderer.onDrawFrame(gl);
执行到这一步其实渲染数据也准备好了,要想把Renderer的数据显示出来,必须调用mEglHelper.swap()
;
4、看下mEglHelper.swap()
的代码
SurfaceView使用双内存缓冲机制,内部有两个Frambuffer,一个进行前台显示,另一个则后台进行绘制,绘制OK后,交互内存进行显示。eglSwapBuffers
的目的是渲染好的FrameBuffer和前台的Framebuffer交互后显示出来
/**
* Display the current render surface.
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
总结一下:
- 所有的渲染逻辑必须在同一个线程,EGLContext、EGLSurface是和线程绑定。
- Render执行渲染必须在EGLContext和EGLSurface生成并绑定后才能进行。
- Render渲染后的数据要想在视图窗口显示,必须调用
eglSwapBuffers
交换Framebuffer
GLThread如何暂停和恢复
1、GLThread如何实现暂停
在GLThread的onPause()
方法中,会将mRequestPaused = true
并通知所有线程。
mRequestPaused = true;
sGLThreadManager.notifyAll();
我们看下mRequestPaused
还有哪里用到?
在GLThread的guardedRun()
方法中可以看到mRequestPaused
被使用,当mPaused != mRequestPaused
会将局部变量pausing赋值。我个人理解将这mRequestPaused赋值给局部变量,是因为这是一个死循环线程,赋值完可以释放mRequestPaused变量,而且暂停时需要销毁EGLContext和EGLSurface耗时。
boolean pausing = false;
if (mPaused != mRequestPaused) {
pausing = mRequestPaused;
mPaused = mRequestPaused;
sGLThreadManager.notifyAll();
if (LOG_PAUSE_RESUME) {
Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
}
}
当pausing = true
时,会release EGLSurface和EGLContext, 释放后mHaveEglSurface
和mHaveEglContext
会被置false, 而此时readyToDraw()
一直返回false, 代码会在最里面的while循环里面运行无法跳到外面的循环,处于等待的过程中
// When pausing, release the EGL surface:
if (pausing && mHaveEglSurface) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
}
stopEglSurfaceLocked();
}
// When pausing, optionally release the EGL Context:
if (pausing && mHaveEglContext) {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view == null ? false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause) {
stopEglContextLocked();
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
}
}
}
2、GLThread如何进行恢复
在执行onResume()
会将mRequestPaused
置成false, mRequestRender
置成true,此时readyToDraw()
返回true,当mEglHelper.start()
返回true后就可以跳出最里面的循环。此时就可以正常执行渲染逻辑。
GLThread如何退出
当时看这个代码时在想一个死循环,如何才能停止呢?看下代码,发现外部并没有直接停止线程的方法,刚开始以为是surfaceDestroyed()
被调用时线程就可以停止了,最后测试发现当surfaceDestroyed()
执行时,GLThread线程有可能还在渲染,此时Surface已经销毁了,这个时候执行到egl.swapBuffers()
时则报0x300D的错误码,也就是EGL_BAD_SURFACE,此时我看到源码中有这段注释,似乎我明白了, 也就是当执行到surfaceDestroyed时,此时Surface已经销毁了,而EGLSurface还存在,并没有来得及销毁。
switch (swapError) {
....
default:
// Other errors typically mean that the current surface is bad,
// probably because the SurfaceView surface has been destroyed,
// but we haven't been notified yet.
// Log the error to help developers understand why rendering stopped.
EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
synchronized(sGLThreadManager) {
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
break;
}
那真正停止线程的逻辑在哪呢?
从上述逻辑分析可知,停止线程的逻辑必须在Surface销毁之前通知GLThread线程,进行销毁处理,不然会出现异常。我搜索了很久,终于让我发现一段代码和GLThread线程之间的关系。我们先看下GLThread中可以停止线程的逻辑只有mShouldExit
这个变量
while (true) {
synchronized (sGLThreadManager) {
while (true) {
if (mShouldExit) {
return;
}
...
}
}
}
追踪一下代码发现mShouldExit
在requestExitAndWait()
方法中,
public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized(sGLThreadManager) {
mShouldExit = true;
sGLThreadManager.notifyAll();
while (! mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
继续追踪代码,终于真相大白了,GLThread线程时在onDetachedFromWindow()
方法中被中止的。
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
结语:
- 从GLSurfaceView源码分析了中学习了GLThread的作用和原理,也模仿GLThread自己在C++调用EGL和OpenGL ES API实现了一遍。
- 从代码分析中发现了自己的不足,比如SurfaceView双缓冲机制的具体细节,Surface和EGLSurface在Framework层的关系,接下来有时间会继续深入挖掘底层的实现。