QVulkanWindow Class

The QVulkanWindow class is a convenience subclass of QWindow to perform Vulkan rendering. More...

Header: #include <QVulkanWindow>
qmake: QT += gui
Since: Qt 5.10
Inherits: QWindow

This class was introduced in Qt 5.10.

Public Types

enum Flag { PersistentResources }
typedef QueueCreateInfoModifier

Static Public Members

Detailed Description

QVulkanWindow is a Vulkan-capable QWindow that manages a Vulkan device, a graphics queue, a command pool and buffer, a depth-stencil image and a double-buffered FIFO swapchain, while taking care of correct behavior when it comes to events like resize, special situations like not having a device queue supporting both graphics and presentation, device lost scenarios, and additional functionality like reading the rendered content back. Conceptually it is the counterpart of QOpenGLWindow in the Vulkan world.

Note: QVulkanWindow does not always eliminate the need to implement a fully custom QWindow subclass as it will not necessarily be sufficient in advanced use cases.

QVulkanWindow can be embedded into QWidget-based user interfaces via QWidget::createWindowContainer(). This approach has a number of limitations, however. Make sure to study the documentation first.

A typical application using QVulkanWindow may look like the following:

   class VulkanRenderer : public QVulkanWindowRenderer
   {
   public:
       VulkanRenderer(QVulkanWindow *w) : m_window(w) { }

       void initResources() override
       {
           m_devFuncs = m_window->vulkanInstance()->deviceFunctions(m_window->device());
           ...
       }
       void initSwapChainResources() override { ... }
       void releaseSwapChainResources() override { ... }
       void releaseResources() override { ... }

       void startNextFrame() override
       {
           VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
           ...
           m_devFuncs->vkCmdBeginRenderPass(...);
           ...
           m_window->frameReady();
       }

   private:
       QVulkanWindow *m_window;
       QVulkanDeviceFunctions *m_devFuncs;
   };

   class VulkanWindow : public QVulkanWindow
   {
   public:
       QVulkanWindowRenderer *createRenderer() override {
           return new VulkanRenderer(this);
       }
   };

   int main(int argc, char *argv[])
   {
       QGuiApplication app(argc, argv);

       QVulkanInstance inst;
       // enable the standard validation layers, when available
       inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
       if (!inst.create())
           qFatal("Failed to create Vulkan instance: %d", inst.errorCode());

       VulkanWindow w;
       w.setVulkanInstance(&inst);
       w.showMaximized();

       return app.exec();
   }

As it can be seen in the example, the main patterns in QVulkanWindow usage are:

  • The QVulkanInstance is associated via QWindow::setVulkanInstance(). It is then retrievable via QWindow::vulkanInstance() from everywhere, on any thread.
  • Similarly to QVulkanInstance, device extensions can be queried via supportedDeviceExtensions() before the actual initialization. Requesting an extension to be enabled is done via setDeviceExtensions(). Such calls must be made before the window becomes visible, that is, before calling show() or similar functions. Unsupported extension requests are gracefully ignored.
  • The renderer is implemented in a QVulkanWindowRenderer subclass, an instance of which is created in the createRenderer() factory function.
  • The core Vulkan commands are exposed via the QVulkanFunctions object, retrievable by calling QVulkanInstance::functions(). Device level functions are available after creating a VkDevice by calling QVulkanInstance::deviceFunctions().
  • The building of the draw calls for the next frame happens in QVulkanWindowRenderer::startNextFrame(). The implementation is expected to add commands to the command buffer returned from currentCommandBuffer(). Returning from the function does not indicate that the commands are ready for submission. Rather, an explicit call to frameReady() is required. This allows asynchronous generation of commands, possibly on multiple threads. Simple implementations will simply call frameReady() at the end of their QVulkanWindowRenderer::startNextFrame().
  • The basic Vulkan resources (physical device, graphics queue, a command pool, the window's main command buffer, image formats, etc.) are exposed on the QVulkanWindow via lightweight getter functions. Some of these are for convenience only, and applications are always free to query, create and manage additional resources directly via the Vulkan API.
  • The renderer lives in the gui/main thread, like the window itself. This thread is then throttled to the presentation rate, similarly to how OpenGL with a swap interval of 1 would behave. However, the renderer implementation is free to utilize multiple threads in any way it sees fit. The accessors like vulkanInstance(), currentCommandBuffer(), etc. can be called from any thread. The submission of the main command buffer, the queueing of present, and the building of the next frame do not start until frameReady() is invoked on the gui/main thread.
  • When the window is made visible, the content is updated automatically. Further updates can be requested by calling QWindow::requestUpdate(). To render continuously, call requestUpdate() after frameReady().

For troubleshooting, enable the logging category qt.vulkan. Critical errors are printed via qWarning() automatically.

Coordinate system differences between OpenGL and Vulkan

There are two notable differences to be aware of: First, with Vulkan Y points down the screen in clip space, while OpenGL uses an upwards pointing Y axis. Second, the standard OpenGL projection matrix assume a near and far plane values of -1 and 1, while Vulkan prefers 0 and 1.

In order to help applications migrate from OpenGL-based code without having to flip Y coordinates in the vertex data, and to allow using QMatrix4x4 functions like QMatrix4x4::perspective() while keeping the Vulkan viewport's minDepth and maxDepth set to 0 and 1, QVulkanWindow provides a correction matrix retrievable by calling clipCorrectionMatrix().

Multisampling

While disabled by default, multisample antialiasing is fully supported by QVulkanWindow. Additional color buffers and resolving into the swapchain's non-multisample buffers are all managed automatically.

To query the supported sample counts, call supportedSampleCounts(). When the returned set contains 4, 8, ..., passing one of those values to setSampleCount() requests multisample rendering.

Note: unlike QSurfaceFormat::setSamples(), the list of supported sample counts are exposed to the applications in advance and there is no automatic falling back to lower sample counts in setSampleCount(). If the requested value is not supported, a warning is shown and a no multisampling will be used.

Reading images back

When supportsGrab() returns true, QVulkanWindow can perform readbacks from the color buffer into a QImage. grab() is a slow and inefficient operation, so frequent usage should be avoided. It is nonetheless valuable since it allows applications to take screenshots, or tools and tests to process and verify the output of the GPU rendering.

sRGB support

While many applications will be fine with the default behavior of QVulkanWindow when it comes to swapchain image formats, setPreferredColorFormats() allows requesting a pre-defined format. This is useful most notably when working in the sRGB color space. Passing a format like VK_FORMAT_B8G8R8A8_SRGB results in choosing an sRGB format, when available.

Validation layers

During application development it can be extremely valuable to have the Vulkan validation layers enabled. As shown in the example code above, calling QVulkanInstance::setLayers() on the QVulkanInstance before QVulkanInstance::create() enables validation, assuming the Vulkan driver stack in the system contains the necessary layers.

Note: Be aware of platform-specific differences. On desktop platforms installing the Vulkan SDK is typically sufficient. However, Android for example requires deploying additional shared libraries together with the application, and also mandates a different list of validation layer names. See the Android Vulkan development pages for more information.

Note: QVulkanWindow does not expose device layers since this functionality has been deprecated since version 1.0.13 of the Vulkan API.

Layers, device features, and extensions

To enable instance layers, call QVulkanInstance::setLayers() before creating the QVulkanInstance. To query what instance layer are available, call QVulkanInstance::supportedLayers().

To enable device extensions, call setDeviceExtensions() early on when setting up the QVulkanWindow. To query what device extensions are available, call supportedDeviceExtensions().

Specifying an unsupported layer or extension is handled gracefully: this will not fail instance or device creation, but the layer or extension request is rather ignored.

When it comes to device features, QVulkanWindow enables all Vulkan 1.0 features that are reported as supported from vkGetPhysicalDeviceFeatures().

See also QVulkanInstance and QWindow.

Member Type Documentation

enum QVulkanWindow::Flag

This enum describes the flags that can be passed to setFlags().

ConstantValueDescription
QVulkanWindow::PersistentResources0x01Ensures no graphics resources are released when the window becomes unexposed. The default behavior is to release everything, and reinitialize later when becoming visible again.

typedef QVulkanWindow::QueueCreateInfoModifier

A function function that is called during graphics initialization to add additAional queues that should be created.

Set if the renderer needs additional queues besides the default graphics queue (e.g. a transfer queue). The provided queue family properties can be used to select the indices for the additional queues. The renderer can subsequently request the actual queue in initResources().

Note when requesting additional graphics queues: Qt itself always requests a graphics queue, you'll need to search queueCreateInfo for the appropriate entry and manipulate it to obtain the additional queue.

See also setQueueCreateInfoModifier().

Member Variable Documentation

const int QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT

This variable holds a constant value that is always equal to or greater than the maximum value of concurrentFrameCount().