Laszlo Agocs

Which OpenGL implementation is my Qt Quick app using today?

Published Wednesday January 18th, 2017
4 Comments on Which OpenGL implementation is my Qt Quick app using today?
Posted in Declarative UI, Dev Loop, Embedded, Graphics, Performance, Qt Quick

Qt Quick-based user interfaces have traditionally been requiring OpenGL, quite unsurprisingly, since the foundation of it all, the Qt Quick 2 scenegraph, is designed exclusively with OpenGL ES 2.0 (the top of the line for mobile/embedded at the time) in mind. As you may have heard, the graphics API story is a bit more inclusive in recent Qt versions, however the default OpenGL-based rendering path is, and is going to be, the number one choice for many applications and devices in the future. This raises the interesting question of OpenGL implementations.

Wait, there is more than one? Isn’t there one for my graphics card, and that’s it?

Kind of, but not quite.

The vendor-provided OpenGL implementation is one thing, but is is not always there to begin with (anyone who attempted to deploy Quick apps on a wide range of older machines running e.g. Windows 7 with no graphics driver installed could likely talk a lot about this…), while in some cases there are alternative options, for instance an open-source stack like Mesa. Some of these stacks can then provide multiple ways of operation (think software rasterizers like Mesa llvmpipe or Direct3D WARP).

As an example, let’s take a look at Windows. The Windows-specific area of the Qt documentation describes the options pretty well. To summarize, one may be using OpenGL proper (a vendor-provided ICD behind opengl32.dll), ANGLE in D3D9 mode, ANGLE in D3D11 mode, ANGLE with the D3D11 WARP software rasterizer, an OpenGL software rasterizer provided by Mesa llvmpipe. That is no fewer than 5 options, and the choice can be made based on the built-in GPU card/driver blacklist or environment variables or hard-coded application preferences. All this is not exactly rocket science, but it does require a certain level of awareness from the developer during development, possibly when reporting bugs and support requests, and naturally also when planning deployment.

Why does this matter?

  • It is not always obvious what is going on. Just starting a Qt Quick application and getting some output does not mean rendering is happening on the optimal path. When the application does not render at the expected speed, the very first thing to verify is if the graphics stack is the expected one. There can always be an unexpected environment variable or blacklist rule present, or the application may pick the wrong graphics stack when there are multiple ones present in the system.
  • Some of the options come with a set of drawbacks and affect the runtime behavior (most importantly, the choice of the Qt Quick render loop used behind the scenes), which in turn can affect performance and can even disable features. (No threaded render loop? No render thread Animators for you.)
  • When reporting issues to the Qt Project bug tracker or Qt Support, it is essential to provide the necessary information about the runtime environment. A mere “OpenGL on Windows” or “Qt Quick on an ARM device” is never sufficient.

QSG_INFO=1 is your friend

When in doubt about graphics performance or before attempting to troubleshoot any sort of graphics issues, do set the QSG_INFO environment variable to 1 and rerun your Qt Quick application.

An alternative in modern Qt versions is to enable the qt.scenegraph.general logging category.

This will print something like the following either on the console or the debug output: (on Windows you can use DebugView for non-console apps when not launching from Qt Creator)

qt.scenegraph.general: threaded render loop
qt.scenegraph.general: Using sg animation driver
qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms
qt.scenegraph.general: texture atlas dimensions: 2048x2048
qt.scenegraph.general: R/G/B/A Buffers:    8 8 8 0
qt.scenegraph.general: Depth Buffer:       24
qt.scenegraph.general: Stencil Buffer:     8
qt.scenegraph.general: Samples:            0
qt.scenegraph.general: GL_VENDOR:          NVIDIA Corporation
qt.scenegraph.general: GL_RENDERER:        GP10B (nvgpu)/integrated
qt.scenegraph.general: GL_VERSION:         OpenGL ES 3.2 NVIDIA 367.00
qt.scenegraph.general: GL_EXTENSIONS:      ...
qt.scenegraph.general: Max Texture Size:  32768
qt.scenegraph.general: Debug context:     false

What does this tell us?

The OpenGL vendor, renderer and version strings. In the example above we see Qt Quick is using an OpenGL ES 3.2 context on some NVIDIA embedded platform, using the vendor’s driver. This look good.

Now, if there happen to be references to llvmpipe, like in the below example, then that should immediately raise a flag: your application is rendering via a software rasterizer. If this is expected, fine. If not, then you should figure out why, because performance is seriously affected (you are not using the GPU at all).

GL_VENDOR: VMware, Inc.
GL_RENDERER: Gallium 0.4 on llvmpipe (LLVM 3.6, 128 bits)
GL_VERSION: 3.0 Mesa 11.2.2

Let’s take another example, this time with ANGLE. Here I just forced the usage of ANGLE by setting QT_OPENGL=angle on an otherwise fully OpenGL capable system:

qt.scenegraph.general: windows render loop
qt.scenegraph.general: Using sg animation driver
t.scenegraph.general: Animation Driver: using vsync: 16.67 ms
qt.scenegraph.general: texture atlas dimensions: 512x512
qt.scenegraph.general: R/G/B/A Buffers:    8 8 8 8
qt.scenegraph.general: Depth Buffer:       24
qt.scenegraph.general: Stencil Buffer:     8
qt.scenegraph.general: Samples:            0
qt.scenegraph.general: GL_VENDOR:          Google Inc.
qt.scenegraph.general: GL_RENDERER:        ANGLE (NVIDIA GeForce GTX 960 Direct3D11 vs_5_0 ps_5_0)
qt.scenegraph.general: GL_VERSION:         OpenGL ES 2.0 (ANGLE 2.1.0.8613f4946861)
qt.scenegraph.general: GL_EXTENSIONS:      ...
qt.scenegraph.general: Max Texture Size:  16384
qt.scenegraph.general: Debug context:     false

The key points are that (1) we are using ANGLE, (2) it is using its D3D11 backend, and (3) the Qt Quick scenegraph is using the (somewhat ill-named) ‘windows’ render loop, meaning no dedicated render thread is present. The usage of D3D9 or D3D11 WARP can be recognized from the renderer string in the same way.

Then there is the Qt Quick scenegraph’s active render loop. This can be threaded, basic or windows. In recent Qt versions the scenegraph documentation describes all of these quite well, including the logic for choosing the loop to use. For experimenting or troubleshooting one can always override by setting the environment variable QSG_RENDER_LOOP to one of the three render loop names.

One common problem, mainly on embedded systems, is sometimes the bizarre speed up of animations. If you find that QML animations are running a lot faster than they should be and that the threaded render loop is in use, there is a good chance the issue is caused by the missing or incorrect vertical sync throttling. Solving this will be platform specific (e.g. in some cases one will need to force making a dedicated FBIO_WAITFORVSYNC ioctl, see QT_QPA_EGLFS_FORCEVSYNC), but armed with the logs from the application at least the root cause can be uncovered quickly and painlessly. (NB as a temporary workaround one can force the basic render loop via QSG_RENDER_LOOP=basic; this will provide more or less correct timing regardless of vsync at the expense of losing smooth animation)

Your other friends: qtdiag and contextinfo

Note that the scenegraph’s log only provides limited system information. It is great for strictly graphics and Quick-related issues, but when making bug reports, especially for Windows, it is strongly recommended to post the output of the qtdiag utility as well. This will provide a lot wider set of system information.

Additionally, the contextinfo example in examples/opengl/contextinfo is a good tool to troubleshoot basic OpenGL bringup problems. Launch it normally, click Create Context, see what happens: does the triangle show up? Does it rotate smoothly? Are the vendor and renderer strings as expected? Then set QT_OPENGL=angle and re-run. Then set QT_ANGLE_PLATFORM=d3d9 and re-run. Then set QT_OPENGL=software and, assuming the opengl32sw.dll shipped with the pre-built Qt packages is accessible, re-run. Or on Linux with Mesa, set LIBGL_ALWAYS_SOFTWARE=1 and see what happens. And so on.

Why am I not getting the right GL implementation?

Now, let’s say the logs reveal we are stuck with a software rasterizer and our beautiful Qt Quick UI runs sluggishly in a maximized full HD window. What can we do to figure out why?

On Windows, enable the logging category qt.qpa.gl. (e.g. set QT_LOGGING_RULES=qt.qpa.gl=true) This will tell why exactly the OpenGL implementation in question was chosen. The typical reasons are:

  • opengl32.dll not providing OpenGL 2.0
  • the card PCI ID or driver vendor/version matching a built-in GPU blacklist rule
  • having the QT_OPENGL or QT_ANGLE_PLATFORM environment variables set
  • having a hard-coded request like Qt::AA_UseSoftwareOpenGL via QCoreApplication::setAttribute().

On Linux, there are typically three reasons:

  • With Mesa, the environment variable LIBGL_ALWAYS_SOFTWARE forces a software rasterizer. Check if it is set, and if it is, investigate why.
  • In some environments no hardware acceleration is available. In some virtual machines for example, you will be stuck with a Mesa llvmpipe based rendering path.
  • Multiple graphics stacks. This can happen on any kind of Linux systems, but is more likely to happen on some embedded device oriented distros. Running ldd on the application and the relevant Qt libraries, and looking for libGLESv2.so may help to figure out what is going on.

A cautionary tale

The Raspberry Pi has at least three OpenGL solutions as of today: the Broadcom graphics stack, Mesa with llvmpipe (software rasterizer), and the upcoming Mesa with proper GPU acceleration (VC4) path.

Unfortunately this can lead to an unholy mess due to some distros prefering to ship Mesa llvmpipe in order to provide some sort of OpenGL under X11 (which the Broadcom stack does not support): Qt applications may unexpectedly pick up Mesa when they should use Broadcom (for Dispmanx without X11), while they may end up with disastrous performance under X11 due to overdriving the poor CPU with software GL rasterization.

While it is easy to blame Qt, JavaScript VMs, scripting languages, C++, and everything but the kitchen sink when one’s Quick application runs slowly, the solution to figure out the root cause is often even easier: QSG_INFO=1.

That’s all for now. Take care, and make sure to check the output from QSG_INFO=1 next time.

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in Declarative UI, Dev Loop, Embedded, Graphics, Performance, Qt Quick

4 comments

Richard says:

Is there any way to query the QtQuickEngine (or QWindow or QQuickWidget etc as appropriate) directly in-code to get this information?

A normal customer is not going to be able to either set these env. variables or to get the output and send it to us, we will just get a report saying “Your application is really slow” or a video of the screen showing animations running at an obviously wrong rate.

We can however ask them to click a button and send a log file.

Laszlo Agocs Laszlo Agocs says:

You can gather most of it, though I am not aware of a single Qt API that would just provide what you need. The GL strings can be queried via glGetString. The actual format the graphics context is using is available via QSurfaceFormat::format() once the QOpenGLContext had a successful create().

Accessing the context itself is simple: http://doc.qt.io/qt-5/qquickwindow.html#openglContext so you can right away dump the QSurfaceFormat in the logs your are sending.

The catch with #1 is that you have to have the scenegraph’s OpenGL context current on the thread where you are making the glGetString calls. So in practice you would either do a custom QQuickItem and do these queries in updatePaintNode(), or, perhaps easier, use http://doc.qt.io/qt-5/qquickwindow.html#scheduleRenderJob with e.g. NoStage to get a QRunnable invoked on the render thread with the context current.

Richard says:

Excellent, thanks.

However, as Qt 5.8 is adding support for a pure 2D backend in the near future, can you please add a direct API call to gather this kind of debugging information?

The ‘real’ OpenGL calls will obviously fail in that case…

Laszlo Agocs Laszlo Agocs says:

The extra considerations introduced in the mix by Qt 5.8’s multi-backend Qt Quick features were left out from the article.

Fortunately, the story there is really simple. It is possible to query the Qt Quick backend in use both from C++ and QML at runtime: https://doc-snapshots.qt.io/qt5-dev/qsgrendererinterface.html#graphicsApi and https://doc-snapshots.qt.io/qt5-dev/qml-qtquick-graphicsinfo.html#api-prop

Commenting closed.

Get started today with Qt Download now