Integrating OpenGL and QtPainter

This post builds upon the following:

QtPainter currently uses old OpenGL, in addition it also assumes that you don't change the OpenGL state.

The only sensible way to deal with this is to draw into your own FBO using your own OpenGL Context, not Qt's OpenGL context.

QPainter therefore requires a compatibility profile as it is currently limited to OpenGL 2.0. Qt 5.9 has a number of fixes that allow CoreProfile to work see bug report:

Basically the example code builds upon what was learnt from following the above original post. It is not a complete sample.
// Qt calls this to draw OpenGL
void MyWidget::paintGL()
{
  makeCurrent();

  QOpenGLPaintDevice device(rect().size()); // This device could be cached - only
                                            // updating when the size of Widget
                                            // changes.
  QPainter painter(&device);

  // Notify QPainter that we are going to start drawing with OpenGL.
  painter.beginNativePainting();

  // To understand what can be drawn where please refer to the Qt documentation
  // for
  //
  // - QOpenGLWidget
  // - QOpenGLWindow
  //
  // There are a number of Qt examples that show how to use both these Qt
  // classes.
  //

  // My OpenGL drawing.
  GLuint textureID = m_application->draw();

  // Set the current OpenGL Context to that of this widget.
  makeCurrent();

  // We reset the viewport as we don't know what the state may be after a
  // QPainter has been used.
  glViewport(0, 0, m_width, m_height);

  // Because this Widget is drawing into a different off-screen display we need
  // to clear the display.
  //
  // If we know we are always going to draw over the whole area then we could
  // just clear the depth buffer. However clearing both the depth and colour
  // buffers is safer.
  //
  // Note: this will clear down the whole area of drawing. If you are drawing
  // with QPainter below then consider clearing the FBO before drawing with
  // QPainter.
  glClearColor(m_clearColor.redF(), m_clearColor.greenF(), m_clearColor.blueF(),
               m_clearColor.alphaF());
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Some sensible OpenGL settings.
  glFrontFace(GL_CW);
  glCullFace(GL_FRONT);
  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
  if (textureID != 0) // check to make sure we have a texture to display.
  {
    // Note: The code below is not strictly optimal. However the over-head of
    // performing the setup for drawing is likely to be quite minimal compared
    // to drawing a map.
    //
    // If performance is critical and all other options have been exhausted then
    // I would suggest using either the AMD or NVIDIA GPU debuggers to look at
    // the frame performance.
    //

    // We will be using the shader we created in initializeGL() to draw the
    // texture.
    //
    // We have to define the display matrix to be a Orthographic projection as
    // we are displaying a 2D plane.
    QMatrix4x4 m;
    // origin lower-left corner
    m.ortho(0.0f, m_width, 0.0f, m_height, -1.0f, 1.0f);

    // Setup the VAO and the texture drawing program so that we can draw the
    // texture.
    // Using a Binder will make sure we release the VAO when the variable
    // instance goes out of scope.
    QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
    m_program->bind(); // activate the shader program
    // Pass in the matrix - used by the shader for displaying the rectangle
    // in the correct position.
    m_program->setUniformValue("matrix", m); 
    // Pass in the screen size
    m_program->setUniformValue("screenSize", m_width, m_height);

    // Create a VBO on the fly - this is not the correct way to do this however
    // when using a QPainter we have to clear a significant amount of resources
    // down or we will crash inside the OpenGL driver.
    //
    // I have dropped to using OpenGL directly here as it did not seem to be
    // possible to clear down resources sufficiently for QPainter to work.
    //
    // This approach is not particularly efficient and should be reconsidered
    // after Qt5.9 release.
    //
    GLuint m_glVBO;
    // Create a data buffer for our rectangle coordinates
    glGenBuffers(1, &m_glVBO);
    // bind the data buffer
    glBindBuffer(GL_ARRAY_BUFFER, m_glVBO);  
    // copy the data to the GPU                         
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * vertData.count(),
                  vertData.constData(), GL_STATIC_DRAW);
    // Enable the first two Vertex attribute indices
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    // point to the xyz data and define how much data per point
    glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE,
                          5 * sizeof(GLfloat), 0);  
    // point to the st data etc...
    glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE,
                          5 * sizeof(GLfloat),(void *)(3 * sizeof(GLfloat))); 

    // Activate Texture unit 0 (see initialiseGL()).
    glActiveTexture(GL_TEXTURE0);
    // Set the texture ID returned from m_application->draw()
    glBindTexture(GL_TEXTURE_2D, textureID); 

    // Draw the rectangle with the texture pasted on to it.
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    // disable and/or release allocated resources
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDeleteBuffers(1, &m_glVBO);
    m_program->release();
  }

  // QPainter expects the following settings - possibly more may be required
  // in future see the QPainter documentation.
  glClear (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glDisable(GL_SCISSOR_TEST);
  glDisable(GL_BLEND);

  // Note: Any resources you have allocated via OpenGL need to be released or
  // turned off by this point.

  // When we have finished with OpenGL we have to call the following function to 
  // notify QPainter.
  painter.endNativePainting();

  //
  // QPainter expects all OpenGL resources to be released and OpenGL to be in
  // a default state.
  //
  // This is why we use a different OpenGL context from the Qt one and why the 
  // code for displaying the texture containing the drawn contents of our
  // application has been written in an inefficent manner.

  // Over draw using QPainter
  painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
  painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
  painter.drawEllipse(80, 80, 200, 100);

  painter.setRenderHints(QPainter::Antialiasing |
                         QPainter::HighQualityAntialiasing);
  QFont font;
  font.setPointSize(24);
  painter.setFont(font);
  painter.setPen(QPen(Qt::red, 12, Qt::DashDotLine, Qt::RoundCap));
  painter.drawText(rect(), Qt::AlignCenter, "QPainter example.");
  painter.end();
}
The following is most of my initialisation code.
// Qt calls this to initalise OpenGL
void MyWidget::initializeGL()
{
  // Initialise OpenGL for Qt - this is similar to using GLEW.
  initializeOpenGLFunctions();

  // For drawing we need a Vertex Attribute Object (VAO) and a
  // Vertex Buffer Object (VBO).
  //
  // Define the coordinates of the Rectangle we will be drawing with my
  // application texture on it.

  m_vao.create();
  QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);

  //
  // We are going to use a triangle fan to draw the rectangle.
  //
  //  4
  //  +---------------------+ 3
  //  |                     |
  //  |                     |
  //  |                     |
  //  |                     |
  //  |                     |
  //  +---------------------+
  //  1                     2
  //
  // The points are defined 1, 2, 3, 4 as shown above (anti-clockwise).
  //
  // The order of the points is defined below and is significant.
  //
  // This assumes the bottom left is [0,0] and top right is [1,1]
  //
  vertData.append(0.0f);   // x
  vertData.append(0.0f);   // y
  vertData.append(0.0f);   // z
  vertData.append(0.0f);   // s - texture
  vertData.append(0.0f);   // t - texture

  vertData.append(1.0f);   // x
  vertData.append(0.0f);   // y
  vertData.append(0.0f);   // z
  vertData.append(1.0f);   // s - texture
  vertData.append(0.0f);   // t - texture

  vertData.append(1.0f);   // x
  vertData.append(1.0f);   // y
  vertData.append(0.0f);   // z
  vertData.append(1.0f);   // s - texture
  vertData.append(1.0f);   // t - texture

  vertData.append(0.0f);   // x
  vertData.append(1.0f);   // y
  vertData.append(0.0f);   // z
  vertData.append(0.0f);   // s - texture
  vertData.append(1.0f);   // t - texture
  // NOTE: normally we would create a VBO here and store the coordinates in the
  // VBO. However QPainter (5.8) for some reason will crash unless we delete our
  // VBO before we use an instance of the class.

  // Create Vertex and Fragment shader to copy the texture to the screen.
  // We store a pointer to these instances because the program does not take
  // ownership.
  m_vShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
  m_vShader->compileSourceCode(vertexShaderSrc);
  m_fShader = new QOpenGLShader(QOpenGLShader::Fragment, this);
  m_fShader->compileSourceCode(fragmentShaderSrc);
  m_program = new QOpenGLShaderProgram;
  m_program->addShader(m_vShader);
  m_program->addShader(m_fShader);
  // We need to set where the shader variables are for later use.
  m_program->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE);
  m_program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE);
  // Link the shader to create a program
  m_program->link();
  // Make the program the current one.
  m_program->bind();
  // Set the texture to use to be the first one.
  m_program->setUniformValue("texture", 0);
  m_program->setUniformValue("screenSize", m_width, m_height);
  m_program->release();


// CREATE my application, it's OpenGL Context which shares texture resources with
// the Qt OpenGL context with.
// This is APPLICATION specific.


  // Set the current OpenGL Context back to this Widget.
  // We don't want to switch OpenGL Context objects too often.
  makeCurrent();
}
The following uses the Qt OpenGL wrapper classes to create a context with sharing setup. This is most of the setup for off-screen rendering with my application. Do not forget that when a window is resized this FBO will also need to be resized.
  // Qt will be creating the OpenGL context for us.
  // In order for the my application to work at its best we ask it to choose
  // a framebuffer configuration with a specific set of parameters.
  QSurfaceFormat format;

  // Want Desktop OpenGL
  format.setRenderableType( QSurfaceFormat::OpenGL );

  // Set the minimum OpenGL version to use
  format.setMajorVersion(3); // has to be >= 3.2
  format.setMinorVersion(3);
  // required for QPainter limitations (until Qt5.9)
  format.setProfile( QSurfaceFormat::CompatibilityProfile );   

#ifndef NDEBUG
  // Debug if building debug
  format.setOption( QSurfaceFormat::DebugContext );
#endif

  // We don't set the application defaults here because of QPainter
  // requirements will be messed up.
  //QSurfaceFormat::setDefaultFormat( format );

  // The following settings are used by my application

  // Request a 24-bit depth buffer. A 16-bit depth buffer will also work.
  format.setDepthBufferSize( 24 );

  // Set the RGBA channel sizes
  format.setGreenBufferSize( 8 );
  format.setBlueBufferSize( 8 );
  format.setRedBufferSize( 8 );
  format.setAlphaBufferSize( 8 );

  // For offscreen rendering we can't use samples as it stops us getting a
  // texture quickly.
  format.setSamples( 0 );

  // The application is managing the buffering using FBO and textures.
  format.setSwapBehavior( QSurfaceFormat::SingleBuffer );

  // Create an OpenGL Context.
  //
  // We need our own context because our application will change a lot of the
  // OpenGL state.
  // Qt also changes a lot of the OpenGL State.
  // It is not possible to track the state changes occurring via Qt.
  //
  m_glContext = new QOpenGLContext;
  m_glContext->setFormat(format);
  // Some OpenGL drivers require the context being shared with to not be current
  contextToShareWith->doneCurrent();
  m_glContext->setShareContext(contextToShareWith);
  m_glContext->create();
  assert(m_glContext->isValid());

  // Create an Offscreen Surface for my application to draw into.
  // This may or may not actually have any Window's associated with it.
  m_qSurface = new QOffscreenSurface();
  m_qSurface->setFormat(m_glContext->format());
  m_qSurface->create();
  assert(m_qSurface->isValid());
To make sure the texture is ready for drawing you must ensure that all commands have drawn:
  m_glContext->functions()->glFlush();
Shaders used:
// OpenGL vertext and fragment shaders for drawing a texture as 1:1 pixels
static const char *vertexShaderSrc =
        "attribute vec4 vertex;\n"
        "attribute vec4 texCoord;\n"
        "varying vec4 texC;\n"
        "uniform mat4 matrix;\n"
        "uniform vec2 screenSize;\n"
        "void main(void)\n"
        "{\n"
        "    gl_Position = matrix * vec4(screenSize * (vertex.xy), 1, 1);\n"
        "    texC = texCoord;\n"
        "}\n";

static const char *fragmentShaderSrc =
        "uniform sampler2D texture;\n"
        "varying vec4 texC;\n"
        "void main(void)\n"
        "{\n"
        "    gl_FragColor = texture2D(texture, texC.st);\n"
        "}\n";
Please remember that this post does not contain all the necessary code.

Comments