Vulkan integration between multiple libraries (e.g. Qt + OgreNext)


So I’ve been working into integrating Qt Quick (QML) and OgreNext when using the Vulkan RenderSystem for use in Gazebo.

Note that I’m talking about Qt Quick. The ‘stable’ Qt Widgets interface (which uses QVulkanWindow) doesn’t have this problem because it’s too simple.

There are various ways to perform an integration:

  1. VK_KHR_external_memory. This should be ‘the one true way’. But I’ve found a worryingly lack of samples. Most of the samples focus on Vulkan -> OpenGL integration but not Vulkan -> Vulkan integration.
    • Vulkan -> OpenGL is buggy on Mesa drivers even with the basic NVIDIA’s sample. Which makes it inviable. Mesa support is a requirement.
    • VK_KHR_external_memory is a complex beast to integrate due to the special way it needs to create a VkImage, and the semaphores to synchronize both VkDevices.
    • VK_KHR_external_memory is a catch-all mechanism, so googling for samples is hard because I find:
      • How to use it in Android (mostly Chromium code). Irrelevant
      • Vulkan -> OpenGL. Irrelevant.
      • Vulkan -> D3D11/12. Irrelevant.
      • Ideally we are looking for an example about same-process synchronization, not inter-process synchronization.
    • Qt must support importing a VkImage (and waiting for the imported semaphore and signaling the exported semaphore) which currently it doesn’t. So I’d have to write code to have Qt request VK_KHR_external_memory and manually setup what’s needed (assuming I can do that without modifying Qt’s source code)
    • Since I couldn’t find A SINGLE EXAMPLE on Vulkan -> Vulkan and we know Vulkan -> OpenGL is buggy on Mesa, there is no guarantee Vulkan -> Vulkan is working on all relevant platforms either.
  2. Qt creates a VkDevice. The VkDevice is sent to OgreNext. This look like the only approach for Qt 5.15, and a viable approach for Qt 6.
  3. OgreNext creates a VkDevice. The VkDevice is sent to Qt. This looks feasible in Qt 6 thanks to the addition of QQuickGraphicsDevice.
  4. The app creates the VkDevice, the VkDevice is sent to Qt & OgreNext. This looks feasible in Qt 6 and OgreNext.

Options 2, 3 & 4 are easier because synchronization becomes much easier as long as both libraries cooperate and aren’t running in separate threads (or they do, but execution never overlaps). A simple vkCmdPipelineBarrier is enough as long as we share the same VkQueue.

Hence due to the amount of effort (in both OgreNext & Qt) and the risks involved, I’m not focusing on VK_KHR_external_memory, but rather options 2 – 4.

Unfortunately I have to support Qt 5.15, which means I’ll end up working on option 2 mostly.

Sharing a VkDevice isn’t as simple

I don’t think this has ever been discussed. Not by Khronos. Not in Qt circles. Not by us either. We were living in our own worlds where every library or app is in control of its own VkDevice and we were happy.

Steam Overlays approaches the issue through Vulkan Layers (which was by design written for this functionality). Although there has been a bit of friction such as in the case of Doom Eternal; it is normal to expect two Vulkan libraries that haven’t been tuned to perfectly work together to encounter a bit of excessive synchronization.

Dear Imgui doesn’t need much either, as it just provides a public API with what they need.

But now our worlds are starting to collide as everyone has beginning to adopt Vulkan; and two libraries that hide Vulkan behind an RHI abstraction are colliding.

And as you may be guessing, you can’t just extract the VkDevice out of one RHI abstraction, insert it into another RHI, and expect it to work.

OpenGL context

Sharing the same OpenGL context is easy. No, I’m not talking about OpenGL context sharing. I’m talking about reusing the same context.

The OpenGL context is unbound, then bound again with a different HWND or GLXDrawable and it just works.

This works because OpenGL has a backwards-compatible approach to extensions. I.e. new extensions are enabled by default but an OpenGL context must work as if that extension weren’t enabled.

I don’t consider this behavior to be a good thing, but it was convenient for seamless passing GL contexts between libraries.

VkDevice

In order to properly receive an externally-created VkDevice OgreNext needs:

  1. VkInstance (optional, but highly recommended)
    • All the extensions used to create that VkInstance
    • All the layers used to create that VkInstance
  2. VkPhysicalDevice
  3. VkDevice
  4. All extensions used to create that VkDevice
  5. VkQueue
  6. VkQueue’s family index and queue index

Yesterday I pushed a sample on how to do exactly this with OgreNext.

Libraries like Qt often don’t need that much. In fact they barely need any extensions. Vanilla Vulkan is enough.

But OgreNext needs extensions such as VK_KHR_maintenance2 & VK_EXT_shader_subgroup_vote to add extra functionality that depends on it.

But some extensions like VK_KHR_get_physical_device_properties2 can’t be used unless the library that creates the VkDevice is aware of it (because the client app must provide a VkPhysicalDeviceFeatures2 to vkCreateDevice).

If we let Qt create the VkDevice, since Qt doesn’t know how to handle VK_KHR_get_physical_device_properties2,
we can’t use functionality that depends on this in OgreNext.

Problem #1: Some stuff can only be set at initialization

Debug callbacks such as VkDebugReportCallbackCreateInfoEXT can only be set when calling vkCreateInstance. This means:

  1. It’d be cool if Vulkan supported adding this callback later (though I understand if this is impossible because it adds heavier synchronization burdens)
  2. Qt doesn’t have a way to pass our own stuff to vkCreateInstance
    • Qt6 QML does support providing an externally-created VkInstance though, so I guess that works
  3. OgreNext currently doesn’t have a way to pass external stuff to vkCreateInstance either
    • OgreNext now does support providing an externally-created VkInstance though, so I guess that works

Problem #2: Can’t query parameters passed at creation, after creation

Applications must track what extensions and layers they requested when creating the VkInstance / VkDevice.

This is not a problem… except when we have to share devices.

Unfortunately there is no way to query Vulkan how its own VkDevice was created (unless we write a Vulkan layer for this). It’s not even possible to query whose VkPhysicalDevice a VkDevice belongs to.

IIRC it was designed this way on purpose to make the driver thinner and simpler.

Problem #3 libs often lose information

You can ask Qt 6 additional extensions, but it will silently drop those that are unsupported.

Fortunately you can tell OgreNext which extensions you asked Qt to use, and OgreNext will check again and filter out those that are unsupported.

Though if in the future Qt starts dropping an extension (or layer) for another reason (other than such extension not being present in the VkInstance/VkDevice) then OgreNext will assume it’s enabled when it’s not.

Admitedly, OgreNext does save this info but doesn’t have a public API yet to retrieve it.

This isn’t a problem if the application creates the Vulkan device and provides such device to both Qt 6 and OgreNext.

Problem #4 libs using Vulkan need to be more or less compatible

I don’t think this can be solved (except with VK_KHR_external_memory)

I am working on getting OgreNext 2.3 and 3.0 compatible with Qt 5.15 – 6.3 so they will work together.

However if in 5 years you want to mix & match OgreNext 3.0 with say Qt 8, there is no guarantee it will work if Qt asks for a VkDevice version or with extensions that OgreNext 3.0 has no idea how deal with.

The same goes if you want to use OgreNext 7.0 with Qt 5.15

Usually the same goes for OpenGL, but in practice you can mix and match a library written against OpenGL 3.3 and it will work just fine with an OpenGL 4.6 context. Perhaps the same will happen with Vulkan… or not.

Time will tell. The never ending struggle between backwards compatibility vs innovation and getting rid of mistakes.

Takeaways

  1. If you’re writing a Vulkan library with interoperability, you need to support being provided externally-created VkInstance/VkDevice.
    • Qt 6 supports this, Qt 5.15 does not
    • OgreNext supports this
  2. If you’re writing a Vulkan library with interop, consider adding an interface for broadcasting debug messages
    • i.e. you can’t register VkDebugReportCallbackCreateInfoEXT because you are not creating the VkInstance
    • But you should provide an interface so that the VkInstance creator can broadcast the messages to all libraries that need it
      • Currently neither Qt nor OgreNext support this (Qt doesn’t use VkDebugReportCallbackCreateInfoEXT though).
  3. The future is likely with VK_KHR_external_memory. It’d allow Qt & OgreNext to run in their own threads as well. But there are no samples!
    • Qt doesn’t support it yet
    • OgreNext doesn’t support it either yet
    • There are no samples to look at
    • There are no guarantees it will actually work (i.e. bug-free)
    • The extension is a bit of a PITA to setup and synchronize considering both devices are Vulkan
    • Khronos should address this. Official samples showing same-process VkDevice -> VkDevice sync would be ideal. The most common case is knowing how to share a single VkImage. How to share VkBuffer or other resources is secondary or should be in another sample.