Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
With Qt 6 well on its way, it's about time we go over some of the internal changes and optimizations made to Qt 3D for the upcoming release.
In a separate article, my colleague Mike Krus has already highlighted the API changes we've made in Qt 3D for Qt 6. This post will dive into the internal changes.
Back in 5.14, we got rid of the Aspect Thread. Yet we still had a Render Thread. On paper, having a dedicated thread would allow you to send drawing commands to the GPU while preparing commands for the next frame. This could potentially help with maintaining a high frame rate, if command submission took a long time. In practice, this worked only in the case that Qt 3D was used as standalone (without QtQuick).
The Qt Quick renderer has blocking synching points. Synching Qt Quick and Qt 3D content forces us to wait for all drawing commands to have been performed before proceeding to the next frame. Not only does this make having a RenderThread pointless, but it also results in separate code paths to handle Qt 3D driven by QtQuick.
Moreover, having an extra thread also adds a cost with general synchronization, scheduling, and memory allocations. In addition, the more threads you have, the more things get complex and hard to troubleshoot. For these reasons, we decided that whatever little speed gain we obtained from having a Render Thread didn't justify keeping it.
In essence, this means that Qt 3D for Qt 6 only uses the main thread and a thread pool to spawn jobs in every frame. The rendering either takes place in the main thread or in the QtQuick SceneGraph thread, if using QtQuick.
The backend of Qt 3D made heavy use of the Qt containers, like QVector and QHash. Much to our surprise, it appeared that QVector was adding a little bit of overhead everywhere we used it (mostly because of copy on write checks). Lots of something small can add up to something big. We were actually paying a high cost simply by using QVector.
Therefore, we've switched to using std::vectors in most of the Qt 3D backend. This work started with 5.15 and was pursued for Qt 6, as well. The API is not as nice as that of Qt's vector but we have seen noticeable improvements in critical code paths on complex scenes. In contrast with the backend, the public API still relies on QVector (well QList in Qt6).
The Qt 5 architecture is tightly tied to OpenGL. Hence, our efforts with Qt 3D were primarily focused on providing an efficient OpenGL renderer. Towards the end of the Qt 5 series, The Qt Company started working on abstracting access to various graphics APIs with RHI (Rendering Hardware Abstraction).
In Qt 6, the tight coupling with OpenGL in the code will be removed in favor of calls made through the RHI abstraction layer. This signifies that Qt and, more specifically, Qt Quick will be able to work with different rendering backends (DirectX, Vulkan, OpenGL, Metal), depending on the target, the platform, and what's available.
Over the past couple of Qt releases, under the hood Qt 3D has slowly been reworked. The aim being to decouple the rendering from the processing code. In that sense, a plugin mechanism was introduced to load the renderer as a plugin. This has been finalized for Qt 6 and the existing OpenGL renderer is now a dedicated plugin. In parallel, we've started working on a new RHI-based render plugin.
The plan for Qt 3D for Qt 6 is to have the RHI-based renderer become the default. However, you will still be able to use the OpenGL render plugin if you wish to. A lot of work has been put into the OpenGL renderer and it is currently more mature and feature complete that what can be done with the RHI plugin. In reality, for most use cases, the RHI renderer will probably be enough.
API-wise this has little impact on code. The only changes required to have Qt 3D work with the RHI backend is to define a RHI compatible QTechnique on custom QMaterial classes. The default materials provided in the Qt 3D Extra modules have been updated.
Of course, this plugin mechanism will also allow you to have dedicated renderers for Metal or Vulkan, if the RHI plugin is deemed insufficient.
By default, Qt 3D will try to load the RHI plugin and let RHI decide which backend to use. However, that behavior can be overridden by using the following environment variables and values:
Note: When using Scene3D, the OpenGL plugin should only be selected if you're forcing RHI to use its OpenGL backend.
The development of RHI is driven with the use cases of Qt Quick and Qt Quick 3D in mind. This implies that not all the features required for more advanced use cases are available.
When it comes to Qt 3D, this means that what the FrameGraph API allows you to describe might not be translatable to existing RHI commands.
One of the interesting features of modern graphics APIs, such as Vulkan, is the ability to use several threads to build the list of rendering commands. This maps perfectly to Qt 3D's multithreaded architecture. Unfortunately, RHI does not currently support multithreading. Due to this limitation, while Qt 3D still leverages multiple threads to prepare commands, it needs to serially generate the RHI commands from a single thread. As a result, things are not likely to be any faster than what was achieved with the OpenGL renderer when it comes to command generation and submission. Arguably, there might still be a gain if the driver works better with whichever RHI backend gets used.
The OpenGL backend maps only to the ES 2 / GL 2 feature set.
Another hurdle with the current implementation is it limits each render target to a single clear call. For instance, you cannot clear the depth buffer after having drawn something. Given that, the workaround is generating more render targets and attachments than would otherwise be required and manually blitting between these.
As can be seen, a fair amount of work has been ongoing these past few months. From new performance optimizations in the backend to new decoupled rendering plugins, Qt 3D is ready for the next major Qt release. While most of these changes will be transparent for Qt 3D users, they still represent nice improvements.
KDAB provides a number of services around Qt 3D, including mentoring your team and embedding Qt 3D code into your application, among others. You can find out more about these services here.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
4 Comments
1 - Dec - 2020
Philip S
Will there be examples / documentation on how to port GLSL shader code to RHI and use RHI?
1 - Dec - 2020
Paul Lemire
There's actually not that much involved to add RHI support to a Qt 3D material.
The first thing to do is to define a new Technique with a graphicsAPIFilter set to use the RHI api version 1.0. When it comes to the shaders they are expected to be provided as GLSL 450 code. The main hassle with that is that we can't have freestanding uniforms but only uniform buffer objects (UBO). One thing to be noted if that UBO bindings 0 and 1 are reserved for Qt 3D's per RenderView uniforms (viewMatrix, projectionMatrix) and per Command uniforms (modelMatrix, mvp). Therefore, user defined UBOs have to start at binding 2 (or use the value auto to let Qt3D take care of it).
It might be a bit of a rough read but here is an actual Qt 3D example being ported to RHI https://codereview.qt-project.org/c/qt/qt3d/+/323755
I'll write proper documentation for Qt 3D this week.
2 - Dec - 2020
Philip S
Super! I look forward to seeing it.
26 - Dec - 2020
Jean-Baptiste T
Why Qt 3D is now an additional library? this is not practical. Will it stay that way?