Render through public Qt API: OpenGL and Vulkan backends, --graphics-api

Replace the QRhi rendering in OverlayController (private Qt API) with the
render backends of the vrdashboard template (src/render/), which use public
Qt API only (QQuickRenderTarget::fromOpenGLTexture / fromVulkanImage). The
binary then runs on another Qt 6 patch release, e.g. the Steam Frame's system
Qt.

The Vulkan backend also does what SteamVR requires and the QRhi path lacked:
the required instance/device extensions and the HMD's GPU (via
openvr_init::renderInterop()), VK_FORMAT_R8G8B8A8_UNORM, and the transition to
TRANSFER_SRC_OPTIMAL after each frame.

- New option --graphics-api opengl|vulkan (default opengl, no fallback).
- Shutdown() calls VR_Shutdown before the render target and Qt's device go
  (crash on the Steam Frame with Vulkan otherwise); it runs only once.
- Mouse y is flipped on every platform: SteamVR reports it in GL convention.
- Render delay 5 -> 16 ms (about 60 fps).
- GuiPrivate is no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matthias Janetschek
2026-10-07 13:41:13 +02:00
co-authored by Claude Opus 5.5
parent 423ea57db7
commit 4826686a4d
15 changed files with 1243 additions and 140 deletions
+71
View File
@@ -6,6 +6,7 @@
#include <QtLogging>
#include <QtDebug>
#include "../openvr/ivrinput_manifest.h"
#include <QByteArray>
namespace openvr_init
{
@@ -136,4 +137,74 @@ void initializeOpenVR( const OpenVrInitializationType initType, int count )
}
}
// Calls an OpenVR "Get...ExtensionsRequired" function and splits its
// space-separated result. `query( buffer, size )` returns the size needed,
// including the terminating zero.
template <typename Query>
static QByteArrayList queryExtensionList( Query query )
{
const uint32_t size = query( nullptr, 0 );
if ( size == 0 )
{
return {};
}
QByteArray buffer( static_cast<int>( size ), '\0' );
query( buffer.data(), size );
QByteArrayList result;
for ( const QByteArray& ext :
buffer.left( buffer.indexOf( '\0' ) ).split( ' ' ) )
{
if ( !ext.isEmpty() )
{
result.append( ext );
}
}
return result;
}
// Same as the template's VrRuntime::renderInterop(). The functions are called
// by the Vulkan backend while it creates its instance and device.
RenderInterop renderInterop()
{
RenderInterop interop;
interop.vulkanInstanceExtensions = []
{
vr::IVRCompositor* compositor = vr::VRCompositor();
if ( !compositor )
{
qWarning() << "IVRCompositor unavailable: cannot query required "
"Vulkan instance extensions";
return QByteArrayList();
}
return queryExtensionList(
[&]( char* buffer, uint32_t size )
{
return compositor->GetVulkanInstanceExtensionsRequired( buffer,
size );
} );
};
interop.vulkanDeviceExtensions = []( VkPhysicalDevice_T* device )
{
vr::IVRCompositor* compositor = vr::VRCompositor();
if ( !compositor )
{
return QByteArrayList();
}
return queryExtensionList(
[&]( char* buffer, uint32_t size )
{
return compositor->GetVulkanDeviceExtensionsRequired(
device, buffer, size );
} );
};
interop.vulkanOutputDevice = []( VkInstance_T* instance )
{
uint64_t device = 0;
vr::VRSystem()->GetOutputDevice(
&device, vr::TextureType_Vulkan, instance );
return reinterpret_cast<VkPhysicalDevice_T*>( device );
};
return interop;
}
} // namespace openvr_init