OpenGL像素缓冲对象(PBO)

免责申明(必读!):本翻译原稿来自以下链接,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

http://www.songho.ca/opengl/gl_pbo.html


OpenGL的ARB_pixel_buffer_object 扩展非常接近ARB_vertex_buffer_object。它是为了将顶点数据(vertex data)和像素数据(Pixel data)存储在缓冲对象里面,简单的扩展了ARB_vertex_buffer_object。这个存储像素的缓冲对象叫做像素缓冲对象(PixelBuffer Object -> PBO)。ARB_pixel_buffer_object扩展借用了整个VBO的框架和api,另外,添加了2个额外的“目标”标志。这些标志协助绑定的PBO内存管理器(OpenGL驱动)决定缓冲对象存储的最好的位置:系统内存,共享内存或者显存。另外,目标标志清楚的指出PBO绑定将用于2种操作:GL_PIXEL_PACK_BUFFER_ARB用于将像素数据传送到PBO,或者GL_PIXEL_UNPACK_BUFFER_ARB将PBO传输到像素数据。


举个例子,glReadPixels()和glGetTexImage()是“打包(pack)”像素操作, 而glDrawPixels(), glTexImage2D()和glTexSubImage2D()是“解压(unpack)”操作.当一个PBO绑定到GL_PIXEL_PACK_BUFFER_ARB标志上时,glReadPixels()从OpenGL的帧缓冲(framebuffer)读取像素数据(pixel data),然后把数据画(打包pack)到PBO中。当一个PBO绑定到GL_PIXEL_UNPACK_ARB标志上时,glDrawPixels()从PBO读取(unpack)或复制像素数据到OpenGL帧缓冲(framebuffer)中。

PBO的主要优势是通过直接的内存访问(Direct Memory Access,DMA)而不用涉及到CPU周期就可以快速的将像素数据传输到或者传输出显卡。PBO的另一个优势是异步的直接内存访问传输,让我们比较下传统的纹理传输方法和使用像素缓冲对象。下面左图是用传统的方式从图像源(图像文件或者视频流)去加载图片数据.图像源数据首先加载到系统内存中,然后,通过glTexImage2D()从系统内存里面拷贝到OpenGL纹理对象。这2个传输步骤(加载和拷贝)都是CPU执行的。


反之,右边的图像,图像数据源可以直接加载到了PBO对象里面并由OpenGL控制。和CPU相关联的地方依旧是将图像数据源加载到PBO中,但是,不参与从PBO传输像素数据(pixel data)到纹理对象(texture object)。相反,GPU(OpenGL驱动)管理从PBO拷贝数据到纹理对象中。这意味着OpenGL执行直接内存访问(DMA)传输操作不用浪费CPU周
期。进一步,OpenGL可以安排一个异步的直接内存访问(DMA)传输操作给后面的执行操作。因此,glTexture2D()立即返回,CPU也可以执行其它的一些事,而不用等待数据传输完毕。

这里有2个主要的途径提高像素数据的传输:流纹理上传异步从帧缓冲读回
---------------------------------------------------------------------------------------------------------------
创建PBO
前面提到,像素缓冲对象借用了顶点缓冲对象的所有api。唯一不同的是有两个不同的额外标志PBO:GL_PIXEL_PACK_BUFFER_ARB和GL_PIXEL_UNPACK_BUFFER_ARB。GL_PIXEL_PACK_BUFFER_ARB是用来将像素数据从OpenGL传输到你的应用的,而GL_PIXEL_UNPACK_BUFFER_ARB是用来将像素数据从应用传输到OpenGL中的。OpenGL参考这些标志决定最好的内存空间给PBO,例如,一个显存用来上传(解压unpacking)纹理,或者系统内存用来读取(打包packing)帧缓冲。然后,这些目标标志只是用来提示而已。OpenGL驱动会为你决定合适的位置。

创建一个PBO需要3步:
  1.通用glGenBuffersARB()分配一个新的缓冲对象。
  2.通过glBindBufferARB()绑定缓冲对象。
  3.通过glBufferDataARB()拷贝像素数据到缓冲对象。

如果你在glBufferDataARB()中指定了一个NULL指针指向源数组,那么PBO只是分配给定的数据大小的内存空间。glBufferDataARB()的最后一个参数是另外一个性能的提示,提示PBO将会用缓冲对象来做什么。GL_STREAM_ARB用于流纹理上传,GL_STREAM_READ_ARB用来异步帧缓冲读回。

更多细节请查看VBO
---------------------------------------------------------------------------------------------------------------
映射PBO(Mapping PBO)
PBO提供了一个内存映射机制来将OpenGL的控制缓冲对象映射到客户端的内存地址空间。所以,客户端可以通过glMapBufferARB()和glUnmapBufferARB()修改一部分的缓冲对象或者整个缓冲。
 
 void* glMapBufferARB(GLenum target, GLenum access);
 GLboolean glUnmapBufferARB(GLenum target);
 
如果成功,glMapBufferARB()返回一个指向缓冲对象的指针,如果失败,返回NULL。target参数是GL_PIEXEL_PACK_BUFFER或者GL_PIXEL_UNPACK_BUFFER_ARB。第二个参数,指定访问的映射缓冲将要做什么。从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

这个demo应用使用PBO上传(解压)流纹理到一个OpenGL纹理对象。你可以按空格键切换到不同的传输模式(单独PBO,双POB,没有PBO)比对执行的不同。

在PBO模式下,纹理源数据每帧直接写入到映射像素缓冲中。然后呢,这些数据通过使用glTexSubImage2D()从PBO传输到纹理对象上。通过使用PBO,OpenGL可以在POB和纹理对象之间使用异步的直接内存访问(DMA)传输。他显著
的提高了纹理的上传性能。如果支持异步的直接内存访问(DMA)传输,glTexSubImage2D()应该立即返回,而CPU可以做其他的工作而不需要等待实际的纹理复制。

为了最大限度的发挥流传输性能,你可以使用多像素缓冲对象。图片显示了2个PBO同时被使用;当纹理源数据被写入到别的PBO中时,glTexSubImage2D()就可以从PBO拷贝像素数据。

对于第n帧,PBO1用于glTexSubImage2D(),PBO2用来获取新的图片数据。对于n+1帧,2个像素缓冲交换规则,然后继续更新纹理。由于异步的直接内存访问(DMA)传输,更新和拷贝流程可以同时进行。CPU更新纹理数据到PBO的同时,GPU从别的PBO拷贝纹理。


<span style="font-size:18px;">// "index"用于拷贝从PBO拷贝像素到一个纹理对象
// "nextIndex"用于更新别的PBO的像素
index = (index + 1)%2;
nextIndex = (index + 1)%2;

// 绑定纹理和PBO
glBindTexture(GL_TEXTURE_2D, textureId);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index]);


// 从PBO拷贝像素到纹理对象
// 使用偏移(offset)代替指针
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT,
                GL_BGRA, GL_UNSIGNED_BYTE, 0);
                
// 绑定PBO用来更新纹理源数据
glBindeBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex]);


// 注意glMapBufferARB()会导致同步问题。
// 如果GPU正处理这个buffer,glMapBufferARB()将等待直到GPU完成它的工作。为了避免等待,你可以在调用
// glMapBufferARB()之前,先调用glBufferDataARB()并传入NULL指针。如果你这么做,前面在PBO里的数据将会
// 被丢弃,然后glMapBufferARB()立即返回一个新分配的指针,即使GPU仍然在处理前面的数据。
glBufferDateARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);


// 映射缓冲对象到客户端内存
GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
                                    
if (ptr)
{
    // 直接在映射缓冲上更新数据
    updatePixels(ptr, DATA_SIZE);
    glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // 释放映射缓冲
}    

// 使用ID为0去释放PBO是个好主意
// 一旦用0绑定,所有像素操作返回平常的方式
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);</span>

---------------------------------------------------------------------------------------------------------------

例子:异步读回


下载源码和二进制:pboPack.zip 

这个demo应用从帧缓冲(左边)读取(打包pack)像素到PBO,然后,改变图像的亮度并把它画回到窗口的右边。你可以通过按空格键来开/关PBO,并测量glReadPixels()的执行速度。

常规的glReadPixels()会阻塞管线,等待直到所有像素数据传输完,然后再返回应用的控制权。相反,使用PBO的glReadPixels()可以安排异步的直接内存访问传输,然后无需等待直接返回。因此,用OpenGL(GPU)直接内存传输数据时,应用(CPU)可以马上执行其他的线程。

这个demo使用2个像素缓冲。在第n帧,应用使用glReadPixels()从OpenGL帧缓冲读取像素到PBO1,同时在PBO2中处理像素数据。这些读和处理可以同时进行,因为glReadPixels()到PBO1是立即返回的,而CPU在PBO2中处理数据是无需等待的。然后,我们每帧在PBO1和PBO2中交换。



<span style="font-size:18px;">// "index"用于从帧缓冲中读取像素数据到PBO
// "nextIndex"用于在其它的PBO中更新像素
index = (index + 1)%2;
nextIndex = (index + 1)%2;

// 设置要读取的目标帧缓冲对象
glReadBuffer(GL_FRONT);

// 从帧缓冲区读取像素到PBO
// glReadPixels()应该立即返回
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[index]);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, 0);

// 映射PBO然后用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);
}

// 回到常规的像素操作
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);</span><span style="font-size: 24px;">
</span>




  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值