源碼解析:Android源碼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被創(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線程,跟進到GLThreadrun()方法。
我們發(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;
}

可以看看DefaultWindowSurfaceFactorycreateWindowSurface()是怎么創(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_DIRTYRENDERMODE_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()最終會回調到EglHelperfinish()函數(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。
還有一個主要是提供waitnotifyAll的功能,進行邏輯阻塞等待喚醒。

外部調用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還是大有裨益的。

本文同步發(fā)布于簡書、CSDN。

End!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容