天天看點

OpenGL 之 EGL 使用實踐EGL 使用實踐

作者:星隕

來源:

音視訊開發進階

OpenGL 是跨平台的、專業的圖形程式設計接口,而接口的實作是由廠商來完成的。

而當我們使用這組接口完成繪制之後,要把結果顯示在螢幕上,就要用到 EGL 來完成這個轉換工作。

EGL 是 OpenGL ES 渲染 API 和本地視窗系統(native platform window system)之間的一個中間接口層,它也主要由廠商來實作。EGL 提供了如下機制:

  • 與裝置的原生視窗系統通信
  • 查詢繪圖表面的可用類型和配置
  • 建立繪圖表面
  • 在 OpenGL ES 和其他圖形渲染 API 之間同步渲染
  • 管理紋理貼圖等渲染資源

為了讓 OpenGL ES 能夠繪制在目前裝置上,我們需要 EGL 作為 OpenGL ES 與裝置的橋梁。

我們可以直接用 GLSurfaceView 來進行 OpenGL 的渲染,就是因為在 GLSurfaceView 的内部已經完成了對 EGL 的使用封裝,當然我們也可以封裝自己的 EGL 環境。

EGL 使用實踐

EGL 的使用要遵循一些固定的步驟,按照這些步驟去配置、建立、渲染、釋放。

  • 建立與本地視窗系統的連接配接

    ·調用 eglGetDisplay 方法得到 EGLDisplay

  • 初始化 EGL 方法

    ·調用 eglInitialize 方法初始化

  • 确定渲染表面的配置資訊

    ·調用 eglChooseConfig 方法得到 EGLConfig

  • 建立渲染上下文

    ·通過 EGLDisplay 和 EGLConfig ,調用 eglCreateContext 方法建立渲染上下文,得到 EGLContext

  • 建立渲染表面

    ·通過 EGLDisplay 和 EGLConfig ,調用 eglCreateWindowSurface 方法建立渲染表面,得到 EGLSurface

  • 綁定上下文

    ·通過 eglMakeCurrent 方法将 EGLSurface、EGLContext、EGLDisplay 三者綁定,接下來就可以使用 OpenGL 進行繪制了。

  • 交換緩沖

    ·當用 OpenGL 繪制結束後,使用 eglSwapBuffers 方法交換前後緩沖,将繪制内容顯示到螢幕上

  • 釋放 EGL 環境

    ·繪制結束,不再需要使用 EGL 時,取消 eglMakeCurrent 的綁定,銷毀 EGLDisplay、EGLSurface、EGLContext。

如果對 EGLDisplay、EGLSurface 、EGLContext 這些抽象概念傻傻分不清楚,可以參考這幅圖:

OpenGL 之 EGL 使用實踐EGL 使用實踐

其中:

  • Display(EGLDisplay) 是對實際顯示裝置的抽象
  • Surface(EGLSurface)是對用來存儲圖像的記憶體區域
  • FrameBuffer 的抽象,包括 Color Buffer, Stencil Buffer ,Depth Buffer
  • Context (EGLContext) 存儲 OpenGL ES繪圖的一些狀态資訊

使用 EGL 的具體步驟如下:

通過

eglGetDisplay

方法建立與本地視窗系統的連接配接,傳回的是

EGLDisplay

類型對象,可以把它抽象了解成裝置的顯示螢幕。

1    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
2    // 建立與本地視窗系統的連接配接
3    mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
4    // 如果建立之後還是 EGL_NO_DISPLAY ,表示建立失敗
5    if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
6        // failed
7    }           

這一步就可以看成是選擇顯示裝置,一般都是選擇預設的顯示裝置,也就是手機螢幕。

初始化 EGL

建立

EGLDisplay

對象之後,要将它進行初始化。

1    // EGL 的主版本号
2    private int[] mMajorVersion = new int[1];
3    // EGL 的次版本号
4    private int[] mMinorVersion = new int[1];
5    boolean result = EGL14.eglInitialize(mEGLDisplay, mMajorVersion, 0, mMajorVersion, 0);
6    if (!result) {
7        // failed
8    }
初始化的           

時候可以獲得 EGL 的主版本号與次版本号。

确定可用的渲染表面(Surface)配置

把 Surface 看成是一個渲染表面,渲染表面包含一些配置資訊,也就是

EGLConfig

屬性:

比如:

  • EGL_RED_SIZE:顔色緩沖區中紅色用幾位來表示
  • EGL_BLUE_SIZE:顔色緩沖區中藍色用幾位來表示

更多的 EGLConfig 屬性參考這裡:

https://www.jianshu.com/p/9db986365cda

有兩種方式用來确定可用的渲染表面配置。

eglGetConfigs

的方法擷取底層視窗系統支援的所有 EGL 渲染表面配置,再用

eglGetConfigAttrib

查詢每個

EGLConfig

的資訊。

1    public static native boolean eglGetConfigs(
 2        EGLDisplay dpy,
 3        EGLConfig[] configs,
 4        int configsOffset,
 5        int config_size,
 6        int[] num_config,
 7        int num_configOffset
 8    );
 9
10    public static native boolean eglGetConfigAttrib(
11        EGLDisplay dpy,
12        EGLConfig config,
13        int attribute,
14        int[] value,
15        int offset
16    );           

另一種是建立好渲染表面配置清單,通過

eglChooseConfig

的方法,找到符合要求的 EGLConfig 配置。

1    public static native boolean eglChooseConfig(
 2        EGLDisplay dpy,
 3        int[] attrib_list,
 4        int attrib_listOffset,
 5        EGLConfig[] configs,
 6        int configsOffset,
 7        int config_size,
 8        int[] num_config,
 9        int num_configOffset
10    );           

第二種方法相對于第一種來說,不用再查詢每一個 EGLConfig 屬性了。

具體使用如下:

1    // 定義 EGLConfig 屬性配置
 2    private static final int[] EGL_CONFIG = {
 3            EGL14.EGL_RED_SIZE, CFG_RED_SIZE,
 4            EGL14.EGL_GREEN_SIZE, CFG_GREEN_SIZE,
 5            EGL14.EGL_BLUE_SIZE, CFG_BLUE_SIZE,
 6            EGL14.EGL_ALPHA_SIZE, CFG_ALPHA_SIZE,
 7            EGL14.EGL_DEPTH_SIZE, CFG_DEPTH_SIZE,
 8            EGL14.EGL_STENCIL_SIZE, CFG_STENCIL_SIZE,
 9            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
10            EGL14.EGL_NONE,
11    };           

首先定義 EGLConfig 屬性配置數組,定義紅、綠、藍、透明度、深度、模闆緩沖的位數,最後要以

EGL14.EGL_NONE

結尾。

1        // 所有符合配置的 EGLConfig 個數
 2        int[] numConfigs = new int[1];
 3        // 所有符合配置的 EGLConfig
 4        EGLConfig[] configs = new EGLConfig[1];
 5
 6        // configs 參數為 null,會在 numConfigs 中輸出所有滿足 EGL_CONFIG 條件的 config 個數
 7        EGL14.eglChooseConfig(mEGLDisplay, EGL_CONFIG, 0, null, 0, 0, numConfigs, 0);
 8        // 得到滿足條件的個數
 9        int num = numConfigs[0];
10        if (num != 0) {
11            // 會擷取所有滿足 EGL_CONFIG 的 config
12            EGL14.eglChooseConfig(mEGLDisplay, EGL_CONFIG, 0, configs, 0, configs.length, numConfigs, 0);
13            // 去第一個
14            mEGLConfig = configs[0];
15        }           

首先通過給 eglChooseConfig 相應參數設定為 null,找到符合條件的 EGLConfig 個數,如果不為空,則再一次調用,取第一個,就是想要的

EGLConfig

配置。

有了

EGLDisplay

EGLConfig

對象,就可以建立 渲染表面

EGLSurface

和 渲染上下文

EGLContext

在建立渲染上下文時,同樣要建立屬性資訊,主要是指定 OpenGL 使用版本。

1    private static final int[] EGL_ATTRIBUTE = {
2            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
3            EGL14.EGL_NONE,
4    };           

同樣,還是以 EGL14.EGL_NONE 結尾。

1    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
2    // 建立上下文
3    mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, EGL_ATTRIBUTE, 0);
4    if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
5        // failed
6    }           

EGL 提供了兩種方式建立渲染表面,一種是可見的,渲染到螢幕上,一種是不可見的,也就離屏的。

  • eglCreatePbufferSurface:建立離屏的渲染表面
  • eglCreateWindowSurface:建立渲染到螢幕的渲染表面

無論使用哪種建立方式,也都需要建立配置資訊。

1    // 建立渲染表面的配置資訊
2    private static final int[] EGL_SURFACE = {
3            EGL14.EGL_NONE,
4    };
5    private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
6    // surface 參數是有 SurfaceView.getHolder 傳遞過來的
7    mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, EGL_SURFACE, 0);           

對于渲染到螢幕上的建立,配置資訊可以不添加什麼,但還是要以

EGL14.EGL_NONE

而對于離屏的渲染表面建立,就還需要提供寬、高等資訊,但是卻不需要提供

surface

的參數了。

1        // 使用 eglCreatePbufferSurface 就需要指定寬和高
2        int[] EGL_SURFACE = {
3                EGL10.EGL_WIDTH, w,
4                EGL10.EGL_HEIGHT, h,
5                EGL10.EGL_NONE,
6        };
7        mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, EGL_SURFACE,0);           

當有了

EGLDisplay

EGLSurface

EGLContext

對象之後,就可以為

EGLContext

綁定上下文了。

1 EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);           

當綁定上下文之後,就可以執行具體的繪制操作了,調用 OpenGL 相關的方法繪制圖形。

交換緩沖,進行顯示

當繪制結束之後,就該把繪制結果顯示在螢幕上了。

1        EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);           

eglSwapBuffers

方法,将背景繪制的緩沖顯示到前台。

釋放操作

當繪制結束時,要進行相應的釋放操作。

1        EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
2        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
3        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
4        EGL14.eglReleaseThread();
5        EGL14.eglTerminate(mEGLDisplay);
6
7        mEGLContext = EGL14.EGL_NO_CONTEXT;
8        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
9        mEGLConfig = null;           

相當于就是把前面操作的 EGL 相關對象都釋放掉。

當完成這樣的一個流程之後,就基本上掌握了 EGL 的大部分使用。

EGL 操作的很多流程都是固定的,可以把它們再單獨做一層封裝,這裡可以參考 grafika 的封裝。

文章中具體代碼部分,可以參考我的 Github 項目,歡迎 Star 。

https://github.com/glumes/AndroidOpenGLTutorial OpenGL 系列文章
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
OpenGL 之 EGL 使用實踐EGL 使用實踐