OpenGL 快速读取回缓冲区
OpenGL reading back buffer quickly
我正在尝试将后台缓冲区的内容读入我自己的缓冲区。 glReadPixels
本身太慢了,我的 FPS 从 50 降到 30。
所以我决定尝试 "asynchronous" 使用 PBuffer 读取,但它崩溃了。
我的代码如下:
如果缓冲区不存在,请创建它们。否则,将后台缓冲区读入指定的内存位置:
static int readIndex = 0;
static int writeIndex = 1;
static GLuint pbo[2] = {0};
void FastCaptureBackBuffer()
{
//Create PBOs:
if (!initBuffers)
{
initBuffers = true;
glGenBuffers(2, pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[1]);
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
//swap read and write.
writeIndex = (writeIndex + 1) % 2;
readIndex = (writeIndex + 1) % 2;
//read back-buffer.
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[writeIndex]);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[readIndex]);
void* data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (data)
{
memcpy(myBuffer, data, width * height * 4);
data = nullptr;
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
然后我做:
BOOL __stdcall HookSwapBuffers(HDC DC)
{
FastCaptureBackBufferPBO();
return CallFunction<BOOL>(GetOriginalAddress(353), DC);
}
所以每次应用程序调用 wglSwapBuffers
时,我都会在它被交换之前读取后台缓冲区。
如何快速读取后台缓冲区?我在上面遗漏了什么?
理想情况下我想:指定一个游戏可以直接渲染到的指针,而不是屏幕,然后我可以手动渲染内存的内容。
任何其他方式,我最终将后台缓冲区复制到我的内存块中,而且速度很慢。
有什么想法吗?
您没有在缓冲区中预留足够的内存:
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
由于您使用 GL_RGBA
作为格式,每个像素需要 4 个字节,这也与您在 memcpy()
调用中使用的内容相匹配:
memcpy(myBuffer, data, width * height * 4);
所以 glBufferData()
调用应该是:
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4, 0, GL_STREAM_READ);
另外,从你的问题中不能完全清楚你为什么使用 HookSwapBuffers()
。我相信如果没有源代码,人们会使用它来拦截 SwapBuffers()
调用。如果您想在自己的代码中捕获渲染,您只需在完成渲染帧后立即调用 glReadPixels()
即可。它将与所有其他 OpenGL 调用按顺序执行,因此它将包含您发出的所有绘制调用的结果。
术语要点:你在这里问的不是"PBuffer"。全名是 "Pixel Buffer Object",通常以其简称 "PBO" 使用。 PBuffer 是完全不同的东西。这是一种用于离屏渲染的旧机制,幸运的是,这些天几乎已经过时了。
Any ideas?
你如何不滥用主帧缓冲区做一些你不应该做的事情(渲染到 window 帧缓冲区并从中读取),而是使用帧缓冲区对象和渲染缓冲区来渲染。您仍然必须使用 glReadPixels,但由于您使用的是屏幕外表面,因此您避免了与 windowing 系统的所有同步。使用 PBO 进行数据传输仍然是值得推荐的,因为它为 OpenGL 实现在调度操作方面提供了更多的自由。我建议如下:
- 渲染到 FBO 渲染缓冲区
- glReadPixels 从渲染缓冲区到 GL_PIXEL_PACK_BUFFER PBO
- 将渲染缓冲区 Blit 到主帧缓冲区
- 交换缓冲区
- 从 PBO 中检索数据
这种安排和操作顺序为 OpenGL 实现提供了足够的余地,可以异步重叠那里发生的一些操作,而不会强加一些停顿的同步点。例如,glReadPixels 和将渲染缓冲区 blitting 到主帧缓冲区不会相互干扰(两者都只从渲染缓冲区读取)。 OpenGL 驱动程序可能会重新安排 glReadPixels 以在 blit 之后或同时实际执行。您实际上可以交换 2 和 3,并且在某些实现中这可能会产生更好的性能。哎呀,你甚至可以在 4 之后移动 2,但是你会失去一些操作重新排序的自由。
我正在尝试将后台缓冲区的内容读入我自己的缓冲区。 glReadPixels
本身太慢了,我的 FPS 从 50 降到 30。
所以我决定尝试 "asynchronous" 使用 PBuffer 读取,但它崩溃了。
我的代码如下:
如果缓冲区不存在,请创建它们。否则,将后台缓冲区读入指定的内存位置:
static int readIndex = 0;
static int writeIndex = 1;
static GLuint pbo[2] = {0};
void FastCaptureBackBuffer()
{
//Create PBOs:
if (!initBuffers)
{
initBuffers = true;
glGenBuffers(2, pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[1]);
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
//swap read and write.
writeIndex = (writeIndex + 1) % 2;
readIndex = (writeIndex + 1) % 2;
//read back-buffer.
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[writeIndex]);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[readIndex]);
void* data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (data)
{
memcpy(myBuffer, data, width * height * 4);
data = nullptr;
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
然后我做:
BOOL __stdcall HookSwapBuffers(HDC DC)
{
FastCaptureBackBufferPBO();
return CallFunction<BOOL>(GetOriginalAddress(353), DC);
}
所以每次应用程序调用 wglSwapBuffers
时,我都会在它被交换之前读取后台缓冲区。
如何快速读取后台缓冲区?我在上面遗漏了什么?
理想情况下我想:指定一个游戏可以直接渲染到的指针,而不是屏幕,然后我可以手动渲染内存的内容。
任何其他方式,我最终将后台缓冲区复制到我的内存块中,而且速度很慢。
有什么想法吗?
您没有在缓冲区中预留足够的内存:
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 1.0f, 0, GL_STREAM_READ);
由于您使用 GL_RGBA
作为格式,每个像素需要 4 个字节,这也与您在 memcpy()
调用中使用的内容相匹配:
memcpy(myBuffer, data, width * height * 4);
所以 glBufferData()
调用应该是:
glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4, 0, GL_STREAM_READ);
另外,从你的问题中不能完全清楚你为什么使用 HookSwapBuffers()
。我相信如果没有源代码,人们会使用它来拦截 SwapBuffers()
调用。如果您想在自己的代码中捕获渲染,您只需在完成渲染帧后立即调用 glReadPixels()
即可。它将与所有其他 OpenGL 调用按顺序执行,因此它将包含您发出的所有绘制调用的结果。
术语要点:你在这里问的不是"PBuffer"。全名是 "Pixel Buffer Object",通常以其简称 "PBO" 使用。 PBuffer 是完全不同的东西。这是一种用于离屏渲染的旧机制,幸运的是,这些天几乎已经过时了。
Any ideas?
你如何不滥用主帧缓冲区做一些你不应该做的事情(渲染到 window 帧缓冲区并从中读取),而是使用帧缓冲区对象和渲染缓冲区来渲染。您仍然必须使用 glReadPixels,但由于您使用的是屏幕外表面,因此您避免了与 windowing 系统的所有同步。使用 PBO 进行数据传输仍然是值得推荐的,因为它为 OpenGL 实现在调度操作方面提供了更多的自由。我建议如下:
- 渲染到 FBO 渲染缓冲区
- glReadPixels 从渲染缓冲区到 GL_PIXEL_PACK_BUFFER PBO
- 将渲染缓冲区 Blit 到主帧缓冲区
- 交换缓冲区
- 从 PBO 中检索数据
这种安排和操作顺序为 OpenGL 实现提供了足够的余地,可以异步重叠那里发生的一些操作,而不会强加一些停顿的同步点。例如,glReadPixels 和将渲染缓冲区 blitting 到主帧缓冲区不会相互干扰(两者都只从渲染缓冲区读取)。 OpenGL 驱动程序可能会重新安排 glReadPixels 以在 blit 之后或同时实际执行。您实际上可以交换 2 和 3,并且在某些实现中这可能会产生更好的性能。哎呀,你甚至可以在 4 之后移动 2,但是你会失去一些操作重新排序的自由。