OpenGL ES總結(六)OpenGL ES中EGL
Agenda:
- EGL是什麼?
- EGL資料型別
- EGL在Android中應用
- EGL的工作流程
- GLSurfaceView與EGL區別
- 簡單Demo
EGL是什麼?
EGL? is an interface between Khronos rendering APIs such as OpenGL ES or OpenVG and the underlying native platform window system.
It handles graphics context management, surface/buffer binding, and rendering synchronization and enables high-performance, accelerated, mixed-mode 2D and 3D rendering using other Khronos APIs.
那麼什麼是EGL?EGL是OpenGL ES和底層的native window system之間的介面,承上啟下。
在Android上,EGL完善了OpenGL ES。利用類似eglCreateWindowSurface的EGL函式可以建立surface 用來render ,有了這個surface你就能往這個surface中利用OpenGL ES函式去畫圖了。OpenGL ES 本質上是一個圖形渲染管線的狀態機,而 EGL 則是用於監控這些狀態以及維護 Frame buffer 和其他渲染 Surface 的外部層。下圖是一個3d遊戲典型的 EGL 系統佈局圖。
EGL資料型別
資料型別 | 值 |
---|---|
EGLBoolean | EGL_TRUE =1, EGL_FALSE=0 |
EGLint | int 資料型別 |
EGLDisplay | 系統顯示 ID 或控制代碼 |
EGLConfig | Surface 的 EGL 配置 |
EGLSurface | 系統視窗或 frame buffer 控制代碼 |
EGLContext | OpenGL ES 圖形上下文 |
NativeDisplayType | Native 系統顯示型別 |
NativeWindowType | Native 系統視窗快取型別 |
NativePixmapType | Native 系統 frame buffer |
EGL在Android中應用
下面是開機動畫BootAnimation中的實現,首先是建立本地環境,
status_t BootAnimation::readyToRun() {
// 建立SurfaceControl
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();
// 設定layerstack
control->setLayer(0x40000000);
SurfaceComposerClient::closeGlobalTransaction();
//獲取Surface
sp<Surface> s = control->getSurface();
// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h, dummy;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
//呼叫eglGetDisplay
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); // 第一步
eglInitialize(display, 0, 0); // 第二步
eglChooseConfig(display, attribs, &config, 1, &numConfigs); // 第三步
//呼叫eglCreateWindowSurface將Surface s轉換為本地視窗,
surface = eglCreateWindowSurface(display, config, s.get(), NULL); // 第四步
context = eglCreateContext(display, config, NULL, NULL); // 第五步
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
//eglMakeCurrent後生成的surface就可以利用opengl畫圖了
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
return NO_ERROR;
}
egl建立好後,呼叫gl相關命令去畫圖,注意eglSwapBuffers(mDisplay, mSurface) 函式是非常重要的一個函式,會去觸發queueBuffer和dequeueBuffer,圖畫就一幀一幀的畫出來了。
bool BootAnimation::android()
{
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
//呼叫eglSwapBuffers會去觸發queuebuffer,dequeuebuffer,
//queuebuffer將畫好的buffer交給surfaceflinger處理,
//dequeuebuffer新建立一個buffer用來畫圖
eglSwapBuffers(mDisplay, mSurface); // 第六步
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime();
do {
nsecs_t now = systemTime();
double time = now - startTime;
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
EGL的工作流程
以上開機動畫,可分如下幾個階段:
1、 獲取Display。
Display代表顯示。獲得Display要呼叫EGLboolean eglGetDisplay(NativeDisplay dpy),引數一般為 EGL_DEFAULT_DISPLAY 。對應開機動畫就是如下程式碼://呼叫eglGetDisplay EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
2、 初始化egl。
呼叫 EGLboolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor),該函式會進行一些內部初始化工作,並傳回EGL版本號(major.minor)。對應開機動畫就是如下程式碼:eglInitialize(display, 0, 0);
3、 選擇Config。
所為Config實際指的是FrameBuffer的引數。一般用EGLboolean eglChooseConfig(EGLDisplay dpy, const EGLint * attr_list, EGLConfig * config, EGLint config_size, EGLint num_config),其中attr_list是以EGL_NONE結束的引數陣列,通常以id,value依次存放,對於個別標識性的屬性可以只有 id,沒有value。另一個辦法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig config, EGLint config_size, EGLint *num_config) 來獲得所有config。這兩個函式都會返回不多於config_size個Config,結果儲存在config[]中,系統的總Config個數儲存 在num_config中。可以利用eglGetConfig()中間兩個引數為0來查詢系統支援的Config總個數。
Config有眾多的Attribute,這些Attribute決定FrameBuffer的格式和能力,通過eglGetConfigAttrib ()來讀取,但不能修改。對應開機動畫就是如下程式碼:eglChooseConfig(display, attribs, &config, 1, &numConfigs);
4、 構造Surface。
Surface實際上就是一個FrameBuffer,通過 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 來建立一個可實際顯示的Surface。系統通常還支援另外兩種Surface:PixmapSurface和PBufferSurface,這兩種都不是可顯示的Surface,PixmapSurface是儲存在系統記憶體中的點陣圖,PBuffer則是儲存在視訊記憶體中的幀。
Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通過eglSurfaceAttrib()設定、eglQuerySurface()讀取。對應開機動畫就是如下程式碼:surface = eglCreateWindowSurface(display, config, s.get(), NULL);
5、 建立Context。
OpenGL的pipeline從程式的角度看就是一個狀態機,有當前的顏色、紋理座標、變換矩陣、絢染模式等一大堆狀態,這些狀態作用於程式提交的頂點 座標等圖元從而形成幀緩衝內的畫素。在OpenGL的程式設計介面中,Context就代表這個狀態機,程式的主要工作就是向Context提供圖元、設定狀態,偶爾也從Context裡獲取一些資訊。
用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)來建立一個Context。對應開機動畫就是如下程式碼:context = eglCreateContext(display, config, NULL, NULL);
6、 繪製。
應用程式通過OpenGL API進行繪製,一幀完成之後,呼叫eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)來顯示。對應開機動畫就是如下程式碼:eglSwapBuffers(mDisplay, mSurface); /
EGL 官網詳細講述了Surface、Display、Context 概念。對應連結:
簡單地說
(1)Display 是圖形顯示裝置(顯示屏)的抽象表示。大部分EGL函式都要帶一個 Display 作為引數
(2)Context 是 OpenGL 狀態機。Context 與 Surface 可以是一對一、多對一、一對多的關係
(3)Surface 是繪圖緩衝,可以是 window、pbuffer、pixmap 三種類型之一
EGL 工作流程為:
(1)初始化
(2)配置
(3)建立Surface(繫結到平臺Windowing系統)
(4)繫結Surface與Context
(5)Main Loop:渲染(OpenGL),交換離線緩衝(offline buffer)與顯示緩衝
(6)釋放資源
GLSurfaceView與EGL區別
- GLSurfaceView隱藏了EGL操作及渲染執行緒的細節,並提供了生命週期回撥方法。
- EGL可以控制渲染迴圈,例如:可以沒法控制幀速(fps),GLSurfaceView不能
簡單Demo:
第一時間獲得部落格更新提醒,以及更多android乾貨,原始碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼或者長按識別二維碼,即可關注。