前言
這篇文章就帶着大家簡單過一下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通用操作的工具類。
首次循環,邏輯會走到這個位置:
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還是大有裨益的。
本文同步釋出于簡書、CSDN。
End!