OpenGL ES:Android平臺(tái)EGL環(huán)境

前言

這篇文章簡(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.javaEglBase10.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)境配置整體流程

  1. 獲取默認(rèn)的EGLDisplay。
  2. 對(duì)EGLDisplay進(jìn)行初始化。
  3. 輸入預(yù)設(shè)置的參數(shù)獲取EGL支持的EGLConfig。
  4. 通過(guò)EGLDisplayEGLConfig創(chuàng)建一個(gè)EGLContext上下文環(huán)境。
  5. 創(chuàng)建一個(gè)EGLSurface來(lái)連接EGL和設(shè)備的屏幕。
  6. 在渲染線(xiàn)程綁定EGLSurfaceEGLContext。
  7. 【進(jìn)行OpenGL ES的API渲染步驟】(與EGL無(wú)關(guān))
  8. 調(diào)用SwapBuffer進(jìn)行雙緩沖切換顯示渲染畫(huà)面。
  9. 釋放EGL相關(guān)資源EGLSurface、EGLContextEGLDisplay。

獲取顯示設(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ù)代表MajorMinor的版本,比如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)拿到EGLDisplayEGLConfig后,就可以開(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í),我們需要用到EGLSurfaceEGL有一個(gè)“橋”的功能,從而使得OpenGL ES的輸出可以渲染到設(shè)備屏幕上;
EGLSurface其實(shí)就是一個(gè)FrameBuffer,通過(guò)EGL庫(kù)提供的eglCreateWindowSurface()可以創(chuàng)建一個(gè)可實(shí)際顯示的Surface;也可以通過(guò)EGL庫(kù)提供的eglCreatePbufferSurface()方法創(chuàng)建一個(gè)OffScreenSurface。

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中只支持SurfaceSurfaceTexture,在EGL10中只支持SurfaceHolderSurfaceTexture。

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)程綁定SurfaceContext,值得注意一點(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!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容