Vulkan Support in Qt 5.10 - Part 3

In the previous posts (part 1, part 2) we covered the introduction and basic Vulkan instance creation bits. It is time to show something on the screen!

QWindow or QVulkanWindow?

If everything goes well, the release of Qt 5.10 will come with at least 5 relevant examples. These are the following (with links to the doc snapshot pages), in increasing order of complexity:

The hellovulkancubes example, this time running on an NVIDIA Shield TV with Android 7.0

Checking the sources for these examples reveals one common aspect: they all use QVulkanWindow, the convenience QWindow subclass that manages the swapchain and window-specifics for you. While it will not always be suitable, QVulkanWindow can significantly decrease the time needed to get started with Vulkan rendering in Qt applications.

Now, what if one has to go the advanced way and needs full control over the swapchain and the window? That is perfectly doable as well, but getting started may be less obvious than the well-documented QVulkanWindow-based approach. Let's take a look.

Using a Plain QWindow + QVulkanInstance

There is currently no simple example for this since things tend to get fairly complicated quite quickly. The Qt sources do provide good references, though: besides the QVulkanWindow sources, there is also a manual test that demonstrates creating a Vulkan-enabled QWindow.

Looking at these revals the main rules for Vulkan-enabled QWindow subclasses:

  • There is a new surface type: VulkanSurface. Any Vulkan-based QWindow must call setSurfaceType(VulkanSurface).
  • Such windows must be associated with a QVulkanInstance. This can be achieved with the previously introduced setVulkanInstance() function.
  • Maintaining the swapchain is left completely to the application. However, a well-behaving implementation is expected to call presentQueued() on the QVulkanInstance right after queuing a present operation (vkQueuePresentKHR).
  • Getting a VkSurfaceKHR must happen through surfaceForWindow().
  • To query if a queue family with in a physical device supports presenting to the window, supportsPresent() can be used, if desired. (like with surfaces, this is very handy since there is no need to deal with vkGetPhysicalDeviceWin32PresentationSupportKHR and friends directly).
  • It is highly likely that any Vulkan-enabled window subclass will need to handle QPlatformSurfaceEvent, QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed in particular. This is because the swapchain must be released before the surface, and with QWindow the surface goes away when the underlying native window is destroyed. This can happen unexpectedly early, depending on how the application is structured, so in order to get a chance to destroy the swapchain at the right time, listening to SurfaceAboutToBeDestroyed can become essential.
  • Understanding exposeEvent() is pretty important as well. While the exact semantics are platform specific, the correct behavior in an exposeEvent() implementation is not: simply check the status via isExposed(), and, if different than before, start or stop the rendering loop. This can, but on most platforms does not have to, include releasing the graphics resources.
  • Similarly, any real graphics initialization has to be tied to the first expose event. Do not kick off such things in the constructor of the QWindow subclass: it may not have a QVulkanInstance associated at that point, and there will definitely not be an underlying native window present at that stage.
  • To implement continous updates to the rendering (which may, depending on your logic, be locked to vsync of course), one of the simplest options is to trigger requestUpdate() on each frame, and then handle QEvent::UpdateRequest in a reimplementation of event(). Note however that on most platforms this is essentially a 5 ms timer, with no actual windowing system backing. Applications are also free to implement whatever update logic they like.

Core API Function Wrappers

What about accessing the Vulkan API? The options are well documented for QVulkanInstance. For most Qt-based applications the expectation is that the core Vulkan 1.0 API will be accessed through the wrapper objects returned from functions() and deviceFunctions(). When it comes to extensions, for instance in order to set up the swapchain when managing it manually, use getInstanceProcAddr().

This is the approach all examples and tests are using as well. This is not mandatory, the option of throwing in a LIBS+=-lvulkan, or using some other wrangler library is always there. Check also the Using C++ Bindings for Vulkan section in the QVulkanInstance docs.

That's all for now, see you in part 4!

Blog Topics: