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