Vulkan WSI, Oculus and SDL2

In today's post I describe how I deal with the Window System Integration in the Vulkan API. So far I have implemented support for Oculus Rift PC headsets and SDL2 widgets for my renderers.

I enjoy that Vulkan can be used without an access to a desktop system. The details of the desktop have been entirely abstracted behind the WSI.

I think the common ways to implement WSI integration into Vulkan are going to be a bit different from how they meant it to be.

Sidekicks

Both SDL2 and Oculus PC SDK require access to variety of instance and device extensions to function. So they have to influence the initialization of the Vulkan Device.

I thought out they are very much like sidekicks so I call them so. The sidekick is not necessarily doing WSI, but any sidekick extends the Vulkan interface somehow.

My Vulkan programs begin like this:

window = sdl.Window()
hmd = vr.HMD()

gpu = GPU(window.wsi, hmd.wsi)

In the future you will be ideally able to initialize it like this:

gpu = GPU(sdl.wsi, vr.wsi)

For now with SDL we have to pass the SDL Window first, because we would have to know which window engine is going to be used: Win32?, Wayland?, Mir?, Xcb? X11? Only the SDL_SysWMinfo provides that information and you have to call it with the window.

On the Oculus PC SDK, the WSI can be deployed when you have a session, but the Oculus session is associated to a single HMD. I do not know what is the status of their multiple-HMD support.

Even with this system it's possible to render into multiple windows because the queue that can support one surface can support them all, but I don't think it's a nice option to leave the possibility there so for now we have to open all the SDL windows that we are rendering to and pass their sidekicks into the GPU initializer.

Perhaps the situation improves with newer SDL releases. A function like SDL_GetPlatformInfo that can be called after SDL_Init would be sufficient.

Note that if you have to restart the Vulkan Device because of this, it means your app loses all the memory references to the GPU memory. That's not a very nice thing to prepare for.

Sidekick interface

The Sidekick interface here is constructed like this:

instance_support (vk, extensions)
setup (vk, instance)
device_support (vk, dev, extensions)
queue_support (vk, dev, queueFamilyIndex)

During the GPU instantiation, the first function called for the sidekicks is the instance_support. The function returns the list of instance extensions that the sidekick needs to function. The input extensions is provided so that the sidekick can optionally include extensions if the ideal setup of extensions is not available.

SDL requires the extension VK_KHR_surface and one of the VK_KHR_*_surface to function.

At the moment Oculus Rift PC SDK is not providing a list of required instance extensions, but I have heard that they are going to provide function to retrieve the necessary instance extensions in the 1.18 SDK.

The setup is called right after the Vulkan instance has been created. This allows the sidekicks to prepare for selecting the physical device.

SDL sidekick initializes the surface at this point and uses the SDL_GetWindowWMInfo to create a SurfaceKHR object, whereas Oculus Rift sidekick has to call the ovr_GetSessionPhysicalDeviceVk to retrieve a reference to the PhysicalDevice they want you to use.

I do not think the ovr_GetSessionPhysicalDeviceVk is entirely satisfactory as a solution, but it will do for now. I would prefer if it was more like the Get*SupportKHR -functions.

Next your application needs to select the suitable device for the work.

I have set up my application to select a queue with both graphics and compute support. I think that's generally reasonable to do and it has not caused any trouble so far.

The device_support will again receive a list of extensions that are available, and has to return a list of the extensions that it wants to activate. If the returned extension list is a superset of the extensions, the application will fail. The sidekick has to return null in order to discard the device as an option.

In the device_support the SDL sidekick returns the KHR swapchain extension whereas the Rift sidekick returns the external_memory related extensions.

The queue_support selects among the queues. The SDL queries the vkGetPhysicalDeviceSurfaceSupportKHR at this point. That function requires a surface that was created in the setup -call. There are also platform-specific support queries that do not require a surface as an argument.

Note that the SurfaceKHR object stays the same even if the window is resized, so you don't need to adjust anything in the GPU sidekicks to allow resizable windows.

Swapchain / RenderTarget setup

After we have obtained the GPU instance, this whole system is very much ready for action. With SDL you obtain the surface from the WSI sidekick and create swapchain like usual. Oculus has the ovr_CreateTextureSwapChainVk which requires few parameters. This is a bit annoying to fill up because the image format and type for the swapchain are described with Oculus parameters, rather than Vulkan's parameters. You may need to translate across the two and that is not nice.

This is pretty much it. Despite that Oculus Rift is using a custom swapchain object, you can retrieve the images, width, height, format, samples like usual and pass them into the renderer which you prefer. Rift SDK provides the ovr_GetTextureSwapChainCurrentIndex and ovr_CommitTextureSwapChain that provide the same functionality as what the vkAcquireNextImageKHR and vkQueuePresentKHR provide.

When designing your renderers you want to acknowledge that you're possibly rendering two eyes with one render pass, and then either rendering into the window with a second render pass or mirroring it in from either one of the rendered eyes.

Similar posts