學習OpenGL-ES: 2 - EGL解析

原文:http://www.cnblogs.com/kiffa/archive/2013/02/21/2921123.html


1, 前言

在前文(學習OpenGL-ES: 1 - 像素、顏色、顯存、環境初始化和EGL)中提到EGL是本地平臺和OpenGL ES之間的抽象層,其完成了本地相關的環境初始化和上下文控制工作,以保證OpenGL ES的平臺無關性。主要包含如下工作:

a,選擇顯示設備

b, 選擇像素格式。

c, 選擇某些特性,比如如果你打算畫中國水墨畫,你需要額外指定宣紙和毛筆。 

d, 申請顯存。 

e, 創建上下文(Context),上下文本質上是一組狀態的集合,描述了在某個特定時刻系統的狀態, 用於處理暫停、恢復、銷燬、重建等情況;

f, 指定當前的環境爲繪製環境 。

總體流程上,EGL按順序分爲若干步驟:

1, 選擇顯示設備display,即上述的a.

2,指定特性,包括上述的像素格式(b)和特定特性(c),根據指定的特性來獲取多個滿足這些特性的config(比如你指定RGB中的R爲5bits,那麼可能會有RGB_565和RGB_555兩種像素格式均滿足此特性),用戶從這些可用的configs中選擇一個,根據display和config獲取繪製用的buffer(一般爲顯存),即上述的d。

3,使用display、config、buffer來創建context,及即上述的e.

4, 使用display、buffer、context 設置當前的渲染環境,即上述的f.

本文將以Android下EGL的使用爲例逐一進行講解。

2,選擇顯示設備及確認EGL版本

EGL有1.0、1.1、1.2、1.3、1.4這幾個版本,Android中使用的是1.4,EGL提供了查詢版本的API,以下爲Android中例子:

複製代碼
    EGL10 egl = (EGL10) EGLContext.getEGL();
    EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); //獲取顯示設備
   
    // Init
    int[] version = new int[2];
    egl.eglInitialize(display, version); //version中存放EGL 版本號,int[0]爲主版本號,int[1]爲子版本號

    String vendor = egl.eglQueryString(display, EGL10.EGL_VENDOR);
    WLog.d("egl vendor: " + vendor); // 打印此版本EGL的實現廠商

    String version = egl.eglQueryString(display, EGL10.EGL_VERSION);
    WLog.d("egl version: " + version);// 打印EGL版本號

    String extension = egl.eglQueryString(display, EGL10.EGL_EXTENSIONS);
    WLog.d("egl extension: " + extension); //打印支持的EGL擴展
複製代碼

 

說明:

1,雖然Android使用(實現)的是EGL 1.4(從打印的版本號中可見), 但在Android 4.2(API 17)以前的版本沒有EGL14,只有EGL10和EGL11,而這兩個版本是不支持OpengGL ES 2.x的,因此在老版本中某些ES 2.x相關的常量參數只能用手寫的硬編碼代替,典型的如設定EGL渲染類型API的參數EGL10.EGL_RENDERABLE_TYPE,這個屬性用不同的賦值指定的不同的渲染API,包括OpenGL,OpenGL ES 1.x, OpenGL ES 2.x,OpenVG等,如果採用ES 2.0,應該設置此值爲: EGL14.EGL_OPENGL_ES2_BIT,但是在Android 4.2之前,沒有EGL14接口,只能採取手寫的硬編碼來指定,類似: EGL_RENDERABLE_TYPE = 4;

2,egl.eglQueryString()用來查詢EGL的相關信息,詳見這裏:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/

3,EGL10.EGL_DEFAULT_DISPLAY 默認對應手機主屏幕。

3,指定(buffer)特性,獲取config

1,構造需要的特性列表

複製代碼
int[] attributes = new int[] { 
        EGL10.EGL_RED_SIZE, 8,  //指定RGB中的R大小(bits)
        EGL10.EGL_GREEN_SIZE, 8, //指定G大小
        EGL10.EGL_BLUE_SIZE, 8,  //指定B大小
        EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四項實際上指定了像素格式
        EGL10.EGL_DEPTH_SIZE, 16, //指定深度緩存(Z Buffer)大小
        EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api類別, 如上一小節描述,這裏或者是硬編碼的4,或者是EGL14.EGL_OPENGL_ES2_BIT 
        EGL10.EGL_NONE };  //總是以EGL10.EGL_NONE結尾
複製代碼

 2, 獲取所有可用的configs,每個config都是EGL系統根據特定規則選擇出來的最符合特性列表要求的一組特性。

 

    EGLConfig config = null;
複製代碼
int[] configNum = new int[1];
    //獲取滿足attributes的config個數。
    egl.eglChooseConfig(display, attributes, null, 0, configNum);
    int num = configNum[0];
    if(num != 0){
        EGLConfig[] configs = new EGLConfig[num];
        //獲取所有滿足attributes的configs
        egl.eglChooseConfig(display, attributes, configs, num, configNum);
        config = configs[0]; //以某種規則選擇一個config,這裏使用了最簡單的規則。      
    }
複製代碼

說明:

1,display和attributes都來自之前的步驟。

2,eglChooseConfig(display, attributes, configs, num, configNum); 用於獲取滿足attributes的所有config,參數1、2其意明顯,參數3用於存放輸出的configs,參數4指定最多輸出多少個config,參數5由EGL系統寫入,表明滿足attributes的config一共有多少個。如果使用eglChooseConfig(display, attributes, null, 0, configNum)這種形式調用,則會在configNum中輸出所有滿足條件的config個數。

3,一般習慣是獲取所有滿足attributes的config個數,再據此分配存放config的數組,獲取所有config,根據某種特定規則,從中選擇其一。

4,API詳細說明和所有可指定的attributes見這裏:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/

5,打印config中的常用attributes:

複製代碼
  /**
   * 打印EGLConfig信息
   * 
   * @param egl
   * @param display
   * @param config
   *          : 指定的EGLConfig
   */
  public static void printEGLConfigAttribs(EGL10 egl, EGLDisplay display, EGLConfig config) {
    int value = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, -1);
    WLog.d("eglconfig: EGL_RED_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, -1);
    WLog.d("eglconfig: EGL_GREEN_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, -1);
    WLog.d("eglconfig: EGL_BLUE_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, -1);
    WLog.d("eglconfig: EGL_ALPHA_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, -1);
    WLog.d("eglconfig: EGL_DEPTH_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, -1);
    WLog.d("eglconfig: EGL_RENDERABL_TYPE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLE_BUFFERS, -1);
    WLog.d("eglconfig: EGL_SAMPLE_BUFFERS: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLES, -1);
    WLog.d("eglconfig: EGL_SAMPLES: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, -1);
    WLog.d("eglconfig: EGL_STENCIL_SIZE: " + value);
  }


 /**
   * 在指定EGLConfig中查找指定attrib的值,如果沒有此屬性,返回指定的默認值
   * 
   * @param egl
   * @param display
   * @param config
   *          : 指定的EGLConfig
   * @param attribute
   *          : 指定的attrib
   * @param defaultValue
   *          : 查找失敗時返回的默認值
   * @return: 查找成功,返回查找值;查找失敗,返回參數中指定的默認值
   */
  static public int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
      int attribute, int defaultValue) {
    int[] val = new int[1];
    if (egl.eglGetConfigAttrib(display, config, attribute, val)) {
      return val[0];
    }
    return defaultValue;
  }
複製代碼

 

4, 獲取顯存

EGLSurface surface = egl.eglCreateWindowSurface(display, config, surfaceHolder, null);

 說明:

 1,詳細的參數說明見這裏:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/

 2,參數surfaceHolder是android.view.SurfaceHolder類型,負責對Android Surface的管理,後續將對此進行較詳細說明,參看第8小節。

3,參數4用於描述WindowSurface類型,初始化方式如同前面小節的egl attributes, 其中一個attribute是EGL_RENDER_BUFFER, 用於描述渲染buffer(所有的繪製在此buffer中進行)類別,取值爲EGL_SINGLE_BUFFER以及默認的EGL_BACK_BUFFER,前者屬於單緩衝,繪製的同時用戶即可見;後者屬於雙緩衝,前端緩衝用於顯示,OpenGL ES 在後端緩衝中進行繪製,繪製完畢後使用eglSwapBuffers()交換前後緩衝,用戶即看到在後緩衝中的內容,如此反覆。其他attributes見官方文檔。

5, 創建context

 int attrs[] = { 
        EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 
        EGL10.EGL_NONE, };
 EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrs);

說明:

函數原型     EGLContext  eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list);

share_context: 是否有context共享,共享的contxt之間亦共享所有數據。EGL_NO_CONTEXT代表不共享。

attrib_list: 目前可用屬性只有EGL_CONTEXT_CLIENT_VERSION, 1代表OpenGL ES 1.x, 2代表2.0。同樣在Android4.2之前,沒有EGL_CONTEXT_CLIENT_VERSION這個屬性,只能使用硬編碼0x3098代替。

函數詳細描述:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/

6, 設置爲當前的渲染環境

egl.eglMakeCurrent(display, surface, surface, contxt);

比較簡單,不做贅述,詳細描述:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/

7,環境初始化完畢,開始使用OpenGL ES 2.0 API 進行繪製。

// 開始使用OpenGL ES 2.0 API 進行繪製。
GLES20.glClearColor(0, 0, 0, 1);
GLES20.clear(GL_COLOR_BUFFER_BIT);

8,關於SurfaceHolder

一般在Android中使用OpenGL ES,總是會從GLSurfaceView和Renderer開始,但是由上面描述的過程可知,只需要提供一個合適的SurfaceHolder,就可以完成整個環境初始化,並進行繪製。GLSurfaceView和Renderer事實上只是在本文描述的基礎上封裝了一些便利的功能,便於開發者開發,比如渲染同步、狀態控制、主(渲染)循環等。那麼,如何提供一個SurfaceHolder,具體的Surface分配過程又是怎樣的呢,這涉及到Android窗口機制,屬於比較大的話題,將在下一節進行描述。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章