1, 前言
在前文(學(xué)習(xí)OpenGL-ES: 1 - 像素、顏色、顯存、環(huán)境初始化和EGL)中提到EGL是本地平臺和OpenGL ES之間的抽象層,其完成了本地相關(guān)的環(huán)境初始化和上下文控制工作,以保證OpenGL ES的平臺無關(guān)性。主要包含如下工作:
a,選擇顯示設(shè)備
b, 選擇像素格式。
c, 選擇某些特性,比如如果你打算畫中國水墨畫,你需要額外指定宣紙和毛筆。
d, 申請顯存。
e, 創(chuàng)建上下文(Context),上下文本質(zhì)上是一組狀態(tài)的集合,描述了在某個特定時刻系統(tǒng)的狀態(tài), 用于處理暫停、恢復(fù)、銷毀、重建等情況;
f, 指定當(dāng)前的環(huán)境為繪制環(huán)境 。
總體流程上,EGL按順序分為若干步驟:
1, 選擇顯示設(shè)備display,即上述的a.
2,指定特性,包括上述的像素格式(b)和特定特性(c),根據(jù)指定的特性來獲取多個滿足這些特性的config(比如你指定RGB中的R為5bits,那么可能會有RGB_565和RGB_555兩種像素格式均滿足此特性),用戶從這些可用的configs中選擇一個,根據(jù)display和config獲取繪制用的buffer(一般為顯存),即上述的d。
3,使用display、config、buffer來創(chuàng)建context,及即上述的e.
4, 使用display、buffer、context 設(shè)置當(dāng)前的渲染環(huán)境,即上述的f.
本文將以Android下EGL的使用為例逐一進(jìn)行講解。
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); //獲取顯示設(shè)備 // 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的實(shí)現(xiàn)廠商 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擴(kuò)展
說明:
1,雖然Android使用(實(shí)現(xiàn))的是EGL 1.4(從打印的版本號中可見), 但在Android 4.2(API 17)以前的版本沒有EGL14,只有EGL10和EGL11,而這兩個版本是不支持OpengGL ES 2.x的,因此在老版本中某些ES 2.x相關(guān)的常量參數(shù)只能用手寫的硬編碼代替,典型的如設(shè)定EGL渲染類型API的參數(shù)EGL10.EGL_RENDERABLE_TYPE,這個屬性用不同的賦值指定的不同的渲染API,包括OpenGL,OpenGL ES 1.x, OpenGL ES 2.x,OpenVG等,如果采用ES 2.0,應(yīng)該設(shè)置此值為: EGL14.EGL_OPENGL_ES2_BIT,但是在Android 4.2之前,沒有EGL14接口,只能采取手寫的硬編碼來指定,類似: EGL_RENDERABLE_TYPE = 4;
2,egl.eglQueryString()用來查詢EGL的相關(guān)信息,詳見這里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
3,EGL10.EGL_DEFAULT_DISPLAY 默認(rèn)對應(yīng)手機(jī)主屏幕。
1,構(gòu)造需要的特性列表
int[] attributes = new int[] { EGL10.EGL_RED_SIZE, 8, //指定RGB中的R大?。╞its) EGL10.EGL_GREEN_SIZE, 8, //指定G大小 EGL10.EGL_BLUE_SIZE, 8, //指定B大小 EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四項實(shí)際上指定了像素格式 EGL10.EGL_DEPTH_SIZE, 16, //指定深度緩存(Z Buffer)大小 EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api類別, 如上一小節(jié)描述,這里或者是硬編碼的4,或者是EGL14.EGL_OPENGL_ES2_BIT EGL10.EGL_NONE }; //總是以EGL10.EGL_NONE結(jié)尾
2, 獲取所有可用的configs,每個config都是EGL系統(tǒng)根據(jù)特定規(guī)則選擇出來的最符合特性列表要求的一組特性。
EGLConfig config = null;
int[] configNum = new int[1]; //獲取滿足attributes的config個數(shù)。 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]; //以某種規(guī)則選擇一個config,這里使用了最簡單的規(guī)則。 }
說明:
1,display和attributes都來自之前的步驟。
2,eglChooseConfig(display, attributes, configs, num, configNum); 用于獲取滿足attributes的所有config,參數(shù)1、2其意明顯,參數(shù)3用于存放輸出的configs,參數(shù)4指定最多輸出多少個config,參數(shù)5由EGL系統(tǒng)寫入,表明滿足attributes的config一共有多少個。如果使用eglChooseConfig(display, attributes, null, 0, configNum)這種形式調(diào)用,則會在configNum中輸出所有滿足條件的config個數(shù)。
3,一般習(xí)慣是獲取所有滿足attributes的config個數(shù),再據(jù)此分配存放config的數(shù)組,獲取所有config,根據(jù)某種特定規(guī)則,從中選擇其一。
4,API詳細(xì)說明和所有可指定的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的值,如果沒有此屬性,返回指定的默認(rèn)值 * * @param egl * @param display * @param config * : 指定的EGLConfig * @param attribute * : 指定的attrib * @param defaultValue * : 查找失敗時返回的默認(rèn)值 * @return: 查找成功,返回查找值;查找失敗,返回參數(shù)中指定的默認(rèn)值 */ 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,詳細(xì)的參數(shù)說明見這里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
2,參數(shù)surfaceHolder是android.view.SurfaceHolder類型,負(fù)責(zé)對Android Surface的管理,后續(xù)將對此進(jìn)行較詳細(xì)說明,參看第8小節(jié)。
3,參數(shù)4用于描述WindowSurface類型,初始化方式如同前面小節(jié)的egl attributes, 其中一個attribute是EGL_RENDER_BUFFER, 用于描述渲染buffer(所有的繪制在此buffer中進(jìn)行)類別,取值為EGL_SINGLE_BUFFER以及默認(rèn)的EGL_BACK_BUFFER,前者屬于單緩沖,繪制的同時用戶即可見;后者屬于雙緩沖,前端緩沖用于顯示,OpenGL ES 在后端緩沖中進(jìn)行繪制,繪制完畢后使用eglSwapBuffers()交換前后緩沖,用戶即看到在后緩沖中的內(nèi)容,如此反復(fù)。其他attributes見官方文檔。
5, 創(chuàng)建context
int attrs[] = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE, }; EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrs);
說明:
函數(shù)原型 EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list);
share_context: 是否有context共享,共享的contxt之間亦共享所有數(shù)據(jù)。EGL_NO_CONTEXT代表不共享。
attrib_list: 目前可用屬性只有EGL_CONTEXT_CLIENT_VERSION, 1代表OpenGL ES 1.x, 2代表2.0。同樣在Android4.2之前,沒有EGL_CONTEXT_CLIENT_VERSION這個屬性,只能使用硬編碼0x3098代替。
函數(shù)詳細(xì)描述:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
egl.eglMakeCurrent(display, surface, surface, contxt);
比較簡單,不做贅述,詳細(xì)描述:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
// 開始使用OpenGL ES 2.0 API 進(jìn)行繪制。GLES20.glClearColor(0, 0, 0, 1);GLES20.clear(GL_COLOR_BUFFER_BIT);
一般在Android中使用OpenGL ES,總是會從GLSurfaceView和Renderer開始,但是由上面描述的過程可知,只需要提供一個合適的SurfaceHolder,就可以完成整個環(huán)境初始化,并進(jìn)行繪制。GLSurfaceView和Renderer事實(shí)上只是在本文描述的基礎(chǔ)上封裝了一些便利的功能,便于開發(fā)者開發(fā),比如渲染同步、狀態(tài)控制、主(渲染)循環(huán)等。那么,如何提供一個SurfaceHolder,具體的Surface分配過程又是怎樣的呢,這涉及到Android窗口機(jī)制,屬于比較大的話題,將在下一節(jié)進(jìn)行描述。