1. 程式人生 > >OpenGL ES EGL介紹

OpenGL ES EGL介紹

前面已經在android平臺上使用OpenGL ES的API瞭解瞭如何建立3D圖形已經使用FBO渲染到紋理進行一些其他的操作,起初我學習OpenGL ES的目的就是為了研究Android平臺上錄製螢幕的方案。到目前為止,基礎知識已經具備了,還差一點需要了解的是Embedded Graphics Library (EGL),EGL是連線OpenGL ES和本地視窗系統的介面,由於OpenGL ES是跨平臺的,引入EGL就是為了遮蔽不同平臺上的區別。本地視窗相關的API提供了訪問本地視窗系統的介面,EGL提供了建立渲染表面,接下來OpenGL ES就可以在這個渲染表面上繪製,同時提供了圖形上下文,用來進行狀態管理。更詳細的資訊可以參考

Khronos EGL Registry
OpenGL和EGL.pngOpenGL和EGL.png
下面這幾篇文章有助於對EGL的理解

Using EGL to connect a native window and OpenGL ES
EGL use for Android native OpenGL ES applications
USING OPENGL ES ON WINDOWS DESKTOPS VIA EG

使用EGL一般為下面的順序
1.使用EGL首先必須建立,建立本地視窗系統和OpenGL ES的連線。

EGLDisplay eglDisplay(EGLNativeDisplayType displayId)

EGL提供了平臺無關型別EGLDisplay表示視窗。定義EGLNativeDisplayType是為了匹配原生視窗系統的顯示型別,對於Windows,EGLNativeDisplayType被定義為HDC,對於Linux系統,被定義為Display*型別,對於Android系統,定義為ANativeWindow *型別,為了方便的將程式碼轉移到不同的作業系統上,應該傳入EGL_DEFAULT_DISPLAY,返回與預設原生視窗的連線。如果連線不可用,則返回EGL_NO_DISPLAY。

2.初始化EGL

建立與本地原生視窗的連線後需要初始化EGL,使用函式eglInitialize進行初始化操作。如果 EGL 不能初始化,它將返回EGL_FALSE,並將EGL錯誤碼設定為EGL_BAD_DISPLAY表示制定了不合法的EGLDisplay,或者EGL_NOT_INITIALIZED表示EGL不能初始化。使用函式eglGetError用來獲取最近一次呼叫EGL函數出錯的錯誤程式碼

EGLBoolean eglInitialize(EGLDisplay display, // 建立的EGL連線
                         EGLint *majorVersion, // 返回EGL主機板版本號
EGLint *minorVersion); // 返回EGL次版本號

初始化EGL示例

EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display == EGL_NO_DISPLAY)
{
    // Unable to open connection to local windowing system
}
if(!eglInitialize(display, &majorVersion, &minorVersion))
{
    // Unable to initialize EGL. Handle and recover
}

3.確定可用的渲染表面(Surface)的配置。一旦初始化了EGL,就可以確定可用渲染表面的型別和配置了。

一種方式是使用eglGetConfigs函式獲取底層視窗系統支援的所有EGL表面配置,然後再使用eglGetConfigAttrib依次查詢每個EGLConfig相關的資訊,EGLConfig包含了渲染表面的所有資訊,包括可用顏色、緩衝區等其他特性。

EGLBoolean eglGetConfigs(EGLDisplay display, EGLConfig *configs, EGLint maxReturnConfigs,EGLint *numConfigs);

EGLBoolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, EGLint attribute, EGLint *value)

另一種方式是指定我們需要的渲染表面配置,讓EGL自己選擇一個符合條件的EGLConfig配置。eglChooseChofig呼叫成功返回EGL_TRUE,失敗時返回EGL_FALSE,如果attribList包含了未定義的EGL屬性,或者屬性值不合法,EGL程式碼被設定為EGL_BAD_ATTRIBUTR

EGLBoolean eglChooseChofig(EGLDispay display, // 建立的和本地視窗系統的連線
                           const EGLint *attribList, // 指定渲染表面的引數列表,可以為null
                           EGLConfig *config,   // 呼叫成功,返會符合條件的EGLConfig列表
                           EGLint maxReturnConfigs, //最多返回的符合條件的EGLConfig個數
                           ELGint *numConfigs );  // 實際返回的符合條件的EGLConfig個數

attribList引數在EGL函式中可以為null或者指向一組以EGL_NONE結尾的鍵對值

// we need this config
EGLint attribList[] ={
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_DEPTH_SIZE, 1,
EGL_NONE
};
const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll only accept 10 configs
EGLint numConfigs;
if(!eglChooseConfig(dpy, attribList, configs, MaxConfigs,
&numConfigs))
{
// Something didn't work … handle error situation
}
else
{
// Everything's okay. Continue to create a rendering surface
}

4.建立渲染表面

有了符合條件的EGLConfig後,就可以通過eglCreateWindowSurface函式建立渲染表面。使用這個函式的前提是要使用原生視窗系統提供的API建立一個視窗。eglCreateWindowSurface中attribList一般可以使用null即可。函式呼叫失敗會返回EGL_NO_SURFACE,並設定對應的錯誤碼。

EGLSurface eglCreateWindowSurface(EGLDisplay display,
                                  EGLConfig config, // 前面選好的可用EGLConfig
                                  EGLNatvieWindowType window, // 指定原生視窗
                                  const EGLint *attribList) // 指定視窗屬性列表,可以為null,一般指定渲染所用的緩衝區使用但緩衝或者後臺緩衝,預設為後者。

建立EGL渲染表面示例

EGLRenderSurface window;
EGLint attribList[] =
{
  EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
  EGL_NONE
);
window = eglCreateWindowSurface(dpy, config, window, attribList);
if(window == EGL_NO_SURFACE)
{
switch(eglGetError())
{
  case EGL_BAD_MATCH:
    // Check window and EGLConfig attributes to determine
    // compatibility, or verify that the EGLConfig
    // supports rendering to a window,
  break;
  case EGL_BAD_CONFIG:
      // Verify that provided EGLConfig is valid
  break;
  case EGL_BAD_NATIVE_WINDOW:
      // Verify that provided EGLNativeWindow is valid
  break;
  case EGL_BAD_ALLOC:
      // Not enough resources available. Handle and recover
  break;
}
}

使用eglCreateWindowSurface函式建立在視窗上的渲染表面,此外還可以使用eglCreatePbufferSurface建立螢幕外渲染表面(Pixel Buffer 畫素緩衝區)。使用Pbuffer一般用於生成紋理貼圖,不過該功能已經被FrameBuffer替代了,使用幀緩衝物件的好處是所有的操作都由OpenGL ES來控制。使用Pbuffer的方法和前面建立視窗渲染表面一樣,需要改動的地方是在選取EGLConfig時,增加EGL_SURFACE_TYPE引數使其值包含EGL_PBUFFER_BIT。而該引數預設值為EGL_WINDOW_BIT。

EGLSurface eglCreatePbufferSurface( EGLDisplay display,
                                   EGLConfig config,
                                   EGLint const * attrib_list // 指定畫素緩衝區屬性列表
                                  );

5.建立渲染上下文

使用eglCreateContext為當前的渲染API建立EGL渲染上下文,返回一個上下文,當前的渲染API是由函式eglBindAPI設定的。OpenGL ES是一個狀態機,用一系列變數描述OpenGL ES當前的狀態如何執行,我們通常使用如下途徑去更改OpenGL狀態:設定選項,操作緩衝。最後,我們使用當前OpenGL上下文來渲染。比如我想告訴OpenGL ES接下來要繪製三角形,可以通過一些上下文變數來改變OpenGL ES的狀態,一旦改變了OpenGL ES的狀態為繪製三角形,下一個命令就會畫出三角形。通過這些狀態設定函式就會改變上下文,接下來的操作總會根據當前上下文的狀態來執行,除非再次重新改變狀態。

// 設定當前的渲染API
EGLBoolean eglBindAPI(
  EGLenum api //可選 EGL_OPENGL_API, EGL_OPENGL_ES_API, or EGL_OPENVG_API
);

EGLContext eglCreateContext(EGLDisplay display, 
                            EGLConfig config, // 前面選好的可用EGLConfig
                            EGLContext shareContext, // 允許多個EGLContext共享特定型別的資料,傳遞EGL_NO_CONTEXT表示不與其他上下文共享資源
                            const EGLint* attribList // 指定操作的屬性列表,只能接受一個屬性EGL_CONTEXT_CLIENT_VERSION用來表示使用的OpenGL ES版本
                           );

建立上下文示例

const ELGint attribList[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE
};
EGLContext context;
context = eglCreateContext(dpy, config, EGL_NO_CONTEXT, attribList);
if(context == EGL_NO_CONTEXT)
{
  EGLError error = eglGetError();
  if(error == EGL_BAD_CONFIG)
  {
    // Handle error and recover
  }
}

6.指定某個EGLContext為當前上下文。使用eglMakeCurrent函式進行當前上下文的繫結。一個程式可能建立多個EGLContext,所以需要關聯特定的EGLContext和渲染表面,一般情況下兩個EGLSurface引數設定成一樣的。

EGLBoolean eglMakeCurrent(EGLDisplay display,
                          EGLSurface draw, // EGL繪圖表面
                          EGLSurface read, // EGL讀取表面
                          EGLContext context // 指定連線到該表面的渲染上下文
                         );

7.使用OpenGL相關的API進行繪製操作。
8.交換EGL的Surface的內部緩衝和EGL建立的和平臺無關的視窗diaplay。EGL實際上維護了兩個buffer,前臺buffer顯示的時候,繪製操作會在後臺buffer上進行。

EGLBoolean eglSwapBuffers(EGLDisplay display, // 指定的EGL和本地視窗的連線
                          EGLSurface surface  // 指定要交換緩衝的EGL繪製表面
                         );

At the first time I learned this function, I was thinking that its purpose is swapping the display and the surface :)) very silly.
Actually, the only thing that you need to focus here is the surface. If the surface is a pixel buffer surface, then nothing to do, the function returns without any error.
If the surface is a double-buffer surface (you often use this), the function will swap the front-buffer and the back-buffer inside the surface. The back-buffer is used to store output of rendering, while front-buffer is used by the native window to show color on your monitors.

如果surface是一個window surface,那麼該函式執行的結果將是將資料給本地視窗,即顯示在螢幕上。如果surface是一個螢幕外渲染surface(pixel buffer),執行該函式沒有效果。

可以看到想要使用OpenGL是比較麻煩的,而這些操作又是固定的,如果把大量的程式碼用在做這些重複的操作上,對於學習OpenGL來說是不必要的,因此就有了一些框架,封裝好了建立EGL環境以及處理事件部分,只需要將重點放在OpenGL的學習上即可。比如C語言庫GLFW,還有OpenGL ES3.0程式設計指南的作者封裝的庫https://github.com/danginsburg/opengles3-book/,esMain函式作為入口,其他的處訊息迴圈,建立視窗等操作不需要我們去處理,只需要關注OpenGL程式碼的編寫即可。在Android系統中提供的GLSurfaceView可以很方便的用來使用OpenGL ES進行編碼,其實也是因為Android的GUI系統將建立EGL環境等部分封裝好了。