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 实现在调度操作方面提供了更多的自由。我建议如下:

  1. 渲染到 FBO 渲染缓冲区
  2. glReadPixels 从渲染缓冲区到 GL_PIXEL_PACK_BUFFER PBO
  3. 将渲染缓冲区 Blit 到主帧缓冲区
  4. 交换缓冲区
  5. 从 PBO 中检索数据

这种安排和操作顺序为 OpenGL 实现提供了足够的余地,可以异步重叠那里发生的一些操作,而不会强加一些停顿的同步点。例如,glReadPixels 和将渲染缓冲区 blitting 到主帧缓冲区不会相互干扰(两者都只从渲染缓冲区读取)。 OpenGL 驱动程序可能会重新安排 glReadPixels 以在 blit 之后或同时实际执行。您实际上可以交换 2 和 3,并且在某些实现中这可能会产生更好的性能。哎呀,你甚至可以在 4 之后移动 2,但是你会失去一些操作重新排序的自由。