【问题标题】:Read pixel data from default framebuffer in OpenGL: Performance of FBO vs. PBO从 OpenGL 中的默认帧缓冲区读取像素数据:FBO 与 PBO 的性能
【发布时间】:2016-08-19 00:20:33
【问题描述】:

我的目标是读取默认 OpenGL 帧缓冲区的内容并将像素数据存储在 cv::Mat 中。显然有两种不同的方法来实现这一点:

1) 同步:使用FBO和glRealPixels

cv::Mat a = cv::Mat::zeros(cv::Size(1920, 1080), CV_8UC3);
glReadPixels(0, 0, 1920, 1080, GL_BGR, GL_UNSIGNED_BYTE, a.data);

2) 异步:使用 PBO 和glReadPixels

cv::Mat b = cv::Mat::zeros(cv::Size(1920, 1080), CV_8UC3);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_userImage);
    glReadPixels(0, 0, 1920, 1080, GL_BGR, GL_UNSIGNED_BYTE, 0);
    unsigned char* ptr = static_cast<unsigned char*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
    std::copy(ptr, ptr + 1920 * 1080 * 3 * sizeof(unsigned char), b.data);
    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

从我收集的关于这个主题的所有信息来看,异步版本 2) 应该快得多。但是,比较两个版本的经过时间会发现差异通常很小,有时版本 1) 事件的性能优于 PBO 变体。

对于性能检查,我插入了以下代码(基于this 答案):

std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
....
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl;

在创建 PBO 时,我还尝试了 使用提示:我在这里没有发现 GL_DYNAMIC_COPYGL_STREAM_READ 之间有太大区别。

我很乐意提供有关如何进一步提高从帧缓冲区读取此像素操作的速度的建议。

【问题讨论】:

  • 您的第二个版本并不是真正的异步,因为您正在阻止结果在拨打glReadPixels() 后立即到达。
  • 你的意思是打电话给std::copy?实际上,如果我注释掉这一行,效果是微乎其微的,而且版本 1 有时会更快。
  • 如果我不将 GPU 缓冲区映射到 CPU 内存,那会有很大的不同,这当然是预期的,但在那之后是必要的,因为我想将 cv::Mat 存储在向量中

标签: c++ opengl


【解决方案1】:

您的第二个版本根本不是异步的,因为您在触发复制后立即映射缓冲区。然后 map 调用将阻塞,直到缓冲区的内容可用,从而有效地同步。

或者:根据驱动程序,实际读取时会阻塞。换句话说,驱动程序可能会以导致页面错误和后续同步的方式实现映射。在您的情况下这并不重要,因为由于std::copy,您仍然可以立即访问该数据。

正确的方法是使用sync objects and fences

保留您的 PBO 设置,但在将 glReadPixels 发布到 PBO 后,通过 glFenceSync 将同步对象插入流中。然后,一段时间后,通过glClientWaitSync 轮询该栅栏同步对象是否完成(或完全等待它)。

如果glClientWaitSync 返回围栏之前的命令已完成,您现在可以从缓冲区中读取,而无需昂贵的 CPU/GPU 同步。 (如果驱动程序特别愚蠢并且尚未将缓冲区内容移动到可映射地址中,尽管您在 PBO 上有使用提示,您可以使用另一个线程来执行映射。glGetBufferSubData 因此可能更便宜,因为数据不需要在可映射范围内。)


如果您需要逐帧执行此操作,您会注意到您很可能需要多个 PBO,也就是说,您需要拥有一小部分 PBO。这是因为在下一帧,前一帧数据的回读还没有完成,相应的栅栏没有发出信号。 (是的,现在 GPU 被大量流水线化,它们将在您的提交队列后面一些帧)。

【讨论】:

  • 我要补充一点,只有在触发操作(glReadPixels 使用 PBO)和读取结果(glMapBuffer、@987654330 @, 等等)。创建一个同步对象并立即等待它并不比直接glReadPixels 好。
猜你喜欢
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多