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:
co-authored by
Claude Opus 5.5
parent
423ea57db7
commit
4826686a4d
+63
-113
@@ -28,12 +28,7 @@
|
||||
#include <qapplication.h>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
#include <rhi/qrhi.h>
|
||||
#include <stdexcept>
|
||||
// Direct3D exists only on Windows.
|
||||
#ifdef _WIN32
|
||||
# include <d3d11.h>
|
||||
#endif
|
||||
|
||||
// application namespace
|
||||
namespace advsettings
|
||||
@@ -52,10 +47,13 @@ int verifyCustomTickRate( const int tickRate )
|
||||
return tickRate;
|
||||
}
|
||||
|
||||
OverlayController::OverlayController( bool desktopMode,
|
||||
bool noSound,
|
||||
QQmlEngine& qmlEngine )
|
||||
: QObject(), m_desktopMode( desktopMode ), m_noSound( noSound ),
|
||||
OverlayController::OverlayController(
|
||||
bool desktopMode,
|
||||
bool noSound,
|
||||
QQmlEngine& qmlEngine,
|
||||
std::unique_ptr<RenderBackend> renderBackend )
|
||||
: QObject(), m_renderBackend( std::move( renderBackend ) ),
|
||||
m_desktopMode( desktopMode ), m_noSound( noSound ),
|
||||
m_verifiedCustomTickRateMs( verifyCustomTickRate( settings::getSetting(
|
||||
settings::IntSetting::APPLICATION_customTickRateMs ) ) ),
|
||||
m_actions(), m_alarm()
|
||||
@@ -331,8 +329,16 @@ void OverlayController::exitApp()
|
||||
// Does not fallthrough
|
||||
}
|
||||
|
||||
// Stops ticking and rendering, disconnects from SteamVR and frees the render
|
||||
// target. Called by exitApp() and by the destructor; runs only once.
|
||||
void OverlayController::Shutdown()
|
||||
{
|
||||
if ( m_shutdownDone )
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_shutdownDone = true;
|
||||
|
||||
disconnect( &m_pumpEventsTimer,
|
||||
SIGNAL( timeout() ),
|
||||
this,
|
||||
@@ -356,9 +362,21 @@ void OverlayController::Shutdown()
|
||||
m_pRenderTimer->stop();
|
||||
m_pRenderTimer.reset();
|
||||
}
|
||||
m_render_pass_descriptor.reset();
|
||||
m_render_target.reset();
|
||||
m_pFBTexture.reset();
|
||||
|
||||
// Order: OpenVR shuts down while the graphics device still exists, then
|
||||
// the render target goes. OpenVR's client holds objects on the device of
|
||||
// the submitted textures; destroying the device first crashed on the
|
||||
// Steam Frame with Vulkan (openvr.h: call VR_Shutdown before destroying
|
||||
// these resources). OVRAS ends with exit(), so this is the only place
|
||||
// where VR_Shutdown runs in overlay mode.
|
||||
vr::VR_Shutdown();
|
||||
if ( m_renderBackend && m_renderBackend->makeCurrent() )
|
||||
{
|
||||
// Frees what the backend created on Qt's device/context, before Qt
|
||||
// destroys it together with m_renderControl.
|
||||
m_renderBackend->releaseRenderTarget();
|
||||
m_renderBackend->detachWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayController::SetWidget( QQuickItem* quickItem,
|
||||
@@ -407,52 +425,31 @@ void OverlayController::SetWidget( QQuickItem* quickItem,
|
||||
m_pRenderTimer.reset( new QTimer() );
|
||||
m_pRenderTimer->moveToThread( qApp->thread() );
|
||||
m_pRenderTimer->setSingleShot( true );
|
||||
m_pRenderTimer->setInterval( 5 );
|
||||
// 16 ms: a frame starts at most 16 ms after a change, about 60 fps.
|
||||
m_pRenderTimer->setInterval( 16 );
|
||||
connect( m_pRenderTimer.get(),
|
||||
SIGNAL( timeout() ),
|
||||
this,
|
||||
SLOT( renderOverlay() ) );
|
||||
|
||||
#if QT_CONFIG( vulkan )
|
||||
if ( m_window.graphicsApi() == QSGRendererInterface::Vulkan )
|
||||
{
|
||||
m_vulkanInstance.reset( new QVulkanInstance );
|
||||
m_vulkanInstance->setExtensions(
|
||||
QQuickGraphicsConfiguration::preferredInstanceExtensions() );
|
||||
if ( !m_vulkanInstance->create() )
|
||||
throw std::runtime_error( "Cannot create vulkan instance" );
|
||||
|
||||
m_window.setVulkanInstance( m_vulkanInstance.get() );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Render target through the render backend (public Qt API only).
|
||||
// Call order: see render/RenderBackend.h.
|
||||
m_renderBackend->configureWindow( &m_window );
|
||||
if ( !m_renderBackend->makeCurrent() )
|
||||
throw std::runtime_error( "render backend: makeCurrent failed" );
|
||||
if ( !m_renderControl.initialize() )
|
||||
throw std::runtime_error( "could not initialize m_renderControl" );
|
||||
QRhi* rhi = this->rhi();
|
||||
QString renderError;
|
||||
if ( !m_renderBackend->attachWindow( &m_window, &renderError ) )
|
||||
throw std::runtime_error( renderError.toStdString() );
|
||||
qInfo() << "Rendering with" << m_renderBackend->name() << "on"
|
||||
<< m_renderBackend->deviceName();
|
||||
|
||||
qInfo() << "Started with" << rhi->backendName();
|
||||
|
||||
m_pFBTexture.reset( rhi->newTexture(
|
||||
QRhiTexture::RGBA8,
|
||||
quickItem->size().toSize(),
|
||||
1,
|
||||
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource ) );
|
||||
if ( !m_pFBTexture->create() )
|
||||
throw std::runtime_error( "RhiTexture not created" );
|
||||
|
||||
m_render_target.reset(
|
||||
rhi->newTextureRenderTarget( { m_pFBTexture.get() } ) );
|
||||
m_render_pass_descriptor.reset(
|
||||
m_render_target->newCompatibleRenderPassDescriptor() );
|
||||
m_render_target->setRenderPassDescriptor(
|
||||
m_render_pass_descriptor.get() );
|
||||
if ( !m_render_target->create() )
|
||||
throw std::runtime_error( "failed to create render target" );
|
||||
|
||||
auto qqrt
|
||||
= QQuickRenderTarget::fromRhiRenderTarget( m_render_target.get() );
|
||||
|
||||
m_window.setRenderTarget( qqrt );
|
||||
const QQuickRenderTarget renderTarget
|
||||
= m_renderBackend->createRenderTarget( quickItem->size().toSize() );
|
||||
if ( renderTarget.isNull() )
|
||||
throw std::runtime_error( "could not create render target" );
|
||||
m_window.setRenderTarget( renderTarget );
|
||||
quickItem->setParentItem( m_window.contentItem() );
|
||||
m_window.setGeometry( 0,
|
||||
0,
|
||||
@@ -551,62 +548,6 @@ void OverlayController::OnRenderRequest()
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayController::SetOverlayFromQRhiTexture( QRhiTexture& tex )
|
||||
{
|
||||
vr::Texture_t vrTex = {};
|
||||
QRhi* rhi = this->rhi();
|
||||
|
||||
switch ( rhi->backend() )
|
||||
{
|
||||
case QRhi::OpenGLES2:
|
||||
{
|
||||
vrTex.handle = reinterpret_cast<void*>( tex.nativeTexture().object );
|
||||
vrTex.eType = vr::TextureType_OpenGL;
|
||||
vrTex.eColorSpace = vr::ColorSpace_Auto;
|
||||
}
|
||||
break;
|
||||
// Direct3D exists only on Windows.
|
||||
#ifdef _WIN32
|
||||
case QRhi::D3D11:
|
||||
{
|
||||
vrTex.handle = reinterpret_cast<ID3D11Texture2D *>( tex.nativeTexture().object );
|
||||
vrTex.eType = vr::TextureType_DirectX;
|
||||
vrTex.eColorSpace = vr::ColorSpace_Auto;
|
||||
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if QT_CONFIG( vulkan )
|
||||
case QRhi::Vulkan:
|
||||
{
|
||||
const QRhiVulkanNativeHandles* vulkan_handles
|
||||
= reinterpret_cast<const QRhiVulkanNativeHandles*>(
|
||||
rhi->nativeHandles() );
|
||||
vr::VRVulkanTextureData_t vulkan = {
|
||||
.m_nImage = tex.nativeTexture().object,
|
||||
.m_pDevice = vulkan_handles->dev,
|
||||
.m_pPhysicalDevice = vulkan_handles->physDev,
|
||||
.m_pInstance = vulkan_handles->inst->vkInstance(),
|
||||
.m_pQueue = vulkan_handles->gfxQueue,
|
||||
.m_nQueueFamilyIndex = vulkan_handles->gfxQueueIdx,
|
||||
.m_nWidth = static_cast<uint32_t>( tex.pixelSize().width() ),
|
||||
.m_nHeight = static_cast<uint32_t>( tex.pixelSize().height() ),
|
||||
.m_nFormat = static_cast<uint32_t>(tex.format()),
|
||||
.m_nSampleCount = static_cast<uint32_t>( tex.sampleCount() ),
|
||||
};
|
||||
vrTex.handle = &vulkan;
|
||||
vrTex.eType = vr::TextureType_Vulkan;
|
||||
vrTex.eColorSpace = vr::ColorSpace_Auto;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
qFatal() << "unimplemented backend:" << rhi->backend();
|
||||
throw std::runtime_error( "unimplemented backend" );
|
||||
}
|
||||
vr::VROverlay()->SetOverlayTexture( m_ulOverlayHandle, &vrTex );
|
||||
}
|
||||
|
||||
void OverlayController::renderOverlay()
|
||||
{
|
||||
if ( !m_desktopMode )
|
||||
@@ -617,13 +558,24 @@ void OverlayController::renderOverlay()
|
||||
&& !vr::VROverlay()->IsOverlayVisible(
|
||||
m_ulOverlayThumbnailHandle ) ) )
|
||||
return;
|
||||
// OpenGL: the context must be current while rendering and while
|
||||
// handing the texture to SteamVR.
|
||||
if ( !m_renderBackend->makeCurrent() )
|
||||
return;
|
||||
// The backends put the texture into the state Qt expects
|
||||
// (beginFrame) and the one SteamVR expects (finishFrame).
|
||||
m_renderBackend->beginFrame();
|
||||
m_renderControl.polishItems();
|
||||
m_renderControl.beginFrame();
|
||||
m_renderControl.sync();
|
||||
m_renderControl.render();
|
||||
m_renderControl.endFrame();
|
||||
m_renderBackend->finishFrame();
|
||||
|
||||
SetOverlayFromQRhiTexture( *m_pFBTexture );
|
||||
vr::VRTextureBounds_t bounds = m_renderBackend->vrTextureBounds();
|
||||
vr::VROverlay()->SetOverlayTextureBounds( m_ulOverlayHandle, &bounds );
|
||||
vr::Texture_t texture = m_renderBackend->vrTexture();
|
||||
vr::VROverlay()->SetOverlayTexture( m_ulOverlayHandle, &texture );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,12 +595,10 @@ bool OverlayController::pollNextEvent( vr::VROverlayHandle_t ulOverlayHandle,
|
||||
|
||||
QPointF OverlayController::getMousePositionForEvent( vr::VREvent_Mouse_t mouse )
|
||||
{
|
||||
//TODO temp mouse is negative??
|
||||
float y = m_window.height() - mouse.y;
|
||||
#ifdef __linux__
|
||||
float h = static_cast<float>( m_window.height() );
|
||||
y = h - y;
|
||||
#endif
|
||||
// SteamVR reports mouse coordinates in GL convention (y counted from the
|
||||
// bottom) on every platform and for every graphics API (verified on the
|
||||
// Steam Frame with OpenGL and Vulkan); Qt counts y from the top.
|
||||
float y = static_cast<float>( m_window.height() ) - mouse.y;
|
||||
return { mouse.x, y };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user