Files
OpenVR-AdvancedSettings-frame/src/openvr/openvr_init.cpp
T
Matthias JanetschekandClaude Opus 5.5 4826686a4d 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>
2026-10-07 13:41:13 +02:00

211 lines
7.3 KiB
C++

#include <string>
#include <thread>
#include <openvr.h>
#include <QMessageBox>
#include "openvr_init.h"
#include <QtLogging>
#include <QtDebug>
#include "../openvr/ivrinput_manifest.h"
#include <QByteArray>
namespace openvr_init
{
bool initializeProperly( const OpenVrInitializationType initType )
{
auto initializationType = vr::EVRApplicationType::VRApplication_Other;
if ( initType == OpenVrInitializationType::Overlay )
{
initializationType = vr::EVRApplicationType::VRApplication_Overlay;
}
else if ( initType == OpenVrInitializationType::Utility )
{
initializationType = vr::EVRApplicationType::VRApplication_Utility;
}
auto initError = vr::VRInitError_None;
vr::VR_Init( &initError, initializationType );
if ( initError != vr::VRInitError_None )
{
if ( initError == vr::VRInitError_Init_HmdNotFound
|| initError == vr::VRInitError_Init_HmdNotFoundPresenceFailed )
{
// In particular in some setups these errors are thrown while
// nothing is wrong with their setup presumably this is some sort of
// race condition
qWarning() << "HMD not Found During Startup";
qWarning() << "steamvr error: "
+ std::string(
vr::VR_GetVRInitErrorAsEnglishDescription(
initError ) );
return false;
}
qCritical() << "Failed to initialize OpenVR: "
+ std::string(
vr::VR_GetVRInitErrorAsEnglishDescription(
initError ) );
// Going to stop Exiting App, This may lead to crashes if OpenVR
// actually fails to start, HOWEVER based on the HMD errors we are
// probably pre-maturely killing ourselves from some sort of OpenVR race
// condition
// exit( EXIT_FAILURE );
return false;
}
qInfo() << "OpenVR initialized successfully.";
return true;
}
void initializeOpenVR( const OpenVrInitializationType initType, int count )
{
bool success = initializeProperly( initType );
// The function call and error message was the same for all version checks.
// Specific error messages are unlikely to be necessary since both the type
// and version are in the string and will be output.
auto reportVersionError
= []( const char* const interfaceAndVersion, const int trynumber )
{
// as of 5.8.1 Based on some information from valve, if ANY interface
// fails to load we should have issues. we will re-initialize a few
// times if that is the case and hope that fixes things
qWarning() << "OpenVR version is invalid: Interface version "
<< interfaceAndVersion << " not found. attempt number: "
<< std::to_string( trynumber );
};
// Check whether OpenVR is too outdated
// Sleep duration is an attempt to hopefully alleviate a possible race
// condition in steamvr
if ( !vr::VR_IsInterfaceVersionValid( vr::IVRSystem_Version ) )
{
reportVersionError( vr::IVRSystem_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRSettings_Version ) )
{
reportVersionError( vr::IVRSettings_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVROverlay_Version ) )
{
reportVersionError( vr::IVROverlay_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRApplications_Version ) )
{
reportVersionError( vr::IVRApplications_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRChaperone_Version ) )
{
reportVersionError( vr::IVRChaperone_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRChaperoneSetup_Version ) )
{
reportVersionError( vr::IVRChaperoneSetup_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRCompositor_Version ) )
{
reportVersionError( vr::IVRCompositor_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRNotifications_Version ) )
{
reportVersionError( vr::IVRNotifications_Version, count );
success = false;
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRInput_Version ) )
{
reportVersionError( vr::IVRInput_Version, count );
success = false;
}
if ( !success && count < 3 )
{
qWarning() << "An error occured on initialization of "
"openvr/steamvr attempting again "
<< std::to_string( count + 1 ) << " of 3 Attempts";
std::this_thread::sleep_for( std::chrono::seconds( 5 ) );
// 5.8.1 Unknown if VR shutdown needs to be callled if vr init fails
initializeOpenVR( initType, ( count + 1 ) );
}
if ( count >= 3 )
{
qCritical() << "initialization errors persist proceeding anyways";
}
}
// 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