我的目标是读取默认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_COPY
和GL_STREAM_READ
之间没有太大区别。我很乐意为您提供一些建议,该建议如何进一步提高帧缓冲区中像素读取操作的速度。
最佳答案
第二个版本根本不是异步的,因为您在触发副本后立即映射了缓冲区。然后,映射调用将阻塞,直到缓冲区的内容可用为止,从而有效地变得同步。
或:根据驱动程序,在实际读取驱动程序时它将阻塞。换句话说,驱动程序可以以导致页面错误和随后的同步的方式来实现映射。实际情况并不重要,因为std::copy
仍可直接访问该数据。
正确执行的正确方法是使用sync objects and fences。
保持您的PBO设置,但是在将glReadPixels
发行到PBO中之后,通过glFenceSync
将同步对象插入流中。然后,一段时间后,通过glClientWaitSync
轮询该篱笆同步对象是否完整(或完全等待)。
如果glClientWaitSync
返回在防护之前完成的命令,则您现在可以从缓冲区读取数据,而无需进行昂贵的CPU / GPU同步。 (如果驱动程序特别愚蠢,并且尚未将缓冲区内容移动到可映射的地址中,则尽管在PBO上有使用提示,您仍可以使用另一个线程来执行映射。因此glGetBufferSubData
可以更便宜,因为数据不会不必在可映射范围内。)
如果您需要逐帧执行此操作,您会注意到很可能需要多个PBO,也就是说,其中的PBO很小。这是因为在下一帧,尚未完成对前一帧数据的回读,并且未发出相应的信号。 (是的,这些天GPU已大量流水线化,它们将在您的提交队列后面一些帧)。