c++resizevertex-shaderqpainterqopenglwidget

QOpenGLWidget with Qpainter: OpenGL drawing only visible after first paintGL call


I would like to use a QOpenGLWidget in QT to create fast line plots. These plots are to be provided with some labels, for which I am currently using QPainter with drawText(). In various examples, the OpenGL commands are first used in the paintGL method and then the Qpainter. E.g. here:

How to use QPainter in QOpenGlWidget's paintGL

QPainter and OpenGL native code in QOpenGLWidget class

QPainter and OpenGL native code in QOpenGLWidget class

I have written a small example widget (C++) in which lines and then text are drawn in paintGL (see below). My problem now is that both the lines and the text can be seen correctly the first time they are drawn. If the paintGL method is called a second time (e.g. after resizing), only the text is still visible, the lines are no longer visible.

I am using QT 6.4.2 with OpenGL 4.6.0 under Windows 10 with a 64-bit application.

Here is the declaration of the widget:

    struct ColorVertex_t {
        float x = 0, y = 0, z = 0, r = 0, g = 0, b = 0, a = 0.8f;

        // OpenGL Helpers
        static const int PositionTupleSize = 3;
        static const int ColorTupleSize = 4;
        static Q_DECL_CONSTEXPR inline int positionOffset() { return offsetof(ColorVertex_t, x); }
        static Q_DECL_CONSTEXPR inline int colorOffset() { return offsetof(ColorVertex_t, r); }
        static Q_DECL_CONSTEXPR inline int stride() { return sizeof(ColorVertex_t); }
    };


    class OpenGLLinePlotWidget : public QOpenGLWidget, protected QOpenGLFunctions
    {
        Q_OBJECT
        
        QOpenGLShaderProgram* m_program;
        QOpenGLVertexArrayObject vao_lines_;    // vertex array object for the lines
        unsigned int vbo_lines_ = 0;            // vertex buffer object for the lines, type GL_ARRAY_BUFFER

        /// vector of vertex coordinates and colors (RGB) for the lines
        std::vector<ColorVertex_t> vertices_lines_;


    public:
        OpenGLLinePlotWidget(QWidget* parent = nullptr);
        ~OpenGLLinePlotWidget();

    protected:
        void initializeGL() override;
        void resizeGL(int w, int h) override;
        void paintGL() override;
        
    };

And here are the definitions:

const char* vertexShaderSourceLine = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec4 aColor; // the color variable has attribute position 1\n"
"\n"
"out vec4 ourColor; // output a color to the fragment shader\n"
"\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"   ourColor = aColor;\n"
"}\0";

const char* fragmentShaderSourceLine = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 ourColor;\n"
"void main()\n"
"{\n"
"   FragColor = ourColor;\n"
"}\n\0";


OpenGLLinePlotWidget::OpenGLLinePlotWidget(QWidget* parent) : QOpenGLWidget(parent), m_program(nullptr)
{
    // Define some line coordinates
    size_t num_vertices = 100;
    vertices_lines_.resize(num_vertices);
    ColorVertex_t vertex;
    for (size_t i = 0; i < num_vertices; ++i) {
        vertex.x = -1. + i * 0.016;
        vertex.y = sin(i * 0.314);
        vertices_lines_[i] = vertex;
    }
}

OpenGLLinePlotWidget::~OpenGLLinePlotWidget()
{
    // Make sure the context is current and then explicitly destroy all underlying OpenGL resources.
    makeCurrent();
    // Actually destroy our OpenGL information
    vao_lines_.destroy();
    delete m_program;
    doneCurrent();
}

/* This function is called by OpenGL and must not be called manually! */
void OpenGLLinePlotWidget::initializeGL()
{
    // Initialize OpenGL Backend
    initializeOpenGLFunctions();
    glClearColor(1.f, 1.f, 1.f, 1.0f);  // white background

    // Create Shader (Do not release until VAO is created)
    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSourceLine);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSourceLine);
    m_program->link();
    m_program->bind();

    // ------------- cursor sample mesh buffers ---------------
    vao_lines_.create();
    vao_lines_.bind();
    glGenBuffers(1, &vbo_lines_);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, ColorVertex_t::stride(), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, ColorVertex_t::stride(), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    vao_lines_.release();
}

void OpenGLLinePlotWidget::resizeGL(int w, int h)
{
}

void OpenGLLinePlotWidget::paintGL()
{
    if (!isValid()) return;
    
    glClear(GL_COLOR_BUFFER_BIT);

    // draw lines with OpenGL
    vao_lines_.bind();
    glLineWidth(2.0f);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(ColorVertex_t) * vertices_lines_.size(), vertices_lines_.data(), GL_DYNAMIC_DRAW);
    glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(vertices_lines_.size()));
    vao_lines_.release();

    // draw some text with QPainter
    QPainter painter(this);
    painter.drawText(10, 20, "Hello World from QPainter in OpenGL!");
}

I have tried the following:

After much searching and trial and error, I'm at a loss and would appreciate any help! Thank you!


Solution

  • [Moved from comment]

    When using a QPainter from within paintGL it's important to note that the underlying Qt code can perform all sorts of operations on the current OpenGL context. Hence it's important to restore all relevant state. In the code shown...

    vao_lines_.bind();
    glLineWidth(2.0f);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(ColorVertex_t) * vertices_lines_.size(), vertices_lines_.data(), GL_DYNAMIC_DRAW);
    glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(vertices_lines_.size()));
    vao_lines_.release();
    

    only the VAO is restored. In this case it's also necessary to restore the shader program with either...

    m_program->bind();
    

    or...

    glUseProgram(m_program->programId());