上篇博文: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 有兩種方法。
- 如果指定的入參 configs 為 NULL,則將返回 EGL_TRUE,並將返回可用的 EGLConfig 的數量。在足夠謹慎的情況下,當沒有返回任何 EGLConfig 的信息,但是知道了可用配置的數量時,我們可以分配足夠的內存,來獲得完成的 EGLConfig 集合
- 在更普遍的情況下,我們一般會分配一個未初始化的 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 的數量超過一個(最多是我們指定的最大配置數量),則將按如下順序排列:
- 按照 EGL_CONFIG_CAVEAT 的值。如果沒有配置注意事項(EGL_CONFIG_CAVEAT 的值為 EGL_NONE)的配置優先,然后是慢速渲染配置(EGL_SLOW_CONFIG),最后是不兼容的配置(EGL_NON_CONFORMANT_CONFIG)
- 按照 EGL_COLOR_BUFFER_TYPE 指定的緩沖區類型
- 按照顏色緩沖區位數降序排列。緩沖區的位數取決於 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 的和。
- 按照 EGL_BUFFER_SIZE 值的升序排列
- 按照 EGL_SAMPLE_BUFFERS 值的升序排列
- 按照 EGL_SAMPLES 數量的升序排列
- 按照 EGL_DEPTH_SIZE 值的升序排列
- 按照 EGL_STENCIL_SIZE 值的升序排列
- 按照 EGL_ALPHA_MASK_SIZE 的值排序(這僅適用於 OpenVG)
- 按照 EGL_NATIVE_VISUAL_TYPE,以實現相關的方式排序
- 按照 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 如果出現以下情況,則出現該錯誤:
- 提供的 EGLConfig 不支持 Pbuffer 表面
- Pbuffer 被用作紋理貼圖(EGL_TEXTURE_FORMAT 不是 EGL_NO_TEXTURE),且指定的 EGL_WIDTH 和 EGL_HEIGHT 時無效的紋理尺寸
- 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 發生以下情況時,會產生該錯誤
- 當前未指定上下文
- 上下文的版本與客戶端(手機實體)支持的版本不兼容
- 或者 draw 或 read 未綁定到當前上下文
- draw 或者 read 是由 eglCreatePbufferFromClientBuffer 函數創建的 Pbuffer,但是創建他們的底層客戶端緩沖區正在被創建他們的客戶端 API 使用
- EGL_BAD_CONTEXT 上下文無效,並且上下文不是 EGL_NO_CONTEXT
- EGL_BAD_SURFACE draw 或者 read 無效,並且不是 EGL_NO_SURFACE
- EGL_BAD_MATCH 出現以下情況時,會產生該錯誤:
- 上下文是 EGL_NO_CONTEXT,但是 draw 或者 read 不是 EGL_NO_SURFACE
- draw 或者 read 的其中一個有效,另一個是 EGL_NO_SURFACE
- 上下文不支持在沒有 draw 或者 read 的情況下綁定,而 draw 和 read 都是 EGL_NO_SURFACE
- 如果 draw 和 read 的渲染內容無法同時放入圖形內存
- EGL_BAD_NATIVE_WINDOW 作為 draw 或者 read 的本機窗口不再有效
- EGL_BAD_CURRENT_SURFACE 如果之前調用線程的上下文未清除命令,並且之前的表面不再有效
- EGL_BAD_ALLOC 用於 draw 和 read 的輔助緩沖區無法分配
- EGL_CONTEXT_LOST 發生意外的電源事件
- EGL_NOT_INITIALIZED 如果指定的顯示(EGLDisplay)有效但未初始化,並且存在以下的情形之一:
- context 不是 EGL_NO_CONTEXT
- 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 提供了幾個函數來處理這種同步任務。
-
- 只用 OpenGL ES,可以調用 glFinish 函數來保證所有渲染已經發生。或者是更高級的同步對象和柵欄。
-
- 不止一種 Khronos API。在切換窗口系統原生渲染 API 之前,可能不知道使用的是哪個 API。為此可以調用 eglWaitClient 函數延遲客戶端的執行,直到某個 Khronos API 的所有渲染完成,再切換 API。該函數執行成功時,會返回 EGL_TRUE,失敗時返回 EGL_FALSE,並設置錯誤碼為 EGL_BAD_CURRENT_SURFACE。
-
- 同樣在不止一種 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 的使用的一些基本介紹。
