原文: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窗口機制,屬於比較大的話題,將在下一節進行描述。