最近在幀緩沖區對象這里卡了一下,不過前面已經了解了相關的OpenGL ES的知識,現在再去了解就感覺輕松多了。現在就進行總結。
基礎知識
我們知道,在應用程序調用任何的OpenGL ES命令之前,需要首先創建一個渲染上下文和繪圖表面,並使之成為現行上下文和表面,之前在渲染的時候,其實一直使用的是原生窗口系統(比如EAGL,GLFW)提供的渲染上下文和繪圖表面(即幀緩沖區)。
一般情況下,我們只需要系統提供的幀緩沖區作為繪圖表面,但是又有些特殊情況,比如陰影貼圖、動態反射、處理后特效等需要渲染到紋理(Render To Texture/RTT)操作的,如果使用系統提供的幀緩沖區,效率會比較低低下。
對於系統提供的幀緩沖區,如果要實現RTT,有下面兩種方式:
- 直接將幀緩沖區的對應區域賦值到紋理來實現RTT。借助glCopyTexImage2D/glCopyTexSubImage2D來從幀緩沖區復制顏色數據到紋理緩沖區,因為需要復制數據,所以操作比較慢,而且受限於EGLSurface的寬高尺寸,紋理的尺寸只能小於等於幀緩沖區尺寸大小。
- 使用連接到紋理的pbuffer來實現RTT。窗口系統提供的EGLSurface必須連接到EGLContext,但是pbuffer和EGLSurface可能需要不同的EGLContext,這樣實現可能效率低。而且,在窗口系統提供的EGLSurface之間轉換可能需要清除所有切換之前渲染的圖像,這會造成CPU的閑置,這種情況下建議不要使用,因為EGLSurface和EGLContext切換相關的開銷很大。
如果應用程序只在屏幕上的表面繪圖,則窗口系統提供的幀緩沖區通常很高效。但是很多應用程序需要你渲染到紋理,使用窗口提供的不太理想,因此需要自定義自己的幀緩沖區。
幀緩沖區對象API支持以下操作:
- 僅使用OpenGL ES命令創建幀緩沖區對象。
- 在單一EGL上下文中創建和使用多個幀緩沖區對象。也就是說,不需要每個幀緩沖區都有一個渲染上下文。
- 創建屏幕外顏色,深度或者模版渲染緩沖區和紋理,並將它們連接到幀緩沖區對象。
- 在多個幀緩沖區之間共享顏色、深度或者模版緩沖區。
- 將紋理直接連接到幀緩沖區作為顏色或者深度,從而避免了進行復制操作的必要。
- 在幀緩沖區之間復制並使幀緩沖區內容失效。
幀緩沖區對象是一組顏色、深度和模版紋理或者渲染目標,和默認的幀緩沖區一樣,自定義的幀緩沖區也包括顏色緩沖區、深度和模版緩沖區,這些邏輯上的緩沖區在FBO中稱之為可附加的圖像,它們是可以附加到FBO的二維像素數組。
FBO包含兩種類型的附加圖像,紋理圖像和渲染緩沖區圖像。附加紋理時,OpenGL渲染到這個紋理圖像,在着色器中可以訪問到這個紋理對象;附加到渲染緩沖區時,OpenGL執行離屏渲染。
但是需要注意的地方是,FBO可以附加多個緩沖區,而且可以靈活的在緩沖區進行切換,FBO中包含一個以上的顏色附加點,各種2D圖像可以連接到幀緩沖區對象中的顏色附加點,但是只有一個深度和模版附加點。如下圖:


使用幀緩沖區對象
創建的過程和創建VBO是類似的。
void glGenFramebuffers(GLsizei n, GLuint* ids)
void glDeleteFramebuffers(GLsizei n, const GLuint* ids)
void glBindFramebuffer(GLenum target, GLuint id)
這里的對象類型是指與附着點相關的對象的類型,如果要連接一個渲染緩沖區對象,則類型可以是GL_RENDERBUFFER,如果連接的是一個紋理對象,那么類型可以是GL_TEXTURE,但是默認值是GL_NONE。
使用渲染緩沖區對象
void glGenRenderbuffers(GLsizei n, GLuint* ids)
void glDeleteRenderbuffers(GLsizei n, const Gluint* ids)
void glBindRenderbuffer(GLenum target, GLuint id)
一旦綁定渲染緩沖區對象,就可以指定保存在渲染緩沖區對象中的圖像大小和格式。
void glRenderbufferStorage(GLenum target,
GLenum internalFormat, GLsizei width, GLsizei height)
void glGetRenderbufferParameteriv(GLenum target, GLenum param, GLint* value)
附着
其中顏色附着可以分為渲染緩沖區附着或者紋理附着。
glFramebufferTexture2D(GLenum target,GLenum attachmentPoint,GLenum textureTarget,GLuint textureId, GLint level)
void glFramebufferRenderbuffer(GLenum target,GLenum attachmentPoint, GLenum renderbufferTarget, GLuint renderbufferId)
其實還可以連接3D紋理的一個圖像作為幀緩沖區附着。
void glFramebufferTextureLayer(GLenum target,GLenum attachment, GLuint texture, GLint level, GLint layer);
檢查幀緩沖區完整性
幀緩沖區對象必須定義為完整的才能夠用作渲染目標。
其中幀緩沖區對象被視為完整的規則如下:
- 確保顏色、深度和模版附着有效;幀緩沖區至少有一個有效的附着,如果沒有任何附着,那么幀緩沖區則是不完整的,因為沒有可以繪制或者讀取的區域。
- 與幀緩沖區對象相關的有效附着必須有相同的寬度和高度。
- 如果存在深度和模版附着,則它們必須是相同的寬度和高度。
- 所有渲染緩沖區附着的GL_RENDERBUFFER_SAMPLES值都相同。如果附着是渲染緩沖區和紋理的結合,則GL_RENDERBUFFER_SAMPLES的值為0。
glCheckFramebufferStatus命令可用於驗證幀緩沖區對象是否完整,其返回的是一個枚舉,是否完整。
GLenum glCheckFramebufferStatus( GLenum target);
GLenum glCheckNamedFramebufferStatus(GLuint framebuffer,GLenum target);
幀緩沖區位塊傳送
幀緩沖區位塊傳送可以高效的將一個矩形區域的像素值從一個幀緩沖區(讀幀緩沖區)復制到另一個幀緩沖區(繪圖幀緩沖區)。幀緩沖區位塊傳送的關鍵應用之一是將一個多重采樣渲染緩沖區解析為一個紋理(用一個幀緩沖區對象,紋理綁定為它的顏色附着)。
void glBlitFramebuffer( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
這個函數是從es 3.0才開始支持。
幀緩沖區失效
幀緩沖區失效為應用程序提供了一個通知驅動程序不再需要幀緩沖區內容的機制。因為可以通知無效,所以這可以使得驅動程序采取多種優化步驟:1.跳過在塊狀渲染(TBR)架構中為了進一步渲染到幀緩沖區而做的不必要的圖塊內容修復;2.跳過多GPU系統中GPU之間不必要的數據復制;3.跳過某些實現中為了改進性能而對特定緩存的刷新。這種功能對於許多應用程序中實現峰值性能很重要,特別是那些執行大量屏幕外渲染的應用。
這個函數在es 3.0中才會實現。
我們需要先了解TBR GPU中的設計,才能知道幀緩沖區失效對於GPU的重要性。TBR GPU常常部署在移動設備上,以最小化GPU和系統內存之間數據傳輸量,從而減少最大的電力消耗者之一,內存帶寬;這通過添加能夠保存少量像素數據的芯片內建快速存儲器來實現,然后,幀緩沖區被區分為許多個圖塊,對於每個圖塊,圖元被渲染到芯片內建的存儲器中,然后結果在完成時被復制到系統內存。因為每個像素的最少量數據(最終像素結果)被復制到系統內存,所以這種方法節約了GPU和系統內存之間的內存帶寬。
有了幀緩沖區失效機制,GPU就可以刪除不再需要的幀緩沖區內容,以減少每個幀保留的內容數量,此外,如果圖塊數據不再有效,GPU還可以消除從芯片內建存儲器到系統內存不必要的數據傳輸,因為GPU和系統內存之間內存帶寬需求明顯降低,所以電力消耗隨之下降,性能則得到改善。
其中glInvalidateFramebuffer和glInvalidateSubFramebuffer命令用於使整個幀緩沖區或者幀緩沖區的像素子區域失效。
void glInvalidateFramebuffer( GLenum target, GLsizei numAttachments, const GLenum *attachments);
void glInvalidateSubFramebuffer( GLenum target, GLsizei numAttachments, const GLenum * attachments, GLint x, GLint y, GLint width, GLint height);
性能提示和技巧
在使用幀緩沖區對象時應該要認真考慮的性能提示:
- 避免頻繁地在渲染到窗口系統提供的幀緩沖區和渲染到幀緩沖區對象之間切換。
- 不要逐幀創建和刪除幀緩沖區和幀緩沖區對象(或者任何其他大型數據對象)。
- 嘗試避免修改用作渲染圖標的幀緩沖區對象附着的紋理,也就是說當指定紋理附着到幀緩沖區對象上以后,不要再進行修改(使用glTexImage2D、glTexSubImage2D、glCopyTexImage2D等)。
- 如果整個紋理圖像將被渲染,則將glTexImage2D和glTexImage3D中的pixel參數設置為NULL,因為原始數據不會被使用。如果你希望圖像包含任何預先定義的像素值,那么在繪制到紋理之前使用glInvalidateFramebuffer清除紋理圖像。
- 盡可能共享幀緩沖區對象使用的用作附着的深度和模版渲染緩沖區,以保證內存占用需求最小,但是需要注意的是,因為幀緩沖區的寬度和高度必須相同。
