Qt5 OpenGL Part 1: Basic Rendering 28


In this section, we will look at some of the OpenGL datatype abstractions provided by Qt5. Such helpers aren’t 100% necessary for creating an OpenGL applicaton, but it greatly reduces the boilerplate, and allows us to focus on rendering and UI instead of worrying about our OpenGL implementaiton.

We will also create – what is more commonly known as the “Hello, World!” of OpenGL – a colored triangle on the screen.

Shoutouts

Before I begin, I’d like to offer a huge thanks to Peter Clark for checking over my code and seeing that it makes sense. I want to present information as accurately as possible, but I don’t claim to be the master of OpenGL. Peter has a great deal more OpenGL experience than me, so it’s been nice to have someone looking over my work for possible errors.

Thanks Peter! :)

Also, huge thanks to A. Tabibi for pointing out an issue in the current implementation. I have updated the tutorial to reflect these changes.

QOpenGLBuffer

QOpenGLBuffer Objects in OpenGL are kind of like unique ids to dynamic memory on the GPU. It’s a little trickier than that, but for those unfamiliar, you can approximate them as GPU dynamic memory. You can provide the GPU with hints for how you will use the memory, and depending on your choice it changes where and how efficiently accessible the information is.

Qt5 provides a helper class that encapsulates all of the functionality of OpenGL Buffers, called QOpenGLBuffer.

These buffers are not dynamically allocated (via new/delete) on the CPU-side, and they are represented internally by an integer id that OpenGL gives us back. So QOpenGLBuffers can be passed around freely without efficiency problems.

However, when destroy() is called, the Buffer is invalid, and should not be used past that point.

QOpenGLVertexArrayObject

It can become tedious to bind and unbind OpenGL Buffers for objects over and over again. Because of this, QOpenGLVertexArrayObjects (VAOs) were introduced. A Vertex Array Object is simply an object that is stored on the GPU which keeps track of all the buffers and bind information that is associated with a draw call.

This is simply another way of reducing code duplication. It’s pretty much a convenience that may or may not – depending on implementation – provide a speed benefit. However, we want to aim to create OpenGL code that is pretty, and easy to digest. We will be binding information on initialization and then allowing the Vertex Array Object to remember our binding information for us.

The typical usage pattern is:

  • For each visual object
    • Bind the Vertex Array Object
    • Set Vertex state, bind attributes, etc.
    • Unbind the Vertex Array Object

Then later on, instead of binding all of the attribute information again for every object at render-time, we can instead

  • For each visual object
    • Bind the Vertex Array Object
    • Draw the object with a glDraw*() function.
    • Unbind the Vertex Array Object

QOpenGLShaderProgram

This is really the core of the tutorial. Normally, shader compilation is a little tricky for first-time OpenGL users. However, Qt5 provides a QOpenGLShaderProgram class abstraction of the functionality that makes our lives a bit easier. This class can take either source strings, QOpenGLShader types, or paths to files. We will be passing things in as resources, because it’s fairly easy to compile a Qt application with it’s resources.

A shader program – if you aren’t familiar with them – is a bit of code that runs on the GPU for processing data. Typically we take data from the CPU side which pairs position information with some attribute information, and that is what the shader program will process. The structure which holds position and attribute information is more commonly known as a Vertex.

A simple shader pipeline involving only a single pass, and two shader types, can be generalized as such:

A generalized shader pipeline.

A generalized shader pipeline.

There are many kinds of shaders, but for now we will only be using Vertex Shaders and Fragment Shaders as mentioned above.

  • Vertex Shader
    • Takes input information (usually in the form of vertex information) and processes the information to find final positions that the GPU must draw and interpolate between. Interpolation will be handled automatically by the device in-between the Vertex Shader and Fragment Shader. The Vertex information we will use is: position, and color.
  • Fragment Shader
    • Takes the interpolated results from the Vertex Shader, and outputs calculations somewhere. Right now, that sounds pretty mysterious, but the base case for a Fragment Shader is to output to the back buffer, so that we can call our system-specific SwapBuffer() command to make it visible.
      • Note: There is some confusion about whether to call this a Fragment shader or Pixel shader. In my opinion, both names are sufficient, and any graphics programmer will know what you mean when you use one name or the other. I prefer OpenGL terminology because the Fragment shader describes what happens to a group of outputs represented by the transformed vertex inputs. (e.g. we don’t output to a “pixel”, and we evaluate many “outputs”)

Basic Rendering

Sending information through the shader pipeline can, at first, feel a little unnatural if you’re coming from a fixed-pipeline perspective. You don’t really have explicit control over what you draw, it’s all about batching so that the GPU can just run with it. It can feel a little daunting at first having so much control over that information dynamically. But in the end, if you’re not already a fan of drawing things using the shader pipeline, you will be. It’s far easier once you understand it, and even kind of fun!

Also fixed-function is so old that I’m surprised I even decided to talk about it here!

However, the first step is having data to send…

1. Create a Vertex class (to simplify things).

Since vertex information (information that makes up a piece of geometry) is user-defined, Qt does not provide a QVertex class out-of-the-box. To simplify matters, we are going to make a class which will act as our vertex information, and we are going to try to make it as Qt as possible (pun intended).

Note: Originally I had prefixed this class with a Q, which was seen as poor design by Qt developers since it isn’t a part of the Qt Framework. So, I have removed this naming convention from my code

So we will create a new Header File named vertex.h, and we will put the following code.

So before we run off to the next step, let’s discuss this Vertex class. There are really only a few Qt-specific things here.

  • Q_DECL_CONSTEXPR
    • A Qt-provided macro which will be set to constexpr if the compiler supports it. If you aren’t aware what this means, don’t worry about it too much. It’s here for completeness, and speeds up only compile-time known values. (Our application does benefit in this case, but wont when we load data dynamically.)
  • Q_DECLARE_TYPEINFO(Vertex, Q_MOVABLE_TYPE)
    • Creates detailed run-time type information for Qt. In this case, the benefit comes from Q_MOVABLE_TYPE, which means that the whole type can be moved via memcpy(). Calling constructors is not important.

And as you can see, we’re also utilizing the QVector3D class for both position and color. Mostly I’m doing this to reduce the need to create my own vector types. This will probably be useful later, though as you can tell you could probably get away with just defining a dumber Vertex type.

If you are unfamiliar with OpenGL, or Vertex information, you may be confused by the *TupleSize, *Offset(), and stride() symbols.

  • *TupleSize
    • How many elements are present in the collection of information representing that data. For example, PositionTupleSize is 3: x, y, z.
  • *Offset()
    • The offset into the structure to access that data. For example, color has to offset itself by 1 QVector3D class. Since we don’t want to account for padding, we simply use offsetof(structure, member).
  • stride()
    • The stride is simply the amount of information that any given attribute will have to move to get to the next bit of data, if the data were held in a contiguous array. Stride is usually sizeof(Type), but here we encapsulate it in a static class-function to make our code cleaner.

2. Add QOpenGL* classes to Window class

So in this application, we are going to be drawing something to the screen. Because of this, we will have to create and allocate some information. However, we’re still doing very little, so the changes will be minimal.

The changes you need to make to window.h are highlighted below:

The implementation is a little beefier, so it will need to be broken up into parts for me to talk about it a bit.
So everything past this point in section 2 will be about window.cpp.

The first change is at the top of window.cpp:

This may look a little weird at first, but remember what we talked about for QOpenGLShaderProgram and Vertex information. This particular array of Vertex information forms a triangle that fits within the screen’s bounds, with one attribute per location (in this case, that attribute will be evaluated as a color, though it could be anything).

So we have points which move Counter Clockwise to form a triangle:

(0.0f, 0.75f, 1.0f) -> (-0.75f, -0.75f, 1.0f) -> (0.75f, -0.75f, 1.0f)

With each point corresponding to a different attribute which we claim is a color:

Red -> Green -> Blue

Next, we’re going to edit the initializeGL() function:

This is where most of our changes will come from. In general, we will be preparing a lot of data, and getting it ready to just pull the glDraw*() trigger. This will allow our update loop to remain efficient. Let’s talk about these sections a bit,

  • Create Shader
    • This is the only thing that is allocated dynamically via new. It’s most likely that this will be passed around, simply deleting the instance will release the shader from GPU memory.
    • As we discussed, Vertex shaders will take our Vertex type, and produce interpolated fragment data. Fragmet shaders will takes the fragment output from the Vertex Shader, and output final resultant data to some buffer. In our case, the resultant data will simply be drawn to screen.
      • Note: We will create the shaders later in the tutorial.
    • Link all of the loaded shaders together. Ideally, we should be checking the return value of this call, because it’s possible for it to fail. We will likely add error handling in another tutorial after we see a little bit of drawing. Though ideally you should be programming offensively, and checking these functions for possible return errors.
    • Bind the shader so that it is the current active shader.
  • Create Buffer
    • Create a buffer for dynamic allocation later.
    • Bind the buffer so that it is the current active buffer.
    • Since we will never change the data that we are about to pass the Buffer, we will say that the Usage Pattern is StaticDraw.
      • There are three types of *Draw: Static, Dynamic, and Stream. We will explore more with these usage patterns later.
    • Allocate and initialize the information.
      • Note: This is a very special case of sizeof(…), since the array’s size and length are known at this point – sizeof(sg_vertexes) will return the size – in bytes – of the data. You cannot always rely on this information, but here is okay.
  • Create Vertex Array Object
    • Create the Vertex Array Object (VAO from here on).
    • Bind the VAO so it is the current active VAO.
      • Note: Everything from here until we release/unbind the VAO will pertain to buffer information for drawing our buffer data.
    • Enable Attribute 0 & 1.
    • Set Attribute 0 to be position, and Attribute 1 to be color.
      • This is where our helpers for *Offset(), *TupleSize, and stride() come into play. As you can see, this greatly simplifies our code.
  • Release (Unbind) All
    • It’s formal for unbinding to happen in the opposite order of which it occurred. So we will unbind the VAO, followed by the Buffer, followed by the Shader.
    • You also might be set-back by the odd naming convention that the Qt team selected; release(). Seems like it might release the information, but it’s documented as an unbind. I’m not sure why it got the name release, nor am I crazy for the name.

Next we can update paintGL() to actually draw our triangle:

As you can see, this is very simple since we’re using VAOs.

  • Bind the shader we want to draw with.
    • Bind the VAO we want to draw.
    • Draw a triangle with 3 indices starting from the 0th index.
    • Unbind the VAO.
  • Unbind the shader.

to account for changes in the vertex array, we are dynamically calculating the number of indices. If you recall from earlier, I claimed that since sg_vertexes is known both in data type and array length, sizeof(sg_vertexes) returns the size of the array in bytes. So it goes without saying that sizeof(sg_vertexes[0]) would return the size of an element in bytes.

QED

QED

And finally, we need to release our information through the teardownGL() function:

3. Create our Shader resources

Next, we need some files to actually load into the shader program. Later we will dynamically load the files, but for now we’re going to bake them into the executable as Qt Resources.

So create a Qt Resource, and we will get started (I named my resource file resources.qrc).

Creating a new resource file.

Creating a new resource file.

After that, we will create two shaders from the “GLSL” “Files and Classes” templates. One will be a Vertex Shader named simple.vert, and another will be a Fragment Shader named simple.frag. We will save them in a subdirectory that we will create in our project directory.

Note: Both of these files should be saved in:
<ProjectDirectory>/shaders/simple.{vert|frag}
<ProjectDirectory> := where the <ProjectName>.pro file is located.

So if my Project is named “1_BasicRendering”, and it’s saved in “C:\QtProjects\1_BasicRendering\”, then I will create a folder named “shaders”, and save the following two files:

“C:\QtProjects\1_BasicRendering\shaders\simple.vert”
“C:\QtProjects\1_BasicRendering\shaders\simple.frag”

We will then add the two files to resources.qrc by first creating an empty Prefix, and then Adding both files.

If done properly, your project layout should look like this:

Project Layout at this point.

Project Layout at this point.

Finally, the last thing we need to do is edit our shader code.

shaders/simple.vert:

At the top of the file, it’s common to put a comment that states what version of shader language you expect this file to run on. This states that the shader should run on OpenGL 3.3. And the shader takes two inputs (position, and color), and has one output: vColor. And in the source of the shader, all we’re doing is promoting the two vec3 types to vec4 via a cast.

Note: I had claimed in the previous tutorial, when talking about QOpenGLFunctions, that we would be able to work with the OpenGL ES 2.0 API. This is still correct, we don’t need any more functionality from the API. But in terms of shader languages, we will instead be targeting OpenGL 3.3 (Desktop version). This means the same code we write here will not port to mobile so easily. It would instead require minor readjustments.

shaders/simple.frag:

The fragment shader is even simpler, it just takes the input color, and outputs the color unchanged. Nothing much really needs to be said about it.

That’s it! After running it, our final executable should look like this:

Our final application.

Our final application.

Summary

In this tutorial, we learned…

  • That Qt5 encapsulates some of the OpenGL datatypes for our convenience.
  • How to create a Vertex class which we will use for passing information to the GPU.
  • The quirks and oddities of QOpenGL* classes, and how to use them instead of QOpenGLFunctions* classes.
  • A refresher on the shader pipeline, and how to add resources to our Qt Application.
  • In the end, we learned how to render a triangle on-screen!

View Code on GitHub

Cheers!


Leave a comment

Your email address will not be published. Required fields are marked *

28 thoughts on “Qt5 OpenGL Part 1: Basic Rendering

  • A. Tabibi

    Thank you for this very nice tutorial!
    But it is appear that function “teardownGL” never call.
    I wrote a qDebug() << "Foo!" in this method, but it did not appear on console window!

    • Trent Post author

      Thanks Tabibi, you’re right!
      That’s odd, I recall testing it. I’ll look into it a little more and report back. But it turns out that later on I learn there is an unavoidable need to call makeCurrent() in the destructor for Window. So for now, you can just simply make teardownGL() a protected method, and have a destructor such that:

      Window::~Window()
      {
      makeCurrent();
      teardownGL();
      }

      Thanks for pointing this out – I’ll try and see what’s going on. :)

    • Trent Post author

      What was happening is that the QObject was being destroyed, and signal/slot pairs were being disconnected before the signal for aboutToBeDestroy() was being emitted. I’ve updated the code above to reflect the change. In the end, it’s better to simply call makeCurrent() and teardownGL() in the destructor manually instead of relying on signals and slots for this one. It saves us headaches down the road too.

  • Sam

    What a greate tutorial, much better than the official document. Thank you for letting me know the basic steps of drawing a single triangle in Qt.

  • Tristan

    Hello,
    nice job done here !

    Just few little things:

    In chapter one in your Window class you have a member function (Private Helpers) call “printContextInformation”,
    while in this chapter is call “printVersionInformation”.

    The second thing is, (as i can see you like use Qt functions/Macros) so why don’t you send “this” as “new QOpenGLShaderProgram();” parameter ? QOpenGLShaderProgram waiting a QObject and your Window class inherit from QOpenGLWindow who inherit from QWindow who inherit from QObject (feew…). Then you could remove it from you destructor. An other way more proper should be to set m_program = Q_NULLPTR after deleting it in destructor and maybe test if m_program exist before in case of Qt destruction call chain.

    In any case, this is a pleasure to read a proper code like this !
    Thank.

    • Trent Post author

      Hey Tristan,

      Thanks for pointing the function name change out! Sometimes it’s hard to keep track of the changes from one chapter to the other, for some reason it looks like I felt the need to change that function name.

      While it’s a good idea to take advantage of the QObject parent-child hierarchy system, I would not pass the window in as the owner. Mainly because the shader should be destroyed before the OpenGL context, and we want it happening in that order explicitly. Documentation shows that we should pass the QOpenGLContext in as the owner. I can’t remember why I didn’t? Maybe it was just an oversight – Thanks for mentioning it!

      And I disagree that it’s more proper to set a pointer to null in the destructor. The reason is this masks the value to a safe null value, hiding potential bad code or harming debugging capabilities. (See: http://stackoverflow.com/questions/3060006/is-it-worth-setting-pointers-to-null-in-a-destructor for some answers). In general this is really the programmers call, but in terms of the standard: After dtor, any interaction with an object is invalid anyways. This seems like a sort of “tabs-or-spaces” holy war territory, so I won’t say that one way is right or the other is wrong, I’m just saying there is a method to this madness. ;)

      EDIT: Also, if you do happen to have a null pointer, you can safely call delete on it. As per the standard “The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect.” (Or see a convo about this: http://stackoverflow.com/questions/27133056/what-happens-when-i-call-delete-on-an-uninitialized-pointer-in-c)

  • Jimmy Sue

    Hi, I’m reading this series this days , and when i run the part 1 project, i got this errors:
    OpenGL ES OpenGL ES 3.0 (ANGLE 2.1.99f075dade7c) ( CoreProfile )
    QOpenGLShader::compile(Vertex): ERROR: 0:1: ‘
    ‘ : invalid version directive
    ERROR: 0:2: ‘layout’ : syntax error

    *** Problematic Vertex shader source code ***
    #version 330
    #line 2
    layout(location = 0) in vec3 position;
    layout(location = 1) in vec3 color;
    out vec4 vColor;

    void main()
    {
    gl_Position = vec4(position, 1.0);
    vColor = vec4(color, 1.0f);
    }

    ***
    QOpenGLShader::compile(Fragment): ERROR: 0:1: ‘
    ‘ : invalid version directive
    ERROR: 0:2: ‘in’ : storage qualifier supported in GLSL ES 3.00 only
    ERROR: 0:3: ‘out’ : storage qualifier supported in GLSL ES 3.00 only

    *** Problematic Fragment shader source code ***
    #version 330
    #ifndef GL_FRAGMENT_PRECISION_HIGH
    #define highp mediump
    #endif

    can you help me

    • Trent Post author

      So these tutorials were written and tested under OpenGL proper, not OpenGL ES. I haven’t tested out much on OpenGL ES, and so there might be some tutorials (maybe all?) that don’t compile properly. Anyways, the problem here is that the version string generated is “#version 330”, but a version string for ES is of the form “#version NNN es” where NNN = some shader version number.

        • Trent Post author

          Hmm, unfortunately I’ve never used that addin. :( When I worked with Qt I used QtCreator in order to ensure that development would be seamless between Windows and Linux. I don’t think I can assist much here.

      • Student

        Thanks for tutorial. I have similar problem

        QOpenGLShader::compile(Vertex): ERROR: 0:1: ‘
        ‘ : invalid version directive
        ERROR: 0:1: ‘layout’ : syntax error

        *** Problematic Vertex shader source code ***
        #version 330
        #line 1
        layout(location = 0) in vec3 position;
        layout(location = 1) in vec3 color;
        out vec4 vColor;

        void main()
        {
        gl_Position = vec4(position, 1.0);
        vColor = vec4(color, 1.0);
        }

        ***
        QOpenGLShader::compile(Fragment): ERROR: 0:1: ‘
        ‘ : invalid version directive
        ERROR: 0:2: ‘in’ : storage qualifier supported in GLSL ES 3.00 only
        ERROR: 0:2: ” : No precision specified for (float)
        ERROR: 0:3: ‘out’ : storage qualifier supported in GLSL ES 3.00 only
        ERROR: 0:3: ” : No precision specified for (float)

        *** Problematic Fragment shader source code ***
        #version 330
        #line 1

        in vec4 vColor;
        out vec4 fColor;

        void main()
        {
        fColor = vColor;
        }

        ***
        I use QtCreator as in tutorial. And why ” ‘layout’ syntax error ” is on 0:1 instead 0:2?

        And how I must add shaders?
        http://imgur.com/a/LZaNe

    • Chencho

      OpenGL thinks it is running on an Embedded System for some reason and it tries to use GLSL ES for some reason. Last time that happened to me I saved my work and reinstalled QT, Nvidia drivers, basically everything and it went back to running without that error. Very odd.

    • Trent Post author

      Hey Sagar,

      The errors seem to point to an issue with attaching the shader files as a resource to the application. Please make sure that you are correctly adding resources to your application. This can be a little finicky, so I suggest doing some digging into some of the Qt Resource documentation to understand it properly: http://doc.qt.io/qt-5/resources.html

      Thanks,
      – Trent Reed

    • Trent Post author

      This function configures the QOpenGLFunctions object so that it has all of the functions populated for the requested OpenGL context. (http://doc.qt.io/qt-5/qopenglfunctions.html#initializeOpenGLFunctions) This is required for OpenGL since functions don’t tend to be statically-linked in, rather we rely on symget kind of functionality to get OpenGL functions – this is why OpenGL Extension Wrangler is a popular GL-library, it does this “wrangling” for us. Qt takes care of the wrangling, afaik.

    • Trent Post author

      This is exactly that – It gets a little more complicated the more “correct” you make your rendering pipeline. I’ve opted for a non-fixed pipeline, so you need to at least use and learn vertex and fragment shaders. Add Qt on top of this and there are more things to learn (resource system, how Qt initializes OpenGL). As far as Qt is concerned, this is as simple as I can make that request without teaching you incorrectly.

      • Dima

        Hi again, Trent!
        I’ve succsefully changed it to OpenGLWidget and tried to test it with Valgrind. But it complains, says: “Invalid write of size 4
        in Window::paintGL()” in glDrawArrays(). Did you face this problem?
        P.S. What you did is awesome! Thanks a lot!

        • Trent Post author

          I don’t recall running into anything like that, but I don’t particularly remember if I tested with Valgrind (bad bad bad!) If there is an error in the code I’m presenting to others here, I’d love to know about it! :)
          Unfortunately, I don’t have much time to look into this now – if you find out what the issue is, let me know and I’ll take a look at what you find. Otherwise, I’ll update this thread if/when I get time to take a look.