天天看點

源碼解析:Android源碼GLSurfaceView源碼解析前言GLSurfaceView怎麼用源碼分析結語

前言

這篇文章就帶着大家簡單過一下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!