談?wù)?eglMakeCurrent、eglSwapBuffers、glFlush 和 glFinish 的區(qū)別
最近有讀者面試被問到了這些高頻知識點(diǎn),再帶大家復(fù)習(xí)一下。

eglMakeCurrent
記得這個調(diào)用嗎?方法原型是:
EGLBoolean eglMakeCurrent(
EGLDisplay display,
EGLSurface draw,
EGLSurface read,
EGLContext context
);
為了弄懂這個方法,我們需要搞清楚 Display 、 Surface 、 Context 這幾個概念。
Display
在使用EGL 的過程中,會發(fā)現(xiàn)EGL 相關(guān)的調(diào)用都經(jīng)常需要我們傳入一個類型為 Display 的參數(shù)。
顧名思義, Display 是一個連接,用于連接設(shè)備上的底層窗口系統(tǒng)。
所以在調(diào)用EGL 方法之前,需要先創(chuàng)建、初始化這個 Display 連接。步驟如下:
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
// 無法打開到底層窗口系統(tǒng)的連接
}
if (!eglInitialize(display, &majorVersion, &minorVersion)) {
// 無法初始化EGL
}
通常情況下傳入 EGL_DEFAULT_DISPLAY 作為 eglGetDisplay 的參數(shù)就可以了,EGL 會自動返回默認(rèn)的 Display 。
Context
Context 不是什么神秘的東西,它僅僅是一個容器,里面放著兩個東西:
-
內(nèi)部狀態(tài)信息 (View port, depth range, clear color, textures, VBO, FBO, ...)
-
調(diào)用緩存 ,保存了在這個
Context下發(fā)起的GL調(diào)用指令。(OpenGL 調(diào)用是異步的)
總的來說, Context 是設(shè)計來存儲渲染相關(guān)的輸入數(shù)據(jù)。
Surface
對應(yīng)的 Surface 則是設(shè)計來存儲渲染相關(guān)的輸出數(shù)據(jù)。
Surface 實(shí)際上是一個對底層窗口對象的拓展、或是一個有著額外輔助緩沖的像素映射(pixmap)。這些輔助緩存包括顏色緩存(color buffer)、深度緩沖(depth buffer)、模板緩沖(stencil buffer)。
再回到 eglMakeCurrent
那么eglMakeCurrent到底做了什么?
當(dāng)發(fā)起 GL 調(diào)用指令(如:glDrawElements)的時候,這個調(diào)用會影響到哪個Context和Surface呢?答案就在 eglMakeCurrent 里。
EGLBoolean eglMakeCurrent(
EGLDisplay display,
EGLSurface draw,
EGLSurface read,
EGLContext context
);
eglMakeCurrent 把 context 綁定到當(dāng)前的渲染線程以及 draw 和 read 指定的Surface。
draw 用于除數(shù)據(jù)回讀(glReadPixels、glCopyTexImage2D和glCopyTexSubImage2D)之外的所有GL 操作。
回讀操作作用于read指定的Surface上的幀緩沖(frame buffer)。
因此,當(dāng)我們在線程T上調(diào)用GL 指令,OpenGL ES 會查詢T線程綁定是哪個Context C,進(jìn)而查詢是哪個Surface draw和哪個Surface read綁定到了這個Context C上。
多個Context
某些情況下,我們想創(chuàng)建、使用多個Context,對于這種情況,需要注意以下幾個情況:
-
不能在2個線程里綁定同一個
Context。 -
不能在2個不同的線程里,綁定相同的
Surface到2個不同的Context上 。 -
在2個不同的線程里,綁定2個不同
Surface到2個Context上,取決于使用的GPU的具體實(shí)現(xiàn),可能成功,也可能失敗。
共享Context
共享Context這種方式在加載階段很有用。由于上傳數(shù)據(jù)到GPU(尤其是紋理數(shù)據(jù)(textures))這類操作很重,如果想要維持幀率穩(wěn)定,應(yīng)該在另一個線程進(jìn)行上傳。
然而,出于上述3種情況的限制,必須在第一個Context之外,創(chuàng)建第二個Context,這個Context將使用第一個Context使用的內(nèi)部狀態(tài)信息。這兩個Context即共享Context。
需要注意的是:這兩個Context共享的只是內(nèi)部狀態(tài)信息,它們兩個并不共享調(diào)用緩存(每個Context各自擁有一個調(diào)用緩存)。
創(chuàng)建第二個Context的方法:
EGLContext eglCreateContext(
EGLDisplay display,
EGLConfig config,
EGLContext share_context,
EGLint const * attrib_list);
第三個參數(shù) share_context 是最重要的,它就是第一個Context。
在第二個線程,不進(jìn)行任何的繪制,只進(jìn)行上傳數(shù)據(jù)到GPU 的操作。所以,給第二個Context 的Surface 應(yīng)該是一個像素緩沖(pixel buffer)Surface。
EGLSurface eglCreatePbufferSurface(
EGLDisplay display,
EGLConfig config,
EGLint const * attrib_list);
eglSwapBuffers
EGLBoolean eglSwapBuffers(
EGLDisplay display,
EGLSurface surface);
第一次看到這個調(diào)用的時候,我以為它的作用是對 display 和 surface 進(jìn)行交換:) 槑
實(shí)際上,這里需要重點(diǎn)注意的是 surface 。如果這里的`surface
是一個像素緩沖(pixel buffer)Surface,那什么都不會發(fā)生,調(diào)用將正確的返回,不報任何錯誤。
但如果 surface 是一個雙重緩沖surface(大多數(shù)情況),這個方法將會交換 surface 內(nèi)部的前端緩沖(front-buffer)和后端緩沖(back-surface)。
后端緩沖用于存儲渲染結(jié)果,前端緩沖則用于底層窗口系統(tǒng),底層窗口系統(tǒng)將緩沖中的顏色信息顯示到設(shè)備上。
glFlush 和 glFinish
OpenGL ES 驅(qū)動和GPU以并行/異步的機(jī)制運(yùn)行。發(fā)起GL 調(diào)用時,為了得到最好的性能,驅(qū)動會嘗試盡快地把調(diào)用指令發(fā)送給GPU。
但GPU 并不會馬上執(zhí)行這些指令,這些指令只是添加到了GPU的指令隊列里等待GPU 執(zhí)行。
如果在短時間內(nèi)發(fā)送大量的GL 指令給GPU,GPU的指令隊列可能滿了,以至于驅(qū)動需要把這些指令保存在Context的調(diào)用緩存里(即上文里提到的Context內(nèi)的調(diào)用緩存)。
那么問題來了,這些等待中的指令何時會發(fā)送給GPU呢?
通常來說,大多數(shù) OpenGL ES 驅(qū)動的實(shí)現(xiàn)可能會在發(fā)起新的(下一個)GL 指令發(fā)起的時候發(fā)送這些指令。如果想要主動執(zhí)行這個操作,那就調(diào)用 glFlush 。
這個操作將會阻塞當(dāng)前線程,直到所有的指令都發(fā)送給了 GPU 。 glFinish 這個命令更加強(qiáng)大,它會阻塞當(dāng)前線程,直到所有的指令都發(fā)送給了GPU,并執(zhí)行完畢。需要注意的是,應(yīng)用程序的性能會因此下降。
glFlush 和 glFinish 被稱為顯式同步操作。某些情況下也會發(fā)生隱式同步操作。調(diào)用 eglSwapBuffers 時,就可能發(fā)生這種情況。
由于這個操作是由驅(qū)動直接執(zhí)行的,此時GPU 可能把所有待執(zhí)行的 glDraw* 繪制指令,作用在一個不符合預(yù)期的surface 緩沖上(如果之前前端緩沖和后端緩沖已經(jīng)交換過了)。
為了防止這種情形,在交換緩沖前,驅(qū)動必須阻塞當(dāng)前線程,等待所有的影響當(dāng)前surface的 glDraw* 指令執(zhí)行完畢。
當(dāng)然,使用雙重緩沖的surfaces時,不需要主動調(diào)用 glFlush 或 glFinish :因?yàn)?/span> eglSwapBuffers 進(jìn)行了隱式同步操作。
但在使用單緩沖surfaces(如上文提到的第二個線程里)的情況,需要及時調(diào)用 glFlush ,例如:在線程退出前,必須調(diào)用 glFlush ,否則,GL 指令可能從未發(fā)送到GPU。
End
原文鏈接: Let’s talk about eglMakeCurrent, eglSwapBuffers, glFlush, glFinish
https://katatunix.wordpress.com/2014/09/17/lets-talk-about-eglmakecurrent-eglswapbuffers-glflush-glfinish/
-- END --
進(jìn)技術(shù)交流群,掃碼添加我的微信:Byte-Flow
獲取相關(guān)資料和源碼
學(xué)習(xí)音視頻、OpenGL ES、Vulkan 、Metal、圖像濾鏡、視頻特效及相關(guān)渲染技術(shù)的付費(fèi)社群,面試指導(dǎo),1v1 簡歷服務(wù),職業(yè)規(guī)劃。
我的付費(fèi)社群
推薦:
Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能
全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了
面試官:如何利用 Shader 實(shí)現(xiàn) RGBA 到 NV21 圖像格式轉(zhuǎn)換?
