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 188.8.131.5213f4946861) 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
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.
That’s all for now. Take care, and make sure to check the output from QSG_INFO=1 next time.