這個(gè)公眾號(hào)會(huì)路線圖 式的遍歷分享音視頻技術(shù):音視頻基礎(chǔ) → 音視頻工具 → 音視頻工程示例 → 音視頻工業(yè)實(shí)戰(zhàn)。
通過(guò)《一看就懂的 OpenGL 基礎(chǔ)概念》一文垦藏,我們介紹了 OpenGL 的角色梆暖、渲染架構(gòu)、狀態(tài)機(jī)掂骏、渲染管線等內(nèi)容轰驳,我們接著來(lái)看看它如何在設(shè)備上實(shí)現(xiàn)渲染。
6芭挽、EGL
我們這里只討論 iOS/Android 設(shè)備滑废,所以這里的 OpenGL 也對(duì)應(yīng)的是 OpenGL ES。
如果我們了解了 OpenGL ES 就會(huì)知道袜爪,雖然它定義了一套移動(dòng)設(shè)備的圖像渲染 API蠕趁,但是并沒(méi)有定義窗口系統(tǒng)。為了讓 GLES 能夠適配各種平臺(tái)辛馆,GLES 需要與知道如何通過(guò)操作系統(tǒng)創(chuàng)建和訪問(wèn)窗口的庫(kù)結(jié)合使用俺陋,這就有了 EGL,EGL 是 OpenGL ES 渲染 API 和本地窗口系統(tǒng)之間的一個(gè)中間接口層昙篙,它主要由系統(tǒng)制造商實(shí)現(xiàn)腊状。EGL 提供如下機(jī)制:
- 與設(shè)備的原生窗口系統(tǒng)通信;
- 查詢繪圖圖層的可用類型和配置苔可;
- 創(chuàng)建繪圖圖層缴挖;
- 在 OpenGL ES 和其他圖形渲染 API 之間同步渲染;
- 管理紋理貼圖等渲染資源焚辅。
EGL 是 OpenGL ES 與設(shè)備的橋梁映屋,以實(shí)現(xiàn)讓 OpenGL ES 能夠在當(dāng)前設(shè)備上進(jìn)行繪制。
EGL 架構(gòu)
6.1同蜻、Android EGL
Android 平臺(tái)自 2.0 版本之后圖形系統(tǒng)的底層渲染均由 OpenGL ES 負(fù)責(zé)棚点,其 EGL 架構(gòu)實(shí)現(xiàn)如下圖所示:
Android EGL 架構(gòu)
-
Display 是對(duì)實(shí)際顯示設(shè)備的抽象。在 Android 上的實(shí)現(xiàn)類是
EGLDisplay
湾蔓。 -
Surface 是對(duì)用來(lái)存儲(chǔ)圖像的內(nèi)存區(qū)域 FrameBuffer 的抽象瘫析,包括 Color Buffer、Stencil Buffer默责、Depth Buffer贬循。在 Android 上的實(shí)現(xiàn)類是
EGLSurface
。 -
Context 存儲(chǔ) OpenGL ES 繪圖的一些狀態(tài)信息桃序。在 Android 上的實(shí)現(xiàn)類是
EGLContext
甘有。
本地窗口相關(guān)的 API 提供了訪問(wèn)本地窗口系統(tǒng)的接口,而 EGL 可以創(chuàng)建渲染表面 EGLSurface 葡缰,同時(shí)提供了圖形渲染上下文 EGLContext亏掀,用來(lái)進(jìn)行狀態(tài)管理忱反,接下來(lái) OpenGL ES 就可以在這個(gè)渲染表面上繪制。
使用 EGL 在平臺(tái)實(shí)現(xiàn)渲染步驟大致如下:
- 1)調(diào)用
eglGetDisplay
來(lái)獲得 EGLDisplay 對(duì)象滤愕,從而建立與平臺(tái)窗口系統(tǒng)的聯(lián)系温算,這個(gè) EGLDisplay 將作為 OpenGL ES 的渲染目標(biāo); - 2)調(diào)用
eglInitialize
初始化 EGL间影; - 3)調(diào)用
eglChooseConfig
來(lái)獲得 EGLConfig 對(duì)象注竿,從而確定渲染表面的配置信息; - 4)通過(guò) EGLDisplay 和 EGLConfig 對(duì)象魂贬,調(diào)用
eglCreateWindowSurface
或eglCreatePbufferSurface
方法得到 EGLSurface巩割,從而創(chuàng)建渲染表面,其中 eglCreateWindowSurface 用于創(chuàng)建屏幕上渲染區(qū)域付燥,eglCreatePbufferSurface 用于創(chuàng)建離屏渲染區(qū)域宣谈; - 5)通過(guò) EGLDisplay 和 EGLConfig,調(diào)用
eglCreateContext
獲得 EGLContext 對(duì)象键科,從而創(chuàng)建渲染上下文闻丑,OpenGL 的任何一條指令都是必須在自己的 OpenGL 上下文環(huán)境中執(zhí)行; - 6)調(diào)用
eglMakeCurrent
將 EGLSurface勋颖、EGLContext嗦嗡、EGLDisplay 三者綁定就完成了上下文綁定,綁定成功之后 OpenGL ES 的環(huán)境就創(chuàng)建好了饭玲,接下來(lái)就可以開(kāi)始渲染了侥祭;
通過(guò)上面的步驟就做好了 EGL 的準(zhǔn)備工作:一方面為 OpenGL ES 渲染提供了目標(biāo) EGLDisplay 及上下文環(huán)境 EGLContext,可以接收到 OpenGl ES 渲染出來(lái)的紋理茄厘;另一方面我們連接好了設(shè)備顯示屏 EGLSurface(這里可能是 SurfaceView 或者 TextureView)矮冬。接下來(lái),由于 OpenGL ES 的渲染必須新開(kāi)一個(gè)線程蚕断,并為該線程綁定顯示設(shè)備及上下文環(huán)境(EGLContext),所以
eglMakeCurrent()
就是來(lái)綁定該線程的顯示設(shè)備及上下文的入挣。
- 7)OpenGL ES 完成繪制后亿乳,調(diào)用
eglSwapBuffers
方法交換前后緩沖,將繪制內(nèi)容顯示到屏幕上径筏,而離屏渲染不需要調(diào)用此方法葛假;
這里需要注意的是 EGL 的工作模式是雙緩沖模式,其內(nèi)部有兩個(gè) FrameBuffer(幀緩沖區(qū)):BackFrameBuffer 和 FrontFrameBuffer滋恬,當(dāng) EGL 將一個(gè) FrameBuffer 顯示到屏幕上的時(shí)候聊训,另一個(gè) FrameBuffer 就在后臺(tái)等待 OpenGL ES 進(jìn)行渲染輸出。
直到調(diào)用了
eglSwapBuffer()
這條指令的時(shí)候恢氯,才會(huì)把前臺(tái)的 FrameBuffer 和后臺(tái)的 FrameBuffer 進(jìn)行交換带斑,這時(shí)界面呈現(xiàn)的就是 OpenGL ES 剛剛渲染的內(nèi)容了鼓寺。這樣做的原因是如果應(yīng)用程序使用單緩沖繪圖時(shí)可能會(huì)存在圖像閃爍的問(wèn)題,因?yàn)閳D像生成不是一下子被繪制出來(lái)的勋磕,而是按照從左到右妈候、從上到下逐像素繪制的。如果最終圖像不是在瞬間全部展示給用戶挂滓,而是通過(guò)把繪制過(guò)程也展示出來(lái)了苦银,這會(huì)導(dǎo)致用戶看到的渲染效果出現(xiàn)閃爍。為了規(guī)避這個(gè)問(wèn)題赶站,可以使用雙緩沖渲染:前緩沖保存著最終輸出的圖像幔虏,它會(huì)在屏幕上顯示;而所有的的渲染指令都會(huì)在后緩沖上繪制贝椿,對(duì)用戶屏蔽從左到右想括、從上到下逐像素繪制的過(guò)程,這樣就可以避免閃爍了团秽。
- 8)繪制結(jié)束后主胧,不再需要使用 EGL 時(shí),需要調(diào)用
eglMakeCurrent
取消綁定习勤,調(diào)用eglDestroyContext
踪栋、eglDestroySurface
、eglTerminate
等函數(shù)銷毀 EGLDisplay图毕、EGLSurface夷都、EGLContext 三個(gè)對(duì)象。
在《RenderDemo(1):用 OpenGL 畫(huà)一個(gè)三角形》 Android Demo 的
KFGLContext
類中就可以看到上面這套流程予颤。不過(guò)囤官,如果你覺(jué)得上述配置 EGL 的流程太麻煩的話,Android 平臺(tái)提供了
GLSurfaceView
類實(shí)現(xiàn)了 Display蛤虐、Surface党饮、Context 的管理,即 GLSurfaceView 內(nèi)部實(shí)現(xiàn)了對(duì) EGL 的封裝驳庭,可以很方便地利用接口 GLSurfaceView.Renderer 的實(shí)現(xiàn)刑顺,使用 OpenGL ES API 進(jìn)行渲染繪制。GLSurfaceView 提升了 OpenGL ES 開(kāi)發(fā)的便利性饲常,當(dāng)然也相應(yīng)的失去了一些靈活性蹲堂。
參考:
- EGL 作用及其使用[1]
- EGL[2]
6.2、iOS EAGL
iOS 平臺(tái)對(duì) EGL 的實(shí)現(xiàn)是 EAGL(Embedded Apple Graphics Library) 贝淤。OpenGL ES 系統(tǒng)與本地窗口(UIKit)系統(tǒng)的橋接由 EAGL 上下文系統(tǒng)實(shí)現(xiàn)柒竞。
與 Android EGL 不同的是,iOS EAGL 不會(huì)讓?xiě)?yīng)用直接向 BackFrameBuffer 和 FrontFrameBuffer 進(jìn)行繪制播聪,也不會(huì)讓?xiě)?yīng)用直接控制雙緩沖區(qū)的交換(swap)朽基,系統(tǒng)自己保留了這些操作權(quán)布隔,以便可以隨時(shí)使用 Core Animation 合成器來(lái)控制顯示的最終外觀。
Core Animation 是 iOS 上圖形渲染和動(dòng)畫(huà)的核心基礎(chǔ)架構(gòu)踩晶。 可以使用托管多種 iOS 系統(tǒng)內(nèi)容的圖層(UIKit执泰、Quartz 2D、OpenGL ES)渡蜻,來(lái)合成應(yīng)用的用戶界面或者其他視覺(jué)顯示术吝。
OpenGL ES 通過(guò) CAEAGLLayer 與 Core Animation 連接,CAEAGLLayer 是一種特殊類型的 Core Animation 圖層茸苇,它的內(nèi)容來(lái)自 OpenGL ES 的 RenderBuffer排苍,Core Animation 將 RenderBuffer 的內(nèi)容與其他圖層合成,并在屏幕上顯示生成的圖像学密。所以同一時(shí)刻可以有任意數(shù)量的層淘衙。Core Animation 合成器會(huì)聯(lián)合這些層并在后幀緩存中產(chǎn)生最終的像素顏色,然后切換緩存腻暮。
Core Animation 與 OpenGL ES 共享 RenderBuffer
一個(gè)應(yīng)用提供的圖層與操作系統(tǒng)提供的圖層混合起來(lái)可以產(chǎn)生最終的顯示外觀彤守。如下圖所示,OpenGL ES 圖層顯示了一個(gè)應(yīng)用生成的旋轉(zhuǎn)立方體哭靖,但是在顯示器頂部的顯示狀態(tài)欄圖層則是由操作系統(tǒng)生成和控制的具垫,此圖顯示的是合并兩個(gè)圖層來(lái)產(chǎn)生后幀緩存中的顏色數(shù)據(jù)的過(guò)程,交換后试幽,我們看到的就是前幀緩存上的內(nèi)容筝蚕。
iOS 多圖層合成
所以,iOS 的 EAGL 配置過(guò)程其實(shí)就是使用 CoreAnimation 的 layer 來(lái)支持 OpenGL ES 渲染的過(guò)程铺坞,步驟大致如下:
- 1)創(chuàng)建一個(gè) EAGL 圖層
CAEAGLLayer
對(duì)象起宽,并設(shè)置好它的屬性; - 2)創(chuàng)建 OpenGL ES 上下文
EAGLContext
济榨,并設(shè)置為當(dāng)前上下文環(huán)境坯沪; - 3)創(chuàng)建一個(gè)顏色渲染緩沖區(qū)對(duì)象
ColorRenderBuffer
,并調(diào)用renderbufferStorage:fromDrawable:
為其分配存儲(chǔ)空間擒滑,這里其實(shí)是將 CAEAGLLayer 的繪制存儲(chǔ)區(qū)共享為了 ColorRenderBuffer 的繪制緩沖區(qū)腐晾。分配緩沖區(qū)需要的寬、高橘忱、像素格式等信息都會(huì)從 layer 中取得赴魁;
需要注意的是卸奉,如果 CAEAGLLayer 的 bounds 或其他屬性變了钝诚,需要重新分配 ColorRenderBuffer 的存儲(chǔ)空間,否則會(huì)出現(xiàn) ColorRenderBuffer 和 CAEAGLLayer 的尺寸不匹配榄棵。
- 4)創(chuàng)建幀緩沖區(qū)
FrameBuffer
對(duì)象凝颇,并將 ColorRenderBuffer 綁定為它的附件潘拱; - 5)從顏色渲染緩沖區(qū) ColorRenderBuffer 獲取寬高信息;
- 6)根據(jù)需要?jiǎng)?chuàng)建一個(gè)深度渲染緩沖區(qū)
DepthRenderBuffer
對(duì)象拧略,并綁定為 FrameBuffer 的附件芦岂; - 7)根據(jù)需要檢測(cè) FrameBuffer 的狀態(tài);
- 8)將 CAEAGLLayer 添加到 Core Animation 的圖層樹(shù)中垫蛆;
- 9)在繪制動(dòng)作完成后禽最,調(diào)用 EAGLContext 的
presentRenderbuffer:
方法,就可以將繪制結(jié)果顯示在屏幕上了袱饭。
在《RenderDemo(1):用 OpenGL 畫(huà)一個(gè)三角形》 iOS Demo 的
DMTriangleRenderView
類中可以看到類似的流程川无,只不過(guò) Demo 中我們是創(chuàng)建了一個(gè) UIView 的子類,并重寫(xiě)它的+layerClass
方法返回CAEAGLLayer
類型來(lái)獲得了一個(gè) CAEAGLLayer 對(duì)象用于 OpenGL ES 渲染虑乖。同樣的懦趋,如果你覺(jué)得上述流程太麻煩,iOS 平臺(tái)還提供了封裝好的
GLKView
來(lái)簡(jiǎn)化我們使用 OpenGL ES疹味,GLKView 是對(duì) CAEAGLLayer 的封裝仅叫,內(nèi)嵌了配置 Core Animation 支持 OpenGL ES 的流程。
參考:
- iOS OpenGL ES 應(yīng)用開(kāi)發(fā)實(shí)踐指南[3]
- iOS OpenGL ES Programming Guide[4]
- OpenGL ES 在 iOS 中的上下文環(huán)境搭建[5]
參考資料
[1]
EGL 作用及其使用: https://blog.csdn.net/u010281924/article/details/105296617
[2]
EGL: https://blog.csdn.net/Kennethdroid/article/details/99655635
[3]
iOS OpenGL ES 應(yīng)用開(kāi)發(fā)實(shí)踐指南: https://book.douban.com/subject/24849591/
[4]
iOS OpenGL ES Programming Guide: https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html
[5]
OpenGL ES 在 iOS 中的上下文環(huán)境搭建: http://www.reibang.com/p/c34c14589e0c