Now that the first beta version of Qt 5.14 is approaching, it's time to talk about one of the most important new features. It's difficult to cover all the details regarding the graphics stack improvements and the path to Qt 6 in one article, so Parts 1 and 2 will cover the background and take a closer look at what will ship with version 5.14. Later, in another series of articles, we will look at the technical details and future directions.
The 5.14 new features page mentions: Added the first preview of the graphics api-independent scene renderer as an optional feature. This allows you to run appropriate Qt Quick applications on top of Vulkan, Metal, or Direct3D 11 instead of OpenGL .
What does this mean in practice?
One of the main goals in Qt 6 is to move away from the direct use of OpenGL in most places in Qt and, with appropriate abstractions, to allow for a wider variety of graphics APIs such as Vulkan, Metal, and Direct3D. Naturally, OpenGL (and OpenGL ES) remains an option. The main motivation for this is not to improve performance, but to provide Qt Everywhere in the future, even on platforms and devices where OpenGL is either unavailable or undesirable. At the same time, the ability to use modern low-level APIs can also open up opportunities when it comes to performance improvements (e.g. lower CPU usage due to lower API overhead) and new ways to work in rendering engines for Qt Quick and other modules like the recently announced Qt Quick 3D.
Also, the ability to render user interfaces using the underlying framework of the most supported graphics API is great news for applications that do their own native 2D or 3D graphics rendering when using Qt to render the user interface. In such a case, Qt is often not in the driver's seat when it comes to deciding which graphics API to use. For example, if a desktop application on macOS wants to use Metal for its own 3D content, relying on Qt Quick to render 2D UI elements, then it is very helpful if Qt Quick also renders through Metal. This will sound familiar to those who have followed the development of graphics in Qt 5.x. Conceptually, this is no different from when support for working in OpenGL core profile contexts was introduced in the Qt Quick renderer. Qt Quick itself doesn't need this, but in order to allow integration of external rendering code related to core profile functions, Qt Quick needs to be aware of and able to handle it. So in that sense the story is a natural extension of what was in Qt 5 and is now being extended to cover non-OpenGL graphics APIs.
All of the above can raise two obvious questions:
• How does this apply to Qt 5.x? Isn't that the whole Qt 6 stuff?
• Why not just use
<name of some graphics API translation solution> (
So what's in Qt 5.14?
Rolling out a complete overhaul of the graphics bits in all (or at least most) places in Qt is indeed meant for Qt 6. However, stopping work on 5.x and trying to invent, develop and refactor everything in one go, hoping that everything will be fine is not very attractive in practice. As the Qt developers have said (and continue to say), the first iteration of any API is likely to be suboptimal. So instead, the Qt developers use a developed parallel approach , focusing on one specific user interface technology in Qt, Qt Quick.
Qt 5.14 is expected to ship with a preview of Qt Quick's new rendering path. By default this is inactive and so there is no visible change for applications internally they go through the same direct OpenGL based code path as in earlier versions. However, those who want to try out the new approach can subscribe: either by setting an environment variable or by requesting it via the C++ API in main().
Looking at a snapshot of the Qt 5.14 documentation, we find the following:
Not all applications will work out of the box when running with QSG_RHI installed. Custom QQuickItem implementations with scene graph nodes making direct OpenGL calls or containing GLSL shader code in custom materials will not work when RHI-based rendering is enabled. The same applies to ShaderEffect elements with GLSL source code. Solutions for creating custom materials and effects in a modern way are mostly already there, but they require appropriate application migration. Early users can experiment with this as early as 5.14 and 5.15, but widespread adoption and migration is naturally not expected until Qt 6.0. On the other hand, many existing QML applications are likely to work even if the underlying rendering engine is passed through a completely different API, such as Vulkan or Metal.
Why not translate layer XYZ?
First of all, it is important to note that the ability to use shader translation APIs and layers such as MoltenVK, MoltenGL, ANGLE, Zink and others still exists even if they are not always available out of the box. For example, MoltenVK allows you to also display Qt Quick user interfaces via Vulkan on macOS. If a Qt Quick app only wants to use Vulkan and still wants to run on macOS, MoltenVK is an option (as long as a properly configured build of Qt is deployed, MoltenVK is available on users' systems, etc.).
Making such a translation layer a required dependency and then enabling and deploying it with Qt is another story altogether.
Needless to say, changing Qt Everywhere to Qt Only Where External Dependencies Allow (Qt Only Where External Dependencies Allow) is not ideal.
Qt targets more platforms and environments than is commonly thought. It can only rely on mandatory third-party dependencies that compile and work in "exotic" environments and are easy to adapt in case of special needs (e.g. INTEGRITY, QNX, specialized embedded Linux environments, systems with broken or under development graphics stacks, the rare need to adapt to private parts, or sometimes the need to interact, perhaps, depending on the vendor, in a non-standard way with various graphics or composition APIs that you thought were long dead, etc. All this requires flexibility and customization at every level the Qt rendering stack).
Doing translation between shading languages (or intermediate formats) at runtime isn't exactly ideal. The shader pipeline in Qt 6 is expected to focus more on working offline or, at the latest, during application build. When the translation layer is in the middle, hiding the reality (what API, what language is actually being used), these efforts quickly become useless in practice, since there is no way to prepare or inject shaders, or bytecode for the real underlying API.
Some of the options mentioned are non-negotiable due to the current state of reality: OpenGL (ES) is and will be the main workhorse for the foreseeable future on many devices. So direct use of a single API could mean that the API is OpenGL or a translation layer that can target OpenGL (and is also quite efficient for low end devices).
Cases where Qt's rendering engines are augmented with custom, native rendering code tend to work best when both parties use the same APIs directly. Using a translation layer is not always blocking in this regard, as long as they allow access to the underlying native objects (imagine, for example, how Direct3D - Qt Quick interaction is made possible by EGL extensions when running Qt 5 on top of ANGLE on Windows) and when that so, this may create another problem that you have to face.
There are licensing implications and issues. Recall that Apache 2.0 is not GPLv2 compliant. Relying on commercial solutions is out of the question anyway.
Based on experience (some with ANGLE and some with MoltenVK), the use of such solutions has never been as easy as initially desired. At some point, the effort required to keep all the options up and running can become too much, in which case that effort is better spent doing things "right" directly with the native API. The intrinsically platform dependent nature of some of these translation solutions is also less than ideal, if one has to use a different one for each Qt target platform then things quickly become untenable.
Thus, instead of relying on low-level API translators, Qt defines its own high-level abstraction for 3D graphics (for internal use, currently not available to applications). This is then supported by API-specific backend implementations, a pattern familiar from many components in Qt. In some cases the backend is native in nature (Metal, D3D), while in some others one backend targets one API but multiple platforms (Vulkan, OpenGL). This is complemented by a new shader control pipeline based on several third party projects such as glslang and SPIRV-Cross. More details about all this will be discussed in the following articles.
Does it really work?
Let's look at an example, namely the well-known Qt5 Cinematic Experience demo application from QUIt Coding. We're using a slightly modified version that updates a few ShaderEffect elements to work with both Qt Quick setpoint render paths.
When the application starts normally with QSG_INFO=1 set, we get:
As the logs printed in the debug results show, this works in OpenGL on a Linux desktop:
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: opengl texture atlas dimensions: 2048x1024 qt.scenegraph.general: GL_VENDOR: X.Org qt.scenegraph.general: GL_RENDERER: AMD Radeon (TM) R9 M360 (VERDE, DRM 3.23.0, 4.15.0-62-generic, LLVM 8.0.1) qt.scenegraph.general: GL_VERSION: 4.5 (Compatibility Profile) Mesa 19.2.0-devel (git-08f1cef 2019-07-25 bionic-oibaf-ppa) qt.scenegraph.general: GL_EXTENSIONS: ... qt.scenegraph.general: Max Texture Size: 16384 qt.scenegraph.general: Debug context: false
How does this change if we set QSG_RHI=1?
qt.scenegraph.general: Using QRhi with backend OpenGL graphics API debug/validation layers: 0 QRhi profiling and debug markers: 0 qt.scenegraph.general: threaded render loop qt.scenegraph.general: Using sg animation driver qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms qt.rhi.general: Created OpenGL context QSurfaceFormat(version 4.5, options QFlags<QSurfaceFormat::FormatOption>(DeprecatedFunctions), depthBufferSize 24, redBufferSize 8, greenBufferSize 8, blueBufferSize 8, alphaBufferSize 0, stencilBufferSize 8, samples -1, swapBehavior QSurfaceFormat::DoubleBuffer, swapInterval 1, colorSpace QSurfaceFormat::DefaultColorSpace, profile QSurfaceFormat::CompatibilityProfile) qt.rhi.general: OpenGL VENDOR: X.Org RENDERER: AMD Radeon (TM) R9 M360 (VERDE, DRM 3.23.0, 4.15.0-62-generic, LLVM 8.0.1) VERSION: 4.5 (Compatibility Profile) Mesa 19.2.0-devel (git-08f1cef 2019-07-25 bionic-oibaf-ppa) qt.scenegraph.general: MSAA sample count for the swapchain is 1. Alpha channel requested = no. qt.scenegraph.general: rhi texture atlas dimensions: 2048x1024
Not much different at first glance. It still seems to be happening via OpenGL. However, internally there is no direct use of OpenGL and no more GLSL shader sources in the Qt Quick scene. Instead, rendering goes through QRhi, Qt's hardware rendering interface (a private API in the QtGui module at the moment).
Now let's set QSG_RHI_BACKEND=vulkan :
qt.scenegraph.general: Using QRhi with backend Vulkan graphics API debug/validation layers: 0 QRhi profiling and debug markers: 0 qt.scenegraph.general: threaded render loop qt.scenegraph.general: Using sg animation driver qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms WARNING: radv is not a conformant vulkan implementation, testing use only. qt.rhi.general: Physical device 0: 'AMD RADV CAPE VERDE (LLVM 8.0.1)' 19.1.99 qt.rhi.general: using this physical device qt.rhi.general: queue family 0: flags=0xf count=1 qt.rhi.general: queue family 1: flags=0xe count=2 qt.rhi.general: 55 device extensions available qt.scenegraph.general: MSAA sample count for the swapchain is 1. Alpha channel requested = no. qt.scenegraph.general: rhi texture atlas dimensions: 2048x1024 qt.rhi.general: Creating new swapchain of 3 buffers, size 1280x720, presentation mode 2
Apparently now rendering happens through Vulkan. However, even the most exotic features of Qt Quick, such as text rendering in a distance field, shader effects, and particles, are present as expected.
Running the application in RenderDoc and capturing a frame gives the following. Qt Quick does build the Vulkan pipeline state objects and command buffers, and the shader code is provided as SPIR-V bytecode.
The second part of this series will look at what Qt 5.14 has to offer for macOS and Windows. After that, let's move on to how it all works under the hood and what the implications will be for applications requiring custom materials and effects.