Android OpenGL ES - EGL源碼解析以及C++實現

PS
咱們在前面的文章中就說過關於EGL的出現緣由以及其做用

OpenGL 是一個跨平臺的API,而不一樣的操做系統(Windows,Android,IOS)各有本身的屏幕渲染實現。因此OpenGL定義了一箇中間接口層EGL(Embedded Graphics Library)標準,具體實現交給各個操做系統自己java

EGL

簡單來講EGL是一箇中間接口層,是一個規範,因爲OpenGL的跨平臺性,因此說這個規範顯得尤爲重要,無論各個操做系統如何蹦躂,都不能脫離我所定義的規範。c++

EGL的一些基礎知識

  • EGLDisplay

EGL定義的一個抽象的系統顯示類,用於操做設備窗口。緩存

  • EGLConfig

EGL配置,如rgba位數jvm

  • EGLSurface

渲染緩存,一塊內存空間,全部要渲染到屏幕上的圖像數據,都要先緩存在EGLSurface上。ide

  • EGLContext

OpenGL上下文,用於存儲OpenGL的繪製狀態信息、數據。函數

初始化EGL的過程能夠說是對上面幾個信息進行配置的過程oop

OpenGL ES繪圖完整流程

咱們在使用Java GLSurfaceView的時候其實只是自定義了Render,該Render實現了GLsurfaceView.Renderer接口,而後自定義的Render中的3個方法就會獲得回調,Android 系統其實幫我省掉了其中的不少步驟。因此咱們這裏來看一下完整流程
(1). 獲取顯示設備(對應於上面的EGLDisplay)this

/*
 * Get an EGL instance */
 mEgl = (EGL10) EGLContext.getEGL();
 
/*
 * Get to the default display. */
 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

(2). 初始化EGLspa

int[] version = new int[2];
//初始化屏幕
if(!mEgl.eglInitialize(mEglDisplay, version)) {
    throw new RuntimeException("eglInitialize failed");
}

(3). 選擇Config(用EGLConfig配置參數)操作系統

//這段代碼的做用是選擇EGL配置, 便可以本身先設定好一個你但願的EGL配置,好比說RGB三種顏色各佔幾位,你能夠隨便配,而EGL可能不能知足你全部的要求,因而它會返回一些與你的要求最接近的配置供你選擇。
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
 num_config)) {
    throw new IllegalArgumentException("eglChooseConfig#2 failed");
}

(4). 建立EGLContext

//從上一步EGL返回的配置列表中選擇一種配置,用來建立EGL Context。
egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
 mEGLContextClientVersion != 0 ? attrib_list : null);

(5). 獲取EGLSurface

//建立一個窗口Surface,能夠當作屏幕所對應的內存
 egl.eglCreateWindowSurface(display, config, nativeWindow, null)
PS
這裏的nativeWindow是GLSurfaceView的surfaceHolder

(6). 綁定渲染環境到當前線程

/*
 * Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */
 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
    /*
    * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ 
     logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
     return false;
 }

循環繪製

loop:{
    //繪製中....
    //(7).交換緩衝區
    mEglHelper.swap();
}

public int swap() {
    if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
        return mEgl.eglGetError();
    }
    return EGL10.EGL_SUCCESS;
}

Java - GLSurfaceView/GLTextureView

上面咱們介紹了EGL的一些基礎知識,接着咱們來看在GLSurfaceView/GLTextureView中EGL的具體實現,咱們來從源碼上剖析Android系統EGL及GL線程

GLThread

咱們來看一下GLThread,GLThread也是從普通的Thread類繼承而來,理論上就是一個普通的線程,爲何它擁有OpenGL繪圖能力?繼續往下看,裏面最重要的部分就是guardedRun()方法

static class GLThread extends Thread {
    ...
    @Override
    public void run() {
      
        try {
                guardedRun();
         } catch (InterruptedException e) {
                // fall thru and exit normally
         } finally {
                sGLThreadManager.threadExiting(this);
         }
    }
}

讓咱們來看一下guardedRun()方法裏有什麼東西,guardedRun()裏大體作的事情:

private void guardedRun() throws InterruptedException {
    while(true){
        //if ready to draw
        ...
        mEglHelper.start();//對應於上面完整流程中的(1)(2)(3)(4)
        
        ...
        mEglHelper.createSurface()//對應於上面完整流程中的(5)(6)
        
        ...
        回調GLSurfaceView.Renderer的onSurfaceCreated();
        ...
        回調GLSurfaceView.Renderer的onSurfaceChanged();
        ...
        回調GLSurfaceView.Renderer的onDrawFrame();
        
        ...
         mEglHelper.swap();//對應於上面完整流程中的(5)(7)
    }
}

從上面咱們的分析得知GLSurfaceView 中的GLThread就是一個普通的線程,只不過它按照了OpenGL繪圖的完整流程正確地操做了下來,所以它有OpenGL的繪圖能力。那麼,若是咱們本身建立一個線程,也按這樣的操做方法,那咱們也能夠在本身建立的線程裏繪圖嗎?答案是確定的(這不正是EGL的接口意義),下面我會給出EGL在Native C/C++中的實現。

Native - EGL

Android Native環境中並不存在現成的EGL環境,因此咱們在進行OpenGL的NDK開發時就必須本身實現EGL環境,那麼如何實現呢,咱們只須要參照GLSurfaceView中的GLThread的寫法就能實現Native中的EGL

PS
一下的內容可能須要你對C/C++以及NDK 有必定熟悉

第1步實現相似於Java GLSurfaceView中的GLThread的功能

gl_render.h

class GLRender {
    private:
         const char *TAG = "GLRender";
         //OpenGL渲染狀態
         enum STATE {
             NO_SURFACE, //沒有有效的surface
             FRESH_SURFACE, //持有一個爲初始化的新的surface
             RENDERING, //初始化完畢,能夠開始渲染
             SURFACE_DESTROY, //surface銷燬
             STOP //中止繪製
         };
         JNIEnv *m_env = NULL;
         //線程依附的jvm環境
         JavaVM *m_jvm_for_thread = NULL;
         //Surface引用,必需要使用引用,不然沒法在線程中操做
         jobject m_surface_ref = NULL;
         //本地屏幕
         ANativeWindow *m_native_window = NULL;
         //EGL顯示錶面
         EglSurface *m_egl_surface = NULL;
         int m_window_width = 0;
         int m_window_height = 0;
         
         // 繪製代理器
         ImageRender *pImageRender;
         
         //OpenGL渲染狀態
         STATE m_state = NO_SURFACE;
         // 初始化相關的方法
         void InitRenderThread();
         bool InitEGL();
         void InitDspWindow(JNIEnv *env);
         // 建立/銷燬 Surface void CreateSurface();
         void DestroySurface();
         // 渲染方法
         void Render();
         void ReleaseSurface();
         void ReleaseWindow();
         // 渲染線程回調方法
         static void sRenderThread(std::shared_ptr<GLRender> that);
    public:
         GLRender(JNIEnv *env);
         ~GLRender();
         //外部傳入Surface
         void SetSurface(jobject surface);
      
         void Stop();
         void SetBitmapRender(ImageRender *bitmapRender);
        // 釋放資源相關方法
         void ReleaseRender();
         
         ImageRender *GetImageRender();
};

gl_render.cpp

//構造函數
GLRender::GLRender(JNIEnv *env) {
     this->m_env = env;
     //獲取JVM虛擬機,爲建立線程做準備
     env->GetJavaVM(&m_jvm_for_thread);
     InitRenderThread();
}
//析構函數
GLRender::~GLRender() {
    delete m_egl_surface;
}

//初始化渲染線程
void GLRender::InitRenderThread() {
    // 使用智能指針,線程結束時,自動刪除本類指針
     std::shared_ptr<GLRender> that(this);
     std::thread t(sRenderThread, that);
     t.detach();
}

//線程回調函數
void GLRender::sRenderThread(std::shared_ptr<GLRender> that) {
    JNIEnv *env;
     //(1) 將線程附加到虛擬機,並獲取env
     if (that->m_jvm_for_thread->AttachCurrentThread(&env, NULL) != JNI_OK) {
            LOGE(that->TAG, "線程初始化異常");
            return; 
     }
     // (2) 初始化 EGL 
    if (!that->InitEGL()) {
         //解除線程和jvm關聯
         that->m_jvm_for_thread->DetachCurrentThread();
         return; 
     }
     
     //進入循環
    while (true) {
            //根據OpenGL渲染狀態進入不一樣的處理
            switch (that->m_state) {
                //刷新Surface,從外面設置Surface後m_state置爲該狀態,說明已經從外部(java層)得到Surface的對象了
                case FRESH_SURFACE:
                     LOGI(that->TAG, "Loop Render FRESH_SURFACE")
                     // (3) 初始化Window
                     that->InitDspWindow(env);
                     // (4) 建立EglSurface
                     that->CreateSurface();
                     // m_state置爲RENDERING狀態進入渲染
                     that->m_state = RENDERING;
                     break; 
                 case RENDERING:
                    LOGI(that->TAG, "Loop Render RENDERING")
                    // (5) 渲染
                    that->Render();
                    break; 
               
                 case STOP:
                    LOGI(that->TAG, "Loop Render STOP")
                    //(6) 解除線程和jvm關聯
                     that->ReleaseRender();
                     that->m_jvm_for_thread->DetachCurrentThread();
                     return; 
                case SURFACE_DESTROY:
                    LOGI(that->TAG, "Loop Render SURFACE_DESTROY")
                    //(7) 釋放資源
                    that->DestroySurface();
                    that->m_state = NO_SURFACE;
                    break; 
                case NO_SURFACE:
                default:
                    break;
     }
    usleep(20000);
 }
}

咱們定義的GLRender各個流程代碼裏已經標註了步驟,雖然代碼量比較多,可是咱們的c++ class分析也是跟java相似,
image.png

PS
上圖中的(3)(4)等步驟對應於代碼中的步驟註釋

小結

本篇文章咱們介紹了EGL,以及分析了Java 中GLSurfaceView中的EGL實現,而後咱們試着參照Java端的代碼實現Native中的EGL環境,關於代碼以及流程圖中的細節,咱們下篇再來分析。

相關文章
相關標籤/搜索