Search code examples
c++qtopenglopenglcontextqopenglwidget

crash from context shared QOpenGLWidget


I have two QOpenGLWidgets(view1, view2)as children in a top-level widget. Qt document says 'When multiple QOpenGLWidgets are added as children to the same top-level widget, their contexts will share with each other'. So, view1 and view2 share OpenGL context. I have tried to render same scene which is initialized in view1's context and the application crashes in view2's paintGL(). What did I wrong?

Here's simplified code:

#include <QtGui/QtGui>
#include <QtWidgets/QOpenGLWidget>
#include <QtWidgets/QApplication>
#include <QtWidgets/QHBoxLayout>

static const char *vertexShaderSource =
    "attribute vec4 posAttr;\n"
    "void main() {\n"
    "   gl_Position = posAttr;\n"
    "}\n";

static const char *fragmentShaderSource =
    "void main() {\n"
    "   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "}\n";

QOpenGLShaderProgram *program = nullptr;
QOpenGLBuffer arrayBuf;
int posAttr = -1;

class View3D : public QOpenGLWidget {
public:
    View3D()
    {
        setMinimumSize(300, 200);
    }
private:
    auto initializeGL() -> void override
    {
        if (program)
            return;

        program = new QOpenGLShaderProgram(this);
        program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
        program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
        program->link();
        posAttr = program->attributeLocation("posAttr");

        program->bind();

        GLfloat vertices[] = {
            0.0f, 0.707f, 0.f,
            -0.5f, -0.5f, 0.f,
            0.5f, -0.5f, 0.f,
        };

        arrayBuf.create();
        arrayBuf.bind();
        arrayBuf.allocate(vertices, sizeof(vertices));

        program->enableAttributeArray(posAttr);
        program->setAttributeBuffer(posAttr, GL_FLOAT, 0, 3, 0);

        program->release();
    }

    auto paintGL() -> void override
    {
        auto f = context()->functions();

        const auto dpr = devicePixelRatio();
        f->glViewport(0, 0, width() * dpr, height() * dpr);

        f->glClear(GL_COLOR_BUFFER_BIT);

        program->bind();
        arrayBuf.bind();

        program->enableAttributeArray(posAttr);
        f->glDrawArrays(GL_TRIANGLES, 0, 3);
        program->disableAttributeArray(posAttr);

        arrayBuf.release();
        program->release();
    }

};

auto main(int argc, char **argv) -> int
{
    QApplication app{argc, argv};

    QWidget w;
    auto hbox = new QHBoxLayout{&w};
    hbox->addWidget(new View3D); // view1
    hbox->addWidget(new View3D); // view2
    w.show();

    return app.exec();
}

Solution

  • It seems that vertex state shall not be shared between contexts even though those contexts are shared.

    Here's fixed version:

    #include <QtGui/QtGui>
    #include <QtWidgets/QOpenGLWidget>
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QHBoxLayout>
    
    static const char *vertexShaderSource =
        "attribute vec4 posAttr;\n"
        "void main() {\n"
        "   gl_Position = posAttr;\n"
        "}\n";
    
    static const char *fragmentShaderSource =
        "void main() {\n"
        "   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
        "}\n";
    
    QOpenGLBuffer *arrayBuf = nullptr;
    QOpenGLShaderProgram *program = nullptr;
    
    class View3D : public QOpenGLWidget {
    public:
        View3D()
        {
            setMinimumSize(300, 200);
        }
    private:
        int posAttr = -1;
        auto initializeGL() -> void override
        {
            if (!arrayBuf) {
                arrayBuf = new QOpenGLBuffer;
                arrayBuf->create();
                arrayBuf->bind();
                GLfloat vertices[] = {
                    0.0f, 0.707f, 0.f,
                    -0.5f, -0.5f, 0.f,
                    0.5f, -0.5f, 0.f,
                };
                arrayBuf->allocate(vertices, sizeof(vertices));
            }
    
            if (!program) {
                program = new QOpenGLShaderProgram(this);
                program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
                program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
                program->link();
            }
    
            posAttr = program->attributeLocation("posAttr");
            program->bind();
            arrayBuf->bind();
            program->enableAttributeArray(posAttr);
            program->setAttributeBuffer(posAttr, GL_FLOAT, 0, 3, 0);
            program->release();
        }
    
        auto paintGL() -> void override
        {
            auto f = context()->functions();
    
            const auto dpr = devicePixelRatio();
            f->glViewport(0, 0, width() * dpr, height() * dpr);
    
            f->glClear(GL_COLOR_BUFFER_BIT);
    
            program->bind();
            arrayBuf->bind();
    
            program->enableAttributeArray(posAttr);
            f->glDrawArrays(GL_TRIANGLES, 0, 3);
            program->disableAttributeArray(posAttr);
    
            arrayBuf->release();
            program->release();
        }
    
    };
    
    auto main(int argc, char **argv) -> int
    {
        QApplication app{argc, argv};
    
        QWidget w;
        auto hbox = new QHBoxLayout{&w};
        hbox->addWidget(new View3D); // view1
        hbox->addWidget(new View3D); // view2
        w.show();
    
        return app.exec();
    }
    

    From observation, sharing vertex attribute state also can cause problem. I knew that I cannot share VAO by OpenGL specification, but I never used VAO explictily here.

    I am not sure that this is inhibited by specification or a bug of driver. However, at least on NVidia driver, it is obvious that you should not share vertex attribute state for any cases including shared contexts.