OpenGL in Qt 5.1 – Part 5

This article concludes our series on what is new in Qt 5.1 with respect to OpenGL. Earlier articles in this series are available at:

More shader stages

In Qt 5.0 the QOpenGLShader and QOpenGLShaderProgram classes only had support for Vertex and Fragment shaders. Qt 5.1 will include support for all shader stages supported by OpenGL:

  • Vertex Shaders
  • Fragment Shaders
  • Geometry Shaders (requires OpenGL 3.2)
  • Tessellation Control and Tessellation Evaluation Shaders (requires OpenGL 4.0)
  • Compute Shaders (requires OpenGL 4.3)

As long as your platform has support for the required OpenGL version, then Qt 5.1 will work there. The availability of Geometry, Tessellation stage and Compute shaders really opens the doors for some interesting effects and order of magnitude improvements in performance over what is possible with plain OpenGL 2.x.

For the future

As we have seen, Qt 5.1 adds a great deal new functionality and enablers around OpenGL. There is still much to do and we will continue adding more enablers to make it easier and safer to write OpenGL code as we move towards Qt 5.2. Some of the items on the radar include Textures, Texture Images, Samplers, Occlusion Queries, Transform feedback objects, Uniform Buffer Objects, Shader Storage Buffer Objects and Shader Program Pipeline Objects, Object labels for debug output

We are very keen to see OpenGL support in Qt continue to improve and will continue to work to do so. I know many people have been asking about the status of Qt3D. Although not part of the Qt3D module, the improvements described here form a good part of the foundations upon which we will build the Qt3D renderer. If all goes to plan then Qt3D will be released along with Qt 5.2.

An example

For now here is a video of an example written using Qt 5.1 (dev branch) and OpenGL. This gives a small taste of what can easily be done using these two great APIs together:

This scene consists of the following:

  • Terrain heightmap texture used to render large terrain region in just one function call!
  • Rendered with GL_PATCHES as we are using tessellation shaders
  • View frustum clipping performed on GPU (needs improvement to remove artifacts)
  • Dynamic tessellation levels determined by screen-space error
  • Wireframe included in single pass using geometry shader
  • GLSL subroutines to quickly change rendering style without changing shader program
  • Dynamic normal vector calculation
  • Texture blending controlled by terrain height, slope, and fragment distance
  • Very simple fog for depth cueing
  • Per-pixel directional lighting

As you can see this is quite some features but we have moved much work to the GPU. In fact the entire rendering function for this example is:

void TerrainTessellationScene::render()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    m_material->bind();
    QOpenGLShaderProgramPtr shader = m_material->shader();
    shader->bind();

    // Set the fragment shader display mode subroutine
    m_funcs->glUniformSubroutinesuiv(
        GL_FRAGMENT_SHADER, 1,
        &m_displayModeSubroutines[m_displayMode] );

    // Set the horizontal and vertical scales applied
    // in the tessellation stages
    shader->setUniformValue( "horizontalScale", m_horizontalScale );
    shader->setUniformValue( "verticalScale", m_verticalScale );

    // Pass in the usual transformation matrices
    QMatrix4x4 viewMatrix = m_camera->viewMatrix();
    QMatrix4x4 modelViewMatrix = viewMatrix * m_modelMatrix;
    QMatrix3x3 worldNormalMatrix = m_modelMatrix.normalMatrix();
    QMatrix3x3 normalMatrix = modelViewMatrix.normalMatrix();
    QMatrix4x4 mvp = m_camera->projectionMatrix() * modelViewMatrix;
    shader->setUniformValue( "modelMatrix", m_modelMatrix );
    shader->setUniformValue( "modelViewMatrix", modelViewMatrix );
    shader->setUniformValue( "worldNormalMatrix", worldNormalMatrix );
    shader->setUniformValue( "normalMatrix", normalMatrix );
    shader->setUniformValue( "mvp", mvp );

    // Set the lighting parameters
    QVector4D worldLightDirection( sinf( m_sunTheta * degToRad ),
                                   cosf( m_sunTheta * degToRad ),
                                   0.0f, 0.0f );
    QMatrix4x4 worldToEyeNormal( normalMatrix );
    QVector4D lightDirection = worldToEyeNormal * worldLightDirection;
    shader->setUniformValue( "light.position", lightDirection );
    shader->setUniformValue( "light.intensity",
                             QVector3D( 1.0f, 1.0f, 1.0f ) );

    // Set the material properties
    shader->setUniformValue( "material.Ka",
                             QVector3D( 0.1f, 0.1f, 0.1f ) );
    shader->setUniformValue( "material.Kd",
                             QVector3D( 1.0f, 1.0f, 1.0f ) );
    shader->setUniformValue( "material.Ks",
                             QVector3D( 0.3f, 0.3f, 0.3f ) );
    shader->setUniformValue( "material.shininess", 10.0f );

    // Render the quad as a patch
    m_vao.bind();
    shader->setPatchVertexCount( 1 );
    glDrawArrays( GL_PATCHES, 0, m_patchCount );
}

Most of the rendering function is just setting uniform variables and could be replaced with the use of a uniform buffer. The complete source code for this example is available at:

Requirements for this example are Qt 5.1 alpha (or a recent build from the stable branch of git) and an OpenGL 4.0 capable system. See the window.cpp file for controls.

There are a number of improvements that could quite easily be made:

  • Use a compute shader to generate terrain on demand as we move around the world
  • Use a floating point texture for more vertical resolution
  • Use a Z-fill pre-pass to reduce fragment shader work load
  • Compute normal vectors once in compute shader rather than every frame
  • Add trees, bushes, rocks, grass using instanced geometry rendering
  • Make the terrain deformable and paintable (e.g. raise/lower land, paint paths, change terrain base color)
  • Use a density function rather than a simple heightmap to create caves and overhangs
  • Use a quad-tree tile approach to further reduce distant triangle count
  • Add more realistic atmospheric scattering
  • Ambient occlusion and shadows

We will cover some of these in future blog articles and in more depth in our Qt and OpenGL trainings. Happy rendering!

Share on FacebookTweet about this on TwitterShare on Google+

65 thoughts on “OpenGL in Qt 5.1 – Part 5

  1. Could you, please, explain what is the overhead in using Qt for real-time 3D applications? Particularly on mobile platforms and also with QQuickView, QGuiApplication ?

    • It all depends what you are trying to do. QGuiApplication + QWindow + QOpenGLContext gives you basically the same as something like GLUT or SDL. It’s entirely up to you how to delegate rendering and other tasks to threads in your application but there is no real overhead in using Qt like this.

      If you want to combine custom OpenGL rendering with Qt Quick 2 then of course there is the small overhead of that technology stack for the runtime. However, it does buy you a very powerful way of creating UIs and property bindings are immensely useful.

      We are aware of many projects using Qt5 + Qt Quick 2 + OpenGL on mobile devices. Performance is as always dependent upon what your hardware can do and how effectively you are using it.

  2. I am writing an application that works using opengl widgets multithreaded. My actual approach is QGraphicsView+QGraphisScene+OpenGL based in qq-26-openglcanvas example. Can I use QWindows+QOpenGLContext+QWidget::createWindowContainer to replace old style? If this is possible, how can I do that?

    • The same technique should still work. Why do you feel the need to change it? If it is to use QOpenGLShaderProgram rather than QGLShaderProgram etc then there is no need as the new QOpenGL* classes should work with QGLWidget and friends.

      An alternative that is now open to you would be to use QML over OpenGL “underlay” or OpenGL-based custom items.

      • I am looking for the best performance as possible for my real time application. The reason to try to change it, is for new application are recomended use of QOpenGLxxx instead of QGLxxx. My problem is when I try to attach for example a QLabel into a layout and then to QWindows container (QWidget) simply this widget not appear, only shown raw opengl. Can you write a little example in how to do that? In respect to your suggestion of use QML, particulary, I feel good programming in C++.

  3. Hi Sean, I’m from Venezuela, I’m currently doing some research for my thesis on real time planet rendering before I get to implement something and I found this article very useful, but it is too much QT-oriented, I would like to know if you have a paper or something about your implementation on the terrain renderer because I can’t find anything documented similar to this (terrain rendering using tessellation shaders), it is something relatively new and I’m almost sure I will use your implementation and make it work on spherical surfaces (I will give you credits on my thesis, I’m asking for permission to take your source and use it).

    If you give me permission, I would like some help to make some of the improvements you mentioned (quad tree, deformable terrain data, compute normal vectors).

    Thanks in advice for your help, I will really appreciate it.

    • Hi Luis, please feel free to build upon this code as you see fit. A good reference is the chapter on this topic in the book OpenGL Insights.

      • Thanks Sean, I’m just downloading a copy of the book right now and it looks very great, this documentation will help with my work, if I get to improve something in your code I will send it to you. Please give me your email so I can contact you personally (if it’s ok and you have some time to help me)

        Thank you again! have a nice day :)

  4. Hi Sean,
    I’m attempting to implement a QML GUI overlay on top of an OpenGL 3.2+ underlay. You mention this approach in your QtDD12 OpenGL with Qt5 talk, via inheriting QQuickView and setting a custom QSurfaceFormat with the newer OpenGL version set to the compatibility profile.

    Unfortunately on OS X there is no compatibility profile, you can only create a 3.2 context using the core profile. Is there any other way of mixing Qt Quick 2 and OpenGL 3.2+, without rendering one of them to an FBO?

    Thanks for you advice and your articles

  5. The tessellation behavior does not seem right : as we come closer to the geometry there is indeed more triangles, but they are all on the same flat squared patch.
    It creates very sharp edges, and that is what tessellation is supposed to avoid.

  6. For an image display tool using QT 5.1, what’s the best way to support multiple windows? I can’t use QMdiArea, because it expects QWidgets not QWindows? And without QWindows all the wonderful QOpenXXX methods aren’t available.

  7. I only see a sky blue scene – any common problem that could cause this? My graphics card is able to handle opengl 4.+

    • Yes, I have the same problem in the output reads:
      Total number of patches = 25600
      QOpenGLContext :: swapBuffers () called with non-exposed window, behavior is undefined

      • Which platform, GPU and driver version do you guys have please? I’ve only tested this on Linux, nVidia GeForce 500 series with a recent binary blob driver from nVidia. You may need to poke around and see which context version you are getting back from the system.

  8. Hello,

    I’m trying to learn OpenGL for Qt from this project, can you help me with one problem? I’m trying to get 3D mouse coords => 2D mouse coords to 3D world coords, I found gluUnProject, but it doesn’t work, so I used QMatrixes from Camera to create own, but still one problem left.

    I have terrain start location, I get mouse into center of screen and I’m getting wrong Z position, it should be 230.0 Z but it is 216.1 Z

    When I rotate with camera on axis X to left (-90 pan degrees), Z became normal, but X coord get wrongs :/

    My code.

    Currently QVector3D on [935, 475] (center) = QVector3D(249.997, 10.0002, 216.095)
    Should be QVector3D(250, 10, 230).

    Have you any idea how to solve this?

  9. Hi,

    I want to get world coords from mouse, I need to locate terrain xyz.

    Because gluUnProject not working, I build own gluUnProject, but I have big trouble with camera, when I rotate camera, world coords are changed and are incorrect.

    When I just start app, my Z coordinate is incorrect, when I turn camera to left by 90 degrees, Z is in normal, but X is corrupted.

    My code is available here.

  10. Hi Sean,

    I keep getting blue screen after compilation like some commenter mentioned. Do you have an idea what causes this? The code compile and It display “Total number of patches = 25600 “, but it show only a blue screen.

    My settings are:

    Qt 5.1.1 (tried both MinGW/VS2010-12)
    Windows 7 64
    AMD 7950 (with latest drives).

    Thanks.

    • Hmmm this sounds like some difference between nVidia vs AMD in GLSL support. I don’t have any AMD cards to test on here. I suspect it’s something in either the tess or geometry shaders.

  11. Same problem here: I’m just getting a blue screen.

    AMD HD6570 (Catalyst 13.9, latest as of Sept. 2013).
    Qt 5.1
    Windows 7 64

    Any chance for an update? I’d love to try this out on my ATI card.

  12. Hi ! I have a few questions about the tessellation demo:

    1. Some functions are called without a pointer, for example:
    glEnable( GL_DEPTH_TEST ); How it works ?

    2. Why you use different qopenglfunctions in the project ? For example material.cpp contains #include , sampler.cpp contains #include Why you don’t use QOpenGLFunctions_4_0_Core everywhere ?

    • Hi.

      1) Some functions are exported by the libraries on all platforms. The most restrictive platform is Windows which only exports OpenGL 1.0 and 1.1 functions.

      2) Yes I probably should have used that consistently throughout. I wrote those parts before QOpenGLFunctions_MAJOR_MINOR existed.

  13. Hello Sean,

    Is it possible to use the WGL_AMD_gpu_association or the WGL_NV_gpu_affinity extensions to choose the graphic card for the QOpenGLContext?

    We tried it with setting the QScreen for the Context with Windows 7 and two NVIDIA Quadro Graphic cards, each connected with one screen, but the result was that only the first graphic card renders the two contexts.

    Is there any way to choose the gpu for a QOpenGLContext?

    Thanks

    • Hi,

      Hmmm, I don’t recall seeing any explicit support for those extensions within Qt. Would be a nice thing to add though.

  14. Hi! I want to use OpenGL 4.* Core funtions without QOpenGLFunctions_4_*_Core pointer: m_funcs->glFunction();
    I was trying to modify Terrain Tesselation demo but without success. Here is a modified class definition:

    class TerrainTessellationScene : public AbstractScene, protected QOpenGLFunctions_4_0_Core

    Then in void TerrainTessellationScene::initialise() I removed the following code:

    m_funcs = m_context->versionFunctions();
    if ( !m_funcs )
    {
    qFatal(“Requires OpenGL >= 4.0″);
    exit( 1 );
    }
    m_funcs->initializeOpenGLFunctions();

    and I added:

    initializeOpenGLFunctions();

    I also removed “mfuncs->” from the code.

    When I run the program it crashes with the exit code: -1073741819

    What is wrong ?

  15. Yes, I only changed the code mentioned above, everything else is the same as in the terrain tessellation demo. void Window::initializeGL() is called after the context is created and set. Could you reproduce that way of using QOpenGLFunctions on your machine ?

  16. Hi Sean:
    Thanks for these tutorials. I found something really strange when i was trying to follow your tutorials to set up OpenGL in Qt.
    Firstly, I have a ATI radeon hd 5750 graphics card. After searching online i found it that it only supports up to openGL 3.2.
    But when i was trying to create openGL context with version 4.3, it succeeded, which really confused me.
    Another thing i was super confused about was i was able to pass in any int number as version numbers, like 1000.11, and create() would just work. But i was not able to resolve function entry points if I did that.
    According to Qt document, context.format() should have the actually created version number for openGL for me to query, but apparently in my case it doesnt.
    So whats wrong?
    Thanks.

  17. Hi ! I made a mistake: In the project I mixed two types of initialization. The second way of initialization GL functions also works fine. I assume that when I call QOpenGLFunctions_4_3_Core::initializeOpenGLFunctions() it automatically uses existing context, right ?

  18. In my project I have many classes which use GL functions and inherit from QOpenGLFunctions_4_3_Core, so in each constructor (or more generally for each object) I should call QOpenGLFunctions_4_3_Core::initializeOpenGLFunctions() or there is other way ?

    • You could change from using inheritance to having a pointer to the QOpenGLFunctions_4_3_Core object that you make available via a static function or some other method.

      If you stick with the inheritance approach then yes you should always call initializeOpenGLFunctions(). However, it only does the function resolution work the first time it is called on any instance as the backends are shared internally within QOpenGLContext. So after the first call to initializeOpenGLFunctions() it is essentially a no-op for further calls.

  19. I’m not sure if I understood you correctly (also my english is not perfect). We have for example three classes which inherit from QOpenGLFunctions_4_3_Core. Class A contains objects of class B and C. The constructor of class A looks like this:
    A::A()
    {
    QOpenGLFunctions_4_3_Core::initializeOpenGLFunctions();
    b = new B;
    c = new C;
    }
    Constructors of class B and C also have to call QOpenGLFunctions_4_3_Core::initializeOpenGLFunctions(); but adresses are resolved dynamically only once when constructor of class A calls QOpenGLFunctions_4_3_Core::initializeOpenGLFunctions(). So what happens when constructors of class B and C call that function ?

    • Yes if using the inheritance approach that is correct. Internally the calls inside of B and C will just get hold of pointers to the internal objects that contain the function addresses. If you are interested in the actual mechanics of this have a look at the implementation of initializeOpenGLFunctions() and how the Backend classes are cached inside of QOpenGLContext.

  20. Hi ! I created a program where my glwindow which inherits form QWindow is part of the QMainWindow (I have there also some other widgets which I use for example to set a light intensity etc). I used QWidget::createWindowContainer function. I turned off v-sync and set a timer interval to “0″ to check how many fps I can get. The result was about 2200 fps, but when I ran the application I got following message: QOpenGLContext::swapBuffers() called with non-exposed window, behavior is undefined. With the timer interval set to the value > 0 I don’t get that message. Moreover when the timer interval is set to 0 and I want for example resize a main window the application hangs up. What do you think about it ?

    • To avoid the warning about non-exposed windows, just check if the window has been exposed (isExposed()) before doing anything in your render function. If the window has not been exposed yet, don’t bother drawing anything and just return.

      Not sure about the lockup when resizing the main window. Have you tried breaking using the debugger to see where it is stuck?

  21. 1. Yes, but a debugger doesn’t contain any message. I have to use “ctrl+ alt + del” and kill the process manually.

    2. I also wonder why I get 64 fps when a timer interval is set to 1 (should be 1000 fps). Other results: interval = 10 also 64 fps, interval = 50 -> 16 fps, interval = 100 -> 9 fps. Only the last result (100 ms -> 9fps) seems to be correct.

  22. I have problem in compiling my opengl library across arm platform.

    Processor:freescale i.mx51
    it is giving error opengles test functionality failed

    Provide some assistance.

    Regards

    D.Elangovan

  23. Hi,

    I was getting a segfault (with all 5 shaders, fault was at linking) or the blue sky (with fragment, vertex, and geometry) when running the app in Qt Creator, Win 7 64 with Intel HD Graphics 4000 OpenGL 4.0. Tried Qt 5.1 mingw48_32, Qt 5.1 msvc2010 32-bit OpenGL, Qt 5.2 msvc2010 32 OpenGL. The qt version doesn’t seem to make a difference. Aside from updating all opengl functions to QOpenGLFunctions_4_Core, which didn’t seem to fix the problem, I tried modifying the shaders. The one offending line I found was line 85 in terraintessellation.tcs:
    //vec4 clipSpacePatchCorners[4];
    After commenting it out the app runs fine. I’m very puzzled though, because clipSpacePatchCorners is used in the shader (without being declared if line 85 is commented out) and despite some errors I get (main problem is, of course, “Could not compile tessellation control shader. Log:” “ERROR: 2:93: ‘clipSpacePatchCorners’ : undeclared identifier”), the app only runs with this change. If I comment out all references to clipSpacePatchCorners, then the app is just the blue sky …

  24. As a quick followup:
    the segfault caused by terraintesselation.tcs was happening during the calculation of max(gl_TessLevelOuter[1], gl_TessLevelOuter[3]). Accessing the array values directly in that function was causing a problem. I copied them in local variables and passed to the max() function, then the shader compiled and linked fine. However, the application showed only the blue sky, no terrain. I figured the ‘else’ clause might have to do something with it (it sets gl_TessLevelOuter and gl_TessLevelInner to 0.0). After commenting the else {} contents out, the terrain did show, but with odd behavior. The individual patches were flickering.
    The app looks ok if terraintesselation.tcs fails to compile.

  25. I didn’t see a license notice in the source download.

    Can I use your example code in an open source project I’m developing? It will most likely be licensed under the zlib license or another permissive license.

    • Hi,
      I’m trying to run your example code on a new Macbook Pro running OSX Mavericks, because I have problems with my own code. However, I get the same errors during runtime, i.e. during compile time of the tesselation shaders with your example: QOpenGLShader::compile(): ERROR: 0:6: Invalid use of layout ‘vertices’
      ERROR: 0:15: Use of undeclared identifier ‘gl_InvocationID’ and more…
      Any hints or thoughts?
      Bernd

      • Which version OpenGL context are you getting back from Mavericks? Not all Mavericks hw is capable of OpenGL 4. Some devices will only give you and OpenGL 3.2 core profile context. Admittedly I’ve not tried this on Mavericks myself yet so there could be other bugs/problems.

        • I get back a 4.1 context as expected. I’m on Qt 5.2.1 now – the versions before had a bug, which gave you back always the version you demanded – not the one you really have. But this is solved. It’s a NVidia GT750m, which should be able to do GLSL 4.1 …

          • I have almost sorted it out: There is a bug in compiling tesselation shaders in Qt. If I compile (only) the tesselation Shaders with the native OpenGL-Funcs, the example works! However, I see the textures only randomly, but this will have others reasons, I assume. Somebody should track down the compilation of tesselation shaders in Qt…
            Bernd

  26. I tried to run the source with a compatibility context and I couldn’t get it run properly. It was crashing when I call versionFunctions() (segfault)

    @terraintessellationscene.h:
    QOpenGLFunctions_4_3_Compatibility* m_funcs;

    @terraintessellationscene.cpp:
    m_funcs = m_context->versionFunctions();
    if ( !m_funcs )
    {
    qFatal(“Requires OpenGL >= 4.0″);
    exit( 1 );
    }
    m_funcs->initializeOpenGLFunctions();

    It seems QOpenGLContext create(), after setting the QSurfaceFormat properly, was completely rejecting the compatibility setting and creating a Core context instead, completely ignoring my request for a compatibility context (format.setProfile( QSurfaceFormat::CompatibilityProfile).

    I discovered the case. In the original code at kdab:
    // Create an OpenGL context
    m_context = new QOpenGLContext;
    m_context->setFormat( format );
    m_context->create();

    by creating the context (create()) before setFormat, I was able to correctly initiate a proper Compatibility Context. It seems that the original source needs to be fixed.

  27. It seems I’ve got lucky with the AMD drivers and the context is not to be created before setFormat:

    “Creating the context before setFormat() is wrong and works by accident since what you are requesting then is a plain OpenGL 2.0 context and the driver most likely gives you 4.3 compatibility. On other drivers or platforms this may fail so be careful.
    The behavior you are seeing is caused by the AMD driver: it refuses to create a proper compatibility profile context unless the forward compatibility set is not set. Qt sets the fwdcompat bit by default, it can be turned off by setting the DeprecatedFunctions option on the QSurfaceFormat. So requesting for Compatibility together with DeprecatedFunctions will give what you want. Other vendors’ drivers do not have this problem, there not setting DeprecatedFunctions (i.e. setting forward compatibility) is ignored for compatibility profiles, as it should be.”

Leave a Reply

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


8 − = three