0
votes

I'm trying to write an application which renders a texture via OpenGL into a QML item. After searching online and trying various things, (felt like a lot of guessing, I have a very limited understanding of OpenGl,) I eventually ran the program through Valgrind and discovered an illegal memory access, meaning I'm not initializing everything needed for the OpenGl to draw correctly. I was hoping someone could look at my rough code and tell me which step I'm forgetting.

I tried to follow and combine the two tutorials in the QT docs, this one (http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html) which shows QML and OpenGl, as well as this one (http://doc.qt.io/qt-5/qtopengl-textures-example.html) which shows how to render a texture using OpenGl and QT. I altered it slightly because my texture was only a 2 dimensional image I wish to display. I was able to successfully follow the latter, rendering my texture to a viewport and drawing it on screen, unfortunately when attempting to integrate QML things went downhill.

My paint function is as follows:

    void glViewer::paint()
    {
        // program is type QOpenGLShaderProgram *
        if (!program) initializeGL();
        if (!program->bind()) std::cerr << "Error: Program failed to bind\n";

        glViewport(0, 0, _viewportSize.width(), _viewportSize.height());
        glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blackF(), clearColor.alphaF());
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        QMatrix4x4 m;
        m.ortho(0.0f, 1.0f, 1.0f, 0.0f, 4.0f, 15.0f);
        m.translate(0.0f, 0.0f, -10.0f);
        program->setUniformValue("matrix", m);

        program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
        program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);

        program->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat));
        program->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));

        texture->bind();

        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

        program->disableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
        program->disableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
        program->release();
        // _window refers the QQuickWindow generated by the QApplication
        _window->resetOpenGLState();
    }

The first line calls intializeGL() if there is no shader program, which is where I believe the issue is. The function is defined as followed:

    void glViewer::initializeGL()
    {
        initializeOpenGLFunctions();

        texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
        texture->setSize(imageDisplayResolution.x,imageDisplayResolution.y,1);
        texture->setMinMagFilters(QOpenGLTexture::Nearest, QOpenGLTexture::Nearest);
        texture->setWrapMode(QOpenGLTexture::ClampToBorder);
        texture->setFormat(QOpenGLTexture::TextureFormat::R32F);
        texture->allocateStorage(QOpenGLTexture::Luminance, QOpenGLTexture::Float32);
        if (!texture->isStorageAllocated()) std::cerr << "Error: Failed to allocate texture!\n";

        QVector<GLfloat> vertData;

        // vertex position 
        vertData.append(0);
        vertData.append(0);
        vertData.append(0);
        // texture coordinate
        vertData.append(0);
        vertData.append(0);

        // vertex position 
        vertData.append(0);
        vertData.append(1);
        vertData.append(0);
        // texture coordinate
        vertData.append(0);
        vertData.append(1);

        // vertex position 
        vertData.append(1);
        vertData.append(1);
        vertData.append(0);
        // texture coordinate
        vertData.append(1);
        vertData.append(1);

        // vertex position 
        vertData.append(1);
        vertData.append(0);
        vertData.append(0);
        // texture coordinate
        vertData.append(1);
        vertData.append(0);

        vbo.create();
        if (!vbo.bind()) std::cerr << "Error: VBO bind has failed\n";
        vbo.allocate(vertData.constData(), vertData.count() * sizeof(GLfloat));

        // Enable or disable server-side GL capabilities.
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);

    #define PROGRAM_VERTEX_ATTRIBUTE 0
    #define PROGRAM_TEXCOORD_ATTRIBUTE 1

        program = new QOpenGLShaderProgram;
        if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex,
                                       "attribute highp vec4 vertex;\n"
                                        "attribute mediump vec4 texCoord;\n"
                                        "varying mediump vec4 texc;\n"
                                        "uniform mediump mat4 matrix;\n"
                                        "void main(void)\n"
                                        "{\n"
                                        "    gl_Position = matrix * vertex;\n"
                                        "    texc = texCoord;\n"
                                        "}\n")) { std::cerr << "Error: Vertex shader failed to compile!\n"; }
        if (!program->addShaderFromSourceCode(QOpenGLShader::Fragment,
                                       "uniform sampler2D texture;\n"
                                        "varying mediump vec4 texc;\n"
                                        "void main(void)\n"
                                        "{\n"
                                        "    gl_FragColor = texture2D(texture, texc.st).rrra;\n"
                                        "}\n")) { std::cerr << "Error: Fragment shader failed to compile!\n"; }
        // Binds the attribute name to the specified location.
        program->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);
        program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);

        if (!program->link()) std::cerr << "Error: Program shaders failed to link\n";
        if (!program->bind()) std::cerr << "Error: Program failed to bind\n";
        program->setUniformValue("texture", 0);
    }

paint() is a slot connect to the QQuickWindow::beforeRendering signal. Stepping through with a debugger tells me all pointers, (texture, program) are not null when paint() is called. None of the OpenGL calls throw errors, the program simply segfaults in paint() on glDrawArrays.

Thanks!

Edit: further debugging has taught me it is successfully going through the paint function once, it's on the second pass that it fails. However, I never see the fully drawn texture, just the application background is filled in.

1

1 Answers

0
votes

The paint function should be written as follows to preserve state and prevent segfaults on drawing:

    if (!program) initializeGL();
    program->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);
    program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
    if (!program->bind()) std::cerr << "Error: Program failed to bind\n";
    texture->bind();
    vbo.bind();

    glClearColor(_clearColor.redF(), _clearColor.greenF(), _clearColor.blackF(), _clearColor.alphaF());
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(viewportPosition.x(), viewportPosition.y(), viewportSize.width(), viewportSize.height());

    QMatrix4x4 m;
    m.ortho(0.0f, 1.0f, 1.0f, 0.0f, 4.0f, 15.0f);
    m.translate(0.0f, 0.0f, -10.0f);
    program->setUniformValue("matrix", m);
    program->setUniformValue("texture", 0);

    program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
    program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
    program->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat));
    program->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    program->disableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
    program->disableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
    program->disableAttributeArray(0);
    program->release();
    texture->release();
    vbo.release();
    _window->resetOpenGLState();