OpenGL ES 3.0(三)EGL 概述

OpenGL ES 定義了一個渲染圖形的規範,但沒有定義窗口系統。爲了讓 GLES 能夠適合各種平臺,GLES 將與知道如何通過操作系統創建和訪問窗口的庫,即 EGL 結合使用。在使用 GLES 進行任何操作之前,必須先創建一個 OpenGL 上下文,這是通過 EGL 實現的。

EGL 提供了以下機制:
1) 和設備的本地窗口系統通信
2) 查詢繪圖表面的可用類型及配置
3) 在 OpenGL ES 3.0 及其它圖形渲染 API 之間同步渲染
4) 管理渲染資源,如紋理貼圖

與窗口系統通信

EGL 在 OpenGL ES 3.0(和其他 Khronos 圖形API)和運行在你的計算機上的本地窗口系統之間提供了一個“粘合”層。在 EGL 確定哪些類型的繪圖表面或底層系統的任何其他特性可用之前,它需要與窗口系統建立一個通信通道。

因爲每個窗口系統都有不同的語義,EGL 提供了一個基本的不透明類型 EGLDisplay,它封裝了所有系統的依賴關係,以便與本地窗口系統進行交互。任何使用 EGL 的應用程序需要執行的第一個操作是創建並初始化一個與本地 EGL dispaly 的連接:

EGLint majorVersion;
EGLint minorVersion;

EGLDisplay display = eglGetDisplay ( EGL_DEFAULT_DISPLAY );
if ( display == EGL_NO_DISPLAY ) {
    // 無法打開與本地窗口系統的連接
}

if ( !eglInitialize ( display, &majorVersion, &minorVersion ) ) {
    // 無法初始化 EGL
}

檢查錯誤

EGL 的大多數函數都會返回 EGL_TRUE 或 EGL_FALSE,但如果調用失敗,它會記錄一個表示錯誤原因的錯誤碼,然而,這個錯誤碼不會直接返回給開發者,開發者需要調用以下函數查詢:

// 返回在特定線程中最近一次 EGL 函數調用產生的錯誤碼
EGLint eglGetError()

確定可用的 surface 配置

EGL 初始化成功之後,我們就可以確定哪些類型和配置的渲染表面可用,通常有兩種方法:
1) 查詢每一個 surface 的配置,並找到一個最好的選擇
2) 指定一組要求,讓 EGL 推薦一個最佳匹配

兩種方式都會返回一個 EGLConfig,它是一個 EGL 內部數據結構的標識符,包含特定 surface 及其特徵的信息(比如每一個顏色分量的位數)。

如果是第一種方式,則使用:

EGLBoolean eglGetConfigs(EGLDisplay display,
                         EGLConfig *configs,
                         EGLint maxReturnConfigs,
                         EGLint *numConfigs)

調用這個函數的方式有兩種:
1) configs 設爲 NULL,numConfigs 會返回可用的 EGLConfig 的數量
2) 分配一組未初始化的 EGLConfig,並設置 maxReturnConfigs 爲數組的長度,調用完成後,numConfigs 會更新爲被更改的 configs 的數量

如果想讓 EGL 選擇匹配的 EGLConfig,則使用 eglChooseConfig:

EGLBoolean eglChooseConfig(EGLDisplay display,
                           const EGLint *attribList,
                           EGLConfig *configs,
                           EGLint maxReturnConfigs,
                           EGLint *numConfigs)

將 attribList 設置爲你認爲重要的首選屬性(如果 attribList 不合法,則錯誤碼爲 EGL_BAD_ATTRIBUTE),比如:

EGLint attribList[] = {
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
    EGL_RED_SIZE, 5,
    EGL_GREEN_SIZE, 6,
    EGL_BLUE_SIZE, 5,
    EGL_DEPTH_SIZE, 1,
    EGL_NONE
};

代碼示例:

const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll accept only 10 configs
EGLint numConfigs;

if ( !eglChooseConfig( display, attribList, configs, MaxConfigs, &numConfigs ) ) {
    // Something did not work ... handle error situation
} else {
    // Everything is okay; continue to create a rendering surface
}

如果執行成功,則返回一組匹配的 EGLConfig,內部按相關優先級排序(具體可查看 eglChooseConfig)。

創建 EGL Window

獲取到適合的 EGLConfig 之後,就可以創建窗口了:

EGLSurface eglCreateWindowSurface(EGLDisplay display,
                                  EGLConfig config,
                                  EGLNativeWindowType window,
                                  const EGLint *attribList)

參數 window 是一個本地窗口,創建本地窗口根據平臺不同而不同,可參考平臺自身的 API。

attribList 可以爲空,傳 NULL 或第一個元素爲 EGL_NONE 的 list,所有相關屬性都會使用默認值

創建渲染上下文(rendering context)

EGLContext 是 OpenGL 3.0 中內部的數據結構,包含了操作所需的所有狀態信息:

EGLContext eglCreateContext(EGLDisplay display,
                            EGLConfig config,
                            EGLContext shareContext,
                            const EGLint *attribList)

函數中的的 attribList 只接受一個屬性:EGL_CONTEXT_CLIENT_VERSION。

之後需要把 EGLContext 和 EGLSurface 關聯起來:

EGLBoolean eglMakeCurrent(EGLDisplay display,
                          EGLSurface draw,
                          EGLSurface read,
                          EGLContext context)

這一步完成之後,就能在當前線程中執行 OpenGL 操作了。

代碼示例

以 Android 平臺爲例,使用 EGL 搭建 OpenGL 上下文需要傳一個 Surface 用於獲取 Window:

ANativeWindow *window = ANativeWindow_fromSurface(env, surface);

之後 EGL 就可以使用這個 window 了:

GLboolean EGLCore::buildContext(ANativeWindow *window) {
    // 與本地窗口系統通信
    mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (mDisplay == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay failed: %d", eglGetError());
        return GL_FALSE;
    }

    GLint majorVersion;
    GLint minorVersion;
    if (!eglInitialize(mDisplay, &majorVersion, &minorVersion)) {
        LOGE("eglInitialize failed: %d", eglGetError());
        return GL_FALSE;
    }

    EGLConfig config;
    // 查找可用的 surface 配置
    EGLint numConfigs = 0;
    EGLint attribList[] = {
            EGL_RED_SIZE, 5,
            EGL_GREEN_SIZE, 6,
            EGL_BLUE_SIZE, 5,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
    };

    // 讓 EGL 推薦匹配的 EGLConfig
    if (!eglChooseConfig(mDisplay, attribList, &config, 1, &numConfigs)) {
        LOGE("eglChooseConfig failed: %d", eglGetError());
        return GL_FALSE;
    }

    if (numConfigs < 1) {
        LOGE("eglChooseConfig get configs number less than one");
        return GL_FALSE;
    }

    // 創建渲染上下文(rendering context)
    GLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
    mContext = eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttribs);
    if (mContext == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed: %d", eglGetError());
        return GL_FALSE;
    }

    EGLint format = 0;
    if (!eglGetConfigAttrib(mDisplay, config, EGL_NATIVE_VISUAL_ID, &format)) {
        LOGE("eglGetConfigAttrib failed: %d", eglGetError());
        return GL_FALSE;
    }
    ANativeWindow_setBuffersGeometry(window, 0, 0, format);

    // 創建 On-Screen 渲染區域
    mSurface = eglCreateWindowSurface(mDisplay, config, window, 0);
    if (mSurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed: %d", eglGetError());
        return GL_FALSE;
    }

    // 把 EGLContext 和 EGLSurface 關聯起來
    if (!eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) {
        LOGE("eglMakeCurrent failed: %d", eglGetError());
        return GL_FALSE;
    }

    LOGD("buildContext succeed");
    return GL_TRUE;
}

不再需要時,記得釋放資源:

void EGLCore::release() {
    eglDestroySurface(mDisplay, mSurface);
    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章