With the official release of Qt 6.0 in December 2020 you might be eager to start porting your Qt 5.x applications, either to use the new features or to deploy your applications for specific setups you haven’t supported before – for example systems using 3D-APIs like Vulkan or Metal. Since Qt 5.0 and Qt 6.0 are roughly 8 years apart and major versions are an opportunity to clean up APIs and to get rid of outdated concepts, one might expect quite some issues when porting from Qt 5.x to Qt 6.0.
As a test case we ported a QML based application that is part of our “basysKom Industrial Showcase” to Qt 6.0. We will refer to it from here on simply as “HMI”. We will use that example for this article and concentrate on the issues we encountered. The actual issues mentioned might be different for your project – so it’s always a good idea to take a look at the official porting guide (https://doc.qt.io/qt-6/portingguide.html) before you get started. Another thing to check is if all the Qt add-ons your application depends on are part of Qt 6.0. Keep in mind that Qt 6.0 is kind of an interim release that is missing a lot of the add-on modules one might rely on. Qt 6.2 will be the first release containing all the add-ons that are supposed to be part of Qt 6. Have a look here for the full details.
Qt 6.0 platform requirements
As a first step we would recommend to check the (new) requirements Qt 6.0 has for your platform – https://doc.qt.io/qt-6/gettingstarted.html#platform-requirements.
In our case that was Linux. Our development machine was running Ubuntu 18.04, which isn’t compatible with Qt 6.0 since it needs at least version 20.04 of Ubuntu. The (sole?) reason is that Qt 6.0 requires a glibc version of 2.28 or higher but on Ubuntu 18.04 the highest available version is 2.27. Luckily we didn’t have to do a fresh installation; a distribution update was sufficient and didn’t break anything for us. But better be safe than sorry, so make sure to backup your vital data.
Check which modules your project uses
Some modules that were available in Qt 5.x have been removed in Qt 6.0. Some of these modules will come back with 6.1 or 6.2, others are gone for good. The Qt 6.0 documentation provides an overview about the modules that are not part of the release – https://doc.qt.io/qt-6/whatsnew60.html#removed-modules-in-qt-6-0.
The highlevel dependencies for our HMI are qml, quick, widgets, opcua, quickcontrols2 and svg. We were lucky here as all of these are part of Qt 6.0.
Porting QtGraphicalEffects
QtGraphicalEffects
. Luckily we only used it for a single animated DropShadow to make some icons glow in a specific color for half a second. We didn’t try to replicate that exact same look with sophisticated, handwritten shaders. Instead we used a color animation on these icons since we already had handwritten shaders in place for this effect. It doesn’t have the shiny glow anymore, but the basic colorization that informs the user about certain states is still there. Note that the future of the QtGraphicalEffects functionality isn’t entirely clear yet. The old (5.15) module will not make it into Qt 6. It will be split out into a Commercial / BSD licensed module outside of Qt (details here). Meanwhile the Qt Quick MultiEffect module by TheQtCompany has become available from the market place.
New Shader Management in QML
With the goal to support not only OpenGL rendering but also Vulkan, Metal and Direct3D 11, Qt did some changes to its internal graphic abstractions – you can find some information at https://www.qt.io/blog/graphics-in-qt-6.0-qrhi-qt-quick-qt-quick-3d. This became an issue when porting our HMI since using OpenGL shader strings in QML ShaderEffects (as vertex or fragment shaders) is not supported anymore. Instead, you have to write shaders in Vulkan-style GLSL and use the qsb tool (part of the ShaderTools module, which can be installed as part of the Qt6 SDK) to compile it into SPIR-V. Source code for other shading languages is then generated by translating the SPIR-V bytecode. That step needs to be done only once or when you make changes to your shader code. There are two ways to do this:
- You can do it manually via the command line. Only the resulting SPIR_V objects (not your initial Vulkan-style shader) need to be added to your project then, although it doesn’t hurt to at least add them to your repository in case you need to makes changes to them later on.
- If you’re using a cmake project, you can add specific commands to your CMakeLists.txt so cmake will automatically do the conversion and add the resulting objects to your project. For this to work your original Vulkan-style shaders are required to be part of your project of course.
Since our HMI is still a traditional qmake project we used the command line solution. The manual for the qsb tool can be found at https://doc.qt.io/qt-6/qtshadertools-qsb.html. The qsb tool is relatively easy to use – normally you call it with parameters that specify for which shader language versions the translations should be done, the output file and the input file. If you’re not doing anything special and require only the basic language versions that Qt internally uses, you can set the exact same parameters that Qt 6 applies internally – for example for QtQuick Image, Text and Rectangle:
qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -o <OUTPUTFILE.qsb> <INPUTFILE.frag>
This makes your shaders compatible with OpenGL ES 2.0 and higher, OpenGL 2.1 and higher, and OpenGL core profile contexts of version 3.2 and higher. If your shaders use functions that require higher versions, you need to adapt the parameters of your qsb command. And of course the target platform of your application needs to support the corresponding versions.
For example in HMI we’re using a fragment shader to colorize icons. We do this so we don’t have to provide different icon variations for all possible color states an icon might need – for example icons on a button that need to be displayed in a different color depending on the state of the button like default, pressed, checked, disabled, etc. That fragment shader was provided as ImageColoringShader.frag and looked like this:
varying lowp vec2 qt_TexCoord0;
uniform lowp vec4 color;
uniform lowp sampler2D sampler;
uniform lowp float qt_Opacity;
void main() {
gl_FragColor = texture2D(sampler, qt_TexCoord0) * color * qt_Opacity;
}
First we had to rewrite that shader in the Vulkan-Style GLSL format. With a bit of research and experimenting we ended up with the following, working solution:
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D sourceSampler;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec4 color;
} ubuf;
void main()
{
fragColor = texture(sourceSampler, qt_TexCoord0) * ubuf.color * ubuf.qt_Opacity;
}
All in and out variables need to have a location qualifier. Vulkan-style GLSL has no separate uniform variables. Instead, GLSL shaders use a uniform block. Qt requires a binding point of 0 and the layout qualifier std140. If you’re only providing one shader type (vertex or fragment) the first entries in that uniform block MUST be qt_Matrix and qt_Opacity – see https://doc-snapshots.qt.io/qt6-dev/qml-qtquick-shadereffect.html#having-one-shader-only. Samplers on the other hand are still declared as separate uniform variables in the shader code.
Now we converted that Vulkan-Style shader with qsb via the following command:
~/Qt/6.0.0/gcc_64/bin/qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -o ImageColoringShader.frag.qsb ImageColoringShader.frag
Actually we tried the conversion multiple times while we were writing the new shader because the conversion fails if the shader code can’t be compiled. This is a very good thing by the way, because in Qt 5 you had to run the actual application to test your shader and the feedback you got in case of an error was quite underwhelming.
We used the Qt resource system to add the ImageColoringShader.frag.qsb to our project and then could use it in a QML ShaderEffect instance as fragment shader:
ShaderEffect {
...
fragmentShader: "qrc:/shader/ImageColoringShader.frag.qsb"
}
That was all?
Indeed it was. Now the HMI was running on Qt 6.0 just like it did on Qt 5.15.
The only thing left was to get rid of some deprecation warnings emitted by the QML engine. Since Qt5.15 the old way to connect to signals in QML Connections is deprecated and throws corresponding warnings. In Qt 6.0 it’s still only a deprecation; but to prepare for future Qt 6.x versions we updated the old Connection objects to the new form.
Previously Connection objects were written like that:
Connections {
target: someId
onSignalName: {...}
}
With Qt Qt5.15 this syntax changed to a javascript function declaration:
Connections {
target: someId
function onSignalName() {...}
}
No dark magic here. Just don’t forget to add the signal’s parameters to your function parameters if necessary.
Conclusion
That’s all! Qt 6 is there, and porting from 5.15 to 6.0 was a breeze. Here is a screenshot of our ported application in its newly found Qt 6 glory.
We understand that our test-case is limited as we only had a small amount of add-ons we depend on. More complex applications will have to wait for 6.1 or 6.2 before considering a port. But the overall experience was promising.