c++qtopenglopengl-compat

Showing a QGraphicsScene as a texture in a full Screen Quad of OpenGL


I need to draw a Quad that occupies my entire view (I'm not sure if viewport is technically accurate) and the load a QGraphicsScene scene onto the Quad as a texture. Here is my code for OpenGLCanvas which simply inherits and reimplements a QOpenGLWidget.

I'm now including all files to compile and reproduce the example

openglcanvas.h:

#ifndef OPENGLCANVAS_H
#define OPENGLCANVAS_H

#include <QOpenGLWidget>
#include "targettest.h"
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLShader>

class OpenGLCanvas : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLCanvas(QWidget *parent = nullptr);

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

private:
    float xrot,yrot,zrot;
    TargetTest targetTest;
//    QOpenGLBuffer buffer;
//    QOpenGLShaderProgram shaderProg;

};

#endif // OPENGLCANVAS_H

openglcanvas.cpp

#include "openglcanvas.h"

OpenGLCanvas::OpenGLCanvas(QWidget *parent):QOpenGLWidget(parent)
{
    xrot = yrot = zrot = 0.0;
    targetTest.initialize(100,100);

    initializeOpenGLFunctions();

}

void OpenGLCanvas::initializeGL(){
    glClearColor(0.0,0.0,1.0,0.0);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
}

void OpenGLCanvas::paintGL(){

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-2, +2, -2, +2, 1.0, 15.0);
    //glOrtho(-1, +1, -1, +1, 1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0, 0.0, -7.0);
    glRotatef(xrot, 1.0, 0.0, 0.0);
    glRotatef(yrot, 0.0, 1.0, 0.0);
    glRotatef(zrot, 0.0, 0.0, 1.0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    targetTest.renderCurrentPosition(0,0,0,0);

    glBindTexture(GL_TEXTURE_2D, targetTest.getFBO()->texture());
    glEnable(GL_TEXTURE_2D);

    glBegin(GL_QUADS);
    glNormal3d(0,0,+1);
    glTexCoord2f(0.0, 0.0); glVertex3d(-1,-1,0);
    glTexCoord2f(0.0, 2.0); glVertex3d(-1,1,0);
    glTexCoord2f(2.0, 2.0); glVertex3d(1,1,0);
    glTexCoord2f(2.0, 0.0); glVertex3d(1,-1,0);
    glEnd();

    glFlush();


}

void OpenGLCanvas::resizeGL(int width, int height)
{

    Q_UNUSED(width) Q_UNUSED(height)

//    int side = qMin(width, height);
//    glViewport((width - side) / 2, (height - side) / 2, side, side);

//    glMatrixMode(GL_PROJECTION);
//    glLoadIdentity();
//    glOrtho(-2, +2, -2, +2, 1.0, 15.0);
//    //glOrtho(-1, +1, -1, +1, 1.0, 1.0);
//    glMatrixMode(GL_MODELVIEW);
}

And here is the code of target test which draws some circles as a QGraphicsScene and renders them to an QOpenGLFramebufferObject:

targettest.h

#ifndef TARGETTEST_H
#define TARGETTEST_H

#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOffscreenSurface>
#include <QDebug>
#include <QOpenGLPaintDevice>


class TargetTest
{
public:
    TargetTest();
    ~TargetTest();

    void initialize(qint32 screenw, qint32 screenh);
    void finalize();
    void renderCurrentPosition(qint32 rx, qint32 ry, qint32 lx, qint32 ly);
    QOpenGLFramebufferObject * getFBO() { return m_pFbo;}


private:

    const qreal K_LARGE_D = 0.1;
    const qreal K_SMALL_D = 0.02;

    QOpenGLContext *m_pOpenGLContext;
    QOpenGLFramebufferObject *m_pFbo;
    QOffscreenSurface *m_pOffscreenSurface;

    QGraphicsScene *canvas;
    QGraphicsEllipseItem *leftEye;
    QGraphicsEllipseItem *rightEye;
    qreal r;
};

#endif // TARGETTEST_H

targettest.cpp

#include "targettest.h"

TargetTest::TargetTest()
{
    canvas = nullptr;
    leftEye = nullptr;
    rightEye = nullptr;
}

void TargetTest::initialize(qint32 screenw, qint32 screenh){

    canvas = new QGraphicsScene(0,0,screenw,screenh);
    canvas->setBackgroundBrush(QBrush(Qt::gray));

    // Qt OpenGL Initialization.
    QSurfaceFormat format;
    format.setMajorVersion( 4 );
    format.setMinorVersion( 1 );
    format.setProfile( QSurfaceFormat::CompatibilityProfile );

    m_pOpenGLContext = new QOpenGLContext();
    m_pOpenGLContext->setFormat( format );
    if( !m_pOpenGLContext->create() ){
        qDebug() << "Open GL Context initialization failed";
    }


    // create an offscreen surface to attach the context and FBO to
    m_pOffscreenSurface = new QOffscreenSurface();
    m_pOffscreenSurface->create();
    m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );

    m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D );

    qreal R = K_LARGE_D*screenw/2;
    r = K_SMALL_D*screenw/2;

    qreal horizontal_target_space =  (screenw -4*2*R)/2;
    qreal vertical_target_space   =  (screenh -4*2*R)/2;
    qreal horizontal_margin       =  R;
    qreal vertical_margin         =  R;
    qreal offset = (R-r);



    QList<QPointF> largeTargetUpperRight;
    largeTargetUpperRight << QPointF(horizontal_margin                                  ,vertical_margin)
                          << QPointF(horizontal_margin + 2*R + horizontal_target_space  ,vertical_margin)
                          << QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin)
                          << QPointF(horizontal_margin                                  ,vertical_margin + 2*R + vertical_target_space)
                          << QPointF(horizontal_margin + 2*R + horizontal_target_space  ,vertical_margin + 2*R + vertical_target_space)
                          << QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin + 2*R + vertical_target_space)
                          << QPointF(horizontal_margin                                  ,vertical_margin + 4*R + 2*vertical_target_space)
                          << QPointF(horizontal_margin + 2*R + horizontal_target_space  ,vertical_margin + 4*R + 2*vertical_target_space)
                          << QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin + 4*R + 2*vertical_target_space);

    for (qint32 i = 0; i < largeTargetUpperRight.size(); i++){
        QGraphicsEllipseItem *circle = canvas->addEllipse(0,0,2*R,2*R,QPen(Qt::black),QBrush(Qt::darkBlue));
        QGraphicsEllipseItem *innerCircle = canvas->addEllipse(0,0,2*r,2*r,QPen(Qt::black),QBrush(Qt::yellow));
        qreal x = largeTargetUpperRight.at(i).x();
        qreal y = largeTargetUpperRight.at(i).y();
        circle->setPos(x,y);
        innerCircle->setPos(x+offset,y+offset);
    }

    // Initializing the
    leftEye = canvas->addEllipse(0,0,2*r,2*r,QPen(),QBrush(QColor(0,0,255,100)));
    rightEye = canvas->addEllipse(0,0,2*r,2*r,QPen(),QBrush(QColor(0,255,0,100)));


}

void TargetTest::renderCurrentPosition(qint32 rx, qint32 ry, qint32 lx, qint32 ly){

    if (!canvas) return;
    leftEye->setPos(lx-r,ly-r);
    rightEye->setPos(rx-r,ry-r);

//    m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );
//    m_pFbo->bind();

    QOpenGLPaintDevice device( m_pFbo->size() );
    QPainter painter( &device );
    canvas->render( &painter );
//    m_pFbo->release();

}

TargetTest::~TargetTest(){
    finalize();
}

void TargetTest::finalize(){
    if (canvas){
        delete canvas;
        canvas = nullptr;
    }
    leftEye = nullptr;
    rightEye = nullptr;
}

For completion this my mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

And my mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

my main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

And finally my mainwindow.ui file

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <widget class="OpenGLCanvas" name="openGLWidget"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <customwidgets>
  <customwidget>
   <class>OpenGLCanvas</class>
   <extends>QOpenGLWidget</extends>
   <header location="global">openglcanvas.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

So what happens is that when I run the program I simply see the screen being filled with the color loaded in glClearColor (I've tested changing the color from black and I can confirm this is what I see) and that is it.

So what am I doing wrong?

UPDATE: I have modified the code in paintGL() with @Rabbid76, however the problem has not changed

UPDATE2: After removing some code as suggested by a comment Now I see this:

Error I see

What is supposed to look like is the little gray square with the circle should occupy the full screen as it should cover the full quad.


Solution

  • There are a few problems with the code shown but the main one is with the way you create and use the FBO. In TargetTest::initialize you have...

    QSurfaceFormat format;
    format.setMajorVersion( 4 );
    format.setMinorVersion( 1 );
    format.setProfile( QSurfaceFormat::CompatibilityProfile );
    
    m_pOpenGLContext = new QOpenGLContext();
    m_pOpenGLContext->setFormat( format );
    if( !m_pOpenGLContext->create() ){
        qDebug() << "Open GL Context initialization failed";
    }
    
    
    // create an offscreen surface to attach the context and FBO to
    m_pOffscreenSurface = new QOffscreenSurface();
    m_pOffscreenSurface->create();
    m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );
    
    m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D );
    

    In this case, however, there's no need to create a new GL context -- just create the FBO in the current context. So the code above becomes simply...

    m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D);
    

    Now consider the TargetTest::renderCurrentPosition implementation...

    if (!canvas)
        return;
    leftEye->setPos(lx-r,ly-r);
    rightEye->setPos(rx-r,ry-r);
    QOpenGLPaintDevice device( m_pFbo->size() );
    QPainter painter( &device );
    canvas->render( &painter );
    

    Here you don't bind the FBO so the QOpenGLPaintDevice is associated with the default frame buffer and the QGraphicsScene is rendered to that rather than the texture attached to the FBO. The above should become...

    if (!canvas)
        return;
    leftEye->setPos(lx-r,ly-r);
    rightEye->setPos(rx-r,ry-r);
    m_pFbo->bind();
    QOpenGLPaintDevice device( m_pFbo->size() );
    QPainter painter( &device );
    canvas->render( &painter );
    m_pFbo->release();
    

    Now the QGraphicsScene will be rendered to the correct texture and used as expected by OpenGLCanvas::paintGL.

    The two changes shown above are the main part of the fix but there are additional issues. Firstly, from the QOpenGLPaintDevice documentation...

    When painting to a QOpenGLPaintDevice using QPainter, the state of the current OpenGL context will be altered by the paint engine to reflect its needs. Applications should not rely upon the OpenGL state being reset to its original conditions, particularly the current shader program, OpenGL viewport, texture units, and drawing modes.

    This is a bit vague but in the case in hand it is necessary to fix up the viewport once rendering to the FBO is complete. So change OpenGLCanvas::paintGL such that it calls glViewport immediately after the call to `TargetTest::renderCurrentPosition)...

    targetTest.renderCurrentPosition(0,0,0,0);
    glViewport(0, 0, width(), height());
    

    One other issue is that you potentially make use of GL calls before any context has been created and made current. Your OpenGLCanvas ctor implementation is...

    OpenGLCanvas::OpenGLCanvas(QWidget *parent):QOpenGLWidget(parent)
    {
        xrot = yrot = zrot = 0.0;
        targetTest.initialize(100,100);
        initializeOpenGLFunctions();
    }
    

    Both targetTest.initialize(100,100) and initializeOpenGLFunctions() assume a valid GL context. These should be removed from the ctor and added to the top of OpenGLCanvas::initializeGL...

    void OpenGLCanvas::initializeGL ()
    {
        initializeOpenGLFunctions();        
        targetTest.initialize(100,100);
        glClearColor(0.0,0.0,1.0,0.0);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    }
    

    Doing all of the above results in (for me at least) the following frame...

    enter image description here


    As an aside, you're currently using the old fixed pipeline functionality such as glVertex3d etc. That has been deprecated for quite some time and may not even be available on certain platforms. Instead you should move to 'modern' OpenGL that makes use of buffer objects, shaders etc. To that end you might want to look at a website such as learnopengl.com.