OpenGL系列教程之九:OpenGL像素缓冲区对象(PBO)

来源:互联网 发布:wordpress 程序员主题 编辑:程序博客网 时间:2024/05/22 00:46

相关主题:顶点缓冲区对象(VBO),帧缓冲区对象(FBO)

下载: pboUnpack.zip, pboPack.zip

  • 概述
  • 创建PBO
  • 映射PBO
  • 例子:使用PBO上传到纹理
  • 例子:使用PBO进行异步地回读



概述

OpenGL PBO

OpenGL中的ARB_pixel_buffer_object扩展和ARB_vertex_buffer_object扩展非常类似。这样非常简单地扩充ARB_vertex_buffer_object是为了不仅能在缓冲区对象中存储顶点数据也能存储像素数据。这个存储像素数据的缓冲区对象叫做像素缓冲区(PBO)。ARB_pixel_buffer_object扩展借用了所有VBO的框架和API,并且添加了两个额外的"target"值。这个“target”值方便PBO的内存管理机制决定缓冲区对象在内存中的最佳位置,是将缓冲区对象放置在系统内存中,还是共享内存中,还是显卡内存中。这个“target”值也清晰地指明了PBO的使用范围:GL_PIXEL_PACK_BUFFER_ARB表示将像素数据“传入”到PBO,GL_PIXEL_UNPACK_BUFFER_ARB表示数据从PBO中“传出”。

例如,glReadPixels()和glGetTexImage()是“传入”操作,glDrawPixels(),glTexImage2D()和glTexSubImage2D()是“传出”操作。当一个PBO使用GL_PIXEL_PACK_BUFFER_ARB 显示使用范围时,glReadPixels()从OpenGL的帧缓冲区中读取像素并将数据写入(”传入“)PBO中。当一个PBO使用GL_PIXEL_UNPACK_BUFFER_ARB 显示使用范围时,glDrawPixels()从PBO中读取(”传出“)数据并将它们复制到OpenGL中的帧缓冲区中。

PBO最大的优点是使用DMA(Direct Memory Access,直接内存访问)的方式快速地将数据传入到显卡或从显卡传出,它不需要等待CPU的周期。PBO的另外一个优点是可以使用异步地DMA方式传输数据。让我们来比较一下传统的纹理传输的方式和使用PBO的区别。如下图所示,第一幅图显示传统的方式从数据源(图片文件或视频流)中加载纹理数据。数据源首先被加载到系统内存中,然后使用glTexImage2D()冲系统内存中复制到OpenGL中的纹理对象中。这两个步骤(加载和复制)都是在CPU中进行的。

第二幅图片则正好相反,数据源能直接被加载到PBO中,而PBO是被OpenGL控制的。CPU也参与了加载数据源到PBO,但是不会将PBO中的纹理数据传输到纹理对象中。反而由GPU(OpenGL驱动器)管理将数据从PBO复制到纹理对象中。这意味着OpenGL进行了DMA方式的传输操作而不需要等待CPU的周期。除此之外,OpenGL甚至还可以使用异步地传输方式。因此glTexImage2D()会立即返回,CPU可以执行其他的事情而不需要等待像素数据传输完。

不使用PBO载入纹理

使用PBO载入纹理

有两种主要的使用PBO提高传输像素数据的方式:上传到纹理和从帧缓冲区中进行异步回读。



创建PBO

像之间提到过的那样,像素缓冲区对象(PBO)借用了顶点缓冲区对象(VBO)中的所有API。唯一的区别是为”target“表示增加了两个值:GL_PIXEL_PACK_BUFFER_ARB和GL_PIXEL_UNPACK_BUFFER_ARB。GL_PIXEL_PACK_BUFFER_ARB用来表示将数据从OpenGL中传输到你的应用程序中,GL_PIXEL_UNPACK_BUFFER_ARB表示将数据从应用程序中传输到OpenGL中。OpenGL使用这些标示来决定将PBO放置在最合适的地方,例如,显卡内存用来上传(unpack)纹理,系统内存用来冲帧缓冲区中读取(pack)数据。然而,这些标记都是单独使用的,OpenGL驱动将会为你指定合适的位置。

创建PBO需要3步:
  1. 使用glGenBuffersARB()创建一个缓冲区对象
  2. 使用glBindBufferARB()绑定一个缓冲区对象
  3. 使用glBufferDataARB()将像素数据复制到缓冲区对象中
如果在glBufferDataARB()中为数据源数组传入了NULL,那么PBO只会分配一个指定大小的空间。glBufferDataARB()函数的最后一个参数是另个用来指定PBO如何使用的性能指标,GL_STREAM_DRAW_ARB表示通过流的方式上传纹理,GL_STREAM_READ_ARB表示异步地从帧缓冲区中进行回读。

请查看VBO中的更多细节。



映射PBO

PBO提供了内存映射的机制来将OpenGL中的缓冲区对象映射到客户端的内存区域中。因此客户端可以通过使用glMapBufferARB()和glUnmapBufferARB()修改缓冲中的部分内容或者整个缓冲区。

[cpp] view plain copy
  1. <span style="white-space:pre">    </span>void* glMapBufferARB(GLenum target, GLenum access)  
  2.   
  3. <span style="white-space:pre">    </span>GLboolean glUnmapBufferARB(GLenum target)  

glMapBufferARB()如果成功将会返回一个指向缓冲区对象的指针,否则将会返回NULL。target参数是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在指定的缓冲区对象中工作完成之后才返回。为了避免等待,你可以首先使用一个空指针调用glBufferDataARB()函数,然后调用glMapBufferARB()。这样OpenGL将会废除旧的缓冲区,并为缓冲区对象分配新的空间。

缓冲区对象使用完后必须使用glUnmapBufferARB()解除映射,glUnmapBufferARB()成功时返回gl_TRUE,否则返回GL_FALSE。



例子:使用PBO上传到纹理



下载源文件和可执行程序: pboUnpack.zip

这个例子程序将纹理流从PBO上传到OpenGL中的纹理对象中。你可以通过按空格键来切换不同的模式(单个PBO,两个PBO,不使用PBO)并比较它们的性能差异。

在PBO模式中纹理源每帧都被直接写入到映射的像素缓冲区中。然后这些数据使用glTexSubImage2D()从PBO中传输到纹理对象中。通过使用PBO,OpenGL能在PBO和纹理对象中使用异步的DMA方式传输数据。它非常有效地提高了纹理传输的性能。如果异步的DMA方式被支持,glTexSubImage2D()会被立即返回,CPU不需要等待实际的纹理复制到PBO完成就可以继续做其他工作了。


使用两个PBO上传纹理
为了最大化地提示传输的性能,你可以使用两个像素缓冲区对象。上图显示了2个PBO同时被使用的情况;glTexSubImage2D()将像素数据从PBO中复制到纹理对象中时纹理源正好被写入到另一个PBO。
对第n帧而言,PBO1在执行glTexSubImage2D()操作,PBO2则在读取纹理。那么在第n+1帧时,2个PBO交换操作并且继续更新纹理。因为异步地DMA方式传输,更新和复制操作可以同时进行。CPU更新纹理源时GPU会从PBO会从PBO中复制数据。

[cpp] view plain copy
  1. // "index" 被用来从PBO中复制像素到纹理对象中  
  2. // "nextIndex" 被用来在另一个PBO中更新像素  
  3. index = (index + 1) % 2;  
  4. nextIndex = (index + 1) % 2;  
  5.   
  6. // 绑定纹理和PBO  
  7. glBindTexture(GL_TEXTURE_2D, textureId);  
  8. glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[index]);  
  9.   
  10. // 从PBO中复制像素到到纹理对象中  
  11. // 使用偏移量而不是指针  
  12. glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT,  
  13.                 GL_BGRA, GL_UNSIGNED_BYTE, 0);  
  14.   
  15. // 绑定PBO来更新纹理源  
  16. glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboIds[nextIndex]);  
  17.   
  18. //注意glMapBufferARB()将会导致一个同步的问题。  
  19. //如果GPU依然在顶点缓冲区中工作,那么glMapBufferARB()函数  
  20. //将会在GPU结束在指定的缓冲区的工作之后才返回。  
  21. //为了避免等待,你可以首先使用一个空指针调用glBufferDataARB()函数,  
  22. //然后调用glMapBufferARB()。在这种情况下,之前的数据将会被舍弃,  
  23. //glMapBufferARB()将立即返回一个新分配区域的指针,  
  24. //即使GPU依然在之前的数据上工作。  
  25. glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_DRAW_ARB);  
  26.   
  27. // 将缓冲区对象映射到客户端内存中  
  28. GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB,  
  29.                                         GL_WRITE_ONLY_ARB);  
  30. if(ptr)  
  31. {  
  32.     // 在映射的缓冲区中直接更新数据  
  33.     updatePixels(ptr, DATA_SIZE);  
  34.     glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // 释放映射的缓冲区  
  35. }  
  36.   
  37. // PBO使用完后使用将它绑定到0是一种释放PBO的很好的方法  
  38. // 当被绑定到0后, all pixel operations are back to normal ways.所有的像素操作变成了正常的方式  
  39. glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);  




例子:使用PBO进行异步地回读




下载源文件和可执行文件: pboPack.zip

这个例子程序从帧缓冲区中读取数据到PBO中,然后修改图片的亮度后又重新绘制在窗口中。你可以通过按空格键了切换PBO模式开启或关闭,来测试glReadPixels()函数的性能。

传统的glReadPixels()函数会阻塞渲染管线,它会一直等待直到所有的像素数据传输完成。然后才会将控制权交给应用程序。相反,使用PBO时glReadPixel()使用异步地DMA方式传输并且能立即返回。因此,应用程序(CPU)可以立即执行其他程序,这时数据会被OPenGL(GPU)使用DMA的方式传输。




这个例子使用两个像素缓冲区。在第n帧,应用程序使用glReadPixels()从OpenGL帧缓冲区中读取数据到PBO1中,并处理PBO2中的像素数据。这个读取和处理的过程可以同时进行,因此glReadPixels()函数会立即返回这样CPU开始处理PBO2中的数据。我们会在每一帧中交替PBO1和PBO2.

[cpp] view plain copy
  1. // "index" 用来表示将数据从帧缓冲区中读入PBO中  
  2. // "nextIndex" 更新另一个PBO的像素  
  3. index = (index + 1) % 2;  
  4. nextIndex = (index + 1) % 2;  
  5.   
  6. // 设置帧缓冲区可读  
  7. glReadBuffer(GL_FRONT);  
  8.   
  9. // 从帧缓冲区中读取数据到PBO中  
  10. // glReadPixels() 会立即返回.  
  11. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[index]);  
  12. glReadPixels(0, 0, WIDTH, HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, 0);  
  13.   
  14. // 映射数据到PBO  
  15. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[nextIndex]);  
  16. GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB,  
  17.                                         GL_READ_ONLY_ARB);  
  18. if(ptr)  
  19. {  
  20.     processPixels(ptr, ...);  
  21.     glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);  
  22. }  
  23.   
  24. // 返回正常的像素操作中  
  25. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);  

0 0