前言
這篇文章就帶著大家簡單過一下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被創(chuàng)建后需要做的處理
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//todo 渲染窗口大小發(fā)生改變的處理
}
@Override
public void onDrawFrame(GL10 gl) {
//todo 執(zhí)行渲染工作
}
});
/*渲染方式,RENDERMODE_WHEN_DIRTY表示被動渲染,RENDERMODE_CONTINUOUSLY表示持續(xù)渲染*/
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
源碼分析
入口方法
我們先從setRenderer(Renderer renderer)這個入口開始。
它主要做了兩件事:
1、檢查環(huán)境和變量同步配置
//檢測環(huán)境
checkRenderThreadState();
//同步配置項,如果沒有設置取默認項(懶加載模式)
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
函數(shù)checkRenderThreadState()檢查了mRenderer是否已經(jīng)存在,存在則拋出異常;換句話說,我們不能在同一個GLSurfaceView調用多次setRenderer(Renderer renderer),會掛!
mEGLConfigChooser、mEGLContextFactory、mEGLWindowSurfaceFactory是用戶在setRenderer之前,可以調用相關方法來進行EGL設置,如果沒有設置則采用默認實現(xiàn)。
mEGLConfigChooser主要是指定了OpenGL ES一些顏色深度、緩存區(qū)深度的一些設定。
mEGLContextFactory主要是提供了創(chuàng)建和銷毀EGL Context上下文的一些處理。
mEGLWindowSurfaceFactory主要是提供了創(chuàng)建和銷毀EGLSurface的一些處理。
2、啟動一個GL線程
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
GLThread就是我們一直所說的GL線程,主要是用于與OpenGL ES環(huán)境進行交互的線程。
入?yún)?code>mThisWeakRef是一個弱引用,指向了GLSurfaceView本身。
GL線程
接下來我們需要分析一下GLThread這個GL線程,跟進到GLThread的run()方法。
我們發(fā)現(xiàn)run()里面有一個方法guardedRun(),這個也就是GL線程的主要邏輯函數(shù),由兩個while(true)循環(huán)組成。
public void run() {
try {
//最最最重要的邏輯
guardedRun();
}
catch (InterruptedException e) {}
finally {}
}
轉進去查看guardedRun()的代碼。
我們先來查看初始化創(chuàng)建相關的代碼。
在代碼開頭創(chuàng)建了一個EglHelper,EglHelper是一個封裝了一些EGL通用操作的工具類。
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
首次循環(huán),邏輯會走到這個位置:
if (! mHaveEglContext) {
if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else {
try {
//進行OpenGL ES環(huán)境初始化
mEglHelper.start();
} catch (RuntimeException t) {}
mHaveEglContext = true;
createEglContext = true;
}
}
流程很明顯,將會調用EglHelper.start()進行OpenGL ES環(huán)境的初始化。
然后將標示量createEglContext設置為true。
查看一下EglHelper.start()的實現(xiàn):
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的內部數(shù)據(jù)結構,返回EGL實現(xiàn)的主版本號和次版本號。
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參數(shù)或者自己創(chuàng)建EglContext的方法
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
mEglSurface = null;
}
如果我們沒有調用setEGLContextFactory()這個方法(除非特殊需求,一般來說也沒有用到),那么GLSurfaceView會默認取DefaultContextFactory()來代替。
在DefaultContextFactory中有一個創(chuàng)建EglContext的方法:
//最簡單的創(chuàng)建EGLContext的方式,如果需要用到多線程共享一個OpenGL ES環(huán)境的話,需要自己實現(xiàn)這個方法處理。
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環(huán)境的初始化就完成了,但是我們知道應該還有一個EGL的Surface需要創(chuàng)建。
后續(xù)的代碼就是處理 EGL的Surface創(chuàng)建 的業(yè)務:
//我們要記得這里將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() {
//檢測環(huán)境,由前面可知,這個都是有值的
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");
}
//如果已經(jīng)創(chuàng)建過EglSurface,這里先銷毀掉唄
destroySurfaceImp();
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//調用mEGLWindowSurfaceFactory工程創(chuàng)建一個EglSurface
//如果我們沒有指定,會默認觸發(fā)DefaultWindowSurfaceFactory的邏輯
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
}
//檢測創(chuàng)建是否成功
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
return false;
}
//將EglContext上下文加載到當前線程環(huán)境
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
return false;
}
return true;
}
可以看看DefaultWindowSurfaceFactory的createWindowSurface()是怎么創(chuàng)建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;
}
也就是說我們可以通過自己實現(xiàn)createWindowSurface()方法實現(xiàn)我們需要的EGLSurface創(chuàng)建方式。
還有一個destroySurfaceImp(),也順便瞧一瞧唄:
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
//清理當前線程的EglContext上下文環(huán)境
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
//我們需要銷毀前面創(chuàng)建出來的EGLSurface
view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
DefaultWindowSurfaceFactory的邏輯看完了。
回歸上面創(chuàng)建流程,緊接著可以看到createGlInterface這個標示量的代碼邏輯塊:
if (createGlInterface) {
gl = (GL10) mEglHelper.createGL();
createGlInterface = false;
}
繼續(xù)跟進mEglHelper.createGL()實現(xiàn):
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環(huán)境已經(jīng)準備完畢了。
順便看看size變化的通知是怎么實現(xiàn)的:
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的寬高已經(jīng)發(fā)生改變。
上面整個流程已經(jīng)將GLSurfaceView的EGL環(huán)境準備完畢了,接下來我們看看是怎么觸發(fā)渲染的。
渲染業(yè)務
還是在guardedRun()這個函數(shù)中,有這么一段代碼:
{
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onDrawFrame(gl);
} finally {}
}
}
int swapError = mEglHelper.swap();
如果邏輯能走到這里,就能觸發(fā)到上層Renderer的onDrawFrame()實現(xiàn),也就是說就能開始OpenGL ES的繪制工作。
我們還記得開頭說過,GLSurfaceView有兩種刷新模式:RENDERMODE_WHEN_DIRTY 和 RENDERMODE_CONTINUOUSLY。
那么下面我們就來分析一下這兩種是怎么進行工作的。
首先我們先看看RENDERMODE_CONTINUOUSLY循環(huán)刷新模式。
先看看設置入口,嗯沒啥重要邏輯。
public void setRenderMode(int renderMode) {
synchronized(sGLThreadManager) {
mRenderMode = renderMode;
}
}
搜索一下代碼,我們發(fā)現(xiàn)mRenderMode在這個readyToDraw()函數(shù)使用到:
private boolean readyToDraw() {
//需要非暫停狀態(tài)、EGLSurface已經(jīng)創(chuàng)建、并寬高不為0
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
//這個就需要仔細看,如果我們設置RENDERMODE_CONTINUOUSLY的話 這條件是成立的!
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
接著我們看看哪里用到這個readyToDraw()函數(shù),在guardedRun()中:
while (true) {
if (readyToDraw()) {
if (! mHaveEglContext) {
//......
}
if (mHaveEglContext && !mHaveEglSurface) {
//......
}
if (mHaveEglSurface) {
//......
//觸發(fā)一次后重置為false。
mRequestRender = false;
//......
break;
}
}
//......
sGLThreadManager.wait();
}
//......
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
view.mRenderer.onDrawFrame(gl);
} finally {}
}
可以得知,如果readyToDraw()返回true的話,邏輯會走到break退出當前while循環(huán)。
自然而然會調用到我們上層OpenGL ES的渲染實現(xiàn)。
如果mRenderMode == RENDERMODE_CONTINUOUSLY的話,那么readyToDraw()在成功初始化后就是true。
也就是在子線程運行時間里會不斷while循環(huán)調用view.mRenderer.onDrawFrame()這個回調。
那么當mRenderMode的值為RENDERMODE_WHEN_DIRTY時,由于mRequestRender默認為false。
那么readyToDraw()返回了false,然后邏輯走到了sGLThreadManager.wait()導致線程阻塞。
很明顯,我們需要看看mRequestRender這個變量被賦值的位置,也就是函數(shù)requestRender()中:
public void requestRender() {
synchronized(sGLThreadManager) {
//重點這里,將mRequestRender設置為true,也就是說接下來readyToDraw()返回true
mRequestRender = true;
//通知guardedRun()里面的sGLThreadManager.wait()解除阻塞繼續(xù)運行
sGLThreadManager.notifyAll();
}
}
可以得出結論,在RenderMode的值為RENDERMODE_WHEN_DIRTY時,我們調用一次requestRender()的話,
相對應的就能觸發(fā)一次onDrawFrame()進行OpenGL的渲染。
關于初始化和渲染觸發(fā)的源碼流程分析,就是上面這些了,剩下來的是一些小功能的源碼簡單解釋。
暫停與恢復
一般來說,GLSurfaceView需要跟隨Activity或者Fragment的生命周期調用對應的
onPause()和onResume()方法,代碼最終會調用到GLThead類的相關方法:
請求暫停相關:
public void onPause() {
synchronized (sGLThreadManager) {
//將要暫停的狀態(tài)標示量
mRequestPaused = true;
//通知GL線程解除阻塞
sGLThreadManager.notifyAll();
//這里是為了保證onPause()調用完畢后,GL線程已經(jīng)是Paused狀態(tài)了
while ((! mExited) && (! mPaused)) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
解除暫停相關:
public void onResume() {
synchronized (sGLThreadManager) {
//解除暫停的狀態(tài)標示量
mRequestPaused = false;
//順便請求重繪一次GL
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
//這里是為了保證onResume()調用完畢后,GL線程已經(jīng)是Resume狀態(tài)了
while ((! mExited) && mPaused && (!mRenderComplete)) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
我們看看這個狀態(tài)是怎么工作的,還是在guardedRun()這個函數(shù)里面:
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()函數(shù)。
private void destroySurfaceImp() {
//將當前線程跟EGL環(huán)境解除綁定
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()函數(shù)。
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;
}
}
接著,我們怎么知道暫停狀態(tài),不然邏輯會跑到渲染回調那邊,但是會在哪里阻塞住呢?看回到readyToDraw():
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
因為mPaused的值是true,所以這里返回false,由上面可知邏輯將會阻塞住等待喚醒。
關于onResume()就簡單了,就是一次重新的EGL環(huán)境初始化過程,這里不做詳細分析。
同步與控制
關于GLThreadManager這個類,其主要提供的是線程同步控制的功能,因為在GLSurfaceView里面存在兩個線程,分別是GL線程和調用線程。
所以我們需要引入synchronized來保證邏輯的一致性,涉及狀態(tài)量變動例如mShouldExit、mRequestPaused、mRequestRender必須用synchronized。
還有一個主要是提供wait和notifyAll的功能,進行邏輯阻塞等待喚醒。
外部調用GL線程
由于操作OpenGL環(huán)境只能通過GL線程,所以GLSurfaceView幫我們提供了queueEvent(Runnable r)這個方法:
public void queueEvent(Runnable r) {
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
如果我們queueEvent了一個Runnable,也就會在guardedRun()里面觸發(fā)到這個邏輯:
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線程的循環(huán)。
結語
GLSurfaceView不僅幫我們簡化了OpenGL ES的使用流程,而且?guī)臀覀兲峁┝艘粋€使用EGL的開發(fā)范例,了解下源碼的流程對于我們開發(fā)GL還是大有裨益的。
End!