Qt with External Vulkan Renderer
Qt has had support for Vulkan integration since version 5.10. Part of this support comes from the QVulkanWindow class. However, this is a very heavy-handed integration. The QVulkanWindow class manages quite a bit of the Vulkan stack, including VkDevice, swapchains, Queues, etc. In order to mange these aspects of Vulkan yourself, Qt does give the capability to use a completely external renderer. Much of this is laid out in this article from Qt, but I still had more trouble than I care to admit putting all the pieces together. In this article I will be outlining how I got Qt working with an external renderer, if only so that I don’t forget.
The crux of what we need is a VkSurfaceKHR from a QWidget so that we have a surface to render to. We get this surface via QVulkanInstance::surfaceForWindow(QWindow*) which returns a VkSurfaceKHR. The QWindow used in this function needs to have two functions called on it before getting a surface: setVulkanInstance(QVulkanInstance*) and setSurfaceType(QSurface::VulkanSurface). Firstly, Vulkan needs a VkInstance to create a surface. Secondly, the default surface type for a QWindow is not VulkanSurface.
A QWindow is not a typical widget to find in a UI form. Fortunately, there is a mechanism to get a QWindow from a generic QWidget within a UI form. The function QWidget::createWindowContainer(QWindow*, QWidget*) takes in a QWindow as an output parameter and a QWidget as a parent. A QWidget wrapping the newly created QWindow is returned from createWindowContainer.
The created QWindow and QWidget are contained within the parent parameter, but they are not identical to the parent container. This is an important distinction because the created QWidget needs to be resized independently. This means overriding resizeEvent(QResizeEvent* event) for the main class wrapping the UI form to resize the wrapper based on the parent size.
The last small piece of the puzzle is in QVulkanInstance creation. By default QVulkanInstance.create() will create a new VkInstance to use. This can be worked around by calling QVulkanInstance.setVkInstance(VkInstance). Using this function allows us to tell Qt to use the same VkInstance as the Vulkan renderer.
To summarize, here is a code/pseudo-code overview of all the steps:
QVulkanInstance instance; instance.setVkInstance(vulkanInstanceFromRenderer); instance.create(); QWindow* window = new QWindow; window->setSurfaceType(QSurface::VulkanSurface); window->setVulkanInstance(instance); QWidget* wrapper = QWidget::createWindowContainer(window, parentWidget); VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window); void UIClass::resizeEvent(QResizeEvent* event) { window->setMinimumSize(ui->widget->size()); }