OpenGL ES 学习:PBO 使用

OpenGL ES 学习:PBO 使用

简介

PBO(Pixel Buffer Object),像素缓冲区对象,它是显存中的一块内存。PBO 类似于 VAO、VBO,也是在 GPU 中开辟内存,只是 VAO、VBO 是用于存储顶点数据,而 PBO 则用于存储像素数据。PBO 使用了 DMA(Direct Memory Access,直接存储器访问) 技术,使得上传和获取渲染数据更快。

与 PBO 绑定的 Target 有两种:

  • GL_PIXEL_PACK_BUFFER
  • GL_PIXEL_UNPACK_BUFFER

这两种 Target 使用场景如下:

PBO Target

OpenGL PBO

从上图可以看出,PBO 可以用于从 FrameBuffer 中读取纹理数据,也可以利用 PBO 将图像数据设置给纹理对象。使用 glReadPixels() 对应从 FrameBuffer 将数据读取到 PBO 中,使用 glGetTexImage() 函数对应从 Texture Object 中读取纹理像素到 PBO 中,这两种处理都是“pack”像素操作,PBO 绑定的 Target 为 GL_PIXEL_PACK_BUFFER;使用 glDrawPixels() 函数对应将 PBO 图像数据绘制在 FrameBuffer 上,glTexImage2D()glTexSubImage2D() 函数对应将图像数据上传到纹理对象,即 Texture Object,这两种处理都是 “unpack” 操作,PBO 绑定的 Target 为 GL_PIXEL_UNPACK_BUFFER;

PBO 可以利用 DMA 技术在 GPU 和 CPU 快速传输像素数据,而不占据 CPU 的运行周期,且是异步 DMA 传输,因此可以用它来实现快速上传和下载像素数据。

下图为一个实例,左侧为不使用 PBO 将像素数据上传的纹理对象。首先将图像数据加载进 CPU 内存,然后使用 glTexImage2D() 函数将像素数据上传到 GPU 内存,即纹理对象,这个过程都是由 CPU 完成,且 glTexImage2D() 相对耗时,因此 CPU 周期会被占用一部分。右图为使用 PBO 上传像素数据到纹理对象,首先将 图像数据加载到 PBO 中,然后同样用 glTexImage2D() 函数将像素数据上传到纹理对象,其中只有加载图像数据这一步为 CPU 操作,第二步则是由 GPU 完成,因此不会占用 CPU 运行周期,即 glTexImage2D() 会立即返回。

在这里插入图片描述

不使用 PBO 上传图像数据到纹理对象                                  使用 PBO 上传图像数据到纹理对象

使用

与 VAO、VBO类似,PBO 创建方法:

  • glGenBuffers() 创建 PBO 对象
  • glBindBuffer() 绑定 PBO 对象
  • glBufferData() 拷贝数据到 PBO 对象

glBufferData() 函数的数据参数如果传 nullptr,则只会分配一个内存,最后一个参数指明该 PBO 对象的用处,GL_STREAM_DRAW 是上传像素数据,GL_STREAM_READ 则为读取纹理数据。
PBO 提供了内存映射的机制将 GPU 内存映射到 CPU 内存地址上,主要使用如下两个函数:

  • void* glMapBuffer(GLenum target, GLenum access)
  • GLboolean glUnmapBuffer(GLenum target)

glMapBuffer() returns the pointer to the buffer object if success. Otherwise it returns NULL. The target parameter is either GL_PIXEL_PACK_BUFFER or GL_PIXEL_UNPACK_BUFFER. The second parameter, access specifies what to do with the mapped buffer; read data from the PBO (GL_READ_ONLY), write data to the PBO (GL_WRITE_ONLY), or both (GL_READ_WRITE).
Note that if GPU is still working with the buffer object, glMapBuffer() will not return until GPU finishes its job with the corresponding buffer object. To avoid this stall(wait), call glBufferData() with NULL pointer right before glMapBuffer(). Then, OpenGL will discard the old buffer, and allocate new memory space for the buffer object.
---- OpenGL Pixel Buffer Object (PBO)

如下为 OpenGL 手册对 glMapBufferRange() 函数的解释:

glMapBufferRange maps all or part of the data store of a buffer object into the client’s address space. – http://docs.gl/gl3/glMapBufferRange

单 PBO 获取渲染数据

使用单个 PBO 配合 FBO 来获取渲染数据的部分代码:

glBindFramebuffer(GL_FRAMEBUFFER, mFBO);

... draw 

glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[0]);
{
    AUTO_TIME_COUNT("PboHelper::draw glReadPixels")
    glReadPixels(0, 0, m_ScreenWidth, m_ScreenHeight, m_Channels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, nullptr);
}
void *pixelData = nullptr;
{
    AUTO_TIME_COUNT("PboHelper::draw glMapBufferRange")
    pixelData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_BufferSize, GL_MAP_READ_BIT);
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);

拿到 pixelData 这个指针之后就可以用来保存图像数据。

经过测试,使用单个 PBO 拿到 1080x2000 的渲染数据耗时约为 9ms(晓龙865设备), 相比直接使用 glReadPixels() 函数快了一倍多。

注:由于设备分辨率是 1080x2003,因此对长和宽做了 4 像素对齐,保存纹理图为 1080x2000。之后听说 16 像素对齐可以使得效率更高,于是尝试使用 16 像素对齐,获得的纹理为 1072x2000 单 PBO 获取时间从 9 ms 左右降低为 4.0 ms 左右。

双 PBO 获取渲染数据

因为 PBO 使用异步 DMA 传输,即将图像加载进 CPU 内存和像素数据上传到 GPU 内存可以并行操作,因此可以使用多个 PBO,从而更好地提高性能。如下为使用两个 PBO 实现上传和下载纹理数据的示意图:

在这里插入图片描述

使用 2 个 PBO 上传图像数据到纹理对象

在这里插入图片描述

使用 2 个 PBO 读取像素数据到 PBO

以第二张图为例,使用 2 个 PBO 提高纹理数据读取性能的流程为,先绑定 PBO_0,然后使用 glReadPixels() 函数将像素数据读取到 PBO_0 中,然后绑定 PBO_1,从 PBO_1 中将像素数据存储下来,然后交换 PBO_0 和 PBO_1 的 index,即下次使用 glReadPixels() 函数将像素数据存入 PBO_1 中,并从 PBO_0 中保存数据,即错帧保存。这样做的话理想结果是性能可以进一步得到提升,不过保存下来的第一帧数据是空白的,因为当时 PBO_1 中还没有数据。

为了使用 PBO,将上述代码做一点更新:

glBindFramebuffer(GL_FRAMEBUFFER, mFBO);

... draw 

// 绑定第一个 pbo
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[m_FirstPboIndex]);
{
    AUTO_TIME_COUNT("PboHelper::draw glReadPixels")
    glReadPixels(0, 0, m_ScreenWidth, m_ScreenHeight, m_Channels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, nullptr);
}
// 绑定第二个 pbo
void *pixelData = nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[1 - m_FirstPboIndex]);
{
    AUTO_TIME_COUNT("PboHelper::draw glMapBufferRange")
    pixelData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_BufferSize, GL_MAP_READ_BIT);
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
// 更换 pbo index
m_FirstPboIndex = 1 - m_FirstPboIndex;

实测结果:上述机型,获得 1072x2000 的 RGBA YUV数据耗时约为 1.5 ms(1000次平均数据),比仅用单 PBO 在性能上又有较大提高。

遗留问题

使用 PBO 来获取渲染数据存在一个问题:像素对齐,对于一个 1080x2003 的屏幕,在经过 16 像素对齐之后,保存下来的渲染数据变为了 1072x2000,和理想中的 size 不一致。如果使用 4 pixels 或 8 pixels 对齐,虽然可以获得 1080x2000 的纹理图,但是性能上提升又无法满足实时需求。

关于这个像素对齐的问题比较疑惑,因为测试中保存的图像类型为 RGBA,即一个像素是 4 个字节,16 个像素就是 64 字节,是否是因为 64 为的设备所以有这种特性,如果有清楚其中原理的同学麻烦解答一下,谢谢!

参考阅读

OpenGL Pixel Buffer Object (PBO) —— 本篇文章将 PBO 的基本原理和使用方法阐述的很清楚,推荐阅读!

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值