1. PBO概念
OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。
OpenGL PBO(像素缓冲区对象) 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。

OpenGL PBO
2. PBO用途
在 OpenGL 开发中,特别是在低端平台上处理高分辨率的图像时,图像数据在内存和显存之前拷贝往往会造成性能瓶颈,而利用 PBO 可以在一定程度上解决这个问题。
使用 PBO 可以在 GPU 的缓存间快速传递像素数据,不影响 CPU 时钟周期,除此之外,PBO 还支持异步传输。

不使用 PBO 加载纹理
上图从文件中加载纹理,图像数据首先被加载到 CPU 内存中,然后通过 glTexImage2D 函数将图像数据从 CPU 内存复制到 OpenGL 纹理对象中 (GPU 内存),两次数据传输(加载和复制)完全由 CPU 执行和控制。

如上图所示,文件中的图像数据可以直接加载到 PBO 中,这个操作是由 CPU 控制。我们可以通过 glMapBufferRange 获取 PBO 对应 GPU 缓冲区的内存地址。
将图像数据加载到 PBO 后,再将图像数据从 PBO 传输到纹理对象中完全是由 GPU 控制,不会占用 CPU 时钟周期。所以,绑定 PBO 后,执行 glTexImage2D (将图像数据从 PBO 传输到纹理对象) 操作,CPU 无需等待,可以立即返回。
通过对比这两种(将图像数据传送到纹理对象中)方式,可以看出,利用 PBO 传输图像数据,省掉了一步 CPU 耗时操作(将图像数据从 CPU 内存复制到 纹理对象中)。
3. 使用方式
3.1 PBO创建
int imgByteSize = m_Image.width * m_Image.height * 4;//RGBA
glGenBuffers(1, &uploadPboId);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId);
glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
glGenBuffers(1, &downloadPboId);
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
PBO 的创建和初始化类似于 VBO ,以上示例表示创建 PBO ,并申请大小为 imgByteSize 的缓冲区。绑定为 GL_PIXEL_UNPACK_BUFFER 表示该 PBO 用于将像素数据从程序传送到 OpenGL 中;绑定为 GL_PIXEL_PACK_BUFFER 表示该 PBO 用于从 OpenGL 中读回像素数据。
从上面内容我们知道,加载图像数据到纹理对象时,CPU 负责将图像数据拷贝到 PBO ,而 GPU 负责将图像数据从 PBO 传送到纹理对象。所以,当我们使用多个 PBO 时,通过交换 PBO 的方式进行拷贝和传送,可以实现这两步操作同时进行。
3.2 使用双PBO加载图像数据到纹理对象

使用两个 PBO 加载图像数据到纹理对象
如图示,利用 2 个 PBO 加载图像数据到纹理对象,使用 glTexSubImage2D 通知 GPU 将图像数据从 PBO1 传送到纹理对象,同时 CPU 将新的图像数据复制到 PBO2 中。
int dataSize = m_RenderImage.width * m_RenderImage.height * 4;
//使用 `glTexSubImage2D` 将图像数据从 PBO1 传送到纹理对象int index = m_FrameIndex % 2;
int nextIndex = (index + 1) % 2;
BEGIN_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")
glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[index]);
//调用 glTexSubImage2D 后立即返回,不影响 CPU 时钟周期
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
END_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")
//更新图像数据,复制到 PBO 中
BEGIN_TIME("PBOSample::UploadPixels Update Image data")
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[nextIndex]);
glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
dataSize,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT);
LOGCATE("PBOSample::UploadPixels bufPtr=%p",bufPtr);
if(bufPtr)
{
memcpy(bufPtr, m_RenderImage.ppPlane[0], static_cast<size_t>(dataSize));
//update image data
int randomRow = rand() % (m_RenderImage.height - 5);
memset(bufPtr + randomRow * m_RenderImage.width * 4, 188,
static_cast<size_t>(m_RenderImage.width * 4 * 5));
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
END_TIME("PBOSample::UploadPixels Update Image data")
对比使用 2 个 PBO 和不使用 PBO 加载图像数据到纹理对象的耗时差别:

使用 2 个 PBO 加载图像数据的耗时

不使用 PBO 加载图像数据的耗时
3.3 使用双PBO从帧缓冲区读回图像数据

使用两个 PBO 从帧缓冲区读回图像数据
如上图所示,利用 2 个 PBO 从帧缓冲区读回图像数据,使用 glReadPixels 通知 GPU 将图像数据从帧缓冲区读回到 PBO1 中,同时 CPU 可以直接处理 PBO2 中的图像数据。
//交换 PBOint index = m_FrameIndex % 2;
int nextIndex = (index + 1) % 2;
//将图像数据从帧缓冲区读回到 PBO 中
BEGIN_TIME("DownloadPixels glReadPixels with PBO")
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[index]);
glReadPixels(0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
END_TIME("DownloadPixels glReadPixels with PBO")
// glMapBufferRange 获取 PBO 缓冲区指针
BEGIN_TIME("DownloadPixels PBO glMapBufferRange")
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[nextIndex]);
GLubyte *bufPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
dataSize,
GL_MAP_READ_BIT));
if (bufPtr) {
nativeImage.ppPlane[0] = bufPtr;
//NativeImageUtil::DumpNativeImage(&nativeImage, "/sdcard/DCIM", "PBO");
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
END_TIME("DownloadPixels PBO glMapBufferRange")
对比下从帧缓冲区读回图像数据,使用 PBO 和不使用 PBO 两种情况的耗时差别:

使用 PBO 从帧缓冲区读回图像数据耗时

glMapBufferRange 操作的耗时

不使用 PBO 从帧缓冲区读回图像数据耗时
3.4 缺点
经测试表明,在部分硬件上glMapBufferRange映射出来的Buffer拷贝极为耗时
官方说明:
Mappings to the data stores of buffer objects may have nonstandard performance characteristics. For example, such mappings may be marked as uncacheable regions of memory, and in such cases reading from them may be very slow. To ensure optimal performance, the client should use the mapping in a fashion consistent with the values of GL_BUFFER_USAGE and access. Using a mapping in a fashion inconsistent with these values is liable to be multiple orders of magnitude slower than using normal memory.
到缓冲区对象的数据存储的映射可能具有非标准的性能特征。例如,这种映射可能被标记为内存的不可缓存区域,在这种情况下,从它们读取可能非常慢。为确保最佳性能,客户端应以与GL\u BUFFER\u USAGE和access值一致的方式使用映射。以与这些值不一致的方式使用映射可能比使用普通内存慢多个数量级。
使用双PBO异步读取会存在帧延迟及第一帧为空的情况,因为使用异步交错读取,实际上在第N帧推理时,读取出的为N-1帧,这在帧率较高情况下影响不大,但是在帧率较低场景,会给用户以延迟较大的体验。
4. 参考
http://www.songho.ca/opengl/gl_pbo.html
https://www.its301.com/article/zhying719/105021076
https://blog.csdn.net/c553110519/article/details/73163539