Part 2 – Modern OpenGL Using Qt 5.5 Tutorial

Have a look at Part 1  if you are new to OpenGL or Qt. In Part 1 I introduced the two technologies and gave some important tips for getting started correctly. If you want to build this project with CMake and Qt Creator, here is a CMakeLists.txt file with instructions on how to do it.

Let’s get started!
This tutorial will show you how to draw a colored triangle similar to the QOpenGLWindow example that ships with Qt but using modern OpenGL. I don’t want to just explain the code, but instead plan on gathering all the little tips and gotchas I have found in order to really help you understand where to go from here.  I chose to develop for OpenGL version 3.1 because that is the max my laptop has and for anyone with money and interest to buy a 3D program they probably have that supported by their graphics card in their computer. 3.1 was released on March 24, 2009. Most graphics cards purchased after that will support 3.1. I will try and keep things as close to the existing examples as possible so that you can see exactly what changes when going from straight OpenGL to using Qt. This example will be broken down as follows.

-Subclass a Qt Window called OpenGLWindow from the Qt OpenGL Window Example
-put our OpenGL code in the initialization and render methods of our subclass
-create a QSurfaceFormat and set its RenderableType to OpenGL and with a version of 3.1
-create a window from our class and set its SurfaceFormat

The first things that need to be done to render OpenGL with Qt is make sure the Window will render at all the right times, make sure our OpenGL context is created at the right time and current and initialize the Qt OpenGL functions. All of this is taken care of in the OpenGLWinow class provided by the Qt example linked above. The one thing I changed was to inherit from QOpenGLFunctions_3_1. This way I will know for sure which functions Qt has for me to use (as opposed to just QOpenGLFunctions).

Initialize() method
The initialize() method will be for
-loading our shaders
-setting our vertices and colors (since they don’t change)
-setting up our VBOs for the corresponding vertex and color arrays
-adding our VBOs to a VAO

Render() method
The render() method will
-set our view scale
-glClear() color
-glDrawArrays to draw our VAO

Now that you know what to expect I will leave the rest of the tips as code comments so you know exactly what I am referring to. If anyone feels up to adding this as an updated example to the Qt examples that would be great. I might do a part 3 with QOpenGLWidget doing the same thing soon.

main.cpp

#include 
#include 
#include 
#include 
#include "main.h"

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    QSurfaceFormat format;
    format.setDepthBufferSize( 4 );
    format.setSamples(24);
    format.setVersion(3, 1);
	format.setRenderableType(QSurfaceFormat::OpenGL);

    ModernTriangle window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    return app.exec();
}


void ModernTriangle::initialize()
{
    glClearColor(0.0f, 0.5f, 1.0f, 1.0f);

    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,
                                    "#version 140\n" //GLSL version 1.4
                                    "in vec3 position;\n" //attribute named position with 3 elements per vertex in
                                    "in vec3 color;\n"
                                    "out vec4 fragColor;\n"
                                    "void main() {\n"
                                    "    fragColor = vec4(color, 1.0);\n"
                                    "    gl_Position = vec4(position, 1.0);\n"
                                    "}\n"
                                    );

    qDebug() << "vertex shader errors if any..." <log();
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,
                                    "#version 140\n" //GLSL version 1.4
                                    "in vec4 fragColor;\n"
                                    "out vec4 finalcolor;\n"
                                    "void main() {\n"
                                    "    finalcolor = fragColor;\n"
                                    "}\n"
                                    );
    qDebug() << "fragment shader errors if any..." <log();
    m_program->link();
    m_program->bind(); // bind Shader (Do not release until VAO is created)

	//location of vertex data arrays must be before they are referenced
	//but location not important otherwise
    static const float vertexPositions[] = {
        -1.0f, 0.0f, 0.0f, //(x,y,z) bottom left
        1.0f, 0.0f, 0.0f, //bottom right
        0.0f, 1.0f, 0.0f //top middle
    };

    static const float vertexColors[] = {
        1.0f, .0f, .0f, //red (r,g,b) values for each vertex
        .0f, 1.0f, .0f, //green
        .0f, .0f, 1.0f //blue
    };

    m_vao.create();
    m_vao.bind(); //sets the Vertex Array Object current to the OpenGL context so we can write attributes to it

    QOpenGLBuffer m_vvbo(QOpenGLBuffer::VertexBuffer);
    m_vvbo.create();
    m_vvbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_vvbo.bind();
    m_vvbo.allocate(vertexPositions, 9 * sizeof(float));
    m_program->enableAttributeArray("position"); //this labels an attribute "position"
                                                //that points to the memory slot from the last buffer allocate()
                                                //the position attribute is an input to our vertex shader
    m_program->setAttributeBuffer("position", GL_FLOAT, 0, 3);

    QOpenGLBuffer m_vcbo(QOpenGLBuffer::VertexBuffer);
    m_vcbo.create();
    m_vcbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_vcbo.bind();
    m_vcbo.allocate(vertexColors, 9 * sizeof(float));
    m_program->enableAttributeArray("color");   //this labels an attribute "color"
                                                //that points to the memory slot from the last buffer allocate()
                                                //the color attribute is an input to our vertex shader
    m_program->setAttributeBuffer("color", GL_FLOAT, 0, 3);

    // Release (unbind) all
	m_vvbo.release();
    m_vcbo.release();	
    m_vao.release();

    m_program->release();
}


void ModernTriangle::render()
{
    const qreal retinaScale = devicePixelRatio();
    glViewport(0, 0, width() * retinaScale, height() * retinaScale);

    // Clear
    glClear(GL_COLOR_BUFFER_BIT);

    // Render using our shader
    m_program->bind();
    m_vao.bind(); //sets 
    glDrawArrays(GL_TRIANGLES, 0, 3);
    m_vao.release();
    m_program->release();
}

main.h

#include "openglwindow.h"
#include 
#include 
#include 
#include 

//header style declaration
class ModernTriangle : public OpenGLWindow
{
public:
    ModernTriangle();

    void initialize() Q_DECL_OVERRIDE;
    void render() Q_DECL_OVERRIDE;

private:
    GLuint vertexLocation;
    GLuint colorLocation;
    GLuint matrixLocation;
	
    QOpenGLVertexArrayObject m_vao; // Our Vertex Array Object
    QOpenGLBuffer m_vvbo;  // Our vertice Vertex Buffer Object
    QOpenGLBuffer m_vcbo; // Our color Vertex Buffer Object

    QOpenGLShaderProgram* m_program;
};

ModernTriangle::ModernTriangle()
{
}

openglwindow.cpp


/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "openglwindow.h"

#include 

#include 
#include 
#include 

//! [1]
OpenGLWindow::OpenGLWindow(QWindow *parent)
    : QWindow(parent)
    , m_update_pending(false)
    , m_animating(false)
    , m_context(0)
    , m_paint_device(0)
{
    setSurfaceType(QWindow::OpenGLSurface);
}

//! [1]

OpenGLWindow::~OpenGLWindow()
{
    delete m_paint_device;
}
//! [2]
void OpenGLWindow::render(QPainter *painter)
{
    Q_UNUSED(painter);
}

void OpenGLWindow::initialize()
{
}

void OpenGLWindow::render()
{
    if (!m_paint_device)
        m_paint_device = new QOpenGLPaintDevice;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    m_paint_device->setSize(size());

    QPainter painter(m_paint_device);
    render(&painter);
}
//! [2]

//! [3]
void OpenGLWindow::renderLater()
{
    if (!m_update_pending) {
        m_update_pending = true;
        QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
    }
}

bool OpenGLWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::UpdateRequest:
        m_update_pending = false;
        renderNow();
        return true;
    default:
        return QWindow::event(event);
    }
}

void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
        renderNow();
}
//! [3]

//! [4]
void OpenGLWindow::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();
        needsInitialize = true;
    }
    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();
    m_context->swapBuffers(this);
    if (m_animating)
        renderLater();
}
//! [4]

void OpenGLWindow::setAnimating(bool animating)
{
    m_animating = animating;

    if (animating)
        renderLater();
}


openglwindow.h


/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include 
#include 

QT_BEGIN_NAMESPACE
class QPainter;
class QOpenGLContext;
class QOpenGLPaintDevice;
QT_END_NAMESPACE

//! [1]
class OpenGLWindow : public QWindow, protected QOpenGLFunctions_3_1
{
    Q_OBJECT
public:
    explicit OpenGLWindow(QWindow *parent = 0);
    ~OpenGLWindow();

    virtual void render(QPainter *painter);
    virtual void render();

    virtual void initialize();

    void setAnimating(bool animating);

public slots:
    void renderLater();
    void renderNow();

protected:
    bool event(QEvent *event) Q_DECL_OVERRIDE;

    void exposeEvent(QExposeEvent *event) Q_DECL_OVERRIDE;

private:
    bool m_update_pending;
    bool m_animating;

    QOpenGLContext *m_context;
    QOpenGLPaintDevice *m_paint_device;
};
//! [1]

3 thoughts on “Part 2 – Modern OpenGL Using Qt 5.5 Tutorial

  1. Dariusz February 17, 2018 / 7:03 pm

    Great example, thank you! But I might have found an issue?

    I tweaked the fragment/vertex parts to be more like this…

    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,
    “#version 450\n” //GLSL version 1.4
    “in vec3 position;\n” //attribute named position with 3 elements per vertex in
    “in vec3 color;\n”
    “out vec4 fragColor;\n”
    “void main() {\n”
    ” fragColor = vec4(color, 1.0);\n”
    ” gl_Position = vec4(position, 1.0);\n”
    “}\n”
    );
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,
    “#version 450\n” //GLSL version 1.4
    “in vec4 fragColor;\n”
    “out vec4 finalcolor;\n”
    “void main() {\n”
    ” finalcolor = fragColor;\n”
    “}\n”
    );

    Like

    • Anonymous February 8, 2020 / 10:47 pm

      Hello DariusZ, You were right. I am just relearning this now so thanks for posting the correction! The only thing you changed that you didn’t need to is the version to get the example to work.

      Like

  2. Andreas March 14, 2020 / 9:05 pm

    Hi, thanks for the tutorial, though it requires some fixing before it can be used. For some reason, the code that’s shown misses includes and the <log(). Won’t compile without tweaking it.

    Also, you appear to create local variables m_vvbo and m_vcbo despite having these buffers already as member variables. Generally, a word on the required lifetime of those buffers would be nice.

    For a proper tutorial I’d recommend adding some more explanation about the things you are doing, especially the init part and the bind/release operations.

    Thanks,
    Andreas

    Like

Leave a comment