前言
這篇文章簡(jiǎn)單介紹一下在A(yíng)ndroid平臺(tái)下的EGL環(huán)境的相關(guān)內(nèi)容,由于OpenGL ES并不負(fù)責(zé)窗口管理以及上下文管理,該職責(zé)由各個(gè)平臺(tái)自行完成;在A(yíng)ndroid平臺(tái)下OpenGL ES的上下文環(huán)境是依賴(lài)EGL的API進(jìn)行搭建的。
對(duì)于EGL這個(gè)框架,谷歌已經(jīng)提供了GLSurfaceView,是一個(gè)已經(jīng)封裝EGL相關(guān)處理的工具類(lèi),但是不夠靈活;對(duì)于更加核心的OpengGL ES的用法(例如多線(xiàn)程共享紋理)則需要開(kāi)發(fā)者自行搭建EGL開(kāi)發(fā)環(huán)境。
按照慣例先上一份源碼 AndroidVideo。
Java相關(guān)核心實(shí)現(xiàn)在 EglBase14.java 和 EglBase10.java。
Native相關(guān)實(shí)現(xiàn),可以參考 egl_base.cpp。
前置知識(shí)
Java層實(shí)現(xiàn)
在Java層,EGL封裝了兩套框架,分別是:
- 位于
javax.microedition.khronos.egl包下的EGL10。 - 位于
android.opengl包下的EGL14。
其主要區(qū)別是:
-
EGL14是在A(yíng)ndroid 4.2(API 17)引入的,換言之API 17以下的版本不支持EGL14。 -
EGL10不支持OpenGL ES 2.x,因此在EGL10中某些相關(guān)常量參數(shù)只能用手寫(xiě)硬編碼代替,例如EGL14.EGL_CONTEXT_CLIENT_VERSION以及EGL14.EGL_OPENGL_ES2_BIT等等。
PS:由于主體流程基本一致,所以本篇以EGL14的代碼進(jìn)行示例。
Native層實(shí)現(xiàn)
程序在Native層使用EGL環(huán)境時(shí)。
需要引入EGL的so庫(kù):
Android.mk:
LOCAL_LDLIBS += -lEGL
CMake:
find_library( EGL-lib
EGL )
需要包含頭文件:
#include <EGL/egl.h>
#include <EGL/eglext.h>
EGL環(huán)境配置整體流程
- 獲取默認(rèn)的
EGLDisplay。 - 對(duì)
EGLDisplay進(jìn)行初始化。 - 輸入預(yù)設(shè)置的參數(shù)獲取
EGL支持的EGLConfig。 - 通過(guò)
EGLDisplay和EGLConfig創(chuàng)建一個(gè)EGLContext上下文環(huán)境。 - 創(chuàng)建一個(gè)
EGLSurface來(lái)連接EGL和設(shè)備的屏幕。 - 在渲染線(xiàn)程綁定
EGLSurface和EGLContext。 - 【進(jìn)行
OpenGL ES的API渲染步驟】(與EGL無(wú)關(guān)) - 調(diào)用
SwapBuffer進(jìn)行雙緩沖切換顯示渲染畫(huà)面。 - 釋放
EGL相關(guān)資源EGLSurface、EGLContext、EGLDisplay。
獲取顯示設(shè)備
首先,EGL是需要知道繪制內(nèi)容的目標(biāo)在哪里,EGLDisplay是一個(gè)封裝了物理屏幕的數(shù)據(jù)類(lèi)型,也可以理解為繪制目標(biāo)的一個(gè)抽象。
通常通過(guò)eglGetDisplay()方法返回EGLDisplay來(lái)作為OpenGL ES的渲染目標(biāo),在該方法中,一般來(lái)說(shuō)都會(huì)將常量EGL_DEFAULT_DISPLAY傳進(jìn)方法中,而各個(gè)手機(jī)廠(chǎng)商則會(huì)返回默認(rèn)的顯示設(shè)備。
java代碼:
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
//需判斷是否成功獲取EGLDisplay
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("Unable to get EGL14 display");
}
c代碼:
EGLDisplay egl_display;
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//需判斷是否成功獲取EGLDisplay
if (egl_display == EGL_NO_DISPLAY)
return error;
一般來(lái)說(shuō),我們需要驗(yàn)證eglGetDisplay()的返回值,如果是EGL_NO_DISPLAY的話(huà),那么就是沒(méi)有獲取默認(rèn)顯示設(shè)備,需要返回給客戶(hù)端上層處理異常。
當(dāng)我們獲取到了EGLDisplay,我們需要調(diào)用eglInitialize()對(duì)其進(jìn)行初始化,該方法會(huì)返回一個(gè)bool變量代表執(zhí)行是否成功。
java代碼:
int version[] = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
throw new RuntimeException("Unable to initialize EGL14");
}
c代碼:
EGLint major, minor;
if (!eglInitialize(egl_display, &major, &minor))
return error;
方法的后面參數(shù)代表Major和Minor的版本,比如EGL的版本號(hào)是1.0,那么Major將返回1,Minor返回0。
如果不關(guān)心版本號(hào),這兩個(gè)參數(shù)可以傳入NULL。
配置輸出格式
當(dāng)我們獲取到EGLDisplay后,其實(shí)已經(jīng)可以將OpenGL ES的輸出與設(shè)備的屏幕橋接起來(lái)了,但是還是需要指定一些配置項(xiàng),例如色彩格式、像素格式、RGBA的表示以及SurfaceType等,實(shí)際上也就是指FrameBuffer的配置參數(shù)。
一般來(lái)說(shuō)不同平臺(tái)的EGL標(biāo)準(zhǔn)是不同的,以下是Android平臺(tái)一個(gè)比較通用的配置參數(shù)(Java的就不列舉了):
const EGLint config_attribs[] = {
EGL_BUFFER_SIZE, 32, //顏色緩沖區(qū)中所有組成顏色的位數(shù)
EGL_ALPHA_SIZE, 8, //顏色緩沖區(qū)中透明度位數(shù)
EGL_BLUE_SIZE, 8, //顏色緩沖區(qū)中藍(lán)色位數(shù)
EGL_GREEN_SIZE, 8, //顏色緩沖區(qū)中綠色位數(shù)
EGL_RED_SIZE, 8, //顏色緩沖區(qū)中紅色位數(shù)
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //渲染窗口支持的布局組成
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, //EGL 窗口支持的類(lèi)型
EGL_NONE
};
PS:EGL的參數(shù)配置一般都以 id,value 依次存放,對(duì)于個(gè)別的屬性可以只有 id 沒(méi)有 value ,并以EGL_NONE標(biāo)識(shí)結(jié)尾信息。
最終可以通過(guò)調(diào)用eglChooseConfig()方法得到配置選項(xiàng)信息:
java代碼:
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
//檢測(cè)返回值是否成功
if (!EGL14.eglChooseConfig(eglDisplay, configAttributes, 0, configs, 0, configs.length,
numConfigs, 0)) {
throw new RuntimeException("eglChooseConfig failed");
}
//如果沒(méi)有配置的Config
if (numConfigs[0] < 0) {
throw new RuntimeException("Unable to find any matching EGL config");
}
EGLConfig eglConfig = configs[0];
//對(duì)應(yīng)的Config不存在
if (eglConfig == null) {
throw new RuntimeException("eglChooseConfig returned null");
}
c代碼:
EGLint num_config;
EGLConfig egl_config;
//檢測(cè)返回值是否成功
if (!eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_config))
return error;
//如果沒(méi)有配置的Config
if (num_config < 0)
return error;
//對(duì)應(yīng)的Config不存在
if (_egl_config == NULL)
return error;
簡(jiǎn)單看一下函數(shù)原型:eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,EGLint *num_config)可以知道:
第2個(gè)參數(shù)attrib_list指的是配置參數(shù)列表,也就是上面的config_attribs[];
第3個(gè)參數(shù)configs返回輸出的EGLConfigs數(shù)據(jù),可能有多個(gè);
第4個(gè)參數(shù)config_size則表示最多需要輸出多少個(gè)EGLConfig;
第5個(gè)參數(shù)num_config則代表滿(mǎn)足配置參數(shù)的EGLConfig的個(gè)數(shù)。
附帶一個(gè)EGLConfig屬性表格:
| 屬性 | 描述 | 默認(rèn)值 |
|---|---|---|
| EGL_BUFFER_SIZE | 顏色緩沖區(qū)中所有組成顏色的位數(shù) | 0 |
| EGL_RED_SIZE | 顏色緩沖區(qū)中紅色位數(shù) | 0 |
| EGL_GREEN_SIZE | 顏色緩沖區(qū)中綠色位數(shù) | 0 |
| EGL_BLUE_SIZE | 顏色緩沖區(qū)中藍(lán)色位數(shù) | 0 |
| EGL_LUMINANCE_SIZE | 顏色緩沖區(qū)中亮度位數(shù) | 0 |
| EGL_ALPHA_SIZE | 顏色緩沖區(qū)中透明度位數(shù) | 0 |
| EGL_ALPHA_MASK_SIZE | 遮擋緩沖區(qū)透明度掩碼位數(shù) | 0 |
| EGL_BIND_TO_TEXTURE_RGB | 綁定到 RGB 貼圖使能為真 | EGL_DONT_CARE |
| EGL_BIND_TO_TEXTURE_RGBA | 綁定到 RGBA 貼圖使能為真 | EGL_DONT_CARE |
| EGL_COLOR_BUFFER_TYPE | 顏色緩沖區(qū)類(lèi)型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER | EGL_RGB_BUFFER |
| EGL_CONFIG_CAVEAT | 配置有關(guān)的警告信息 | EGL_DONT_CARE |
| EGL_CONFIG_ID | 唯一的 EGLConfig 標(biāo)示值 | EGL_DONT_CARE |
| EGL_CONFORMANT | 使用EGLConfig 創(chuàng)建的上下文符合要求時(shí)為真 | — |
| EGL_DEPTH_SIZE | 深度緩沖區(qū)位數(shù) | 0 |
| EGL_LEVEL | 幀緩沖區(qū)水平 | 0 |
| EGL_MAX_PBUFFER_WIDTH | 使用EGLConfig 創(chuàng)建的PBuffer的最大寬度 | — |
| EGL_MAX_PBUFFER_HEIGHT | 使用EGLConfig 創(chuàng)建的PBuffer最大高度 | — |
| EGL_MAX_PBUFFER_PIXELS | 使用EGLConfig 創(chuàng)建的PBuffer最大尺寸 | — |
| EGL_MAX_SWAP_INTERVAL | 最大緩沖區(qū)交換間隔 | EGL_DONT_CARE |
| EGL_MIN_SWAP_INTERVAL | 最小緩沖區(qū)交換間隔 | EGL_DONT_CARE |
| EGL_NATIVE_RENDERABLE | 如果操作系統(tǒng)渲染庫(kù)能夠使用EGLConfig 創(chuàng)建渲染渲染窗口 | EGL_DONT_CARE |
| EGL_NATIVE_VISUAL_ID | 與操作系統(tǒng)通訊的可視ID句柄 | EGL_DONT_CARE |
| EGL_NATIVE_VISUAL_TYPE | 與操作系統(tǒng)通訊的可視ID類(lèi)型 | EGL_DONT_CARE |
| EGL_RENDERABLE_TYPE | 渲染窗口支持的布局組成標(biāo)示符的遮擋位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that | EGL_OPENGL_ES_BIT |
| EGL_SAMPLE_BUFFERS | 可用的多重采樣緩沖區(qū)位數(shù) | 0 |
| EGL_SAMPLES | 每像素多重采樣數(shù) | 0 |
| EGL_S TENCIL_SIZE | 模板緩沖區(qū)位數(shù) | 0 |
| EGL_SURFACE_TYPE | EGL 窗口支持的類(lèi)型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT | EGL_WINDOW_BIT |
| EGL_TRANSPARENT_TYPE | 支持的透明度類(lèi)型 | EGL_NONE |
| EGL_TRANSPARENT_RED_VALUE | 透明度的紅色解釋 | EGL_DONT_CARE |
| EGL_TRANSPARENT_GRE EN_VALUE | 透明度的綠色解釋 | EGL_DONT_CARE |
| EGL_TRANSPARENT_BLUE_VALUE | 透明度的蘭色解釋 | EGL_DONT_CARE |
創(chuàng)建EGL上下文環(huán)境
當(dāng)拿到EGLDisplay和EGLConfig后,就可以開(kāi)始創(chuàng)建EGL的上下文環(huán)境EGLContext了。
EGLContext的存在是因?yàn)?code>OpenGL ES所創(chuàng)建的資源對(duì)于開(kāi)發(fā)者來(lái)說(shuō)可見(jiàn)的僅僅只是一個(gè) ID 而已,而其實(shí)際內(nèi)容依賴(lài)于這個(gè)上下文。
一個(gè)EGLContext只能在一個(gè)線(xiàn)程中使用,如果將EGLContext所持有的OpengGL資源在多線(xiàn)程間共享,那么需要用到共享上下文(share context)。
簡(jiǎn)單看下EGLContext的創(chuàng)建代碼:
java代碼:
//指定OpenGL ES2版本
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
//創(chuàng)建EGLContext上下文
EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, null, contextAttributes, 0);
//需要檢測(cè)Context是否存在
if (eglContext == EGL14.EGL_NO_CONTEXT) {
throw new RuntimeException("Failed to create EGL context");
}
c代碼:
EGLContext egl_context;
//指定OpenGL ES2版本
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
//創(chuàng)建EGLContext上下文
egl_context = eglCreateContext(egl_display, egl_config, NULL, context_attribs);
//需要檢測(cè)Context是否存在
if (egl_context == EGL_NO_CONTEXT)
return error;
函數(shù)eglCreateContext()的第三個(gè)參數(shù)可以傳入一個(gè)EGLContext的變量,該變量的意義是指可以與正在創(chuàng)建的上下文環(huán)境共享OpenGL ES資源,包括紋理、FrameBuffer以及其他的Buffer等資源。
如果傳入NULL代表不需要與其他的OpenGL ES上下文共享任何資源。
連接EGL和設(shè)備屏幕
當(dāng)我們需要將EGL跟設(shè)備的屏幕橋接起來(lái)時(shí),我們需要用到EGLSurface讓EGL有一個(gè)“橋”的功能,從而使得OpenGL ES的輸出可以渲染到設(shè)備屏幕上;
EGLSurface其實(shí)就是一個(gè)FrameBuffer,通過(guò)EGL庫(kù)提供的eglCreateWindowSurface()可以創(chuàng)建一個(gè)可實(shí)際顯示的Surface;也可以通過(guò)EGL庫(kù)提供的eglCreatePbufferSurface()方法創(chuàng)建一個(gè)OffScreen的Surface。
java代碼:
//創(chuàng)建可顯示的Surface
EGLSurface eglSurface;
int[] surfaceAttribs = {EGL14.EGL_NONE};
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);
if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create window surface");
}
這里需要強(qiáng)調(diào)一點(diǎn)的是eglCreateWindowSurface()的第三個(gè)入?yún)?code>surface雖然可以傳入Object;但是在EGL14中只支持Surface和SurfaceTexture,在EGL10中只支持SurfaceHolder和SurfaceTexture。
c代碼:
//創(chuàng)建可顯示的Surface
EGLSurface egl_surface;
const EGLint attribs[] = { EGL_NONE };
egl_surface = eglCreateWindowSurface(egl_display, egl_config, window, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
在Native層,eglCreateWindowSurface()的第三個(gè)入?yún)?code>window是需要傳入一個(gè)ANativeWindow對(duì)象,也就是本地設(shè)備屏幕的表示。
我們可以通過(guò)Surface(由SurfaceView或者TextureView獲得或者構(gòu)建出來(lái)的Surface對(duì)象)來(lái)構(gòu)建ANativeWindow。
需要引入頭文件:
#include <android/native_window.h>
#include <android/native_window_jni.h>
獲取ANativeWindow的代碼如下:
//surface也就是一個(gè)jobject,對(duì)應(yīng)java層的Surface。
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
如果我們要做離屏渲染的話(huà),就需要用到離屏處理的Surface,也就是創(chuàng)建一個(gè)PBufferSurface,PBufferSurface的保存位置是在顯存中的幀,具體代碼可以參考。
java代碼:
//創(chuàng)建離屏Surface
EGLSurface eglSurface;
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0);
if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create pixel buffer surface");
}
c代碼:
//創(chuàng)建離屏Surface
EGLSurface egl_surface;
const EGLint attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE
};
egl_surface = eglCreatePbufferSurface(egl_display, egl_config, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
另外說(shuō)一點(diǎn),EGLSurface還支持參數(shù)的查詢(xún)與設(shè)置,例如我們想知道新創(chuàng)建的Surface的寬高,那么可以用到下面的方法。
java代碼:
//查詢(xún)Surface的width
int[] array = new int[1];
EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, array, 0);
//設(shè)置Surface的width
if (!EGL14.eglSurfaceAttrib(eglDisplay, eglSurface, EGL14.EGL_WIDTH, 600))
throw new RuntimeException("eglSurfaceAttrib fail");
c代碼:
//查詢(xún)Surface的width
EGLint value;
eglQuerySurface(_egl_display, _egl_surface, EGL_WIDTH, &value);
//設(shè)置Surface的width
if (!eglSurfaceAttrib(_egl_display, _egl_surface, EGL_WIDTH, 600))
return error;
EGL變量與線(xiàn)程的綁定
一般來(lái)說(shuō),開(kāi)發(fā)者需要為OpenGL ES開(kāi)辟一個(gè)新的線(xiàn)程,來(lái)執(zhí)行渲染操作,并且需要為該線(xiàn)程綁定顯示設(shè)備EGLSurface和上下文環(huán)境EGLContext。
每個(gè)線(xiàn)程都需要綁定一個(gè)上下文,才可以開(kāi)始執(zhí)行OpenGL ES指令,我們可以通過(guò)eglMakeCurrent來(lái)為該線(xiàn)程綁定Surface和Context,值得注意一點(diǎn)的是一個(gè)EGLContext只能綁定到一個(gè)線(xiàn)程上面。
java代碼:
if (!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) {
throw new RuntimeException("detachCurrent failed");
}
c代碼:
if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context))
return error;
可以通過(guò)返回值來(lái)判斷eglMakeCurrent()是否成功,這個(gè)也是必要的。
OpenGL的渲染
涉及到OpenGl ES的繪圖API和紋理相關(guān)方面的知識(shí),本篇這里不做介紹,請(qǐng)關(guān)注后續(xù)相關(guān)文章。
雙緩沖機(jī)制
EGL在初始化的時(shí)候默認(rèn)設(shè)置的是雙緩沖模式,也就是兩份FrameBuffer;
即一份緩沖用于繪制圖像,一份緩沖用于顯示圖像,每次顯示時(shí)需要交換兩份緩沖。
我們需要在OpenGL ES繪制完畢后,調(diào)用
eglSwapBuffers(egl_display, egl_surface);
將前臺(tái)的FrameBuffer和后臺(tái)FrameBuffer進(jìn)行交換。
EGL的資源釋放
當(dāng)然,EGL在我們不需要的時(shí)候也是需要進(jìn)行釋放的。
我們需要將線(xiàn)程跟EGL環(huán)境解除綁定:
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
然后要銷(xiāo)毀EGLSurface:
eglDestroySurface(egl_display, egl_surface);
接著清理掉上下文環(huán)境:
eglDestroyContext(egl_display, egl_context);
最終關(guān)閉掉顯示設(shè)備:
eglTerminate(egl_display);
通過(guò)上面也就是完成了一個(gè)EGL的資源釋放工作。
最后說(shuō)一些
在A(yíng)ndroid平臺(tái)上面,當(dāng)程序切到后臺(tái)的時(shí)候,需要釋放EGL環(huán)境,在應(yīng)用挪回前臺(tái)時(shí),重新初始化相關(guān)環(huán)境。
不過(guò)谷歌提供了一個(gè)已經(jīng)封裝完善的GLSurfaceView提供OpenGL ES的繪制環(huán)境,普通需求下采用GLSurfaceView進(jìn)行繪制也是一個(gè)不錯(cuò)的選擇。
有興趣可以讀讀這篇文章:源碼解析:Android源碼GLSurfaceView源碼解析。
最后放一個(gè)EGL的官方文檔地址。
結(jié)語(yǔ)
這篇文章簡(jiǎn)單介紹了一下Android平臺(tái)下的EGL環(huán)境的相關(guān)內(nèi)容,并提供了基于java層EGL14和native層的EGL相關(guān)API的使用示例。
后續(xù)文章將介紹怎么在EGL環(huán)境下進(jìn)行OpenGL ES相關(guān) API的一些使用。
本文同步發(fā)布于簡(jiǎn)書(shū)、CSDN。
End!