In the previous instalment we looked at the background for Qt 5.10's upcoming Vulkan support. Let's now start digging out into the details.

Obtaining a Vulkan-Enabled Qt Build

When building Qt from sources on Windows, Linux or Android, Vulkan support is enabled automatically whenever a Vulkan header is detected in the build environment. Windows is handled specially in the sense that the environment variable VULKAN_SDK - set by the LunarG Vulkan SDK - is picked up automatically.

Check the output of configure (also available afterwards in config.summary):

Qt Gui:

...

Vulkan ................................. yes

...

If it says no, go to qtbase/config.tests/qpa/vulkan, run make, and see why it did not compile.

As mentioned in part 1, neither the QtGui library nor the platform plugins link directly to libvulkan or similar. Same applies to Qt applications by default. This comes very handy here: a Vulkan-enabled Qt build is perfectly fine for deployment also to systems without any Vulkan libraries. No headache with missing DLLs and such. Naturally, once it turns out Vulkan is not available at runtime, QVulkanInstance::create() will fail and will return false always. It must also be noted that the applications themselves can choose to link to a Vulkan (loader) library, if they have a reason to do so: all it takes is adding LIBS+=-lvulkan or similar to the .pro file.

Getting a Vulkan Instance

In Vulkan all per-application state is stored in a VkInstance object, see the the specification for a detailed overview. In Qt, Vulkan instances are represented by QVulkanInstance. This is backed by a QPlatformVulkanInstance following the usual QPA patterns. The platform plugins, at least the ones that are interested in providing Vulkan support, are expected to provide an implementation for it under the hood. As described earlier, this currently covers windows, xcb and android.

Following the familiar pattern from QWindow and the QOpenGL* classes, QVulkanInstance performs no initialization until create() is called. The loading of the Vulkan library (or the loader library which in turn routes to a vendor implementation) happens only at this point. (with a few exceptions, see below)

The resulting VkInstance can be retrieved via vkInstance().

Quite unsurprisingly, QVulkanInstance allows specifying the usual instance configuration options, like the desired API version, and, most importantly, the list of layers and extensions to enable.

While the Qt APIs allow including unsupported layers and extensions too - since it filters them out automatically - it may still be necessary in some cases to examine the names and versions of all supported layers and extensions. This can be done at any time - even before calling create() - via supportedExtensions() and supportedLayers(). These will naturally trigger an early loading of the Vulkan implementation when needed.

It is worth knowing that the surface-related extensions that are required for basic operation, such as VK_KHR_surface or VK_KHR_win32_surface, are automatically added to the list by Qt, and applications do not have to worry about these.

Typical main() Patterns

In the end the main() function for a Qt application with a Vulkan-capable window (or a Vulkan-capable window embedded into a QWidget hierarchy) will typically look like the following:

int main(int argc, char **argv) { QGuiApplication app(argc, argv); // or QApplication when widgets are involved const bool enableLogsAndValidation = ... QVulkanInstance inst; if (enableLogsAndValidation) { QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true")); #ifndef Q_OS_ANDROID inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); #else // see Android-specifics at https://developer.android.com/ndk/guides/graphics/validation-layer.html inst.setLayers(QByteArrayList() << "VK_LAYER_GOOGLE_threading" << "VK_LAYER_LUNARG_parameter_validation" << "VK_LAYER_LUNARG_object_tracker" << "VK_LAYER_LUNARG_core_validation" << "VK_LAYER_LUNARG_image" << "VK_LAYER_LUNARG_swapchain" << "VK_LAYER_GOOGLE_unique_objects"); #endif } if (!inst.create()) qFatal("Failed to create Vulkan instance: %d", inst.errorCode()); MyVulkanWindow w; w.setVulkanInstance(&inst); w.resize(1024, 768); w.show(); return app.exec(); }

In most cases there will be a single QVulkanInstance. This can live on the stack, but has to be ready before creating the QWindow or QVulkanWindow-derived window objects since they will need to be associated with a QVulkanInstance. (more on this and other window-related topics in part 3)

The logging category qt.vulkan can be very helpful for troubleshooting. When enabled, both QVulkanInstance and, if used, QVulkanWindow will print a number of interesting things on the debug output, during initialization in particular. The hard-coded setFilerRules() call in the code snippet above is not necessarily the best approach always, but works well for platforms where environment variables (QT_LOGGING_RULES) are problematic. On Windows and Linux it is better to control this via the environment or configuration files.

When it comes to output from Vulkan and, first and foremost, the validation layers, QVulkanInstance offers the convenience of automatically redirecting these messages to qDebug. By default VK_EXT_debug_report gets enabled and redirection is active. If this is not desired, set the corresponding flag before calling create().

Working with External Graphics Engines

During the lifetime of the Qt 5.x series, there has been a growing focus on making Qt Quick and the underlying OpenGL enablers more interoperable with foreign engines. This led to productizing QQuickRenderControl, the enhancements to QOpenGLContext for adopting existing native contexts, and similar improvements all over the stack.

In the same spirit QVulkanInstance allows adopting an existing VkInstance. All this takes is calling setVkInstance() before create(). This way every aspect of the VkInstance creation is up to the application or some other framework, and QVulkanInstance will merely wrap the provided VkInstance object instead of constructing a new one from scratch.

That's all for now, stay tuned for part 3!