天天看點

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 ES EGL介紹

下面這幾篇文章有助于對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, ,
EGL_GREEN_SIZE, ,
EGL_BLUE_SIZE, ,
EGL_DEPTH_SIZE, ,
EGL_NONE
};
const EGLint MaxConfigs = ;
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, ,
    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環境等部分封裝好了。