前言
這篇文章簡(jiǎn)單介紹一下在Android平臺(tái)下的EGL
環(huán)境的相關(guān)內(nèi)容毙沾,由于OpenGL ES
并不負(fù)責(zé)窗口管理以及上下文管理柑司,該職責(zé)由各個(gè)平臺(tái)自行完成;在Android平臺(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
的用法(例如多線程共享紋理)則需要開(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
是在Android 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è)備的屏幕。 - 在渲染線程綁定
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ī)廠商則會(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
的話涤伐,那么就是沒(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è)線程中使用交播,如果將EGLContext
所持有的OpengGL
資源在多線程間共享重虑,那么需要用到共享上下文(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);
如果我們要做離屏渲染的話,就需要用到離屏處理的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變量與線程的綁定
一般來(lái)說(shuō),開(kāi)發(fā)者需要為OpenGL ES
開(kāi)辟一個(gè)新的線程梯嗽,來(lái)執(zhí)行渲染操作齿尽,并且需要為該線程綁定顯示設(shè)備EGLSurface
和上下文環(huán)境EGLContext
。
每個(gè)線程都需要綁定一個(gè)上下文灯节,才可以開(kāi)始執(zhí)行OpenGL ES
指令循头,我們可以通過(guò)eglMakeCurrent
來(lái)為該線程綁定Surface
和Context
,值得注意一點(diǎn)的是一個(gè)EGLContext
只能綁定到一個(gè)線程上面炎疆。
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)行釋放的碌上。
我們需要將線程跟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ō)一些
在Android平臺(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!