Android OpenGL 開發---EGL 的使用


上篇博文:Android OpenGL 開發---概念與入門

EGL 內容介紹

說明:Khronos 是 OpenGL, OpenGL ES, OpenVG 和 EGL 等規范的定義者。以下的代碼主要是用 Android 書寫,但規范是 EGL 規范。

EGL 是 Khronos 組織定義的用於管理繪圖表面(窗口只是繪圖表面的一種類型,還有其他的類型)的 API,EGL 提供了 OpenGL ES(以及其他 Khronos 圖形 API(如 OpenVG))和不同操作系統(Android、Windows 等)之間的一個 “結合層次”。即 EGL 定義了 Khronos API 如何與底層窗口系統交流,是 Khronos 定義的規范,相當於一個框架,具體的實現由各個操作系統確定。它是在 OpenGL ES 等規范中討論的概念,故應和這些規范的文檔結合起來閱讀,且其 API 的說明文檔也應在 Khronos 網站上尋找。注意:IOS 提供了自己的 EGL API 實現,稱為 EAGL。

通常,在 Android 中,EGL14 實現的是 EGL 1.4 規范。其相當於 Google 官方對 JAVA 的 EGL10(EGL 1.0 規范)的一次重新設計。通常,我們使用 EGL14 中的函數。而 EGL15 是 EGL 1.5 規范,其在設計時,僅僅是做了規范的補充,並未重新設計。通常 EGL14、EGL15 與 GLES20 等一起使用。GLES20 是 OpenGL ES 2.0 規范的實現。OpenGL ES 1.x 規范因為是早期版本,受限於機器性能和架構設計等,基本可以不再使用。而 OpenGL ES 2.x 規范從 Android 2.3 版本后開始支持,目前市面上的所有手機都支持。相比於 1.0,OpenGL ES 2.0 引入了可編程圖形管線,具體的渲染相關使用專門的着色語言來表達。

下面,介紹一下 EGL 的使用流程。

1. EGL 與操作系統窗口系統通信

在 EGL 能夠確定可用的繪制表面類型之前,它必須打開和窗口系統的通信渠道。因為每個窗口系統都有不同的語義,所以 EGL 提供了基本的不對操作系統透明的類型---EGLDisplay。該類型封裝了所有系統相關性,用於和原生窗口系統交流。任何使用 EGL 的應用程序,第一個操作必須是創建和初始化與本地 EGL 顯示的連接。以下代碼展示如何創建連接:

/**
 * EGL14 實現了 EGL1.4 定義的規范
 * EGL15 等主要是 EGL 新增規范的補充
 * 
 * eglGetDisplay 方法定義了如何獲取本地 EGL 顯示。
 *
 * EGL_DEFAULT_DISPLAY 是默認的連接。
 * EGL_NO_DISPLAY 表示連接不可用,進而說明 EGL 與 OpenGL ES 不可用
 * */
private void initEGLDisplay() {
    // 獲取本地默認的顯示
    EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    if(display == EGL14.EGL_NO_DISPLAY) {
        // 具體的錯誤查詢方法下面講述
        Log.e("NO", "Link Error");
        return;
    }
    // 做接下來的操作
}

2. 檢查錯誤

上面的代碼中,我們已經知道 EGL 會發生錯誤,也可以獲取錯誤。

EGL 中的大部分函數,在成功時會返回 EGL_TRUE,否則返回 EGL_FALSE。但是,EGL 所做的不僅是告訴我們調用是否失敗,它還將記錄錯誤,指示故障原因。不過,這個錯誤代碼並不會直接告訴我們,我們需要查詢規范,才能知道每個的含義。可以調用 eglGetError() 函數獲取錯誤,以下是錯誤的說明:

private void getError() {
    int errorCode = EGL14.eglGetError();
    switch (errorCode) {
        case EGL14.EGL_SUCCESS:
            println("函數執行成功,無錯誤---沒有錯誤");
            break;
        case EGL14.EGL_NOT_INITIALIZED:
            println("對於特定的 Display, EGL 未初始化,或者不能初始化---沒有初始化");
            break;
        case EGL14.EGL_BAD_ACCESS:
            println("EGL 無法訪問資源(如 Context 綁定在了其他線程)---訪問失敗");
            break;
        case EGL14.EGL_BAD_ALLOC:
            println("對於請求的操作,EGL 分配資源失敗---分配失敗");
            break;
        case EGL14.EGL_BAD_ATTRIBUTE:
            println("未知的屬性,或者屬性已失效---錯誤的屬性");
            break;
        case EGL14.EGL_BAD_CONTEXT:
            println("EGLContext(上下文) 錯誤或無效---錯誤的上下文");
            break;
        case EGL14.EGL_BAD_CONFIG:
            println("EGLConfig(配置) 錯誤或無效---錯誤的配置");
            break;
        case EGL14.EGL_BAD_DISPLAY:
            println("EGLDisplay(顯示) 錯誤或無效---錯誤的顯示設備對象");
            break;
        case EGL14.EGL_BAD_SURFACE:
            println("未知的屬性,或者屬性已失效---錯誤的Surface對象");
            break;
        case EGL14.EGL_BAD_CURRENT_SURFACE:
            println("窗口,緩沖和像素圖(三種 Surface)的調用線程的 Surface 錯誤或無效---當前Surface對象錯誤");
            break;
        case EGL14.EGL_BAD_MATCH:
            println("參數不符(如有效的 Context 申請緩沖,但緩沖不是有效的 Surface 提供)---無法匹配");
            break;
        case EGL14.EGL_BAD_PARAMETER:
            println("錯誤的參數");
            break;
        case EGL14.EGL_BAD_NATIVE_PIXMAP:
            println("NativePixmapType 對象未指向有效的本地像素圖對象---錯誤的像素圖");
            break;
        case EGL14.EGL_BAD_NATIVE_WINDOW:
            println("NativeWindowType 對象未指向有效的本地窗口對象---錯誤的本地窗口對象");
            break;
        case EGL14.EGL_CONTEXT_LOST:
            println("電源錯誤事件發生,Open GL重新初始化,上下文等狀態重置---上下文丟失");
            break;
        default:
            break;
    }
}

private void println(String s) {
    System.out.println(s);
}

3. 初始化 EGL

成功打開連接之后,需要初始化 EGL。可以調用如下函數完成:

// 標准的 EGL 定義

EGLBoolean eglInitialize(EGLDisplay display, EGLint *majorVersion, EGLint *minorVersion);

// display:      指定 EGL 的顯示連接
// majorVersion: 存儲指定 EGL 實現,返回的主版本號,可能為 NULL
// minorVersion: 存儲指定 EGL 實現,返回的次版本號,可能為 NULL

在 Android 中,其具體實現如下:

// 定義一個 2 維數組,用於存放獲取到的版本號,主版本號放在 version[0],次版本號放在 version[1]
int[] version = new int[2];

// 初始化 EGL, eglDisplay 的獲取在上面講述了
boolean isSuccess = EGL14.eglInitialize(eglDisplay, version, 0, version, 1);

if(!isSuccess) {
    switch (EGL14.eglGetError()) {
        case EGL14.EGL_BAD_DISPLAY:
            println("無效的 EGLDisplay 參數");
            break;
        case EGL14.EGL_NOT_INITIALIZED:
            println("EGL 不能初始化");
            break;
        default:
            println("發生錯誤:" + EGL14.eglGetError());
            break;
    }
    return;
}
// 初始化成功,往下走
// ......

這個函數初始化 EGL 內部數據結構,返回 EGL 實現的主版本號和次版本號。如果 EGL 無法初始化,函數會返回 EGL_FALSE,並將 EGL 錯誤代碼設置為:

  • EGL_BAD_DISPLAY --- 如果 display 不是有效的 EGLDisplay
  • EGL_NOT_INITIALIZED --- 如果 EGL 不能初始化

4. 確定可用表面配置

一旦初始化了 EGL,就可以確定可用渲染表面的類型和配置,這有兩種方法:

  • 查詢每個表面配置,找出最好的選擇
  • 指定一組需求,讓 EGL 推薦最佳匹配

在許多情況下,使用第二種方法更簡單,而且最有可能得到用第一種方法找到的匹配。

在任何一種情況下,EGL 都將返回一個 EGLConfig,這是包含有關特定表面及其特征(如每個顏色分量的位數、與 EGLConfig 相關的深度緩沖區(如果有的話))的 EGL 內部數據結構的標識符。可以用 eglGetConfigAttrib 函數查詢 EGLConfig 的任何屬性。后面會講。

調用以下函數,可以查詢系統支持的所有 EGL 表面配置:

boolean eglGetConfigs(EGLDisplay display, EGLConfig[] configs, int configsOffset, int config_size, int[] num_config, int num_configOffset);
// display:          EGL 連接的顯示
// configs:          指定的 configs 列表(第 2 個參數)
// configsOffset:    列表取值的起始偏移值
// config_size:      指定的 configs 列表的尺寸
// num_config:       存放獲取到的配置數量。一般來說,此列表僅存放大小,列表長度設置為 1 即可(第 5 個參數)
// num_configOffset: 存值時的起始偏移值(第 6 個參數)

函數在調用成功時,會返回 EGL_TRUE。失敗時,會返回 EGL_FALSE,並將 EGL 錯誤代碼設置為:

  • EGL_NOT_INITIALIZED --- 如果 display 不能初始化
  • EGL_BAD_PARAMETER --- 如果 num_config(用於存放 返回結果的大小 的列表(上述說明中的第 5 個參數))為空

調用 eglGetConfigs 有兩種方法。

  1. 如果指定的入參 configs 為 NULL,則將返回 EGL_TRUE,並將返回可用的 EGLConfig 的數量。在足夠謹慎的情況下,當沒有返回任何 EGLConfig 的信息,但是知道了可用配置的數量時,我們可以分配足夠的內存,來獲得完成的 EGLConfig 集合
  2. 在更普遍的情況下,我們一般會分配一個未初始化的 EGLConfig 值的數組((上述說明中的第 2 個參數)),並作為參數傳遞給 eglGetConfigs 函數。函數調用完成時,會獲取到配置列表((上述說明中的第 2 個參數,此參數被修改過,存放了結果)),並根據上述說明中的第 5 和第 6 個參數來確定列表中可用的配置數量和位置(即根據 5、6 確定結果在 2 中的位置)。當然,獲取到的配置結果的大小不會超過傳入的未初始化的 EGLConfig 值的數組的大小。

5. 查詢 EGLConfig 屬性

此處,說明一下與 EGLConfig 相關的 EGL 值,並說明如果檢索這些值。

EGLConfig 包含關於 EGL 啟用的表面的所有信息。其包括關於可用顏色、與配置相關的其他緩沖區(深度和模板緩沖區)、表面類型等等。可以用下面的函數查詢與 EGLConfig 相關的特定屬性:

boolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, int attribute, int[] value, int offset);

// display:   EGL 的顯示連接
// config:    待查詢的配置
// attribute: 指定返回的特定屬性
// value:     指定的返回值
// offset:    結果放入數組時的偏移值,一般為 0

上述函數在調用成功時返回 EGL_TRUE,失敗時返回 EGL_FALSE,並將 EGL 錯誤代碼設置為:

  • EGL_BAD_DISPLAY: 顯示無效
  • EGL_NOT_INITIALIZED: 顯示未初始化
  • EGL_BAD_CONFIG: 配置無效
  • EGL_BAD_ATTRIBUTE: 屬性無效

下面是在 eglGetConfigAttrib 函數中所有的可用的 EGLConfig 屬性列表:

屬性名 描述 默認值
EGL_ALPHA_SIZE 顏色緩沖區中的 Alpha 值位數 0
EGL_ALPHA_MASK_SIZE 掩碼緩沖區中的 Alpha 掩碼位數0,掩碼緩沖區只被 OpenVG 使用 0
EGL_BIND_TO_TEXTURE_RGB 如果可以綁定到 RGB 紋理,則返回 EGL_TRUE,否則返回 EGL_FALSE EGL_DONT_CARE
EGL_BIND_TO_TEXTURE_RGBA 如果可以綁定到 RGBA 紋理,則返回 EGL_TRUE,否則返回 EGL_FALSE EGL_DONT_CARE
EGL_BLUE_SIZE 顏色緩沖區中藍色分量的位數 0
EGL_BUFFER_SIZE 顏色緩沖區中所有顏色分量的位數,其值是 EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE, 和 EGL_ALPHA_SIZE 的位數之和 0
EGL_COLOR_BUFFER_TYPE 顏色緩沖區類型:EGL_RGB_BUFFER 或者 EGL_LUMINANCE_BUFFER EGL_RGB_BUFFER
EGL_CONFIG_CAVEAT 和配置相關的任何注意事項,可取的值為:EGL_NONE, EGL_SLOW_CONFIG, 和 EGL_NON_CONFORMANT EGL_DONT_CARE
EGL_CONFIG_ID EGLConfig的唯一標識符的值 EGL_DONT_CARE
EGL_CONFORMANT 如果用此配置創建的上下文是有效的,則返回真 ---
EGL_DEPTH_SIZE 深度緩沖區位數 0
EGL_GREEN_SIZE 顏色緩沖區中綠色分量的位數 0
EGL_LEVEL 幀緩沖區的級別 0
EGL_LUMINANCE_SIZE 顏色緩沖區中的亮度位數 0
EGL_MAX_PBUFFER_WIDTH 像素緩沖區表面(pixel buffer surface)的最大寬度 ---
EGL_MAX_PBUFFER_HEIGHT 像素緩沖區表面(pixel buffer surface)的最大高度 ---
EGL_MAX_PBUFFER_PIXELS 像素緩沖區表面(pixel buffer surface)的最大尺寸 ---
EGL_MAX_SWAP_INTERVAL 最大緩沖區交換間隔(參見 eglSwapInterval 函數) EGL_DONT_CARE
EGL_MIN_SWAP_INTERVAL 最小緩沖區交換間隔(參見 eglSwapInterval 函數) EGL_DONT_CARE
EGL_NATIVE_RENDERABLE 如果原生渲染 API 可以渲染此 EGLConfig 創建的表面,則為真 EGL_DONT_CARE
EGL_NATIVE_VISUAL_ID 原生窗口系統的可視 ID 的句柄 EGL_DONT_CARE
EGL_NATIVE_VISUAL_TYPE 原生窗口系統的可視類型 EGL_DONT_CARE
EGL_RED_SIZE 顏色緩沖區中紅色分量的位數 0
EGL_RENDERABLE_TYPE 位掩碼,代表 EGL 支持的渲染類型的接口。由 EGL_OPENGL_ES_BIT、EGL_OPENGL_ES2_BIT、EGL_OPENGL_ES3 _BIT_KHR(需要 EGL_KHR_create_context 擴展)、EGL_OPENGL_BIT 或 EGL_OPENVG_BIT 組成 EGL_OPENGL_ES_BIT
EGL_SAMPLE_BUFFERS 可用的多重采用緩沖區的數量 0
EGL_SAMPLES 每個像素的樣本數量 0
EGL_STENCIL_SIZE 模板緩沖區位數 0
EGL_SURFACE_TYPE 支持的 EGL 表面類型。可能是:EGL_WINDOW_BIT、EGL_PIXMAP_BIT、EGL_PBUFFER_BIT、EGL_MULTISAMPLE_RESOLVE_BOX_BIT、EGL_SWAP_BEHAVIOR_PRESERVED_BIT、EGL_VG_COLORSPACE_LINEAR_BIT 或 EGL_VG_ALPHA_FORMAT_PRE_BIT EGL_WINDOW_BIT
EGL_TRANSPARENT_TYPE 支持的透明度類型:EGL_NONE 或者 EGL_TRANSPARENT_RGB EGL_NONE
EGL_TRANSPARENT_RED_VALUE 透明的紅色值 EGL_DONT_CARE
EGL_TRANSPARENT_GREEN_VALUE 透明的綠色值 EGL_DONT_CARE
EGL_TRANSPARENT_BLUE_VALUE 透明的藍色值 EGL_DONT_CARE
EGL_MATCH_NATIVE_PIXMAP 匹配本地的像素圖,該屬性只能通過 eglChooseConfig 獲取到,無法通過 eglGetConfigAttrib 獲取到 EGL_NONE

6. 選擇 EGLCONFIG 屬性

如果我們不關心所有的屬性,那么可以使用 eglChooseConfig 函數,此函數可以指定我們關心的重要的屬性,並返回最佳匹配結果:

boolean eglChooseConfig(EGLDisplay display, int[] attrib_list, int attrib_listOffset, EGLConfig[] configs, int configsOffset, int config_size, int[] num_config, int num_configOffset);

// display:            連接的 EGL 顯示
// attrib_list:        指定待查詢的 EGLConfig 匹配的屬性列表
// attrib_listOffset:  屬性列表的取值位移
// configs:            EGLConfig 的配置列表
// configsOffset:      配置的取值偏移
// config_size:        配置列表的尺寸
// num_config:         指定返回的配置大小,數組長度一般設置為 1 即可
// num_configOffset:   取值偏移0

函數調用成功時,返回 EGL_TRUE,否則返回 EGL_FALSE,並將錯誤碼置為以下的值:

  • EGL_BAD_DISPLAY 顯示無效
  • EGL_BAD_ATTRIBUTE 屬性錯誤或無效
  • EGL_NOT_INITIALIZED 顯示(EGLDisplay)未初始化
  • EGL_BAD_PARAMETER num_config 為空

我們在設置屬性列表時,需要設置相關屬性的默認值。如果不設置,則會使用上述表格中的默認值。例如,我們需要獲取支持 5 位紅色和藍色分量、6 位綠色分量(常用的 RGB565 格式)的渲染表面、一個深度緩沖區和 OpenGL 3.0 的 EGLConfig,則可以指定以下的數組:

int[] attrs = new int[] {
    // 屬性名                     默認值
    EGL14.EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,
    EGL14.EGL_RED_SIZE, 5,
    EGL14.EGL_GREEN_SIZE, 6,
    EGL14.EGL_BLUE_SIZE, 5,
    EGL14.EGL_DEPTH_SIZE, 1,
    // 屬性定義結束
    EGL14.EGL_NONE
};

// 注:使用 EGL_OPENGL_ES3_BIT_KHR 需要使用 EGL_KHR_create_context 擴展。該屬性在 eglext.h (EGL v1.4) 中定義

下面是一個查詢的代碼示范:

// 存儲返回的配置數量
int []numConfigs = new int[1];
EGLConfig[]configs = new EGLConfig[1];
if (!EGL14.eglChooseConfig(eglDisplay, attrs, 0, configs, 0, configs.length, numConfigs, 0)) {
    // 獲取屬性出錯
    return;
}
// 獲取屬性成功

如果 eglChooseConfig 匹配成功,則將返回一組匹配相應屬性列表的標准的 EGLConfig。如果 EGLConfig 的數量超過一個(最多是我們指定的最大配置數量),則將按如下順序排列:

  1. 按照 EGL_CONFIG_CAVEAT 的值。如果沒有配置注意事項(EGL_CONFIG_CAVEAT 的值為 EGL_NONE)的配置優先,然后是慢速渲染配置(EGL_SLOW_CONFIG),最后是不兼容的配置(EGL_NON_CONFORMANT_CONFIG)
  2. 按照 EGL_COLOR_BUFFER_TYPE 指定的緩沖區類型
  3. 按照顏色緩沖區位數降序排列。緩沖區的位數取決於 EGL_COLOR_BUFFER_TYPE,至少是為特定顏色通道指定的值。當緩沖區類型為 EGL_RGB_BUFFER 時,位數是 EGL_RED_SIZE、EGL_GREEN_SIZE、EGL_BLUE_SIZE的和。當顏色緩沖區類型為 EGL_LUMINANCE_BUFFER 時,位數是 EGL_LUMINANCE_SIZE 與 EGL_ALPHA_SIZE 的和。
  4. 按照 EGL_BUFFER_SIZE 值的升序排列
  5. 按照 EGL_SAMPLE_BUFFERS 值的升序排列
  6. 按照 EGL_SAMPLES 數量的升序排列
  7. 按照 EGL_DEPTH_SIZE 值的升序排列
  8. 按照 EGL_STENCIL_SIZE 值的升序排列
  9. 按照 EGL_ALPHA_MASK_SIZE 的值排序(這僅適用於 OpenVG)
  10. 按照 EGL_NATIVE_VISUAL_TYPE,以實現相關的方式排序
  11. 按照 EGL_CONFIG_ID 值的升序排列

上述列表中未提及到的參數不用於排列過程

同時,需要注意以下事項:

  • 因為第 3 條排序規則的存在,為了匹配最佳的屬性格式,必須添加額外的邏輯檢查。如:我們要求的是 RGB565,但是 RGB888 會先出現在返回結果中。
  • 如果指定屬性列表時,沒有設置對應的默認值,則默認的渲染表面是屏幕上的窗口(EGL_SURFACE_TYPE 屬性對應的值)。

7. 創建屏幕上的渲染區域:EGL 窗口

一旦我們有了符合渲染需求的 EGLConfig,就為創建窗口做好了准備。可以調用以下函數來創建窗口:

EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, Object window, int[] attrib_list, int offset);

// display      指定 EGL 的顯示連接
// config       指定的配置
// window       指定原生窗口,在 Android 中,可傳入 SurfaceHolder 對象
// attrib_list  指定窗口屬性列表,可能為 NULL
// offset       屬性列表的取值偏移

上面的屬性列表可以取以下值,因為 EGL 還可以支持其他 API(如 Open VG),故創建時,有些屬性不適用。下面是 OpenGL ES 支持的屬性:

屬性名 描述 默認值
EGL_RENDER_BUFFER 指定渲染用的緩沖區(EGL_SINGLE_BUFFER,或者后台緩沖區 EGL_BACK_BUFFER) EGL_BACK_BUFFER
EGL_GL_COLORSPACE 指定 Open GL 和 Open GL ES 渲染表面時,用到的顏色空間,此屬性在 EGL1.5 中被定義 EGL_GL_COLORSPACE_SRGB

說明,對於 OpenGL ES 3.0,只支持雙緩沖區窗口。

當屬性列表 attrib_list 為空,或者 EGL_NONE 作為屬性第一個元素時,所有的屬性值都將使用默認值。當調用失敗時,函數會返回 EGL_NO_SURFACE,並設置以下錯誤:

  • EGL_BAD_DISPLAY 顯示錯誤
  • EGL_NOT_INITIALIZED 顯示未初始化
  • EGL_BAD_CONFIG 配置錯誤,配置未得到系統支持
  • EGL_BAD_NATIVE_WINDOW 本地原生窗口錯誤
  • EGL_BAD_ATTRIBUTE 屬性錯誤
  • EGL_BAD_ALLOC 分配錯誤,如無法分配窗口資源,或者已有 EGLSurface 與本地原生窗口關聯時
  • EGL_BAD_MATCH 匹配錯誤 EGLConfig 不匹配原生窗口系統。或者 EGLConfig 不支持渲染到窗口(EGL_SURFACE_TYPE 屬性沒有設置 EGL_WINDOW_BIT)

下面是一個創建 EGL 窗口的代碼,參照了 GLSurfaceView:

EGLSurface surface = EGL14.eglCreateWindowSurface(display, config, view.getHolder(), null, 0);
if(surface == null) {
    Log.e("Test", "窗口為空");
    return;
}
if(surface == EGL14.EGL_NO_SURFACE) {
    int error = EGL14.eglGetError();
    // 處理錯誤
    return;
}
// 函數執行成功,往下走

下面,介紹另外一種 EGL 渲染區域:EGL Pbuffer。

8. 創建屏幕外渲染區域:EGL Pbuffer

除了可以用 OpenGL ES 在屏幕上的窗口渲染之外,還可以渲染被稱為 Pbuffer(像素緩沖區 Pixel buffer 的簡寫) 的不可見屏幕外表面。和窗口一樣,Pbuffer 可以利用 OpenGL ES 中的任何硬件加速。Pbuffer 常用於生成紋理貼圖。當然,如果想要的是渲染到一個紋理,建議使用幀緩沖區對象代替 Pbuffer,因為幀緩沖區更高效。不過,在某些幀緩沖區無法使用的情況下,Pbuffer 仍然有效,比如用 OpenGL ES 在屏幕外表面渲染,然后將其作為其他 API(如 OpenVG)中的紋理。

和窗口一樣,Pbuffer 支持 OpenGL ES 的所有渲染機制。主要的區別是:

  • 窗口:渲染的內容可以在屏幕上顯示,渲染完成時,需要交換緩沖區
  • Pbuffer:渲染的內容無法在屏幕上顯示,渲染完成時,無需交換緩沖區。而是從 Pbuffer 中將數值直接復制到應用程序,或者是將 Pbuffer 的綁定更改為紋理,則 Pbuffer 的渲染對目標紋理生效

創建 Pbuffer 和創建 EGL 窗口非常類似。為了創建 Pbuffer,需要和窗口一樣找到 EGLConfig,並作一處修改;我們需要擴增 EGL_SURFACE_TYPE 的值,使其包含 EGL_PBUFFER_BIT。擁有適用的 EGLConfig 之后,就可以用如下函數創建 Pbuffer:

EGLSurface eglCreatePbufferSurface(EGLDisplay display, EGLConfig config, int[] attrib_list, int offset);

// display       指定的 EGL 顯示
// config        指定的配置
// attrib_list   指定像素緩沖區的屬性列表,可能為 NULL
// offset        屬性數組取值的偏移值

在 OpenGL ES 中,下面是可用的屬性列表:

屬性名 描述 默認值
EGL_WIDTH 指定 Pbuffer 的寬度(以像素表示) 0
EGL_HEIGHT 指定 Pbuffer 的高度(以像素表示) 0
EGL_LARGEST_PBUFFER 如果請求的大小不可用,則選擇最大的可用 Pbuffer,有效值為 EGL_TRUE 和 EGL_FALSE EGL_FALSE
EGL_TEXTURE_FORMAT 如果 Pbuffer 指定到一個紋理貼圖,則指定紋理格式類型,有效值是 EGL_NO_TEXTURE, EGL_TEXTURE_RGB, 和 EGL_TEXTURE_RGBA EGL_NO_TEXTURE(表示 Pbuffer 不能直接指定到紋理)
EGL_TEXTURE_TARGET 指定 Pbuffer 作為紋理貼圖時,應該連接到的相關紋理目標,有效值是 EGL_NO_TEXTURE, 和 EGL_TEXTURE_2D EGL_NO_TEXTURE
EGL_MIPMAP_TEXTURE 指定 是否應該為 紋理mipmap 分配存儲,有效值是 EGL_TRUE 和 EGL_FALSE EGL_FALSE
EGL_GL_COLORSPACE 指定 Open GL 和 Open GL ES 渲染表面時,用到的顏色空間,此屬性在 EGL1.5 中被定義 EGL_GL_COLORSPACE_SRGB

和窗口創建一樣,函數調用失敗時,會返回 EGL_NO_SURFACE,並設置以下錯誤碼:

  • EGL_BAD_DISPLAY EGL 顯示連接錯誤
  • EGL_NOT_INITIALIZED EGL 顯示未初始化
  • EGL_BAD_CONFIG 配置無效
  • EGL_BAD_ATTRIBUTE 如果指定了 EGL_MIPMAP_TEXTURE, EGL_TEXTURE_FORMAT, 或者 EGL_TEXTURE_TARGET,但是提供的 EGLConfig 不支持 OpenGL ES 渲染(如只支持 OpenVG 渲染),則發生該錯誤
  • EGL_BAD_ALLOC EGL Pbuffer 因為缺少資源而無法分配時,發生該錯誤
  • EGL_BAD_PARAMETER 如果屬性指定的 EGL_WIDTH 或 EGL_HEIGHT 是負值,則發生該錯誤
  • EGL_BAD_MATCH 如果出現以下情況,則出現該錯誤:
    1. 提供的 EGLConfig 不支持 Pbuffer 表面
    2. Pbuffer 被用作紋理貼圖(EGL_TEXTURE_FORMAT 不是 EGL_NO_TEXTURE),且指定的 EGL_WIDTH 和 EGL_HEIGHT 時無效的紋理尺寸
    3. EGL_TEXTURE_FORMAT 和 EGL_TEXTURE_TARGET 設置為 EGL_NO_TEXTURE,而其他屬性沒有設置成 EGL_NO_TEXTURE

9. 創建一個渲染上下文

渲染上下文是 OpenGL ES 的內部數據結構,包含操作所需的所有狀態信息。在 OpenGL ES 中,必須要有個上下文才能繪圖。可以用如下函數創建上下文:

EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list, int offset);

// display       指定的 EGL 顯示
// config        指定的配置
// share_context 允許多個 EGL 上下文共享特定類型的數據,比如着色器程序和紋理貼圖,使用 EGL_NO_CONTEXT 表示沒有共享
// attrib_list   指定上下文使用的屬性列表,只有一個可接受的屬性: EGL_CONTEXT_CLIENT_VERSION。該屬性用於指定與我們所使用的 OpenGL ES 版本相關的上下文類型。默認值是1(即指定 OpenGL ES 1.X 版本的上下文類型)
// offset        屬性列表的取值偏移

函數執行成功時,會返回一個指向新創建上下文的句柄。如果失敗,則會返回 EGL_NO_CONTEXT,並設置如以下錯誤碼(不全,官方文檔未說明。以實際返回為准):

  • EGL_BAD_CONFIG 配置無效

下面是創建上下文的一段示范代碼:

int []contextAttribs = {
    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, // EGL14 一般與 GLES20 結合使用
    EGL14.EGL_NONE
};
eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribs,0);
if(eglContext== EGL14.EGL_NO_CONTEXT) {
    throw new RuntimeException("EGL error "+EGL14.eglGetError());
}

10. 指定某個 EGLContext 為當前上下文

終於,我們來到了最后一步,完成這一步后,我們就能開始渲染了。

因為一個應用程序可能創建多個 EGLContext 用於不同的用途,所以我們需要關聯特定的 EGLContext 和渲染表面。這一步驟通常叫做 "指定當前上下文"。使用下列函數,關聯特定的 EGLContext 和 EGLSurface:

boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);

// display       指定的 EGL 顯示
// draw          指定的 EGL 繪圖表面
// read          指定的 EGL 讀取表面
// context       指定連接到該表面的渲染上下文

函數執行成功時,會返回 EGL_TRUE,否則返回 EGL_FALSE,並設置以下錯誤碼:

  • EGL_BAD_MATCH draw 或 read 與 context 上下文不兼容
  • EGL_BAD_ACCESS 發生以下情況時,會產生該錯誤
    1. 當前未指定上下文
    2. 上下文的版本與客戶端(手機實體)支持的版本不兼容
    3. 或者 draw 或 read 未綁定到當前上下文
    4. draw 或者 read 是由 eglCreatePbufferFromClientBuffer 函數創建的 Pbuffer,但是創建他們的底層客戶端緩沖區正在被創建他們的客戶端 API 使用
  • EGL_BAD_CONTEXT 上下文無效,並且上下文不是 EGL_NO_CONTEXT
  • EGL_BAD_SURFACE draw 或者 read 無效,並且不是 EGL_NO_SURFACE
  • EGL_BAD_MATCH 出現以下情況時,會產生該錯誤:
    1. 上下文是 EGL_NO_CONTEXT,但是 draw 或者 read 不是 EGL_NO_SURFACE
    2. draw 或者 read 的其中一個有效,另一個是 EGL_NO_SURFACE
    3. 上下文不支持在沒有 draw 或者 read 的情況下綁定,而 draw 和 read 都是 EGL_NO_SURFACE
    4. 如果 draw 和 read 的渲染內容無法同時放入圖形內存
  • EGL_BAD_NATIVE_WINDOW 作為 draw 或者 read 的本機窗口不再有效
  • EGL_BAD_CURRENT_SURFACE 如果之前調用線程的上下文未清除命令,並且之前的表面不再有效
  • EGL_BAD_ALLOC 用於 draw 和 read 的輔助緩沖區無法分配
  • EGL_CONTEXT_LOST 發生意外的電源事件
  • EGL_NOT_INITIALIZED 如果指定的顯示(EGLDisplay)有效但未初始化,並且存在以下的情形之一:
    1. context 不是 EGL_NO_CONTEXT
    2. draw 或者 read 不是 EGL_NO_SURFACE
  • EGL_BAD_DISPLAY EGL 指定的顯示無效。部分 EGL 實現允許 EGL_NO_DISPLAY 作為 eglMakeCurrent 函數的有效顯示參數。但這並不是 EGL 的標准定義,應被視為特定廠商的擴展。

我們注意到函數需要兩個 EGLSurface 表面。盡管這種方法具有靈活性(在 EGL 的高級用法中將被利用到),但是通常我們把 draw 和 read 設置為同一個值---我們前面創建的渲染表面(窗口或者 Pbuffer)。注意,因為 EGL 規范要求 eglMakeCurrent 實現進行一次刷新,所以這一調用對於基於圖塊的架構代價很高。

講述完用 EGL 渲染前的所有准備后,我們正式整合所有步驟,輸出一個完整的代碼示范。

11. 同步渲染

此部分是擴展內容,不是必須的步驟。不想看的可以跳過,看下面的代碼示范。

有時我們會碰到,協調多個圖形 API 在單個窗口中渲染的情況。在這種情況下,需要讓應用程序允許多個庫渲染到共享窗口。EGL 提供了幾個函數來處理這種同步任務。

    1. 只用 OpenGL ES,可以調用 glFinish 函數來保證所有渲染已經發生。或者是更高級的同步對象和柵欄。
    1. 不止一種 Khronos API。在切換窗口系統原生渲染 API 之前,可能不知道使用的是哪個 API。為此可以調用 eglWaitClient 函數延遲客戶端的執行,直到某個 Khronos API 的所有渲染完成,再切換 API。該函數執行成功時,會返回 EGL_TRUE,失敗時返回 EGL_FALSE,並設置錯誤碼為 EGL_BAD_CURRENT_SURFACE。
    1. 同樣在不止一種 Khronos API 的情況,如果想要保證原生窗口系統的渲染完成,則可以調用 eglWaitNative(EGLInt engine)。參數可設置為 EGL_CORE_NATIVE_ENGINE,代表支持的最常見引擎,其他實現視為 EGL 擴展。函數執行成功,返回 EGL_TRUE,否則返回 EGL_FALSE,並設置錯誤碼為 EGL_BAD_PARAMETER。

步驟整合與代碼示范

下面是一段示范,可能無法運行,但是說明了 EGL 執行與初始化的流程。

/**
  * 初始化 EGL,初始化成功,返回 true,否則返回 false
  * */
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private boolean initWindow() {
    // 1. 獲取 EAL 顯示
    EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    if(eglDisplay == EGL14.EGL_NO_DISPLAY) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    // 2. 初始化 EGL
    int[] version = new int[2];
    if(!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    // 3. 確定配置
    int[] configAttribs = new int[] {
        EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_WINDOW_BIT,
        EGL14.EGL_RED_SIZE, 8,
        EGL14.EGL_GREEN_SIZE, 8,
        EGL14.EGL_BLUE_SIZE, 8,
        EGL14.EGL_DEPTH_SIZE, 24,
        EGL14.EGL_NONE
    };
    EGLConfig[] configs = new EGLConfig[1];
    int[] numConfigs = new int[1];
    if(!EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0,
                              configs.length, numConfigs, 0)) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    if(configs[0] == null) {
        return false;
    }
    // 4. 創建渲染表面,此處是創建窗口
    EGLSurface window = EGL14.eglCreateWindowSurface(eglDisplay,
                                                     configs[0], glSurfaceView.getHolder(), null, 0);
    if(window == EGL14.EGL_NO_SURFACE) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    // 5. 創建上下文
    int[] contextAttribs = new int[] {
        EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL14.EGL_NONE
    };
    EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, configs[0],
                                                   EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
    if(eglContext == EGL14.EGL_NO_CONTEXT) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    // 6. 綁定上下文與表面
    if(!EGL14.eglMakeCurrent(eglDisplay, window, window, eglContext)) {
        Log.e("initWindow", EGL14.eglGetError() + "");
        return false;
    }
    Log.d("initWindow", "初始化成功");
    return true;
}

小結

從上述的說明中,我們可以得到下面的關於 EGL 的使用的流程圖:

以上便是關於 EGL 的使用的一些基本介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM