OpenGL Pixel Buffer Object (PBO)
Overview
OpenGL pixel_buffer_object
扩展非常接近 vertex_buffer_object
。它只是扩展出 vertex_buffer_object
扩展,以便不仅将顶点数据并且将像素数据也存储到缓冲区对象中。这种存储像素数据的缓冲对象称为像素缓冲对象(PBO)。 pixel_buffer_object
扩展借用了所有 VBO 框架和 API,此外还添加了 2 个额外的“target”令牌。这些令牌协助 PBO 内存管理器(OpenGL 驱动程序)确定缓冲对象的最佳位置;系统内存、共享内存或显存。此外,target令牌定义了绑定的 PBO 将用于 2 个不同的操作:GL_PIXEL_PACK_BUFFER
将像素数据传输到 PBO,或 GL_PIXEL_UNPACK_BUFFER
将像素数据从 PBO 传输。
例如,glReadPixels()
和glGetTexImage()
是“pack”像素操作,而 glDrawPixels()
,glTexImage2D()
和glTexSubImage2D()
是“unpack”操作。当 PBO 与 GL_PIXEL_PACK_BUFFER
令牌绑定时,glReadPixels()
从 OpenGL 帧缓冲区读取像素数据并将数据写入(打包)到 PBO。当 PBO 与 GL_PIXEL_UNPACK_BUFFER
令牌绑定时,glDrawPixels()
从 PBO 读取(解包)像素数据并将它们复制到 OpenGL 帧缓冲区。
PBO 的主要优点是通过 DMA(直接内存访问)与图形卡进行快速像素数据传输,而无需占用 CPU 周期。而且,PBO 的另一个优点是异步 DMA 传输。
让我们将传统的纹理传输方法与使用像素缓冲区对象进行比较。下图左侧是从图像源(图像文件或视频流)加载纹理数据的常规方式。材质源首先加载到系统内存中,然后使用 glTexImage2D()
从系统内存复制到 OpenGL 纹理对象。这 2 个传输过程(加载和复制)都由 CPU 执行。
有两种主要的 PBO 方法可以提高像素数据传输的性能:流式纹理更新(streaming texture update)和从帧缓冲区异步回读(asynchronous read_back from the framebuffer)。
Creating PBO
如前所述,Pixel Buffer Object
借用了Vertex Buffer Object
的所有 API。唯一的区别是 PBO 有 2 个额外的令牌:GL_PIXEL_PACK_BUFFER
和 GL_PIXEL_UNPACK_BUFFER
。
GL_PIXEL_PACK_BUFFER
用于将像素数据从 OpenGL 传输到您的应用程序,而 GL_PIXEL_UNPACK_BUFFER
表示将像素数据从应用程序传输到 OpenGL。OpenGL 参考这些标记来确定 PBO 的最佳内存空间,例如,用于上传(unpacking)纹理的显存,或用于读取(pack)帧缓冲区的系统内存。但是,这些目标标记只是举例。 具体实现时OpenGL 驱动程序为您决定合适的位置。
Creating a PBO requires 3 steps:
- 使用
glGenBuffers()
生成一个新的缓冲区对象。 - 使用
glBindBuffer()
绑定缓冲区对象。 - 使用
glBufferData()
将像素数据复制到缓冲区对象。
如果在glBufferData()
中指定指向源数组的为 NULL 指针,则 PBO 仅分配具有给定数据大小的内存空间。 glBufferData()
的最后一个参数是 PBO 的另一个性能提示,用于提供如何使用缓冲区对象。
GL_STREAM_DRAW
用于流式纹理上传,GL_STREAM_READ
用于异步帧缓冲区回读。
请查看 VBO文档 以获取更多详细信息。
Mapping PBO
PBO 提供了一种内存映射机制,将OpenGL 控制的缓冲区对象映射到客户端的内存地址空间。因此,客户端可以使用glMapBuffer()
和 glUnmapBuffer()
修改缓冲区对象的一部分或整个缓冲区。
void* glMapBuffer(GLenum target, GLenum access)
GLboolean glUnmapBuffer(GLenum target)
如果映射成功,glMapBuffer()
将返回指向缓冲区对象的指针。否则返回NULL。target参数是 GL_PIXEL_PACK_BUFFER
或 GL_PIXEL_UNPACK_BUFFER
。第二个参数,access 指定如何处理映射的缓冲区;从 PBO 读取数据 (GL_READ_ONLY
),将数据写入 PBO (GL_WRITE_ONLY
),或两者 (GL_READ_WRITE
)。
如果 GPU 仍在使用缓冲区对象,则 glMapBuffer()
将不会返回,直到 GPU 使用相应的缓冲区对象完成其工作。为了避免这种停顿(等待),请在 glMapBuffer()
之前使用 NULL 指针调用 glBufferData()
。然后,OpenGL 将丢弃旧缓冲区,并为缓冲区对象分配新的内存空间。
使用 PBO 后,缓冲区对象必须使用 glUnmapBuffer()
取消映射。如果成功,glUnmapBuffer()
返回 GL_TRUE
。否则,它返回 GL_FALSE
。
Others
为了最大化流传输性能,您可以使用多个像素缓冲区对象。该图显示同时使用 2 个 PBO; glTexSubImage2D()
从 PBO 复制像素数据,同时将纹理源写入另一个 PBO。对于第 n 帧,PBO 1 用于 glTexSubImage2D()
,PBO 2 用于获取新的纹理源。对于第 n+1 帧,2 个像素缓冲区正在切换角色并继续更新纹理。由于异步 DMA 传输,更新和复制过程可以同时执行。 CPU 将纹理源更新到 PBO,而 GPU 从另一个 PBO 复制纹理。
传统的 glReadPixels() 会阻塞管道并等待所有像素数据传输完毕。然后,它将控制权返回给应用程序。相反,带有 PBO 的 glReadPixels() 可以调度异步 DMA 传输并立即返回而不会停顿。因此,应用程序 (CPU) 可以立即执行其他进程,同时通过 OpenGL (GPU) 使用 DMA 传输数据。此演示使用 2 个像素缓冲区。在第 n 帧,应用程序使用 glReadPixels() 将像素数据从 OpenGL 帧缓冲区读取到 PBO 1,并在 PBO 2 中处理像素数据。这些读取和处理可以同时执行,因为 glReadPixels() 到 PBO 1 立即返回,CPU 立即开始处理 PBO 2 中的数据。并且,我们在每一帧上在 PBO 1 和 PBO 2 之间交替。