參考原文:openGL Pixel Buffer Object (PBO)
我個人覺得:PBO(像素緩沖對象)是基于數據存儲優化的管理器,有點像共享內存的設計思想。
OpenGL PBO
OpenGL ARB_pixel_buffer_object的擴展非常接近ARB_vertex_buffer_object。
它只是ARB_vertex_buffer_object的擴展,以便不僅存儲頂點數據,而且像素數據到緩沖區對象。
存儲像素數據的該緩沖對象稱為像素緩沖對象(PBO)。
ARB_pixel_buffer_object擴展借用了所有VBO框架和API,另外,添加了2個額外的"target" tokens。
這些tokens輔助PBO存儲器管理器(OpenGL驅動器)來確定緩沖器對象的最佳位置;
系統存儲器,共享存儲器或視頻存儲器。
此外,"target" tokens清楚地指定綁定的PBO將在兩個不同操作中的一個中使用;
GL_PIXEL_PACK_BUFFER_ARB用于將像素數據傳輸到PBO,或GL_PIXEL_UNPACK_BUFFER_ARB用于從PBO傳輸像素數據。
例如,glReadPixels()和glGetTexImage()是“pack” 像素操作,glDrawPixels(),glTexImage2D()和glTexSubImage2D()是
“解包”操作。
當PBO與GL_PIXEL_PACK_BUFFER_ARB令牌綁定時,glReadPixels()從OpenGL幀緩沖區讀取像素數據,并將數據寫入(打包)到PBO中。
當PBO與GL_PIXEL_UNPACK_BUFFER_ARB令牌綁定時,glDrawPixels()從PBO讀取(解壓縮)像素數據并將其復制到OpenGL幀緩沖區。
PBO的主要優點是通過DMA(直接存儲器訪問)實現快速像素數據傳輸到圖形卡和從圖形卡傳輸,而不會影響CPU周期。
而且,PBO的另一個優點是異步DMA傳輸。
讓我們比較一個傳統的紋理傳輸方法和使用一個像素緩沖對象。
下圖的左側是從圖像源(圖像文件或視頻流)加載紋理數據的常規方法。
源首先被加載到系統內存中,然后從系統內存復制到一個帶有glTexImage2D()的OpenGL紋理對象。
這兩個傳送過程(加載和復制)都由CPU執行。
無PBO的紋理加載
紋理加載與PBO
相反,在右側圖中,圖像源可以直接加載到由OpenGL控制的PBO中。
CPU仍然涉及將源加載到PBO,但是不用于將像素數據從PBO傳送到紋理對象。
相反,GPU(OpenGL驅動程序)管理將數據從PBO復制到紋理對象。
這意味著OpenGL執行DMA傳輸操作,而不會浪費CPU周期。
此外,OpenGL可以調度異步DMA傳輸以供稍后執行。
因此,glTexImage2D()立即返回,CPU可以執行其他操作,而不用等待像素傳輸完成。
有兩個主要的PBO方法來提高像素數據傳輸的性能:
創建PBO
如前所述,Pixel Buffer Object從 頂點緩沖區對象中
借用所有API 。
唯一的區別是PBO有2個額外的token:
GL_PIXEL_PACK_BUFFER_ARB和GL_PIXEL_UNPACK_BUFFER_ARB。
GL_PIXEL_PACK_BUFFER_ARB用于將像素數據從OpenGL傳輸到應用程序,GL_PIXEL_UNPACK_BUFFER_ARB表示將像素數據從應用程序傳輸到OpenGL。
OpenGL指的是這些token以確定PBO的最佳存儲空間,例如,用于上傳(拆包)紋理的視頻存儲器或用于讀取(打包)幀緩沖區的系統存儲器。
然而,這些target token只是提示。
OpenGL驅動程序決定適當的位置。
創建PBO需要3個步驟:
使用glGenBuffersARB()生成一個新的緩沖區對象。
使用glBindBufferARB()綁定緩沖區對象。
使用glBufferDataARB()
將像素數據復制到緩沖區對象。
如果在glBufferDataARB()中指定了指向源數組的NULL指針,則PBO只分配給定數據大小的內存空間。
glBufferDataARB()的最后一個參數是PBO提供如何使用緩沖區對象的另一個性能提示。
GL_STREAM_DRAW_ARB用于流紋理上傳,
GL_STREAM_READ_ARB用于異步幀緩沖讀回。
請檢查
VBO了解更多詳情。
映射PBO
PBO提供了一種存儲器映射機制,以將OpenGL控制的緩沖器對象映射到客戶機的存儲器地址空間。
因此,客戶端可以通過使用
glMapBufferARB()和glUnmapBufferARB()
來修改緩沖區對象或整個緩沖區的一部分。
void* glMapBufferARB(GLenum target, GLenum access)
GLboolean glUnmapBufferARB(GLenum target)
如果成功,glMapBufferARB()返回指向緩沖區對象的指針。
否則返回NULL。
該目標參數是GL_PIXEL_PACK_BUFFER_ARB或GL_PIXEL_UNPACK_BUFFER_ARB。
第二個參數,
access
指定如何處理映射緩沖區;
從PBO讀取數據(GL_READ_ONLY_ARB),將數據寫入PBO(GL_WRITE_ONLY_ARB)或兩者(GL_READ_WRITE_ARB)。
注意,如果GPU仍然使用緩沖區對象,glMapBufferARB()將不會返回,直到GPU完成其作業與相應的緩沖區對象。
為了避免這種停頓(等待),在glMapBufferARB()之前調用帶有NULL指針的glBufferDataARB()。
然后,OpenGL將丟棄舊的緩沖區,并為緩沖區對象分配新的內存空間。
緩沖區對象必須在使用PBO后通過glUnmapBufferARB()取消映射。
如果成功,glUnmapBufferARB()返回GL_TRUE。
否則,它返回GL_FALSE。
示例:流紋理上傳
下載源碼和二進制文件:
pboUnpack.zip(Updated:2014-04-24)
這個演示應用程序使用PBO上傳(解壓縮)流紋理到一個OpenGL紋理對象。
您可以通過按空格鍵切換到不同的傳輸模式(單個PBO,雙PBO和無PBO),并比較性能差異。
在PBO模式中每個幀,紋理源直接寫在映射的像素緩沖器上。
然后,使用glTexSubImage2D()將這些數據從PBO傳輸到紋理對象。
通過使用PBO,OpenGL可以在PBO和紋理對象之間執行異步DMA傳輸。
它顯著增加了紋理上傳性能。
如果支持異步DMA傳輸,glTexSubImage2D()應立即返回,并且CPU可以處理其他作業,而不等待實際的紋理復制。
要最大化流傳輸性能,您可以使用多個像素緩沖區對象。
該圖顯示了2個PBO同時使用;
當紋理源正在寫入另一個PBO時,glTexSubImage2D()從PBO復制像素數據。
對于第n幀,
PBO 1
用于glTexSubImage2D(),
PBO 2
用于獲取新的紋理源。
對于第n + 1幀,2個像素緩沖器正在切換角色并繼續更新紋理。
由于異步DMA傳輸,更新和復制過程可以同時執行。
CPU將紋理源更新為PBO,而GPU從其他PBO復制紋理。
// "index" is used to copy pixels from a PBO to a texture object
// "nextIndex" is used to update pixels in the other PBO
index = (index + 1) % 2;
nextIndex = (index + 1) % 2;
// bind the texture and PBO
glBindTexture(GL_TEXTURE_2D, textureId);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index]);
// copy pixels from PBO to texture object// Use offset instead of ponter.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT,
GL_BGRA, GL_UNSIGNED_BYTE, 0);
// bind PBO to update texture source
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex]);
// Note that glMapBufferARB() causes sync issue.// If GPU is working with this buffer, glMapBufferARB() will wait(stall)// until GPU to finish its job. To avoid waiting (idle), you can call// first glBufferDataARB() with NULL pointer before glMapBufferARB().// If you do that, the previous data in PBO will be discarded and// glMapBufferARB() returns a new allocated pointer immediately// even if GPU is still working with the previous data.
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);
// map the buffer object into client's memory
GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB,
GL_WRITE_ONLY_ARB);
if(ptr)
{
// update data directly on the mapped buffer
updatePixels(ptr, DATA_SIZE);
glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release the mapped buffer
}
// it is good idea to release PBOs with ID 0 after use.// Once bound with 0, all pixel operations are back to normal ways.
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
示例:異步讀回
下載源碼和二進制文件:
pboPack.zip(Updated:2014-05-13)。
此演示應用程序將幀緩沖區(左側)的像素數據讀取(打包)到PBO,然后在修改圖像的亮度之后將其繪制回窗口的右側。
您可以通過按空格鍵切換PBO開/關,并測量glReadPixels()的性能。
傳統的glReadPixels()阻塞流水線并等待,直到所有像素數據被傳輸。
然后,它將控制權返回給應用程序。
相反,具有PBO的glReadPixels()可以調度異步DMA傳輸并立即返回而不會停頓。
因此,應用程序(CPU)可以立即執行其他進程,同時通過OpenGL(GPU)使用DMA傳輸數據。
應用程序使用glReadPixels()從OpenGL幀
緩沖區讀取像素數據到PBO 1,并處理PBO 2中的像素數據.
這些讀取和處理可以同時執行,因為glReadPixels()到PBO 1立即返回,并且CPU開始在PBO 2中無延遲地處理數據。
并且,我們在每個幀上
在PBO 1和PBO 2之間交替
// "index" is used to read pixels from framebuffer to a PBO
// "nextIndex" is used to update pixels in the other PBO
index = (index + 1) % 2;
nextIndex = (index + 1) % 2;
// set the target framebuffer to read
glReadBuffer(GL_FRONT);
// read pixels from framebuffer to PBO// glReadPixels() should return immediately.
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[index]);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, 0);
// map the PBO to process its data by CPU
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[nextIndex]);
GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB,
GL_READ_ONLY_ARB);
if(ptr)
{
processPixels(ptr, ...);
glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
}
// back to conventional pixel operation
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);