Search code examples
opengltexturesshadercompute-shaderimage-load

OpenGL imageSize is always zero


I wrote a simple test case to get the height of an image within a compute shader and write it to an SSBO. I've used the SSBO code before, and I know that part works fine. I used apitrace to inspect the state during the glDispatchCompute call, and I can see both the original texture and the image bound to the correct image unit. However, imageSize always returns zero (the output is all zeros, with the exception of some leftover -1s at the end because the division with the workgroup size rounds down). No OpenGL errors are thrown.

I based this test case on one of my earlier questions which included code to bind an SSBO to a compute shader (I use it here to get debug output from the compute shader).

class ComputeShaderWindow : public QOpenGLWindow {
  public:
    void initializeGL() {
        // Create the opengl functions object
        gl = context()->versionFunctions<QOpenGLFunctions_4_3_Core>();
        m_compute_program = new QOpenGLShaderProgram(this);
        auto compute_shader_s = fs::readFile(
                                    "test_assets/example_compute_shader.comp");
        QImage img("test_assets/input/out.png");
        // Adds the compute shader, then links and binds it
        m_compute_program->addShaderFromSourceCode(QOpenGLShader::Compute,
                compute_shader_s);
        m_compute_program->link();
        m_compute_program->bind();

        GLuint frame;

        // Create the texture
        gl->glGenTextures(1, &frame);

        // Bind the texture
        gl->glBindTexture(GL_TEXTURE_2D, frame);

        // Fill the texture with the image
        gl->glTexImage2D(GL_TEXTURE_2D,
                         0,
                         GL_RGB8,
                         img.width(),
                         img.height(),
                         0,
                         GL_BGRA,
                         GL_UNSIGNED_BYTE,
                         img.bits());
        GLuint image_unit = 1;
        // Get the location of the image uniform
        GLuint uniform_location = gl->glGetUniformLocation(
                                      m_compute_program->programId(),
                                      "video_frame");
        // Set location to 0 (a unique value that we choose)
        gl->glUniform1i(uniform_location, image_unit);
        // Bind layer of texture to image unit
        gl->glBindImageTexture(image_unit,
                               frame,
                               0,
                               GL_FALSE,
                               0,
                               GL_READ_ONLY,
                               GL_RGBA8UI);
        // We should only need the bit for shader image access,
        // but for the purpose of this example, I set all the bits
        // just to be safe
        gl->glMemoryBarrier(GL_ALL_BARRIER_BITS);

        // SSBO stuff to get output from the shader
        GLfloat* default_values = new GLfloat[NUM_INVOCATIONS];
        std::fill(default_values, default_values + NUM_INVOCATIONS, -1.0);
        GLuint ssbo;
        gl->glGenBuffers(1, &ssbo);
        gl->glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
        gl->glBufferData(GL_SHADER_STORAGE_BUFFER,
                         NUM_INVOCATIONS * sizeof(float),
                         &default_values[0],
                         GL_STATIC_DRAW);
        gl->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
        gl->glDispatchCompute(NUM_INVOCATIONS / WORKGROUP_SIZE, 1, 1);
        gl->glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
        gl->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);

        // Now read from the buffer so that we can check its values
        GLfloat* read_data = (GLfloat*) gl->glMapBuffer(GL_SHADER_STORAGE_BUFFER,
                             GL_READ_ONLY);
        std::vector<GLfloat> buffer_data(NUM_INVOCATIONS);

        // Read from buffer
        for (int i = 0; i < NUM_INVOCATIONS; i++) {
            DEBUG(read_data[i]);
        }
        DEBUG("Done!");

        gl->glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
        assert(gl->glGetError() == GL_NO_ERROR);
    }

    void resizeGL(int width, int height) {

    }

    void paintGL() {

    }

    void teardownGL() {

    }

  private:
    QOpenGLFunctions_4_3_Core* gl;
    QOpenGLShaderProgram* m_compute_program;
    static constexpr int NUM_INVOCATIONS = 9000;
    static constexpr int WORKGROUP_SIZE = 128;
};

As for the compute shader:

#version 430 core

layout(local_size_x = 128) in;

layout(rgba8ui, binding = 1) readonly uniform uimage2D video_frame;

layout(std430, binding = 0) writeonly buffer SSBO {
    float data[];
};

void main() {
    uint ident = int(gl_GlobalInvocationID);
    uint num_workgroups = int(gl_WorkGroupID);
    // Write the height of the image into the buffer
    data[ident] = float(imageSize(video_frame).y);
}

Solution

  • Turns out I forgot the texture parameters:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    

    No clue why that breaks imageSize() calls though.