1
\$\begingroup\$

I need to read depth buffer back to cpu memory. It may be few frames old, so I use glReadPixels with a buffer bound to GL_PIXEL_PACK_BUFFER. I use several buffers and ping-pong them. Finally, I read from the PBO with glGetBufferSubData. I have tried creating the buffers with GL_STATIC_READ, GL_DYNAMIC_READ and GL_STREAM_READ all with same results. Unfortunately, all this is still horribly slow.

class DepthBuffer
{
private:
    static const uint32 PboCount = 2;
    Buffer buffer;
    uint32 index;
    uint32 pbo[PboCount];
    uint32 w[PboCount], h[PboCount];

public:
    DepthBuffer();
    ~DepthBuffer();

    void performCopy(uint32 fbo, uint32 w, uint32 h);

    // returns 0..1 in logarithmic depth
    float valuePix(uint32 x, uint32 y); // pix in 0..(cw/ch-1)
    float valueNdc(float x, float y); // ndc in -1..1
};


DepthBuffer::DepthBuffer()
    : index(0), pbo{ 0, 0 }, w{ 0, 0 }, h{ 0, 0 }
{
    glGenBuffers(PboCount, pbo);

}

DepthBuffer::~DepthBuffer()
{
    glDeleteBuffers(PboCount, pbo);

}

void DepthBuffer::performCopy(uint32 fbo, uint32 paramW, uint32 paramH)
{
    // copy framebuffer to pbo

    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[index]);
    if (w[index] != paramW || h[index] != paramH)
    {
        glBufferData(GL_PIXEL_PACK_BUFFER,
            paramW * paramH * sizeof(float),
            nullptr, GL_STREAM_READ);
        w[index] = paramW;
        h[index] = paramH;
    }

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    CHECK_GL_FRAMEBUFFER(GL_READ_FRAMEBUFFER);
    glReadPixels(0, 0, w[index], h[index],
        GL_DEPTH_COMPONENT, GL_FLOAT, 0);

    CHECK_GL("read the depth (framebuffer to pbo)");

    // copy gpu pbo to cpu buffer
    index = (index + 1) % PboCount;

    float *depths = nullptr;
    {
        uint32 reqsiz = w[index] * h[index] * sizeof(float);
        if (buffer.size() < reqsiz)
            buffer.allocate(reqsiz);
        depths = (float*)buffer.data();
    }

    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[index]);
    glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0,
        w[index] * h[index] * sizeof(float), depths);

    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    CHECK_GL("read the depth (pbo to cpu)");
}

float DepthBuffer::valuePix(uint32 x, uint32 y)
{
    if (w[index] * h[index] == 0)
        return nan1();
    assert(x < w[index] && y < h[index]);
    return ((float*)buffer.data())[x + y * w[index]];
}

float DepthBuffer::valueNdc(float x, float y)
{
    assert(x >= -1 && x <= 1 && y >= -1 && y <= 1);
    return valuePix((x * 0.5 + 0.5) * (w[index] - 1),
        (y * 0.5 + 0.5) * (h[index] - 1));
}

The glReadPixels is taking all the time (on cpu). Am I doing something wrong? I thought that reading into the PBO should be asynchronous. Thanks.

\$\endgroup\$
1
\$\begingroup\$
glReadPixels(0, 0, w[index], h[index], GL_DEPTH_COMPONENT, GL_FLOAT, 0);

Performance issues of this nature are typically caused by the OpenGL driver needing to do a format conversion during the read or write operation. Depending how the driver implements this format conversion, this could sometimes be as bad as needing to block all pending operations, copy the buffer off to CPU-side scratch memory, do the conversion on the CPU, then upload again to your PBO.

The trick here is to ensure that your glReadPixels parameters match the internal hardware format of your depth buffer; so for example, in your case if you had a D32F depth buffer then the glReadPixels call should copy directly without needing to go through an intermediate conversion. On the other hand, if you had a D24S8 depth/stencil buffer then, with the glReadPixels parameters you use, it would need to be converted.

You can experiemnt with different parameters to your glReadPixels call to determine the best fit for your depth buffer format.

\$\endgroup\$
  • \$\begingroup\$ Thanks for quick answer. My source is either GL_DEPTH24_STENCIL8 or GL_DEPTH_COMPONENT24. The first gives me a strange error: <1282>, <api>, <error>, <high>, <GL_INVALID_OPERATION error generated. Drawable does not have stencil buffer> (nvidia driver). Is it actually possible to use PBO for stencil? I used GL_UNSIGNED_INT_24_8 for the glReadPixels type. For the second, I do not know what type to use. I do not see any 24 bit integer type. I will probably convert it myself by rendering into different texture. \$\endgroup\$ – Tomas Apr 5 at 16:07
  • 1
    \$\begingroup\$ I have it working now. To conclude: I use usual full screen space rectangle with a shader that reads the depth (from the depth texture) and converts it into "color" that is written into texture (bound as attachment to a FBO). Afterwards, I copy the texture to PBO the usual way with glReadPixels. The intermediate texture is GL_RGBA8. The shader uses floatBitsToUint and few more operatios. On the cpu side, when reading the depth from the PBO (in the next frame), I just cast each texel "color" to float and it matches. \$\endgroup\$ – Tomas Apr 25 at 15:02
  • \$\begingroup\$ @Tomas - wow, that sounds an incredibly convoluted way to achieve something that the API should be making simple. Congrats and well done on getting the result. \$\endgroup\$ – Maximus Minimus Apr 25 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.