1. 程式人生 > >OpenGL VBO, PBO與FBO

OpenGL VBO, PBO與FBO

轉自:

http://blog.csdn.net/ym19860303/article/details/9400609

原文也是轉載,沒有提供最原始出處

近日開發相機,使用GLSurfaceView開發相機,可以解決預覽速度的優化的問題,主要是藉助OPENGL和shader來直接獲取PBO和FBO的預覽資料來進一步為相機提速,需要對OPENGL有一定的瞭解。

VBO,Vertex Buffer Array

    為了加快顯示速度,顯示卡增加了一個擴充套件,即VBO。它本質上是儲存幾何資料的快取。它直接把頂點資料放置到顯示卡中的快取記憶體,極大提高了繪製速度。

     這個擴充套件用到ARB_vertex_buffer_object,它可以直接像頂點陣列那樣使用。唯一不同的地方在於它需要將資料載入顯示卡的高效快取,因此需要佔用渲染時間。

     [參考文章1] 給出了一個使用VBO擴充套件的例子程式。[參考文章2]羅列了與VBO操作相關的函式

[參考文章3]歸納出VBO擴充套件的用法流程,並總結出與紋理用法流程的相似性:

初始化階段:
1. glGenBuffersARB(1, &nVBOVertices); //生成一個控制代碼
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices); //宣告該控制代碼為一個vbo控制代碼,並選擇之
3. glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(vertices), vertices,GL_STATIC_DRAW); //將頂點集上傳至server端

使用階段:
1. glEnableClientState

(GL_VERTEX_ARRAY); //開始使用vbo
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices);  //選擇當前使用的vbo
3. glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));  //指定vbo頂點格式
4. glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount ); //畫吧
5. glDisableClientState(GL_VERTEX_ARRAY); //停止使用vbo

收尾階段:
1. glDeleteBuffersARB(1,&nVBOVertices); //刪除控制代碼,同時刪除server端頂點緩衝

再來看看紋理緩衝是怎麼使用的,其實差不多:

初始化階段:
1. glGenTextures
(1, &texID);//建立控制代碼
2. glBindTexture(GL_TEXTURE_2D, texID); //設定控制代碼型別
3. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->GetWidth(), img->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, raw_rgba); //上傳紋理緩衝

使用階段:
1. glEnable(GL_TEXTURE_2D); //開始使用紋理緩衝
2. glBindTexture(GL_TEXTURE_2D, texID); //選擇當前使用的紋理緩衝
3. 傳送頂點和紋理座標,畫吧...省略
4. glDisable(GL_TEXTURE_2D); //停止使用紋理

收尾階段:
1. glDeleteTextures(1,&texID);//刪除控制代碼,,同時刪除server端緩衝

參考文章:

PBO,即Pixel Buffer Object也是用於GPU的擴充套件(ARB_vertex_buffer_object)。這裡的快取當然就是GPU的快取。PBO與VBO擴充套件類似,只不過它儲存的是畫素資料而不是頂點資料。PBO借用了VBO框架和所有API函式形式,並加了上兩個"target"標誌。這兩個標識是:

  • GL_PIXEL_PACK_BUFFER_ARB 將畫素資料傳給PBO
  • GL_PIXEL_UNPACK_BUFFER_ARB 從PBO得到畫素資料

這裡的“pack”還是“unpack”,可分別理解為“傳給”和“得到”。它們也都可以統一理解為“拷貝”,也就是畫素資料的“傳遞”。

    比如說,glReadPixel就是資料從幀快取(framebuffer)到記憶體(memory),可理解為“pack”;glDrawPixel是從記憶體到幀快取,可理解為“unpack”;glGetTexImage是從紋理物件到記憶體,可理解為“pack”;glTexImage2d從記憶體(memory)到紋理物件(texture object),可理解為“unpack”。

   下圖是PBO與Framebuffer和Text物件之間的傳遞。

圖1 opengl PBO

   使用PBO的好處是快速的畫素資料傳遞,它採用了一種叫DMA(Direct Memory Access)的技術,無需CPU介入。另一個PBO的優點是,這種DMA是非同步的。我們可以通過下面兩張圖來比較使用PBO的與傳統的紋理傳遞的過程。

   圖2是用傳統的方法從影象源(如影象檔案或視訊)載入影象資料到紋理物件的過程。畫素資料首先存到系統記憶體中,接著使用glTexImage2D將資料從系統記憶體拷貝到紋理物件。包含的兩個子過程均需要有CPU執行。而從圖3中,我們可以看到畫素資料直接載入到PBO中,這個過程仍需要CPU來執行,但是從資料從PBO到紋理物件的過程則由GPU來執行DMA,而不需要CPU參與。而且opengl可安排非同步DMA,不必馬上進行畫素資料的傳遞。因此,相比而言,圖3中的glTexImage2D立即返回而不是馬上執行,這樣CPU可以執行其它的操作而不需要等待畫素資料傳遞的結束。

圖2 不使用PBO的紋理載入

圖3 使用PBO的紋理載入

    GL_PIXEL_PACK_BUFFER_ARB用於將畫素資料從opengl傳遞給應用程式,GL_PIXEL_UNPACK_BUFFER_ARB則是將畫素資料從應用程式傳遞給opengl。 

生成PBO

   生成PBO分成3步:

1. 用glGenBuffersARB()生成快取物件;

2. 用glBindBufferARB()繫結快取物件;

3. 用glBufferDataARB()將畫素資料拷貝到快取物件。

   如果在glBufferDataARB函式中將一個NULL的指標給源陣列,PBO只會為之分配一個給定大小的記憶體空間。glBufferDataARB的另一個引數是關於PBO的效能引數(hint),表明快取物件如何使用。該引數取GL_STREAM_DRAW_ARB表明是載入,GL_STREAM_READ_ARB表明是非同步幀快取讀出。

對映PBO

  PBO提供了一種將opengl控制的快取物件與客戶端地址空間進行記憶體對映的機制。所以客戶端可通過glMapBufferARB()和glUnmapbufferARB就可以修改快取物件的部分或整個資料。

    void* glMapBufferARB(GLenum target, GLenum access);

 GLboolean glUnmapBufferARB(GLenum target);        
   glMapBufferARB返回快取物件的指標。引數target取GL_PIXEL_PACK_BUFFER_ARB或GL_PIXEL_UNPACK_BUFFER_ARB,引數access是能對對映快取的操作,可取GL_READ_ONLY_ARB、GL_WRITE_ONLY_ARB和GL_READ_WRITE_ARB,分別表明可從PBO讀、可向PBO寫,可從PBO讀也可向PBO寫。

    要注意:如果GPU正在對快取物件進行操作,glMapBufferARB不會返回快取物件直到GPU結束了對快取物件的處理。為了避免等待,在呼叫glMapBufferARB之前,先呼叫glBufferDataARB(用NULL指標作為引數),這時OpenGL將丟棄老的快取物件,為新的快取物件分配空間。

    在客戶端使用PBO後,應呼叫glUnmapBufferARB來取消對映。glUnmapBufferARB返回GL_TRUE表明成功,否則返回GL_FALSE。

____________________________________________________

Demo

例子程式pboUnpack.zip使用了不同方式來比較將紋理傳給OpenGL的模式:

  • 使用一個PBO;
  • 使用兩個PBO;
  • 不使用PBO;

通過按空格鍵,可以不同模式間切換。

在PBO模式下,每幀的紋理源(畫素)都是在對映PBO狀態下直接寫進去的。再通過呼叫glTexSubImage2D將PBO中的畫素傳遞給紋理物件。通過使用紋理物件可以在PBO和紋理物件間進行非同步DMA傳遞。它能大大提高畫素傳遞的效能。
由於glTexSubImage2D立即返回,因此CPU能夠直接進行其它工作,無需等待實際的畫素傳遞。

圖4 兩個PBO更新紋理


為了將畫素傳遞的效能最大化,可以使用多個PBO物件。圖4中表明同時使用了兩個PBO。在glTexSubImage2D將畫素資料從PBO拷貝出來的同時,另一份畫素資料寫進了另一個PBO。
在第n幀時,PBO1用於glTexSubImage2D,而PBO2用於生成一個新的紋理物件了。再到n+1幀時,兩個PBO則互換了角色。由於非同步DMA傳遞,畫素資料的更新和拷貝過程可同時進行,即CPU將紋理源更新到PBO,同時GPU將從另一PBO中拷貝出紋理。


例子程式pboPack.zip從視窗的左邊讀出(pack)畫素資料到PBO,在更改它的亮度後,把它在視窗的右邊繪製出來。通過按空格鍵,可以看glReadPixels的效能。

傳統使用glReadPixels將阻塞渲染管道(流水線),直到所有的畫素資料傳遞完成,才會將控制權交還給應用程式。相反,使用PBO的glReadPixels可以排程非同步DMA傳遞,能夠立即返回而不用等待。因此,CPU可以在OpenGL(GPU)傳遞畫素資料的時候進行其它處理。

圖5 用兩個PBO非同步glReadPixels

例子程式也使用了兩個PBO,在第n幀時,應用幀快取讀出畫素資料到PBO1中,同時在PBO中對畫素資料進行處理。讀與寫的過程可同時進行,是因為,在呼叫glReadPixels時立即返回了,而CPU立即處理PBO2而不會有延遲。在下一幀時,PBO1和PBO2的角色互換。

參考文章


FBO, Frame Buffer Object

在OpenGL渲染流水線上,幾何資料和紋理資料被多次轉換、多次測試,最後以2維畫素的形式顯示在螢幕上。而OpenGL流水線上最後顯示階段畫素所在處,稱為幀快取。幀快取可視為2維陣列,或OpenGL使用的儲存區域,它包括了:顏色快取、深度快取、模板快取和累積快取。
一般情況下,幀快取由window系統生成並管理,供OpenGL使用。這種預設的幀快取稱之為“window系統生成”(window-system-provided)的幀快取。

在OpenGL擴充套件中,GL_EXT_framebuffer_object提供了另外一種不能夠顯示的幀快取介面,幀快取物件(FBO)。這種幀快取稱之為“應用生成”幀快取,以區別於“window系統生成”幀快取。通過使用幀快取物件(FBO),OpenGL應用程式可以將顯示內容輸出到“應用生成”幀快取,而不是傳統的“window系統生成”幀快取。這個過程全部由OpenGL控制。 

和window系統提供的幀快取一樣,FBO也有一組相應儲存顏色、深度和模板(注意沒有累積)資料的快取區域。我們把FBO中儲存這些資料的區域稱之為“快取關聯影象”(framebuffer-attached image)。它完全由OpenGL管理控制。

快取關聯影象分為兩類:紋理快取渲染(顯示)快取(renderbuffer)。如果紋理物件的影象資料關聯到幀快取,opengl執行的將是“渲染到紋理”(render to texture)操作。如果渲染快取物件的影象資料關聯到幀快取,opengl執行的將是“離線渲染”(offscreen rendering)。

渲染快取物件是GL_EXT_framebuffer_object中定義的新的一種儲存型別。這用於渲染過程中儲存單幅2維影象。下面的圖1描述了FBO、紋理物件和渲染快取物件之間的關係。

                                圖1 FBO、紋理物件和渲染快取物件之間的關係

 從圖中可以看出,FBO有多個顏色關聯:(GL_COLOR_ATTACHMENT0_EXT,..., GL_COLOR_ATTACHMENTn_EXT), 一個深度關聯(GL_DEPTH_ATTACHMENT_EXT)和一個模板關聯 (GL_STENCIL_ATTACHMENT_EXT)。顏色關聯的數目最少有一個,最大數目是與實體的顯示卡相關的,可以GL_MAX_COLOR_ATTACHMENTS_EXT查詢得到。FBO採用多個顏色關聯,這樣可以同時將多個顏色快取渲染(繪製)到多個FBO關聯儲存區,即“多渲染目標”MRT(multiple render target)。MRT可用GL_ARB_draw_buffers完成。

注意:FBO本身並沒有影象資料儲存區,只有多個關聯

 FBO提供了一種高效的切換機制:將前一個幀快取關聯影象從FBO分離,將一個新的可關聯幀快取影象與FBO關聯。FBO對幀快取關聯影象的切換要比對FBO的切換速度更快。用glFramebufferTexture2DEXT(),可以進行二維紋理物件的切換,用glFramebufferRenderbufferEXT可切換渲染快取物件。

——————————————————————————————————————————————

FBO的生成

FBO的生成過程與VBO的生成過程類似。

glGenFramebuffersEXT()

void glGenFramebuffersEXT(GLsizei n, GLuint* ids) void glDeleteFramebuffersEXT(GLsizei n, const GLuint* ids)

glGenFramebuffersEXT需要兩個引數,一個是要生成的幀快取個數,一個是儲存幀快取ID的地址指標。如果幀快取的ID為0,表明為預設的幀快取,也即window系統生成的幀快取。

glDeleteFramebuffersExt可用於刪除不再使用的FBO。

glBindFramebufferEXT()

void glBindFramebufferEXT(GLenum target, GLuint id)

 一旦生成FBO,可用glBindFramebufferEXT來啟用它。第一個引數target應為GL_FRAMEBUFFER_EXT,第二個引數為FBO的ID。如果不再使用FBO,應呼叫該函式,這時引數ID置為0。

———————————————————————————————

渲染快取物件

 渲染快取物件是新新增的,專門用於離線渲染。它允許將一個場景直接渲染到一個渲染快取物件中,而不是渲染到紋理物件。渲染快取物件是用於儲存單幅影象的簡單儲存物件。該影象是按一種可渲染的內部格式儲存。可以說,渲染快取物件是儲存OpenGL的邏輯快取,它不具有像紋理那樣的格式。

_______________________________________________________________________________

glGenRenderbuffersEXT()

void glGenRenderbufferEXT(GLsizei target, GLuint* ids)

void glDeleteRenderbuffersEXT(GLsizei n, const GLuint* ids)

glGenRenderbuffersEXT一旦生成渲染快取, 返回非零整數。

_________________________________________________________________

glBindRenderbufferEXT()

void glBindRenderbufferEXT(GLenum target, GLuint id)

和其它OpenGL物件一樣,你需要在使用它之前對渲染快取物件進行繫結。其中的target引數應為GL_RENDERBUFFER_EXT。

__________________________________________________________________________________

glRenderbufferStorageEXT()

void glBindRenderbufferEXT(GLenum target, GLuint id)

生成了渲染物件後,還需要分配空間儲存資料。glRenderbufferStorageEXT用來完成這件事。該函式的第一個引數必須是GL_RENDERBUFFER_EXT。第二個引數可以是用於顏色的(如GL_RGB, GL_RGBA等等),用於深度的(GL_DEPTH_COMPOENT),用於模板的(GL_STENCIL_INDEX)。渲染快取儲存的影象其大小還是按畫素的寬×高來計算,它不得大於常量GL_MAX_RENDERBUFFER_SIZE_EXT;否則會產生GL_INVALID_VALUE的錯誤。

_________________________________________________________________________________

glGetRenderbufferParameterivEXT()

void glGetRenderbufferParameterivEXT(GLenum target, GLenum param,

GLint* value);

 用glGetRenderbufferParameterivEXT可以得到當前渲染物件的屬性引數。target必須為GL_RENDERBUFFER_EXT,第二個引數param從下面的常量中取,value為得到返回值的指標。

  GL_RENDERBUFFER_WIDTH_EXT
  GL_RENDERBUFFER_HEIGHT_EXT
  GL_RENDERBUFFER_INTERNAL_FORMAT_EXT
  GL_RENDERBUFFER_RED_SIZE_EXT
  GL_RENDERBUFFER_GREEN_SIZE_EXT
  GL_RENDERBUFFER_BLUE_SIZE_EXT
  GL_RENDERBUFFER_ALPHA_SIZE_EXT
  GL_RENDERBUFFER_DEPTH_SIZE_EXT
  GL_RENDERBUFFER_STENCIL_SIZE_EXT
----————————————————————————————————----

將影象與FBO關聯

FBO本身並沒有儲存任何影象,我們只是將幀快取可關聯影象(紋理和渲染物件)與FBO進行了關聯。這種機制可以讓我們在FBO中進行幀快取可關聯影象的快速切換。這樣就用不著不必要地拷貝,也減少了記憶體消耗。比如說一個紋理可與多個FBO關聯,這樣它的影象儲存區就能夠讓多個FBO共享。

    ______________________________________________________________________________________

   將一個紋理關聯到FBO

glFramebufferTexture2DEXT(GLenum target,

GLenum attachmentPoint,

GLenum textureTarget,

GLuint textureId,

GLint  level)

glFramebufferTexture2DEXT可將2D紋理影象關聯到FBO上。第一個引數target必須為GL_FRAMEBUFFER_EXT。引數attachment為FBO上的關聯點。FBO上有多個顏色關聯點(GL_COLOR_ATTACHMENT0_EXT, ..., GL_COLOR_ATTACHMENTn_EXT),一個GL_DEPTH_ATTACHMENT_EXT和GL_STENCIL_ATTACHMENT_EXT。引數textureTarget大數多情況下為GL_TEXTURE_2D。引數textureId為紋理物件的ID號。引數level為關聯的紋理的mipmap等級。

如果textureId取0,則當前關聯的紋理將與FBO分離。如果紋理物件被刪除了,還它關聯著一個FBO的話,該紋理影象會自動與其分離。如果它關聯的是多個FBO,它刪除時只與當前的FBO分離,不會與其它FBO分離。

______________________________________________________________________________________

   將一個渲染快取影象關聯到FBO

glFramebufferRenderbufferEXT(GLenum target,

GLenum attachmentPoint,

GLenum renderbufferTarget,

GLuint renderbufferId)

渲染快取影象可用glFramebufferRenderbufferEXT,來與FBO關聯。引數target和attachmentpoint和前一個函式glFramebufferTexture2DEXT一樣。第三個函式必須為GL_RENDERBUFFER_EXT, 引數renderbufferId為渲染物件的Id。

如果renderbufferId設為0,當前FBO中的渲染影象將與其分離。如果渲染物件刪除時還與FBO關聯著,那它會自動與當前的FBO分離,但不會與其它關聯的FBO分離。

 ----————————————————————————————————----

檢查FBO狀態

 在影象(紋理或渲染緩衝)與FBO關聯,在招待FBO的操作之前,都需要對FBO的狀態進行檢查。獲取FBO狀態函式為glCheckFramebufferStatusEXT。如果FBO不完整,那麼繪製和讀取的命令(glBegin(), glCopyTexImage2D()等)都會失敗。

GLenum glCheckFramebufferStatusEXT(GLenum target);

glCheckFramebufferStatusEXT對當前幀快取的引數和相關聯的影象進行驗證。注意該函式不能在glBegin()/glEnd()函式對中間使中。target引數必須為GL_FRAMEBUFFER_EXT。它返回一個非零的狀態值。如果FBO通過了檢查,則返回GL_FRAMEBUFFER_COMPLETE_EXT。

   FBO必須滿足以下條件:

  • 關聯影象的寬高不能為0;
  • 如果關聯影象關聯的是顏色關聯點,那麼影象必須為顏色可渲染的內部格式(如GL_RGBA,  GL_DEPTH_COMPONENT, GL_LUMINANCE等);
  • 如果關聯影象關聯的是GL_DEPTH_ATTACHMENT_EXT,影象必須是深度可渲染的內部格式(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_EXT等)。
  • 如果關聯影象關聯的是GL_STENCIL_ATTACHMENT_EXT,影象必須是模板可渲染的內部格式(GL_STENCIL_INDEX, GL_STENCIL_INDEX8_EXT等)。
  • FBO必須至少有一個影象關聯;
  • 所有關聯到顏色關聯點的影象必須使用同一個內部格式。

注意,即使所有的條件都得到滿足,opengl驅動程式可能也不支援某些內部格式和引數的組合。如果出現這種情況,glCheckFramebufferStatusEXT將返回GL_FRAMEBUFFER_UNSUPPORTED_EXT.

   ----————————————————————————————————----

示例:渲染到紋理

有時,你可能需要生成動態的紋理,如做鏡面效果、動態立方體/環境貼圖、陰影等效果時。動態紋理

可以通過將渲染的場影繪製到紋理上來完成這些效果。傳統解決渲染到紋理的方法是將場景繪製到普通的幀快取上,然後用glCopyTexSubImage2D拷貝幀快取影象到紋理上。

使用FBO,我們可以直接將場景繪製到紋理上,而用不著window系統提供的幀快取。而且,我們可以減少資料拷貝(從幀快取到紋理)的過程。

例程中提供了用FBO和不用FBO來完成渲染到紋理的程式碼,以例比較它們的效能。除了效能上的優點,如果紋理分辯率大於無FBO模式下渲染視窗的大小,視窗區域外的部分將被裁剪,但FBO不會出現這種問題。你可以生成比顯示視窗大得多的幀快取影象

   以下程式碼在渲染迴圈啟動之前,先對FBO和幀快取可關聯影象進行了設定(初始化)。程式中,不僅紋理影象關聯到FBO,一個渲染快取影象也關聯到FBO的深度關聯點。我們並沒有實際用到這個深度快取,只不過,FBO本身需要用它來做深度測試。我們如果沒有把一個深度可渲染影象關聯到FBO,那麼可能會因為不能進行深度測試而無法進行成功渲染。如果渲染過程中需要模板測試,也可以在程式中加上可渲染影象與GL_STENCIL_ATTACHMENT_EXT關聯。

渲染到紋理的過程和普通繪製過程一樣。我們只需將渲染的目的地放在FBO即可。

注意,glGenerateMipmapEXT()也是作為FBO擴充套件的一部分,用於在修改紋理影象的基級(base level)之後,顯式生成mipmap。如果GL_GENERATE_MIPMAP設定為GL_TRUE,glTex{Sub}Image2D()和glCopyTex{Sub}Image2D()將啟用自動mipmap的生成。然而,在紋理的基級修改後,FBO不會自動生成它的MIPMAP。這是因為FBO不呼叫glCopyTex{Sub}Image2D()來修改紋理。因此,glGenerateMipmapEXT()必須顯式的呼叫來生成mipmap。

參考文章