原始碼解析:Android原始碼GLSurfaceView原始碼解析
前言
這篇文章就帶著大家簡單過一下Android的GLSurfaceView/">SurfaceView
原始碼的一些主要的處理流程。
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還是大有裨益的。
本文同步釋出於簡書、ofollow,noindex">CSDN 。
End!