作者:星隕
來源:
音視訊開發進階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 這些抽象概念傻傻分不清楚,可以參考這幅圖:

其中:
- 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 系列文章「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。