From 70364c0a90577a0991e18d844fcefc4f2666343b Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:28:44 +0000 Subject: [PATCH 01/28] all: Support for multiple virtual connectors WIP, but should be getting there. Still some to-dos on the side of cursor setting and misc. operations on the WaylandBackend. --- src/Backends/DRMBackend.cpp | 226 +++--- src/Backends/HeadlessBackend.cpp | 21 +- src/Backends/OpenVRBackend.cpp | 1084 +++++++++++++++++------------ src/Backends/SDLBackend.cpp | 72 +- src/Backends/WaylandBackend.cpp | 948 +++++++++++++------------ src/backend.cpp | 44 +- src/backend.h | 125 +++- src/main.cpp | 2 + src/steamcompmgr.cpp | 1117 +++++++++++++++++------------- src/steamcompmgr.hpp | 1 - src/steamcompmgr_shared.hpp | 15 + src/vblankmanager.cpp | 19 +- 12 files changed, 2131 insertions(+), 1543 deletions(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 0b121e8416..4c1000b343 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -72,8 +72,96 @@ gamescope::ConVar cv_drm_debug_disable_color_range( "drm_debug_disable_col gamescope::ConVar cv_drm_debug_disable_explicit_sync( "drm_debug_disable_explicit_sync", false, "Force disable explicit sync on the DRM backend." ); gamescope::ConVar cv_drm_debug_disable_in_fence_fd( "drm_debug_disable_in_fence_fd", false, "Force disable IN_FENCE_FD being set to avoid over-synchronization on the DRM backend." ); + +int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync ); + +struct saved_mode { + int width; + int height; + int refresh; +}; + namespace gamescope { + class CDRMPlane; + class CDRMCRTC; + class CDRMConnector; +} + +struct drm_t { + bool bUseLiftoff; + + int fd = -1; + + int preferred_width, preferred_height, preferred_refresh; + + uint64_t cursor_width, cursor_height; + bool allow_modifiers; + struct wlr_drm_format_set formats; + + std::vector< std::unique_ptr< gamescope::CDRMPlane > > planes; + std::vector< std::unique_ptr< gamescope::CDRMCRTC > > crtcs; + std::unordered_map< uint32_t, gamescope::CDRMConnector > connectors; + + gamescope::CDRMPlane *pPrimaryPlane; + gamescope::CDRMCRTC *pCRTC; + gamescope::CDRMConnector *pConnector; + + struct wlr_drm_format_set primary_formats; + + drmModeAtomicReq *req; + uint32_t flags; + + struct liftoff_device *lo_device; + struct liftoff_output *lo_output; + struct liftoff_layer *lo_layers[ k_nMaxLayers ]; + + std::shared_ptr sdr_static_metadata; + + struct drm_state_t { + std::shared_ptr mode_id; + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; + amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } current, pending; + + // FBs in the atomic request, but not yet submitted to KMS + // Accessed only on req thread + std::vector> m_FbIdsInRequest; + + // FBs currently queued to go on screen. + // May be accessed by page flip handler thread and req thread, thus mutex. + std::mutex m_QueuedFbIdsMutex; + std::vector> m_QueuedFbIds; + // FBs currently on screen. + // Accessed only on page flip handler thread. + std::mutex m_mutVisibleFbIds; + std::vector> m_VisibleFbIds; + + std::atomic < uint32_t > uPendingFlipCount = { 0 }; + + std::atomic < bool > paused = { false }; + std::atomic < int > out_of_date = { false }; + std::atomic < bool > needs_modeset = { false }; + + std::unordered_map< std::string, int > connector_priorities; + + char *device_name = nullptr; +}; + +void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); +bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); + + +using namespace std::literals; + +struct drm_t g_DRM = {}; + +namespace gamescope +{ + class CDRMBackend; + std::tuple GetKernelVersion() { utsname name; @@ -259,10 +347,10 @@ namespace gamescope CRTCProperties m_Props; }; - class CDRMConnector final : public IBackendConnector, public CDRMAtomicTypedObject + class CDRMConnector final : public CBaseBackendConnector, public CDRMAtomicTypedObject { public: - CDRMConnector( drmModeConnector *pConnector ); + CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector ); void RefreshState(); @@ -343,6 +431,14 @@ namespace gamescope const BackendConnectorHDRInfo &GetHDRInfo() const override { return m_Mutable.HDR; } + virtual bool IsVRRActive() const override + { + if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties().VRR_ENABLED ) + return false; + + return !!g_DRM.pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); + } + virtual std::span GetModes() const override { return m_Mutable.BackendModes; } bool SupportsVRR() const override @@ -371,17 +467,20 @@ namespace gamescope } } - void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; using DRMModeGenerator = std::function; const DRMModeGenerator &GetModeGenerator() const { return m_Mutable.fnDynamicModeGenerator; } + void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); private: void ParseEDID(); + + CDRMBackend *m_pBackend = nullptr; CAutoDeletePtr m_pConnector; struct MutableConnectorState @@ -393,8 +492,8 @@ namespace gamescope char szMakePNP[4]{}; char szModel[16]{}; const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. - std::vector ValidDynamicRefreshRates{}; DRMModeGenerator fnDynamicModeGenerator; + std::vector ValidDynamicRefreshRates{}; std::vector EdidData; // Raw, unmodified. std::vector BackendModes; @@ -420,82 +519,6 @@ namespace gamescope }; } -struct saved_mode { - int width; - int height; - int refresh; -}; - -struct drm_t { - bool bUseLiftoff; - - int fd = -1; - - int preferred_width, preferred_height, preferred_refresh; - - uint64_t cursor_width, cursor_height; - bool allow_modifiers; - struct wlr_drm_format_set formats; - - std::vector< std::unique_ptr< gamescope::CDRMPlane > > planes; - std::vector< std::unique_ptr< gamescope::CDRMCRTC > > crtcs; - std::unordered_map< uint32_t, gamescope::CDRMConnector > connectors; - - gamescope::CDRMPlane *pPrimaryPlane; - gamescope::CDRMCRTC *pCRTC; - gamescope::CDRMConnector *pConnector; - - struct wlr_drm_format_set primary_formats; - - drmModeAtomicReq *req; - uint32_t flags; - - struct liftoff_device *lo_device; - struct liftoff_output *lo_output; - struct liftoff_layer *lo_layers[ k_nMaxLayers ]; - - std::shared_ptr sdr_static_metadata; - - struct drm_state_t { - std::shared_ptr mode_id; - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; - amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } current, pending; - - // FBs in the atomic request, but not yet submitted to KMS - // Accessed only on req thread - std::vector> m_FbIdsInRequest; - - // FBs currently queued to go on screen. - // May be accessed by page flip handler thread and req thread, thus mutex. - std::mutex m_QueuedFbIdsMutex; - std::vector> m_QueuedFbIds; - // FBs currently on screen. - // Accessed only on page flip handler thread. - std::mutex m_mutVisibleFbIds; - std::vector> m_VisibleFbIds; - - std::atomic < uint32_t > uPendingFlipCount = { 0 }; - - std::atomic < bool > paused = { false }; - std::atomic < int > out_of_date = { false }; - std::atomic < bool > needs_modeset = { false }; - - std::unordered_map< std::string, int > connector_priorities; - - char *device_name = nullptr; -}; - -void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); -bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - - -using namespace std::literals; - -struct drm_t g_DRM = {}; - uint32_t g_nDRMFormat = DRM_FORMAT_INVALID; uint32_t g_nDRMFormatOverlay = DRM_FORMAT_INVALID; // for partial composition, we may have more limited formats than base planes + alpha. bool g_bRotated = false; @@ -682,7 +705,7 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi DRMPresentCtx *pCtx = reinterpret_cast( data ); // Make this const when we move into CDRMBackend. - GetBackend()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; + GetBackend()->GetCurrentConnector()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; if ( !g_DRM.pCRTC ) return; @@ -768,7 +791,7 @@ static bool refresh_state( drm_t *drm ) drm->connectors.emplace( std::piecewise_construct, std::forward_as_tuple( uConnectorId ), - std::forward_as_tuple( pConnector ) ); + std::forward_as_tuple( reinterpret_cast( GetBackend() ), pConnector ) ); } } @@ -1963,8 +1986,9 @@ namespace gamescope ///////////////////////// // CDRMConnector ///////////////////////// - CDRMConnector::CDRMConnector( drmModeConnector *pConnector ) + CDRMConnector::CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector ) : CDRMAtomicTypedObject( pConnector->connector_id ) + , m_pBackend{ pBackend } , m_pConnector{ pConnector, []( drmModeConnector *pConnector ){ drmModeFreeConnector( pConnector ); } } { RefreshState(); @@ -2043,6 +2067,11 @@ namespace gamescope ParseEDID(); } + int CDRMConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { + return HackyDRMPresent( pFrameInfo, bAsync ); + } + void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode ) { if ( this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL && g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO ) @@ -3230,8 +3259,13 @@ namespace gamescope return true; } - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) { + static uint64_t s_ulLastTime = get_time_in_nanos(); + uint64_t ulNow = get_time_in_nanos(); + drm_log.debugf( "CDRMBackend::Present Begin: %lu -> delta: %lu", ulNow, ulNow - s_ulLastTime ); + s_ulLastTime = ulNow; + bool bWantsPartialComposite = pFrameInfo->layerCount >= 3 && !kDisablePartialComposition; static bool s_bWasFirstFrame = true; @@ -3584,14 +3618,6 @@ namespace gamescope return nullptr; } - virtual bool IsVRRActive() const override - { - if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties().VRR_ENABLED ) - return false; - - return !!g_DRM.pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); - } - virtual bool SupportsPlaneHardwareCursor() const override { return true; @@ -3702,14 +3728,14 @@ namespace gamescope drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest ); } - m_PresentFeedback.m_uQueuedPresents++; + GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents++; uint32_t uCurrentPresentCtx = m_uNextPresentCtx; m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; - m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = m_PresentFeedback.m_uQueuedPresents; + m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents; - drm_log.debugf("flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents); - gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents ); + drm_log.debugf("flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents); + gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents ); ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, &m_PresentCtxs[uCurrentPresentCtx] ); if ( ret != 0 ) @@ -3735,7 +3761,7 @@ namespace gamescope // Clear our refs. drm->m_FbIdsInRequest.clear(); - m_PresentFeedback.m_uQueuedPresents--; + GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents--; if ( isPageFlip ) drm->uPendingFlipCount--; @@ -3806,3 +3832,9 @@ namespace gamescope return Set( new CDRMBackend{} ); } } + +int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync ) +{ + return static_cast( GetBackend() )->Present( pFrameInfo, bAsync ); +} + diff --git a/src/Backends/HeadlessBackend.cpp b/src/Backends/HeadlessBackend.cpp index 49987f69fc..205485fe1f 100644 --- a/src/Backends/HeadlessBackend.cpp +++ b/src/Backends/HeadlessBackend.cpp @@ -8,7 +8,7 @@ extern int g_nPreferredOutputHeight; namespace gamescope { - class CHeadlessConnector final : public IBackendConnector + class CHeadlessConnector final : public CBaseBackendConnector { public: CHeadlessConnector() @@ -38,6 +38,10 @@ namespace gamescope { return m_HDRInfo; } + virtual bool IsVRRActive() const override + { + return false; + } virtual std::span GetModes() const override { return std::span{}; @@ -81,6 +85,11 @@ namespace gamescope return "Virtual Display"; } + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + { + return 0; + } + private: BackendConnectorHDRInfo m_HDRInfo{}; }; @@ -157,11 +166,6 @@ namespace gamescope return true; } - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override - { - return 0; - } - virtual void DirtyState( bool bForce, bool bForceModeset ) override { } @@ -202,11 +206,6 @@ namespace gamescope return nullptr; } - virtual bool IsVRRActive() const override - { - return false; - } - virtual bool SupportsPlaneHardwareCursor() const override { return false; diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index c39caa54b7..34e11791cd 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -48,6 +48,8 @@ extern gamescope::ConVar cv_hdr_enabled; extern uint64_t g_SteamCompMgrLimitedAppRefreshCycle; +void MakeFocusDirty(); + static LogScope openvr_log("openvr"); static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); @@ -62,6 +64,7 @@ gamescope::ConVar cv_vr_trackpad_relative_mouse_mode( "vr_trackpad_relativ gamescope::ConVar cv_vr_trackpad_sensitivity( "vr_trackpad_sensitivity", 1500.f, "Sensitivity for VR Trackpad Mode" ); gamescope::ConVar cv_vr_trackpad_click_time( "vr_trackpad_click_time", 250'000'000ul, "Time to consider a 'click' vs a 'drag' when using trackpad mode. In nanoseconds." ); gamescope::ConVar cv_vr_trackpad_click_max_delta( "vr_trackpad_click_max_delta", 0.14f, "Max amount the cursor can move before not clicking." ); +gamescope::ConVar cv_vr_debug_force_opaque( "vr_debug_force_opaque", false, "Force textures to be treated as opaque." ); // Just below half of 120Hz, so we always at least poll input once per frame, regardless of cadence/cycles. gamescope::ConVar cv_vr_poll_rate( "vr_poll_rate", 4'000'000ul, "Time between input polls. In nanoseconds." ); @@ -170,98 +173,10 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, namespace gamescope { - class CVROverlayConnector final : public IBackendConnector - { - public: - - ////////////////////// - // IBackendConnector - ////////////////////// - - CVROverlayConnector() - { - } - virtual ~CVROverlayConnector() - { - } - - virtual GamescopeScreenType GetScreenType() const override - { - return GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - virtual GamescopePanelOrientation GetCurrentOrientation() const override - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - virtual bool SupportsHDR() const override - { - return false; - } - virtual bool IsHDRActive() const override - { - return false; - } - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override - { - return m_HDRInfo; - } - virtual std::span GetModes() const override - { - return std::span{}; - } - - virtual bool SupportsVRR() const override - { - return false; - } - - virtual std::span GetRawEDID() const override - { - return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; - } - virtual std::span GetValidDynamicRefreshRates() const override - { - return std::span{}; - } - - virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override - { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; - } - - virtual const char *GetName() const override - { - return "OpenVR"; - } - virtual const char *GetMake() const override - { - return "Gamescope"; - } - virtual const char *GetModel() const override - { - return "Virtual Display"; - } - - bool UpdateEdid() - { - m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight ); - - return true; - } - - private: - BackendConnectorHDRInfo m_HDRInfo{}; - std::vector m_FakeEdid; - }; - - class COpenVRBackend; + class COpenVRPlane; + class COpenVRFb; + class COpenVRConnector; class COpenVRFb final : public CBaseBackendFb { @@ -295,7 +210,7 @@ namespace gamescope class COpenVRPlane { public: - COpenVRPlane( COpenVRBackend *pBackend ); + COpenVRPlane( COpenVRConnector *pConnector ); ~COpenVRPlane(); bool Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow ); @@ -309,21 +224,104 @@ namespace gamescope uint32_t GetSortOrder() const { return m_uSortOrder; } bool IsSubview() const { return m_bIsSubview; } + COpenVRBackend *GetBackend() const { return m_pBackend; } + private: + COpenVRConnector *m_pConnector = nullptr; COpenVRBackend *m_pBackend = nullptr; + std::string m_sDashboardOverlayKey; + bool m_bIsSubview = false; uint32_t m_uSortOrder = 0; vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; }; + class COpenVRConnector final : public CBaseBackendConnector, public INestedHints + { + public: + + COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ); + + ////////////////////// + // IBackendConnector + ////////////////////// + + ~COpenVRConnector(); + virtual GamescopeScreenType GetScreenType() const override; + virtual GamescopePanelOrientation GetCurrentOrientation() const override; + virtual bool SupportsHDR() const override; + virtual bool IsHDRActive() const override; + virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; + virtual bool IsVRRActive() const override; + virtual std::span GetModes() const override; + + virtual bool SupportsVRR() const override; + + virtual std::span GetRawEDID() const override; + virtual std::span GetValidDynamicRefreshRates() const override; + + virtual void GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; + + virtual const char *GetName() const override; + virtual const char *GetMake() const override; + virtual const char *GetModel() const override; + + virtual VBlankScheduleTime FrameSync() override; + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + + virtual INestedHints *GetNestedHints() override + { + return this; + } + + /////////////////// + // INestedHints + /////////////////// + + virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetRelativeMouseMode( bool bRelative ) override; + virtual void SetVisible( bool bVisible ) override; + virtual void SetTitle( std::shared_ptr szTitle ) override; + virtual void SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; + + bool UpdateEdid(); + + bool Init(); + + COpenVRBackend *GetBackend() const { return m_pBackend; } + + COpenVRPlane *GetPrimaryPlane() + { + return &m_Planes[0]; + } + + std::span GetPlanes() { return std::span( &m_Planes[0], std::size( m_Planes ) ); } + + bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } + bool IsRelativeMouse() const { return m_bRelativeMouse; } + + private: + COpenVRBackend *m_pBackend = nullptr; + COpenVRPlane m_Planes[8]; + + BackendConnectorHDRInfo m_HDRInfo{}; + std::vector m_FakeEdid; + + bool m_bNudgeToVisible = false; + std::atomic m_bRelativeMouse = false; + }; - class COpenVRBackend final : public CBaseBackend, public INestedHints + class COpenVRBackend final : public CBaseBackend { public: COpenVRBackend() - : m_Planes{ this, this, this, this, this, this, this, this } { } @@ -421,9 +419,6 @@ namespace gamescope } } - if ( m_szOverlayKey.empty() ) - m_szOverlayKey = std::string( "gamescope." ) + wlserver_get_wl_display_name(); - if ( !m_pchOverlayName ) m_pchOverlayName = "Gamescope"; @@ -483,19 +478,13 @@ namespace gamescope virtual bool PostInit() override { + if ( m_szOverlayKey.empty() ) + m_szOverlayKey = std::string( "gamescope." ) + wlserver_get_wl_display_name(); + m_pIME = create_local_ime(); if ( !m_pIME ) return false; - for ( uint32_t i = 0; i < 8; i++ ) - m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); - - m_Connector.UpdateEdid(); - this->HackUpdatePatchedEdid(); - - if ( cv_hdr_enabled && m_Connector.GetHDRInfo().bExposeHDRSupport ) - setenv( "DXVK_HDR", "1", false ); - // This breaks cursor intersection right now. // Come back to me later. //Ratio aspectRatio{ g_nOutputWidth, g_nOutputHeight }; @@ -542,102 +531,6 @@ namespace gamescope return true; } - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override - { - bool bNeedsFullComposite = false; - - // TODO: Dedupe some of this composite check code between us and drm.cpp - bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); - - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; - - bNeedsFullComposite |= cv_composite_force; - bNeedsFullComposite |= pFrameInfo->useFSRLayer0; - bNeedsFullComposite |= pFrameInfo->useNISLayer0; - bNeedsFullComposite |= pFrameInfo->blurLayer0; - bNeedsFullComposite |= bNeedsCompositeFromFilter; - bNeedsFullComposite |= g_bColorSliderInUse; - bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - bNeedsFullComposite |= !UsesModifiers(); - - if ( g_bOutputHDREnabled ) - bNeedsFullComposite |= g_bHDRItmEnable; - - if ( !SupportsColorManagement() ) - bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); - - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); - - if ( !bNeedsFullComposite ) - { - bool bNeedsBacking = true; - if ( pFrameInfo->layerCount >= 1 ) - { - if ( pFrameInfo->layers[0].isScreenSize() && ( !pFrameInfo->layers[0].hasAlpha() || cv_vr_transparent_backing ) ) - bNeedsBacking = false; - } - - uint32_t uCurrentPlane = 0; - if ( bNeedsBacking ) - { - COpenVRPlane *pPlane = &m_Planes[uCurrentPlane++]; - pPlane->Present( - OpenVRPlaneState - { - .pTexture = m_pBlackTexture.get(), - .flSrcWidth = double( g_nOutputWidth ), - .flSrcHeight = double( g_nOutputHeight ), - .nDstWidth = int32_t( g_nOutputWidth ), - .nDstHeight = int32_t( g_nOutputHeight ), - .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, - .bOpaque = !cv_vr_transparent_backing, - .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f, - } ); - } - - for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) - m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr ); - } - else - { - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); - if ( !oCompositeResult ) - { - openvr_log.errorf( "vulkan_composite failed" ); - return -EINVAL; - } - - vulkan_wait( *oCompositeResult, true ); - - FrameInfo_t::Layer_t compositeLayer{}; - compositeLayer.scale.x = 1.0; - compositeLayer.scale.y = 1.0; - compositeLayer.opacity = 1.0; - compositeLayer.zpos = g_zposBase; - - compositeLayer.tex = vulkan_get_last_output_image( false, false ); - compositeLayer.applyColorMgmt = false; - - compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; - compositeLayer.ctm = nullptr; - compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - GetPrimaryPlane()->Present( &compositeLayer ); - - for ( int i = 1; i < 8; i++ ) - m_Planes[i].Present( nullptr ); - } - - - GetVBlankTimer().UpdateWasCompositing( true ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - this->PollState(); - - return 0; - } - virtual void DirtyState( bool bForce, bool bForceModeset ) override { } @@ -733,21 +626,16 @@ namespace gamescope virtual IBackendConnector *GetCurrentConnector() override { - return &m_Connector; + return m_pFocusConnector; } virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override { if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return &m_Connector; + return GetCurrentConnector(); return nullptr; } - virtual bool IsVRRActive() const override - { - return false; - } - virtual bool SupportsPlaneHardwareCursor() const override { return false; @@ -780,7 +668,7 @@ namespace gamescope if ( ShouldNudgeToVisible() ) return true; - return m_bOverlayVisible.load(); + return m_nOverlaysVisible.load() != 0; } virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override @@ -798,6 +686,17 @@ namespace gamescope if ( !GetCurrentConnector() ) return; + // XXX: We should do this a better way that handles per-window and appid stuff + // down the line + if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport ) + { + setenv( "DXVK_HDR", "1", true ); + } + else + { + setenv( "DXVK_HDR", "0", true ); + } + WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false ); } @@ -805,101 +704,53 @@ namespace gamescope { return true; } - virtual VBlankScheduleTime FrameSync() override - { - WaitUntilVisible(); - - if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) - openvr_log.errorf( "WaitFrameSync failed!" ); - - uint64_t ulNow = get_time_in_nanos(); - return VBlankScheduleTime - { - .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now. - .ulScheduledWakeupPoint = ulNow, - }; - } virtual TouchClickMode GetTouchClickMode() override { - if ( cv_vr_trackpad_relative_mouse_mode && m_bRelativeMouse ) + COpenVRConnector *pConnector = static_cast( GetCurrentConnector() ); + if ( cv_vr_trackpad_relative_mouse_mode && pConnector && pConnector->IsRelativeMouse() ) { return TouchClickModes::Trackpad; } - return CBaseBackend::GetTouchClickMode(); - } + if ( VirtualConnectorInSteamPerAppState() ) + { + if ( !VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ) ) + return TouchClickModes::Left; + } - virtual INestedHints *GetNestedHints() override - { - return this; + return CBaseBackend::GetTouchClickMode(); } - /////////////////// - // INestedHints - /////////////////// - - virtual void SetCursorImage( std::shared_ptr info ) override + bool UsesVirtualConnectors() override { + return true; } - virtual void SetRelativeMouseMode( bool bRelative ) override + std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override { - if ( bRelative != m_bRelativeMouse ) + std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey ); + + bool bSetCurrentConnector = false; { - for ( COpenVRPlane &plane : m_Planes ) + if ( !m_pFocusConnector ) { - vr::VROverlay()->SetOverlayFlag( plane.GetOverlay(), vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && bRelative ); + SetFocus( pConnector.get() ); + bSetCurrentConnector = true; } - m_bRelativeMouse = bRelative; } - } - virtual void SetVisible( bool bVisible ) override - { - vr::VROverlay()->SetOverlayFlag( GetPrimaryPlane()->GetOverlay(), vr::VROverlayFlags_VisibleInDashboard, bVisible ); - } - virtual void SetTitle( std::shared_ptr szTitle ) override - { - if ( !m_bExplicitOverlayName ) - vr::VROverlay()->SetOverlayName( GetPrimaryPlane()->GetOverlay(), szTitle ? szTitle->c_str() : m_pchOverlayName ); - } - virtual void SetIcon( std::shared_ptr> uIconPixels ) override - { - if ( cv_vr_use_window_icons && uIconPixels && uIconPixels->size() >= 3 ) + if ( !pConnector->Init() ) { - const uint32_t uWidth = (*uIconPixels)[0]; - const uint32_t uHeight = (*uIconPixels)[1]; - - struct rgba_t + if ( bSetCurrentConnector ) { - uint8_t r,g,b,a; - }; - - for ( uint32_t& val : *uIconPixels ) - { - rgba_t rgb = *((rgba_t*)&val); - std::swap(rgb.r, rgb.b); - val = *((uint32_t*)&rgb); + SetFocus( nullptr ); } - - vr::VROverlay()->SetOverlayRaw( GetPrimaryPlane()->GetOverlayThumbnail(), &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); + return nullptr; } - else if ( m_pchOverlayIcon ) - { - vr::VROverlay()->SetOverlayFromFile( GetPrimaryPlane()->GetOverlayThumbnail(), m_pchOverlayIcon ); - } - else - { - vr::VROverlay()->ClearOverlayTexture( GetPrimaryPlane()->GetOverlayThumbnail() ); - } - } - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override - { - // Do nothing. - } - virtual std::shared_ptr GetHostCursor() override - { - return nullptr; + + std::scoped_lock lock{ m_mutActiveConnectors }; + m_pActiveConnectors.push_back( pConnector.get() ); + return pConnector; } vr::IVRIPCResourceManagerClient *GetIPCResourceManager() @@ -923,10 +774,10 @@ namespace gamescope float GetPhysicalCurvature() const { return m_flPhysicalCurvature; } float GetPhysicalPreCurvePitch() const { return m_flPhysicalPreCurvePitch; } float GetScrollSpeed() const { return m_flScrollSpeed; } - bool IsRelativeMouse() const { return m_bRelativeMouse; } bool ShouldNudgeToVisible() const { return m_bNudgeToVisible; } - bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } + + CVulkanTexture *GetBlackTexture() { return m_pBlackTexture.get(); } protected: @@ -936,17 +787,19 @@ namespace gamescope private: - COpenVRPlane *GetPrimaryPlane() - { - return &m_Planes[0]; - } - void WaitUntilVisible() { if ( ShouldNudgeToVisible() ) return; - m_bOverlayVisible.wait( false ); + m_nOverlaysVisible.wait( 0 ); + } + + void SetFocus( COpenVRConnector *pFocus ) + { + COpenVRConnector *pPreviousFocus = m_pFocusConnector.exchange( pFocus ); + if ( pPreviousFocus != pFocus ) + MakeFocusDirty(); } void VRInputThread() @@ -957,170 +810,202 @@ namespace gamescope // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. while (true) { - for ( COpenVRPlane &plane : m_Planes ) { - vr::VREvent_t vrEvent; - while( vr::VROverlay()->PollNextOverlayEvent( plane.GetOverlay(), &vrEvent, sizeof( vrEvent ) ) ) - { - switch( vrEvent.eventType ) - { - case vr::VREvent_OverlayClosed: - case vr::VREvent_Quit: - { - if ( !plane.IsSubview() ) - { - raise( SIGTERM ); - } - break; - } + std::scoped_lock lock{ m_mutActiveConnectors }; - case vr::VREvent_KeyboardCharInput: - { - if (m_pIME) - { - type_text(m_pIME, vrEvent.data.keyboard.cNewInput); - } - break; - } + for ( COpenVRConnector *pConnector : m_pActiveConnectors ) + { + bool bIsSteam = VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ); - case vr::VREvent_MouseMove: + for ( COpenVRPlane &plane : pConnector->GetPlanes() ) + { + vr::VREvent_t vrEvent; + while( vr::VROverlay()->PollNextOverlayEvent( plane.GetOverlay(), &vrEvent, sizeof( vrEvent ) ) ) { - float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); - float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); - - TouchClickMode eMode = GetTouchClickMode(); - // Always warp a cursor, even if it's invisible, so we get hover events. - bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor; - - if ( eMode == TouchClickModes::Trackpad ) + switch( vrEvent.eventType ) { - glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos; - m_vScreenTrackpadPos = glm::vec2{ flX, flY }; - - if ( m_bMouseDown ) + case vr::VREvent_OverlayClosed: + case vr::VREvent_Quit: { - glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos ); - // We are based off normalized coords, so we need to fix the aspect ratio - // or we get different sensitivities on X and Y. - vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); - - vDelta *= float( cv_vr_trackpad_sensitivity ); - - wlserver_lock(); - wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp ); - wlserver_unlock(); + if ( bIsSteam ) + { + if ( !plane.IsSubview() ) + { + raise( SIGTERM ); + } + } + else + { + // How do we quit a game? + // Do we? + } + break; } - } - else - { - wlserver_lock(); - wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor ); - wlserver_unlock(); - } - break; - } - case vr::VREvent_MouseButtonUp: - case vr::VREvent_MouseButtonDown: - { - float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); - float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); - - uint64_t ulNow = get_time_in_nanos(); - - if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) - { - m_ulMouseDownTime = ulNow; - m_bMouseDown = true; - } - else - { - m_bMouseDown = false; - } - TouchClickMode eMode = GetTouchClickMode(); - if ( eMode == TouchClickModes::Trackpad ) - { - m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + case vr::VREvent_KeyboardCharInput: + { + if (m_pIME) + { + type_text(m_pIME, vrEvent.data.keyboard.cNewInput); + } + break; + } - if ( vrEvent.eventType == vr::VREvent_MouseButtonUp ) + case vr::VREvent_MouseMove: { - glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos ); - vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); - float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) ); + SetFocus( pConnector ); + float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); + float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); + + TouchClickMode eMode = GetTouchClickMode(); + // Always warp a cursor, even if it's invisible, so we get hover events. + bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor; - uint64_t ulClickTime = ulNow - m_ulMouseDownTime; - if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta ) + if ( eMode == TouchClickModes::Trackpad ) + { + glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos; + m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + + if ( m_bMouseDown ) + { + glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos ); + // We are based off normalized coords, so we need to fix the aspect ratio + // or we get different sensitivities on X and Y. + vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); + + vDelta *= float( cv_vr_trackpad_sensitivity ); + + wlserver_lock(); + wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp ); + wlserver_unlock(); + } + } + else { wlserver_lock(); - wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp ); + wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor ); wlserver_unlock(); + } + break; + } + case vr::VREvent_FocusEnter: + { + SetFocus( pConnector ); + break; + } + case vr::VREvent_MouseButtonUp: + case vr::VREvent_MouseButtonDown: + { + SetFocus( pConnector ); - sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 ); + float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); + float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); - wlserver_lock(); - wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp ); - wlserver_unlock(); + uint64_t ulNow = get_time_in_nanos(); + + if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) + { + m_ulMouseDownTime = ulNow; + m_bMouseDown = true; + } + else + { + m_bMouseDown = false; + } + + TouchClickMode eMode = GetTouchClickMode(); + if ( eMode == TouchClickModes::Trackpad ) + { + m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + + if ( vrEvent.eventType == vr::VREvent_MouseButtonUp ) + { + glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos ); + vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); + float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) ); + + uint64_t ulClickTime = ulNow - m_ulMouseDownTime; + if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta ) + { + wlserver_lock(); + wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp ); + wlserver_unlock(); + + sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 ); + + wlserver_lock(); + wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp ); + wlserver_unlock(); + } + else + { + m_vScreenStartTrackpadPos = m_vScreenTrackpadPos; + } + } } else { - m_vScreenStartTrackpadPos = m_vScreenTrackpadPos; + wlserver_lock(); + if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) + wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp ); + else + wlserver_touchup( 0, ++m_uFakeTimestamp ); + wlserver_unlock(); } + break; } - } - else - { - wlserver_lock(); - if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) - wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp ); - else - wlserver_touchup( 0, ++m_uFakeTimestamp ); - wlserver_unlock(); - } - break; - } - case vr::VREvent_ScrollSmooth: - { - float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed; - float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed; - wlserver_lock(); - wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp ); - wlserver_unlock(); - break; - } + case vr::VREvent_ScrollSmooth: + { + SetFocus( pConnector ); + float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed; + float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed; + wlserver_lock(); + wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp ); + wlserver_unlock(); + break; + } - case vr::VREvent_ButtonPress: - { - vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; + case vr::VREvent_ButtonPress: + { + SetFocus( pConnector ); + vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; - if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) - break; + if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) + break; - if (button == vr::k_EButton_Steam) - openvr_log.infof("STEAM button pressed."); - else - openvr_log.infof("QAM button pressed."); + if (button == vr::k_EButton_Steam) + openvr_log.infof("STEAM button pressed."); + else + openvr_log.infof("QAM button pressed."); - wlserver_open_steam_menu( button == vr::k_EButton_QAM ); - break; - } + wlserver_open_steam_menu( button == vr::k_EButton_QAM ); + break; + } - case vr::VREvent_OverlayShown: - case vr::VREvent_OverlayHidden: - { - // Only handle this for the base plane. - // Subviews can be hidden if we hide them ourselves, - // or for other reasons. - if ( !plane.IsSubview() ) - { - m_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; - m_bOverlayVisible.notify_all(); + case vr::VREvent_OverlayShown: + case vr::VREvent_OverlayHidden: + { + // Only handle this for the base plane. + // Subviews can be hidden if we hide them ourselves, + // or for other reasons. + if ( !plane.IsSubview() ) + { + if ( vrEvent.eventType == vr::VREvent_OverlayShown ) + m_nOverlaysVisible++; + else + m_nOverlaysVisible--; + + m_nOverlaysVisible.notify_all(); + assert( m_nOverlaysVisible >= 0 ); + } + break; + } + + default: + break; } - break; } - - default: - break; } } } @@ -1128,7 +1013,6 @@ namespace gamescope } } - CVROverlayConnector m_Connector; std::string m_szOverlayKey; const char *m_pchOverlayName = nullptr; const char *m_pchOverlayIcon = nullptr; @@ -1138,19 +1022,18 @@ namespace gamescope bool m_bEnableControlBarKeyboard = false; bool m_bEnableControlBarClose = false; bool m_bModal = false; - std::atomic m_bRelativeMouse = false; float m_flPhysicalWidth = 2.0f; float m_flPhysicalCurvature = 0.0f; float m_flPhysicalPreCurvePitch = 0.0f; float m_flScrollSpeed = 1.0f; - COpenVRPlane m_Planes[8]; + // TODO: Restructure and remove the need for this. wlserver_input_method *m_pIME = nullptr; OwningRc m_pBlackTexture; - std::atomic m_bOverlayVisible = { false }; + std::atomic m_nOverlaysVisible = { 0 }; vr::IVRIPCResourceManagerClient *m_pIPCResourceManager = nullptr; std::unordered_map> m_FormatModifiers; @@ -1162,8 +1045,311 @@ namespace gamescope // Fake "trackpad" tracking for the whole overlay panel. glm::vec2 m_vScreenTrackpadPos{}; glm::vec2 m_vScreenStartTrackpadPos{}; + + friend COpenVRConnector; + std::vector m_pActiveConnectors; + std::mutex m_mutActiveConnectors; + std::atomic m_pFocusConnector; }; + //////////////////// + // COpenVRConnector + //////////////////// + + COpenVRConnector::COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ) + : CBaseBackendConnector{ ulVirtualConnectorKey } + , m_pBackend{ pBackend } + , m_Planes{ this, this, this, this, this, this, this, this } + { + + } + + COpenVRConnector::~COpenVRConnector() + { + std::scoped_lock lock{ m_pBackend->m_mutActiveConnectors }; + + auto iter = m_pBackend->m_pActiveConnectors.begin(); + for ( ; iter != m_pBackend->m_pActiveConnectors.end(); iter++ ) + { + if ( *iter == this ) + break; + } + if ( iter != m_pBackend->m_pActiveConnectors.end() ) + m_pBackend->m_pActiveConnectors.erase( iter ); + + COpenVRConnector *pThis = this; + m_pBackend->m_pFocusConnector.compare_exchange_strong( pThis, nullptr ); + } + + GamescopeScreenType COpenVRConnector::GetScreenType() const + { + return GAMESCOPE_SCREEN_TYPE_INTERNAL; + } + GamescopePanelOrientation COpenVRConnector::GetCurrentOrientation() const + { + return GAMESCOPE_PANEL_ORIENTATION_0; + } + bool COpenVRConnector::SupportsHDR() const + { + return false; + } + bool COpenVRConnector::IsHDRActive() const + { + return false; + } + const BackendConnectorHDRInfo &COpenVRConnector::GetHDRInfo() const + { + return m_HDRInfo; + } + bool COpenVRConnector::IsVRRActive() const + { + return false; + } + std::span COpenVRConnector::GetModes() const + { + return std::span{}; + } + + bool COpenVRConnector::SupportsVRR() const + { + return false; + } + + std::span COpenVRConnector::GetRawEDID() const + { + return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; + } + std::span COpenVRConnector::GetValidDynamicRefreshRates() const + { + return std::span{}; + } + + void COpenVRConnector::GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + { + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; + } + + const char *COpenVRConnector::GetName() const + { + return "OpenVR"; + } + const char *COpenVRConnector::GetMake() const + { + return "Gamescope"; + } + const char *COpenVRConnector::GetModel() const + { + return "Virtual Display"; + } + + VBlankScheduleTime COpenVRConnector::FrameSync() + { + m_pBackend->WaitUntilVisible(); + + if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) + openvr_log.errorf( "WaitFrameSync failed!" ); + + uint64_t ulNow = get_time_in_nanos(); + return VBlankScheduleTime + { + .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now. + .ulScheduledWakeupPoint = ulNow, + }; + } + + int COpenVRConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { + bool bNeedsFullComposite = false; + + // TODO: Dedupe some of this composite check code between us and drm.cpp + bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); + + bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; + + bNeedsFullComposite |= cv_composite_force; + bNeedsFullComposite |= pFrameInfo->useFSRLayer0; + bNeedsFullComposite |= pFrameInfo->useNISLayer0; + bNeedsFullComposite |= pFrameInfo->blurLayer0; + bNeedsFullComposite |= bNeedsCompositeFromFilter; + bNeedsFullComposite |= g_bColorSliderInUse; + bNeedsFullComposite |= pFrameInfo->bFadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty(); + bNeedsFullComposite |= !m_pBackend->UsesModifiers(); + + if ( g_bOutputHDREnabled ) + bNeedsFullComposite |= g_bHDRItmEnable; + + if ( !m_pBackend->SupportsColorManagement() ) + bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); + + bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); + + if ( !bNeedsFullComposite ) + { + bool bNeedsBacking = true; + if ( pFrameInfo->layerCount >= 1 ) + { + if ( pFrameInfo->layers[0].isScreenSize() && ( !pFrameInfo->layers[0].hasAlpha() || cv_vr_transparent_backing ) ) + bNeedsBacking = false; + } + + uint32_t uCurrentPlane = 0; + if ( bNeedsBacking ) + { + COpenVRPlane *pPlane = &m_Planes[uCurrentPlane++]; + pPlane->Present( + OpenVRPlaneState + { + .pTexture = m_pBackend->GetBlackTexture(), + .flSrcWidth = double( g_nOutputWidth ), + .flSrcHeight = double( g_nOutputHeight ), + .nDstWidth = int32_t( g_nOutputWidth ), + .nDstHeight = int32_t( g_nOutputHeight ), + .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, + .bOpaque = !cv_vr_transparent_backing, + .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f, + } ); + } + + for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) + m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr ); + } + else + { + std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + if ( !oCompositeResult ) + { + openvr_log.errorf( "vulkan_composite failed" ); + return -EINVAL; + } + + vulkan_wait( *oCompositeResult, true ); + + FrameInfo_t::Layer_t compositeLayer{}; + compositeLayer.scale.x = 1.0; + compositeLayer.scale.y = 1.0; + compositeLayer.opacity = 1.0; + compositeLayer.zpos = g_zposBase; + + compositeLayer.tex = vulkan_get_last_output_image( false, false ); + compositeLayer.applyColorMgmt = false; + + compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; + compositeLayer.ctm = nullptr; + compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + GetPrimaryPlane()->Present( &compositeLayer ); + + for ( int i = 1; i < 8; i++ ) + m_Planes[i].Present( nullptr ); + } + + + GetVBlankTimer().UpdateWasCompositing( true ); + GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + m_pBackend->PollState(); + + return 0; + } + + /////////////////// + // INestedHints + /////////////////// + + void COpenVRConnector::SetCursorImage( std::shared_ptr info ) + { + } + void COpenVRConnector::SetRelativeMouseMode( bool bRelative ) + { + if ( bRelative != m_bRelativeMouse ) + { + for ( COpenVRPlane &plane : m_Planes ) + { + vr::VROverlay()->SetOverlayFlag( plane.GetOverlay(), vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && bRelative ); + } + m_bRelativeMouse = bRelative; + } + } + void COpenVRConnector::SetVisible( bool bVisible ) + { + vr::VROverlay()->SetOverlayFlag( GetPrimaryPlane()->GetOverlay(), vr::VROverlayFlags_VisibleInDashboard, bVisible ); + } + void COpenVRConnector::SetTitle( std::shared_ptr szTitle ) + { + if ( !m_pBackend->m_bExplicitOverlayName ) + vr::VROverlay()->SetOverlayName( GetPrimaryPlane()->GetOverlay(), szTitle ? szTitle->c_str() : m_pBackend->GetOverlayName() ); + } + void COpenVRConnector::SetIcon( std::shared_ptr> uIconPixels ) + { + if ( cv_vr_use_window_icons && uIconPixels && uIconPixels->size() >= 3 ) + { + const uint32_t uWidth = (*uIconPixels)[0]; + const uint32_t uHeight = (*uIconPixels)[1]; + + struct rgba_t + { + uint8_t r,g,b,a; + }; + + for ( uint32_t& val : *uIconPixels ) + { + rgba_t rgb = *((rgba_t*)&val); + std::swap(rgb.r, rgb.b); + val = *((uint32_t*)&rgb); + } + + vr::VROverlay()->SetOverlayRaw( GetPrimaryPlane()->GetOverlayThumbnail(), &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); + } + else if ( m_pBackend->GetOverlayIcon() ) + { + vr::VROverlay()->SetOverlayFromFile( GetPrimaryPlane()->GetOverlayThumbnail(), m_pBackend->GetOverlayIcon() ); + } + else + { + vr::VROverlay()->ClearOverlayTexture( GetPrimaryPlane()->GetOverlayThumbnail() ); + } + } + + void COpenVRConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + { + // Do nothing + } + + bool COpenVRConnector::UpdateEdid() + { + m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight ); + + return true; + } + + + bool COpenVRConnector::Init() + { + m_bNudgeToVisible = m_pBackend->ShouldNudgeToVisible(); + + for ( uint32_t i = 0; i < 8; i++ ) + { + bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); + if ( !bSuccess ) + return false; + } + + UpdateEdid(); + m_pBackend->HackUpdatePatchedEdid(); + + if ( g_bForceRelativeMouse ) + this->SetRelativeMouseMode( true ); + + return true; + } + ///////////////////////// // COpenVRFb ///////////////////////// @@ -1186,12 +1372,18 @@ namespace gamescope // COpenVRPlane ///////////////////////// - COpenVRPlane::COpenVRPlane( COpenVRBackend *pBackend ) - : m_pBackend{ pBackend } + COpenVRPlane::COpenVRPlane( COpenVRConnector *pConnector ) + : m_pConnector{ pConnector } + , m_pBackend{ pConnector->GetBackend() } { } COpenVRPlane::~COpenVRPlane() { + if ( m_hOverlayThumbnail != vr::k_ulOverlayHandleInvalid ) + vr::VROverlay()->DestroyOverlay( m_hOverlayThumbnail ); + + if ( m_hOverlay != vr::k_ulOverlayHandleInvalid ) + vr::VROverlay()->DestroyOverlay( m_hOverlay ); } bool COpenVRPlane::Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow ) @@ -1203,10 +1395,26 @@ namespace gamescope m_uSortOrder = pSiblingBelow->GetSortOrder() + 1; } + std::string sOverlayKey = m_pBackend->GetOverlayKey(); + + VirtualConnectorStrategy eStrategy = cv_backend_virtual_connector_strategy; + if ( !VirtualConnectorStrategyIsSingleOutput( eStrategy ) ) + { + uint64_t ulKey = m_pConnector->GetVirtualConnectorKey(); + bool bIsSteam = VirtualConnectorKeyIsSteam( ulKey ); + if ( !bIsSteam ) + { + sOverlayKey += ".app"; + sOverlayKey += std::to_string( m_pConnector->GetVirtualConnectorKey() ); + } + } + if ( !m_bIsSubview ) { + m_sDashboardOverlayKey = sOverlayKey; + vr::VROverlay()->CreateDashboardOverlay( - m_pBackend->GetOverlayKey(), + sOverlayKey.c_str(), m_pBackend->GetOverlayName(), &m_hOverlay, &m_hOverlayThumbnail ); @@ -1216,7 +1424,7 @@ namespace gamescope vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_pBackend->IsModal() ); vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && m_pBackend->IsRelativeMouse() ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && m_pConnector->IsRelativeMouse() ); vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_pBackend->GetPhysicalWidth() ); vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_pBackend->GetPhysicalCurvature() ); @@ -1233,7 +1441,7 @@ namespace gamescope } else { - std::string szSubviewName = m_pBackend->GetOverlayKey() + std::string(".layer") + std::to_string( (uintptr_t)this ); + std::string szSubviewName = sOverlayKey + std::string(".layer") + std::to_string( m_uSortOrder ); vr::VROverlay()->CreateSubviewOverlay( pParent->GetOverlay(), szSubviewName.c_str(), "Gamescope Layer", &m_hOverlay ); } @@ -1257,7 +1465,7 @@ namespace gamescope if ( m_pBackend->UsesModifiers() ) { - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, oState->bOpaque || !DRMFormatHasAlpha( oState->pTexture->drmFormat() ) ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, oState->bOpaque || !DRMFormatHasAlpha( oState->pTexture->drmFormat() ) || cv_vr_debug_force_opaque ); vr::HmdVector2_t vMouseScale = { @@ -1307,9 +1515,9 @@ namespace gamescope vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); } - if ( !m_bIsSubview && m_pBackend->ConsumeNudgeToVisible() ) + if ( !m_bIsSubview && m_pConnector->ConsumeNudgeToVisible() ) { - vr::VROverlay()->ShowDashboard( m_pBackend->GetOverlayKey() ); + vr::VROverlay()->ShowDashboard( m_sDashboardOverlayKey.c_str() ); } } else diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp index 6d50f8d34e..d79b60f67b 100644 --- a/src/Backends/SDLBackend.cpp +++ b/src/Backends/SDLBackend.cpp @@ -37,8 +37,6 @@ extern int g_nPreferredOutputHeight; namespace gamescope { - extern std::shared_ptr GetX11HostCursor(); - enum class SDLInitState { SDLInit_Waiting, @@ -57,7 +55,7 @@ namespace gamescope GAMESCOPE_SDL_EVENT_COUNT, }; - class CSDLConnector final : public IBackendConnector + class CSDLConnector final : public CBaseBackendConnector { public: CSDLConnector(); @@ -74,6 +72,7 @@ namespace gamescope virtual bool SupportsHDR() const override; virtual bool IsHDRActive() const override; virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; + virtual bool IsVRRActive() const override; virtual std::span GetModes() const override; virtual bool SupportsVRR() const override; @@ -99,6 +98,8 @@ namespace gamescope return "Virtual Display"; } + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + //-- SDL_Window *GetSDLWindow() const { return m_pWindow; } @@ -126,7 +127,6 @@ namespace gamescope virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override; virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; virtual bool PollState() override; @@ -139,7 +139,6 @@ namespace gamescope virtual IBackendConnector *GetCurrentConnector() override; virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override; - virtual bool IsVRRActive() const override; virtual bool SupportsPlaneHardwareCursor() const override; virtual bool SupportsTearing() const override; @@ -152,8 +151,6 @@ namespace gamescope virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; - virtual INestedHints *GetNestedHints() override; - /////////////////// // INestedHints /////////////////// @@ -249,7 +246,7 @@ namespace gamescope if ( !SDL_Vulkan_CreateSurface( m_pWindow, vulkan_get_instance(), &m_pVkSurface ) ) { - fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); + fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError () ); return false; } @@ -277,6 +274,10 @@ namespace gamescope { return m_HDRInfo; } + bool CSDLConnector::IsVRRActive() const + { + return false; + } std::span CSDLConnector::GetModes() const { return std::span{}; @@ -317,6 +318,27 @@ namespace gamescope } } + int CSDLConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { + // TODO: Resolve const crap + std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + if ( !oCompositeResult ) + return -EINVAL; + + vulkan_present_to_window(); + + // TODO: Hook up PresentationFeedback. + + // Wait for the composite result on our side *after* we + // commit the buffer to the compositor to avoid a bubble. + vulkan_wait( *oCompositeResult, true ); + + GetVBlankTimer().UpdateWasCompositing( true ); + GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + return 0; + } + //////////////// // CSDLBackend //////////////// @@ -364,26 +386,6 @@ namespace gamescope return true; } - int CSDLBackend::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) - { - // TODO: Resolve const crap - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); - if ( !oCompositeResult ) - return -EINVAL; - - vulkan_present_to_window(); - - // TODO: Hook up PresentationFeedback. - - // Wait for the composite result on our side *after* we - // commit the buffer to the compositor to avoid a bubble. - vulkan_wait( *oCompositeResult, true ); - - GetVBlankTimer().UpdateWasCompositing( true ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - return 0; - } void CSDLBackend::DirtyState( bool bForce, bool bForceModeset ) { } @@ -422,10 +424,6 @@ namespace gamescope return nullptr; } - bool CSDLBackend::IsVRRActive() const - { - return false; - } bool CSDLBackend::SupportsPlaneHardwareCursor() const { @@ -464,11 +462,6 @@ namespace gamescope return uvecSize; } - INestedHints *CSDLBackend::GetNestedHints() - { - return this; - } - /////////////////// // INestedHints /////////////////// @@ -498,6 +491,7 @@ namespace gamescope m_pApplicationIcon = uIconPixels; PushUserEvent( GAMESCOPE_SDL_EVENT_ICON ); } + void CSDLBackend::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) { if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) @@ -505,10 +499,6 @@ namespace gamescope else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) SDL_SetPrimarySelectionText(szContents->c_str()); } - std::shared_ptr CSDLBackend::GetHostCursor() - { - return GetX11HostCursor(); - } void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) { diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 08af8bca1b..ec28d28153 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -99,58 +99,6 @@ namespace gamescope class CWaylandBackend; class CWaylandFb; - class CWaylandConnector final : public IBackendConnector - { - public: - CWaylandConnector( CWaylandBackend *pBackend ); - bool UpdateEdid(); - - virtual ~CWaylandConnector(); - - ///////////////////// - // IBackendConnector - ///////////////////// - - virtual gamescope::GamescopeScreenType GetScreenType() const override; - virtual GamescopePanelOrientation GetCurrentOrientation() const override; - virtual bool SupportsHDR() const override; - virtual bool IsHDRActive() const override; - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; - virtual std::span GetModes() const override; - - virtual bool SupportsVRR() const override; - - virtual std::span GetRawEDID() const override; - virtual std::span GetValidDynamicRefreshRates() const override; - - virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; - - virtual const char *GetName() const override - { - return "Wayland"; - } - virtual const char *GetMake() const override - { - return "Gamescope"; - } - virtual const char *GetModel() const override - { - return "Virtual Display"; - } - private: - - friend CWaylandPlane; - - BackendConnectorHDRInfo m_HDRInfo{}; - displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709; - std::vector m_FakeEdid; - - CWaylandBackend *m_pBackend = nullptr; - }; - struct WaylandPlaneState { wl_buffer *pBuffer; @@ -185,7 +133,7 @@ namespace gamescope class CWaylandPlane { public: - CWaylandPlane( CWaylandBackend *pBackend ); + CWaylandPlane( CWaylandConnector *pBackend ); ~CWaylandPlane(); bool Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow ); @@ -258,16 +206,17 @@ namespace gamescope void Wayland_FractionalScale_PreferredScale( wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale ); static const wp_fractional_scale_v1_listener s_FractionalScaleListener; + CWaylandConnector *m_pConnector = nullptr; CWaylandBackend *m_pBackend = nullptr; CWaylandPlane *m_pParent = nullptr; wl_surface *m_pSurface = nullptr; wp_viewport *m_pViewport = nullptr; - libdecor_frame *m_pFrame = nullptr; - wl_subsurface *m_pSubsurface = nullptr; frog_color_managed_surface *m_pFrogColorManagedSurface = nullptr; xx_color_management_surface_v3 *m_pXXColorManagedSurface = nullptr; wp_fractional_scale_v1 *m_pFractionalScale = nullptr; + wl_subsurface *m_pSubsurface = nullptr; + libdecor_frame *m_pFrame = nullptr; libdecor_window_state m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; std::vector m_pOutputs; bool m_bNeedsDecorCommit = false; @@ -349,6 +298,93 @@ namespace gamescope int32_t nScale = 1; }; + + class CWaylandConnector final : public CBaseBackendConnector, public INestedHints + { + public: + CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ); + virtual ~CWaylandConnector(); + + bool UpdateEdid(); + bool Init(); + void SetFullscreen( bool bFullscreen ); // Thread safe, can be called from the input thread. + void UpdateFullscreenState(); + + + bool HostCompositorIsCurrentlyVRR() const { return m_bHostCompositorIsCurrentlyVRR; } + void SetHostCompositorIsCurrentlyVRR( bool bActive ) { m_bHostCompositorIsCurrentlyVRR = bActive; } + bool CurrentDisplaySupportsVRR() const { return HostCompositorIsCurrentlyVRR(); } + CWaylandBackend *GetBackend() const { return m_pBackend; } + + ///////////////////// + // IBackendConnector + ///////////////////// + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + + virtual gamescope::GamescopeScreenType GetScreenType() const override; + virtual GamescopePanelOrientation GetCurrentOrientation() const override; + virtual bool SupportsHDR() const override; + virtual bool IsHDRActive() const override; + virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; + virtual bool IsVRRActive() const override; + virtual std::span GetModes() const override; + + virtual bool SupportsVRR() const override; + + virtual std::span GetRawEDID() const override; + virtual std::span GetValidDynamicRefreshRates() const override; + + virtual void GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; + + virtual const char *GetName() const override + { + return "Wayland"; + } + virtual const char *GetMake() const override + { + return "Gamescope"; + } + virtual const char *GetModel() const override + { + return "Virtual Display"; + } + + virtual INestedHints *GetNestedHints() override + { + return this; + } + + /////////////////// + // INestedHints + /////////////////// + + virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetRelativeMouseMode( bool bRelative ) override; + virtual void SetVisible( bool bVisible ) override; + virtual void SetTitle( std::shared_ptr szTitle ) override; + virtual void SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; + private: + + friend CWaylandPlane; + + BackendConnectorHDRInfo m_HDRInfo{}; + displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709; + std::vector m_FakeEdid; + + CWaylandBackend *m_pBackend = nullptr; + + CWaylandPlane m_Planes[8]; + bool m_bVisible = true; + std::atomic m_bDesiredFullscreenState = { false }; + + bool m_bHostCompositorIsCurrentlyVRR = false; + }; + class CWaylandFb final : public CBaseBackendFb { public: @@ -505,7 +541,7 @@ namespace gamescope .relative_motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ), }; - class CWaylandBackend : public CBaseBackend, public INestedHints + class CWaylandBackend : public CBaseBackend { public: CWaylandBackend(); @@ -522,7 +558,6 @@ namespace gamescope virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override; virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; virtual bool PollState() override; @@ -535,7 +570,6 @@ namespace gamescope virtual IBackendConnector *GetCurrentConnector() override; virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override; - virtual bool IsVRRActive() const override; virtual bool SupportsPlaneHardwareCursor() const override; virtual bool SupportsTearing() const override; @@ -549,19 +583,8 @@ namespace gamescope virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; virtual void HackUpdatePatchedEdid() override; - virtual INestedHints *GetNestedHints() override; - - /////////////////// - // INestedHints - /////////////////// - - virtual void SetCursorImage( std::shared_ptr info ) override; - virtual void SetRelativeMouseMode( bool bRelative ) override; - virtual void SetVisible( bool bVisible ) override; - virtual void SetTitle( std::shared_ptr szTitle ) override; - virtual void SetIcon( std::shared_ptr> uIconPixels ) override; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; - virtual std::shared_ptr GetHostCursor() override; + virtual bool UsesVirtualConnectors() override; + virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; protected: virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; @@ -590,12 +613,8 @@ namespace gamescope xdg_toplevel_icon_manager_v1 *GetToplevelIconManager() const { return m_pToplevelIconManager; } libdecor *GetLibDecor() const { return m_pLibDecor; } - void SetFullscreen( bool bFullscreen ); // Thread safe, can be called from the input thread. void UpdateFullscreenState(); - bool HostCompositorIsCurrentlyVRR() const { return m_bHostCompositorIsCurrentlyVRR; } - void SetHostCompositorIsCurrentlyVRR( bool bActive ) { m_bHostCompositorIsCurrentlyVRR = bActive; } - WaylandOutputInfo *GetOutputInfo( wl_output *pOutput ) { auto iter = m_pOutputs.find( pOutput ); @@ -605,8 +624,8 @@ namespace gamescope return &iter->second; } - bool CurrentDisplaySupportsVRR() const { return HostCompositorIsCurrentlyVRR(); } wl_region *GetFullRegion() const { return m_pFullRegion; } + CWaylandFb *GetBlackFb() const { return m_BlackFb.get(); } private: @@ -642,9 +661,6 @@ namespace gamescope CWaylandInputThread m_InputThread; - CWaylandConnector m_Connector; - CWaylandPlane m_Planes[8]; - wl_display *m_pDisplay = nullptr; wl_shm *m_pShm = nullptr; wl_compositor *m_pCompositor = nullptr; @@ -665,6 +681,9 @@ namespace gamescope wp_fractional_scale_manager_v1 *m_pFractionalScaleManager = nullptr; xdg_toplevel_icon_manager_v1 *m_pToplevelIconManager = nullptr; + // TODO: Restructure and remove the need for this. + std::shared_ptr m_pFocusConnector; + struct { std::vector ePrimaries; @@ -698,11 +717,6 @@ namespace gamescope wl_surface *m_pCursorSurface = nullptr; std::shared_ptr m_pDefaultCursorInfo; wl_surface *m_pDefaultCursorSurface = nullptr; - - bool m_bVisible = true; - std::atomic m_bDesiredFullscreenState = { false }; - - bool m_bHostCompositorIsCurrentlyVRR = false; }; const wl_registry_listener CWaylandBackend::s_RegistryListener = { @@ -813,8 +827,10 @@ namespace gamescope // CWaylandConnector ////////////////// - CWaylandConnector::CWaylandConnector( CWaylandBackend *pBackend ) - : m_pBackend( pBackend ) + CWaylandConnector::CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ) + : CBaseBackendConnector{ ulVirtualConnectorKey } + , m_pBackend( pBackend ) + , m_Planes{ this, this, this, this, this, this, this, this } { m_HDRInfo.bAlwaysPatchEdid = true; } @@ -830,80 +846,385 @@ namespace gamescope return true; } - GamescopeScreenType CWaylandConnector::GetScreenType() const - { - return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation() const - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - bool CWaylandConnector::SupportsHDR() const - { - return GetHDRInfo().IsHDR10(); - } - bool CWaylandConnector::IsHDRActive() const - { - // XXX: blah - return false; - } - const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo() const - { - return m_HDRInfo; - } - std::span CWaylandConnector::GetModes() const + bool CWaylandConnector::Init() { - return std::span{}; - } + for ( uint32_t i = 0; i < 8; i++ ) + { + bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); + if ( !bSuccess ) + return false; + } - bool CWaylandConnector::SupportsVRR() const - { - return m_pBackend->CurrentDisplaySupportsVRR(); + if ( g_bFullscreen ) + { + m_bDesiredFullscreenState = true; + g_bFullscreen = false; + UpdateFullscreenState(); + } + + UpdateEdid(); + m_pBackend->HackUpdatePatchedEdid(); + + if ( g_bForceRelativeMouse ) + this->SetRelativeMouseMode( true ); + + return true; } - std::span CWaylandConnector::GetRawEDID() const + void CWaylandConnector::SetFullscreen( bool bFullscreen ) { - return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; + m_bDesiredFullscreenState = bFullscreen; } - std::span CWaylandConnector::GetValidDynamicRefreshRates() const + + void CWaylandConnector::UpdateFullscreenState() { - return std::span{}; + if ( !m_bVisible ) + g_bFullscreen = false; + + if ( m_bDesiredFullscreenState != g_bFullscreen && m_bVisible ) + { + if ( m_bDesiredFullscreenState ) + libdecor_frame_set_fullscreen( m_Planes[0].GetFrame(), nullptr ); + else + libdecor_frame_unset_fullscreen( m_Planes[0].GetFrame() ); + + g_bFullscreen = m_bDesiredFullscreenState; + } } - void CWaylandConnector::GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + int CWaylandConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) { - *displayColorimetry = m_DisplayColorimetry; - *displayEOTF = EOTF_Gamma22; + UpdateFullscreenState(); - if ( bHDR10 && GetHDRInfo().IsHDR10() ) + bool bNeedsFullComposite = false; + + if ( !m_bVisible ) { - // For HDR10 output, expected content colorspace != native colorspace. - *outputEncodingColorimetry = displaycolorimetry_2020; - *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF; + uint32_t uCurrentPlane = 0; + for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) + m_Planes[uCurrentPlane++].Present( nullptr ); } else { - // We always use default 'perceptual' intent, so - // this should be correct for SDR content. - *outputEncodingColorimetry = m_DisplayColorimetry; - *outputEncodingEOTF = EOTF_Gamma22; - } - } - - ////////////////// - // CWaylandPlane - ////////////////// + // TODO: Dedupe some of this composite check code between us and drm.cpp + bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); - CWaylandPlane::CWaylandPlane( CWaylandBackend *pBackend ) - : m_pBackend( pBackend ) - { - } + bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; - CWaylandPlane::~CWaylandPlane() - { + bNeedsFullComposite |= cv_composite_force; + bNeedsFullComposite |= pFrameInfo->useFSRLayer0; + bNeedsFullComposite |= pFrameInfo->useNISLayer0; + bNeedsFullComposite |= pFrameInfo->blurLayer0; + bNeedsFullComposite |= bNeedsCompositeFromFilter; + bNeedsFullComposite |= g_bColorSliderInUse; + bNeedsFullComposite |= pFrameInfo->bFadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty(); + + if ( g_bOutputHDREnabled ) + bNeedsFullComposite |= g_bHDRItmEnable; + + if ( !m_pBackend->SupportsColorManagement() ) + bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); + + bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); + + if ( !bNeedsFullComposite ) + { + bool bNeedsBacking = true; + if ( pFrameInfo->layerCount >= 1 ) + { + if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() ) + bNeedsBacking = false; + } + + uint32_t uCurrentPlane = 0; + if ( bNeedsBacking ) + { + m_pBackend->GetBlackFb()->OnCompositorAcquire(); + + CWaylandPlane *pPlane = &m_Planes[uCurrentPlane++]; + pPlane->Present( + WaylandPlaneState + { + .pBuffer = m_pBackend->GetBlackFb()->GetHostBuffer(), + .flSrcWidth = 1.0, + .flSrcHeight = 1.0, + .nDstWidth = int32_t( g_nOutputWidth ), + .nDstHeight = int32_t( g_nOutputHeight ), + .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, + .bOpaque = true, + .uFractionalScale = pPlane->GetScale(), + } ); + } + + for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) + m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr ); + } + else + { + std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + + if ( !oCompositeResult ) + { + xdg_log.errorf( "vulkan_composite failed" ); + return -EINVAL; + } + + vulkan_wait( *oCompositeResult, true ); + + FrameInfo_t::Layer_t compositeLayer{}; + compositeLayer.scale.x = 1.0; + compositeLayer.scale.y = 1.0; + compositeLayer.opacity = 1.0; + compositeLayer.zpos = g_zposBase; + + compositeLayer.tex = vulkan_get_last_output_image( false, false ); + compositeLayer.applyColorMgmt = false; + + compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; + compositeLayer.ctm = nullptr; + compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + m_Planes[0].Present( &compositeLayer ); + + for ( int i = 1; i < 8; i++ ) + m_Planes[i].Present( nullptr ); + } + } + + for ( int i = 7; i >= 0; i-- ) + m_Planes[i].Commit(); + + wl_display_flush( m_pBackend->GetDisplay() ); + + GetVBlankTimer().UpdateWasCompositing( bNeedsFullComposite ); + GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + m_pBackend->PollState(); + + return 0; + } + + GamescopeScreenType CWaylandConnector::GetScreenType() const + { + return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + } + GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation() const + { + return GAMESCOPE_PANEL_ORIENTATION_0; + } + bool CWaylandConnector::SupportsHDR() const + { + return GetHDRInfo().IsHDR10(); + } + bool CWaylandConnector::IsHDRActive() const + { + // XXX: blah + return false; + } + const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo() const + { + return m_HDRInfo; + } + bool CWaylandConnector::IsVRRActive() const + { + return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR; + } + std::span CWaylandConnector::GetModes() const + { + return std::span{}; + } + + bool CWaylandConnector::SupportsVRR() const + { + return CurrentDisplaySupportsVRR(); + } + + std::span CWaylandConnector::GetRawEDID() const + { + return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; + } + std::span CWaylandConnector::GetValidDynamicRefreshRates() const + { + return std::span{}; + } + + void CWaylandConnector::GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + { + *displayColorimetry = m_DisplayColorimetry; + *displayEOTF = EOTF_Gamma22; + + if ( bHDR10 && GetHDRInfo().IsHDR10() ) + { + // For HDR10 output, expected content colorspace != native colorspace. + *outputEncodingColorimetry = displaycolorimetry_2020; + *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF; + } + else + { + // We always use default 'perceptual' intent, so + // this should be correct for SDR content. + *outputEncodingColorimetry = m_DisplayColorimetry; + *outputEncodingEOTF = EOTF_Gamma22; + } + } + + + void CWaylandConnector::SetCursorImage( std::shared_ptr info ) + { + // XXX(strategy): FIXME FIXME FIXME +#if 0 + m_pCursorInfo = info; + + if ( m_pCursorSurface ) + { + wl_surface_destroy( m_pCursorSurface ); + m_pCursorSurface = nullptr; + } + + m_pCursorSurface = CursorInfoToSurface( info ); + + UpdateCursor(); +#endif + } + void CWaylandConnector::SetRelativeMouseMode( bool bRelative ) + { + // XXX(strategy): FIXME FIXME FIXME +#if 0 + if ( !m_pPointer ) + return; + + if ( !!bRelative != !!m_pLockedPointer ) + { + if ( m_pLockedPointer ) + { + assert( m_pRelativePointer ); + + zwp_locked_pointer_v1_destroy( m_pLockedPointer ); + m_pLockedPointer = nullptr; + + zwp_relative_pointer_v1_destroy( m_pRelativePointer ); + m_pRelativePointer = nullptr; + } + else + { + assert( !m_pRelativePointer ); + + m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( m_pPointerConstraints, m_Planes[0].GetSurface(), m_pPointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT ); + m_pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer ); + } + + m_InputThread.SetRelativePointer( bRelative ); + + UpdateCursor(); + } +#endif + } + void CWaylandConnector::SetVisible( bool bVisible ) + { + if ( m_bVisible == bVisible ) + return; + + m_bVisible = bVisible; + force_repaint(); + } + void CWaylandConnector::SetTitle( std::shared_ptr pAppTitle ) + { + std::string szTitle = pAppTitle ? *pAppTitle : "gamescope"; + if ( g_bGrabbed ) + szTitle += " (grabbed)"; + libdecor_frame_set_title( m_Planes[0].GetFrame(), szTitle.c_str() ); + } + void CWaylandConnector::SetIcon( std::shared_ptr> uIconPixels ) + { + // XXX(strategy): FIXME FIXME FIXME +#if 0 + if ( !m_pToplevelIconManager ) + return; + + if ( uIconPixels && uIconPixels->size() >= 3 ) + { + xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pToplevelIconManager ); + if ( !pIcon ) + { + xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" ); + return; + } + defer( xdg_toplevel_icon_v1_destroy( pIcon ) ); + + const uint32_t uWidth = ( *uIconPixels )[0]; + const uint32_t uHeight = ( *uIconPixels )[1]; + + const uint32_t uStride = uWidth * 4; + const uint32_t uSize = uStride * uHeight; + int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[2] ); + if ( nFd < 0 ) + { + xdg_log.errorf( "Failed to create/map shm buffer" ); + return; + } + defer( close( nFd ) ); + + wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize ); + defer( wl_shm_pool_destroy( pPool ) ); + + wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 ); + defer( wl_buffer_destroy( pBuffer ) ); + + xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 ); + + xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), pIcon ); + } + else + { + xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), nullptr ); + } +#endif + } + + void CWaylandConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + { + // Do nothing + } + + ////////////////// + // CWaylandPlane + ////////////////// + + CWaylandPlane::CWaylandPlane( CWaylandConnector *pConnector ) + : m_pConnector{ pConnector } + , m_pBackend{ pConnector->GetBackend() } + { + } + + CWaylandPlane::~CWaylandPlane() + { + std::scoped_lock lock{ m_PlaneStateLock }; + + m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; + m_pOutputs.clear(); + m_bNeedsDecorCommit = false; + + m_oCurrentPlaneState = std::nullopt; + + if ( m_pFrame ) + libdecor_frame_unref( m_pFrame ); // Ew. + + if ( m_pSubsurface ) + wl_subsurface_destroy( m_pSubsurface ); + if ( m_pFractionalScale ) + wp_fractional_scale_v1_destroy( m_pFractionalScale ); + if ( m_pXXColorManagedSurface ) + xx_color_management_surface_v3_destroy( m_pXXColorManagedSurface ); + if ( m_pFrogColorManagedSurface ) + frog_color_managed_surface_destroy( m_pFrogColorManagedSurface ); + if ( m_pViewport ) + wp_viewport_destroy( m_pViewport ); + if ( m_pSurface ) + wl_surface_destroy( m_pSurface ); } bool CWaylandPlane::Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow ) @@ -1122,7 +1443,7 @@ namespace gamescope if ( m_pParent ) return; - if ( !m_pBackend->HostCompositorIsCurrentlyVRR() ) + if ( !m_pConnector->HostCompositorIsCurrentlyVRR() ) return; if ( m_pOutputs.empty() ) @@ -1140,6 +1461,7 @@ namespace gamescope if ( nLargestRefreshRateMhz && nLargestRefreshRateMhz != g_nOutputRefresh ) { + // TODO(strategy): We should pick the largest refresh rate. xdg_log.infof( "Changed refresh to: %.3fhz", ConvertmHzToHz( (float) nLargestRefreshRateMhz ) ); g_nOutputRefresh = nLargestRefreshRateMhz; } @@ -1174,6 +1496,10 @@ namespace gamescope int nWidth, nHeight; if ( !libdecor_configuration_get_content_size( pConfiguration, m_pFrame, &nWidth, &nHeight ) ) { + // XXX(virtual connector): Move g_nOutputWidth etc to connector. + // Right now we are doubling this up when we should not be. + // + // Which is causing problems. nWidth = WaylandScaleToLogical( g_nOutputWidth, uScale ); nHeight = WaylandScaleToLogical( g_nOutputHeight, uScale ); } @@ -1214,11 +1540,11 @@ namespace gamescope g_nOutputRefresh = nRefresh; } - m_pBackend->SetHostCompositorIsCurrentlyVRR( false ); + m_pConnector->SetHostCompositorIsCurrentlyVRR( false ); } else { - m_pBackend->SetHostCompositorIsCurrentlyVRR( true ); + m_pConnector->SetHostCompositorIsCurrentlyVRR( true ); UpdateVRRRefreshRate(); } @@ -1252,14 +1578,14 @@ namespace gamescope uint32_t uMinLuminance, uint32_t uMaxFullFrameLuminance ) { - auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo; + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ); pHDRInfo->eOutputEncodingEOTF = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ) ? EOTF_PQ : EOTF_Gamma22; pHDRInfo->uMaxContentLightLevel = uMaxLuminance; pHDRInfo->uMaxFrameAverageLuminance = uMaxFullFrameLuminance; pHDRInfo->uMinContentLightLevel = uMinLuminance; - auto *pDisplayColorimetry = &m_pBackend->m_Connector.m_DisplayColorimetry; + auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry; pDisplayColorimetry->primaries.r = glm::vec2{ uOutputDisplayPrimaryRedX * 0.00002f, uOutputDisplayPrimaryRedY * 0.00002f }; pDisplayColorimetry->primaries.g = glm::vec2{ uOutputDisplayPrimaryGreenX * 0.00002f, uOutputDisplayPrimaryGreenY * 0.00002f }; pDisplayColorimetry->primaries.b = glm::vec2{ uOutputDisplayPrimaryBlueX * 0.00002f, uOutputDisplayPrimaryBlueY * 0.00002f }; @@ -1323,7 +1649,7 @@ namespace gamescope } void CWaylandPlane::Wayland_XXImageDescriptionInfo_TFNamed( xx_image_description_info_v3 *pImageDescInfo, uint32_t uTF) { - auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo; + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && uTF == XX_COLOR_MANAGER_V3_TRANSFER_FUNCTION_ST2084_PQ ); pHDRInfo->eOutputEncodingEOTF = ( cv_hdr_enabled && uTF == XX_COLOR_MANAGER_V3_TRANSFER_FUNCTION_ST2084_PQ ) ? EOTF_PQ : EOTF_Gamma22; } @@ -1333,7 +1659,7 @@ namespace gamescope } void CWaylandPlane::Wayland_XXImageDescriptionInfo_TargetPrimaries( xx_image_description_info_v3 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY ) { - auto *pDisplayColorimetry = &m_pBackend->m_Connector.m_DisplayColorimetry; + auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry; pDisplayColorimetry->primaries.r = glm::vec2{ nRedX / 10000.0f, nRedY / 10000.0f }; pDisplayColorimetry->primaries.g = glm::vec2{ nGreenX / 10000.0f, nGreenY / 10000.0f }; pDisplayColorimetry->primaries.b = glm::vec2{ nBlueX / 10000.0f, nBlueY / 10000.0f }; @@ -1345,12 +1671,12 @@ namespace gamescope } void CWaylandPlane::Wayland_XXImageDescriptionInfo_Target_MaxCLL( xx_image_description_info_v3 *pImageDescInfo, uint32_t uMaxCLL ) { - auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo; + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->uMaxContentLightLevel = uMaxCLL; } void CWaylandPlane::Wayland_XXImageDescriptionInfo_Target_MaxFALL( xx_image_description_info_v3 *pImageDescInfo, uint32_t uMaxFALL ) { - auto *pHDRInfo = &m_pBackend->m_Connector.m_HDRInfo; + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->uMaxFrameAverageLuminance = uMaxFALL; } @@ -1382,8 +1708,6 @@ namespace gamescope }; CWaylandBackend::CWaylandBackend() - : m_Connector( this ) - , m_Planes{ this, this, this, this, this, this, this, this } { } @@ -1497,29 +1821,11 @@ namespace gamescope return true; } - bool CWaylandBackend::PostInit() - { - m_pFullRegion = wl_compositor_create_region( m_pCompositor ); - wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX ); - - for ( uint32_t i = 0; i < 8; i++ ) - m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); - - m_Connector.UpdateEdid(); - this->HackUpdatePatchedEdid(); - - if ( cv_hdr_enabled && m_Connector.GetHDRInfo().bExposeHDRSupport ) - { - setenv( "DXVK_HDR", "1", false ); - } - - if ( g_bFullscreen ) - { - m_bDesiredFullscreenState = true; - g_bFullscreen = false; - UpdateFullscreenState(); - } - + bool CWaylandBackend::PostInit() + { + m_pFullRegion = wl_compositor_create_region( m_pCompositor ); + wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX ); + if ( m_pSinglePixelBufferManager ) { wl_buffer *pBlackBuffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( m_pSinglePixelBufferManager, 0, 0, 0, ~0u ); @@ -1546,9 +1852,6 @@ namespace gamescope m_pDefaultCursorInfo = GetX11HostCursor(); m_pDefaultCursorSurface = CursorInfoToSurface( m_pDefaultCursorInfo ); - if ( g_bForceRelativeMouse ) - this->SetRelativeMouseMode( true ); - return true; } @@ -1602,118 +1905,6 @@ namespace gamescope return true; } - int CWaylandBackend::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) - { - UpdateFullscreenState(); - - bool bNeedsFullComposite = false; - - if ( !m_bVisible ) - { - uint32_t uCurrentPlane = 0; - for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) - m_Planes[uCurrentPlane++].Present( nullptr ); - } - else - { - // TODO: Dedupe some of this composite check code between us and drm.cpp - bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); - - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; - - bNeedsFullComposite |= cv_composite_force; - bNeedsFullComposite |= pFrameInfo->useFSRLayer0; - bNeedsFullComposite |= pFrameInfo->useNISLayer0; - bNeedsFullComposite |= pFrameInfo->blurLayer0; - bNeedsFullComposite |= bNeedsCompositeFromFilter; - bNeedsFullComposite |= g_bColorSliderInUse; - bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - - if ( g_bOutputHDREnabled ) - bNeedsFullComposite |= g_bHDRItmEnable; - - if ( !SupportsColorManagement() ) - bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); - - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); - - if ( !bNeedsFullComposite ) - { - bool bNeedsBacking = true; - if ( pFrameInfo->layerCount >= 1 ) - { - if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() ) - bNeedsBacking = false; - } - - uint32_t uCurrentPlane = 0; - if ( bNeedsBacking ) - { - m_BlackFb->OnCompositorAcquire(); - - CWaylandPlane *pPlane = &m_Planes[uCurrentPlane++]; - pPlane->Present( - WaylandPlaneState - { - .pBuffer = m_BlackFb->GetHostBuffer(), - .flSrcWidth = 1.0, - .flSrcHeight = 1.0, - .nDstWidth = int32_t( g_nOutputWidth ), - .nDstHeight = int32_t( g_nOutputHeight ), - .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, - .bOpaque = true, - .uFractionalScale = pPlane->GetScale(), - } ); - } - - for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) - m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr ); - } - else - { - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); - - if ( !oCompositeResult ) - { - xdg_log.errorf( "vulkan_composite failed" ); - return -EINVAL; - } - - vulkan_wait( *oCompositeResult, true ); - - FrameInfo_t::Layer_t compositeLayer{}; - compositeLayer.scale.x = 1.0; - compositeLayer.scale.y = 1.0; - compositeLayer.opacity = 1.0; - compositeLayer.zpos = g_zposBase; - - compositeLayer.tex = vulkan_get_last_output_image( false, false ); - compositeLayer.applyColorMgmt = false; - - compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; - compositeLayer.ctm = nullptr; - compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - m_Planes[0].Present( &compositeLayer ); - - for ( int i = 1; i < 8; i++ ) - m_Planes[i].Present( nullptr ); - } - } - - for ( int i = 7; i >= 0; i-- ) - m_Planes[i].Commit(); - - wl_display_flush( m_pDisplay ); - - GetVBlankTimer().UpdateWasCompositing( bNeedsFullComposite ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - this->PollState(); - - return 0; - } void CWaylandBackend::DirtyState( bool bForce, bool bForceModeset ) { } @@ -1808,19 +1999,15 @@ namespace gamescope IBackendConnector *CWaylandBackend::GetCurrentConnector() { - return &m_Connector; + return m_pFocusConnector.get(); } IBackendConnector *CWaylandBackend::GetConnector( GamescopeScreenType eScreenType ) { if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return &m_Connector; + return GetCurrentConnector(); return nullptr; } - bool CWaylandBackend::IsVRRActive() const - { - return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR; - } bool CWaylandBackend::SupportsPlaneHardwareCursor() const { @@ -1846,7 +2033,7 @@ namespace gamescope bool CWaylandBackend::SupportsExplicitSync() const { return true; - } + } bool CWaylandBackend::IsVisible() const { @@ -1863,12 +2050,36 @@ namespace gamescope if ( !GetCurrentConnector() ) return; + // XXX: We should do this a better way that handles per-window and appid stuff + // down the line + if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport ) + { + setenv( "DXVK_HDR", "1", true ); + } + else + { + setenv( "DXVK_HDR", "0", true ); + } + WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false ); } - INestedHints *CWaylandBackend::GetNestedHints() + bool CWaylandBackend::UsesVirtualConnectors() + { + return true; + } + std::shared_ptr CWaylandBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) { - return this; + std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey ); + if ( !m_pFocusConnector ) + m_pFocusConnector = pConnector; + + if ( !pConnector->Init() ) + { + return nullptr; + } + + return pConnector; } /////////////////// @@ -1901,118 +2112,6 @@ namespace gamescope return nFd; } - void CWaylandBackend::SetCursorImage( std::shared_ptr info ) - { - m_pCursorInfo = info; - - if ( m_pCursorSurface ) - { - wl_surface_destroy( m_pCursorSurface ); - m_pCursorSurface = nullptr; - } - - m_pCursorSurface = CursorInfoToSurface( info ); - - UpdateCursor(); - } - void CWaylandBackend::SetRelativeMouseMode( bool bRelative ) - { - if ( !m_pPointer ) - return; - - if ( !!bRelative != !!m_pLockedPointer ) - { - if ( m_pLockedPointer ) - { - assert( m_pRelativePointer ); - - zwp_locked_pointer_v1_destroy( m_pLockedPointer ); - m_pLockedPointer = nullptr; - - zwp_relative_pointer_v1_destroy( m_pRelativePointer ); - m_pRelativePointer = nullptr; - } - else - { - assert( !m_pRelativePointer ); - - m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( m_pPointerConstraints, m_Planes[0].GetSurface(), m_pPointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT ); - m_pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer ); - } - - m_InputThread.SetRelativePointer( bRelative ); - - UpdateCursor(); - } - } - void CWaylandBackend::SetVisible( bool bVisible ) - { - if ( m_bVisible == bVisible ) - return; - - m_bVisible = bVisible; - force_repaint(); - } - void CWaylandBackend::SetTitle( std::shared_ptr pAppTitle ) - { - std::string szTitle = pAppTitle ? *pAppTitle : "gamescope"; - if ( g_bGrabbed ) - szTitle += " (grabbed)"; - libdecor_frame_set_title( m_Planes[0].GetFrame(), szTitle.c_str() ); - } - void CWaylandBackend::SetIcon( std::shared_ptr> uIconPixels ) - { - if ( !m_pToplevelIconManager ) - return; - - if ( uIconPixels && uIconPixels->size() >= 3 ) - { - xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pToplevelIconManager ); - if ( !pIcon ) - { - xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" ); - return; - } - defer( xdg_toplevel_icon_v1_destroy( pIcon ) ); - - const uint32_t uWidth = ( *uIconPixels )[0]; - const uint32_t uHeight = ( *uIconPixels )[1]; - - const uint32_t uStride = uWidth * 4; - const uint32_t uSize = uStride * uHeight; - int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[2] ); - if ( nFd < 0 ) - { - xdg_log.errorf( "Failed to create/map shm buffer" ); - return; - } - defer( close( nFd ) ); - - wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize ); - defer( wl_shm_pool_destroy( pPool ) ); - - wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 ); - defer( wl_buffer_destroy( pBuffer ) ); - - xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 ); - - xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), pIcon ); - } - else - { - xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), nullptr ); - } - } - void CWaylandBackend::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) - { - // Do nothing - } - - std::shared_ptr CWaylandBackend::GetHostCursor() - { - return m_pDefaultCursorInfo; - } - void CWaylandBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) { // Do nothing. @@ -2074,27 +2173,6 @@ namespace gamescope } } - void CWaylandBackend::SetFullscreen( bool bFullscreen ) - { - m_bDesiredFullscreenState = bFullscreen; - } - - void CWaylandBackend::UpdateFullscreenState() - { - if ( !m_bVisible ) - g_bFullscreen = false; - - if ( m_bDesiredFullscreenState != g_bFullscreen && m_bVisible ) - { - if ( m_bDesiredFullscreenState ) - libdecor_frame_set_fullscreen( m_Planes[0].GetFrame(), nullptr ); - else - libdecor_frame_unset_fullscreen( m_Planes[0].GetFrame() ); - - g_bFullscreen = m_bDesiredFullscreenState; - } - } - ///////////////////// // Wayland Callbacks ///////////////////// @@ -2474,7 +2552,7 @@ namespace gamescope { if ( !bPressed ) { - m_pBackend->SetFullscreen( !g_bFullscreen ); + static_cast< CWaylandConnector * >( m_pBackend->GetCurrentConnector() )->SetFullscreen( !g_bFullscreen ); } return; } diff --git a/src/backend.cpp b/src/backend.cpp index 8a6bbe8ed9..c7c1f0d419 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -12,6 +12,8 @@ extern bool env_to_bool(const char *env); namespace gamescope { + ConVar cv_backend_virtual_connector_strategy( "backend_virtual_connector_strategy", VirtualConnectorStrategies::SingleApplication ); + ///////////// // IBackend ///////////// @@ -122,6 +124,17 @@ namespace gamescope } } + ///////////////////////// + // CBaseBackendConnector + ///////////////////////// + + VBlankScheduleTime CBaseBackendConnector::FrameSync() + { + VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); + sleep_until_nanos( schedule.ulScheduledWakeupPoint ); + return schedule; + } + ///////////////// // CBaseBackend ///////////////// @@ -132,18 +145,6 @@ namespace gamescope return bForceTimerFd; } - INestedHints *CBaseBackend::GetNestedHints() - { - return nullptr; - } - - VBlankScheduleTime CBaseBackend::FrameSync() - { - VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); - sleep_until_nanos( schedule.ulScheduledWakeupPoint ); - return schedule; - } - ConVar cv_touch_external_display_trackpad( "touch_external_display_trackpad", false, "If we are using an external display, should we treat the internal display's touch as a trackpad insteaad?" ); ConVar cv_touch_click_mode( "touch_click_mode", TouchClickModes::Left, "The default action to perform on touch." ); TouchClickMode CBaseBackend::GetTouchClickMode() @@ -161,7 +162,6 @@ namespace gamescope void CBaseBackend::DumpDebugInfo() { console_log.infof( "Uses Modifiers: %s", this->UsesModifiers() ? "true" : "false" ); - console_log.infof( "VRR Active: %s", this->IsVRRActive() ? "true" : "false" ); console_log.infof( "Supports Plane Hardware Cursor: %s (not relevant for nested backends)", this->SupportsPlaneHardwareCursor() ? "true" : "false" ); console_log.infof( "Supports Tearing: %s", this->SupportsTearing() ? "true" : "false" ); console_log.infof( "Uses Vulkan Swapchain: %s", this->UsesVulkanSwapchain() ? "true" : "false" ); @@ -169,11 +169,21 @@ namespace gamescope console_log.infof( "Supports Explicit Sync: %s", this->SupportsExplicitSync() ? "true" : "false" ); console_log.infof( "Current Screen Type: %s", this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ? "Internal" : "External" ); console_log.infof( "Is Visible: %s", this->IsVisible() ? "true" : "false" ); - console_log.infof( "Is Nested: %s", this->GetNestedHints() != nullptr ? "true" : "false" ); console_log.infof( "Needs Frame Sync: %s", this->NeedsFrameSync() ? "true" : "false" ); - console_log.infof( "Total Presents Queued: %lu", this->PresentationFeedback().TotalPresentsQueued() ); - console_log.infof( "Total Presents Completed: %lu", this->PresentationFeedback().TotalPresentsCompleted() ); - console_log.infof( "Current Presents In Flight: %lu", this->PresentationFeedback().CurrentPresentsInFlight() ); + console_log.infof( "VRR Active: %s", this->GetCurrentConnector()->IsVRRActive() ? "true" : "false" ); + console_log.infof( "Total Presents Queued: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsQueued() ); + console_log.infof( "Total Presents Completed: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsCompleted() ); + console_log.infof( "Current Presents In Flight: %lu", this->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() ); + } + + bool CBaseBackend::UsesVirtualConnectors() + { + return false; + } + std::shared_ptr CBaseBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) + { + assert( false ); + return nullptr; } ConCommand cc_backend_info( "backend_info", "Dump debug info about the backend state", diff --git a/src/backend.h b/src/backend.h index dd295f4ac0..99c2d37f75 100644 --- a/src/backend.h +++ b/src/backend.h @@ -15,16 +15,57 @@ #include #include #include +#include struct wlr_buffer; struct wlr_dmabuf_attributes; struct FrameInfo_t; +extern bool steamMode; + namespace gamescope { struct VBlankScheduleTime; class BackendBlob; + class INestedHints; + + namespace VirtualConnectorStrategies + { + enum VirtualConnectorStrategy : uint32_t + { + SingleApplication = 0x1, + SteamControlled = 0x2, + PerAppId = 0x4, + PerWindow = 0x8, + + Count, + }; + } + using VirtualConnectorStrategy = VirtualConnectorStrategies::VirtualConnectorStrategy; + using VirtualConnectorKey_t = uint64_t; + extern ConVar cv_backend_virtual_connector_strategy; + + static constexpr bool VirtualConnectorStrategyIsSingleOutput( VirtualConnectorStrategy eStrategy ) + { + return eStrategy == VirtualConnectorStrategies::SingleApplication || eStrategy == VirtualConnectorStrategies::SteamControlled; + } + + static inline bool VirtualConnectorIsSingleOutput() + { + return VirtualConnectorStrategyIsSingleOutput( cv_backend_virtual_connector_strategy ); + } + + static inline bool VirtualConnectorInSteamPerAppState() + { + return steamMode && cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::PerAppId; + } + + static inline bool VirtualConnectorKeyIsSteam( VirtualConnectorKey_t ulKey ) + { + return VirtualConnectorInSteamPerAppState() && ulKey == 769; + } + namespace TouchClickModes { @@ -84,6 +125,19 @@ namespace gamescope uint32_t uRefresh; // Hz }; + struct BackendPresentFeedback + { + public: + uint64_t CurrentPresentsInFlight() const { return TotalPresentsQueued() - TotalPresentsCompleted(); } + + // Across the lifetime of the backend. + uint64_t TotalPresentsQueued() const { return m_uQueuedPresents.load(); } + uint64_t TotalPresentsCompleted() const { return m_uCompletedPresents.load(); } + + std::atomic m_uQueuedPresents = { 0u }; + std::atomic m_uCompletedPresents = { 0u }; + }; + class IBackendConnector { public: @@ -94,6 +148,7 @@ namespace gamescope virtual bool SupportsHDR() const = 0; virtual bool IsHDRActive() const = 0; virtual const BackendConnectorHDRInfo &GetHDRInfo() const = 0; + virtual bool IsVRRActive() const = 0; virtual std::span GetModes() const = 0; virtual bool SupportsVRR() const = 0; @@ -109,6 +164,39 @@ namespace gamescope virtual const char *GetName() const = 0; virtual const char *GetMake() const = 0; virtual const char *GetModel() const = 0; + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0; + virtual VBlankScheduleTime FrameSync() = 0; + virtual BackendPresentFeedback& PresentationFeedback() = 0; + + virtual uint64_t GetVirtualConnectorKey() const = 0; + + virtual INestedHints *GetNestedHints() = 0; + }; + + class CBaseBackendConnector : public IBackendConnector + { + public: + CBaseBackendConnector() + { + } + CBaseBackendConnector( uint64_t ulVirtualConnectorKey ) + : m_ulVirtualConnectorKey{ ulVirtualConnectorKey } + { + } + + virtual ~CBaseBackendConnector() + { + + } + + virtual VBlankScheduleTime FrameSync() override; + virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; } + virtual uint64_t GetVirtualConnectorKey() const override { return m_ulVirtualConnectorKey; } + virtual INestedHints *GetNestedHints() override { return nullptr; } + protected: + uint64_t m_ulVirtualConnectorKey = 0; + BackendPresentFeedback m_PresentFeedback{}; }; class INestedHints @@ -131,20 +219,6 @@ namespace gamescope virtual void SetTitle( std::shared_ptr szTitle ) = 0; virtual void SetIcon( std::shared_ptr> uIconPixels ) = 0; virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) = 0; - virtual std::shared_ptr GetHostCursor() = 0; - }; - - struct BackendPresentFeedback - { - public: - uint64_t CurrentPresentsInFlight() const { return TotalPresentsQueued() - TotalPresentsCompleted(); } - - // Across the lifetime of the backend. - uint64_t TotalPresentsQueued() const { return m_uQueuedPresents.load(); } - uint64_t TotalPresentsCompleted() const { return m_uCompletedPresents.load(); } - - std::atomic m_uQueuedPresents = { 0u }; - std::atomic m_uCompletedPresents = { 0u }; }; class IBackendFb : public IRcObject @@ -184,7 +258,6 @@ namespace gamescope virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const = 0; virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0; - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0; virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; virtual bool PollState() = 0; @@ -214,9 +287,6 @@ namespace gamescope virtual IBackendConnector *GetCurrentConnector() = 0; virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0; - // Might want to move this to connector someday, but it lives in CRTC. - virtual bool IsVRRActive() const = 0; - virtual bool SupportsPlaneHardwareCursor() const = 0; virtual bool SupportsTearing() const = 0; @@ -237,22 +307,19 @@ namespace gamescope virtual bool IsVisible() const = 0; virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const = 0; - virtual INestedHints *GetNestedHints() = 0; - // This will move to the connector and be deprecated soon. virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) = 0; virtual void HackUpdatePatchedEdid() = 0; virtual bool NeedsFrameSync() const = 0; - virtual VBlankScheduleTime FrameSync() = 0; - - // TODO: Make me const someday. - virtual BackendPresentFeedback& PresentationFeedback() = 0; virtual TouchClickMode GetTouchClickMode() = 0; virtual void DumpDebugInfo() = 0; + virtual bool UsesVirtualConnectors() = 0; + virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) = 0; + static IBackend *Get(); template static bool Set(); @@ -269,21 +336,17 @@ namespace gamescope class CBaseBackend : public IBackend { public: - virtual INestedHints *GetNestedHints() override; - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override { return false; } virtual void HackUpdatePatchedEdid() override {} virtual bool NeedsFrameSync() const override; - virtual VBlankScheduleTime FrameSync() override; - - virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; } virtual TouchClickMode GetTouchClickMode() override; virtual void DumpDebugInfo() override; - protected: - BackendPresentFeedback m_PresentFeedback{}; + + virtual bool UsesVirtualConnectors() override; + virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; }; // This is a blob of data that may be associated with diff --git a/src/main.cpp b/src/main.cpp index 9dff5c4d85..7230ebeb8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -720,6 +720,8 @@ int main(int argc, char **argv) break; case 'e': steamMode = true; + if ( gamescope::cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::SingleApplication ) + gamescope::cv_backend_virtual_connector_strategy = gamescope::VirtualConnectorStrategies::SteamControlled; break; case 0: // long options without a short option opt_name = gamescope_options[opt_index].name; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index a8f44d1ef2..981d6e207a 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -166,9 +166,21 @@ uint32_t g_reshade_technique_idx = 0; bool g_bSteamIsActiveWindow = false; bool g_bForceInternal = false; +namespace gamescope +{ + extern std::shared_ptr GetX11HostCursor(); +} + static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows(); static bool -pick_primary_focus_and_override(focus_t *out, Window focusControlWindow, const std::vector& vecPossibleFocusWindows, bool globalFocus, const std::vector& ctxFocusControlAppIDs); +pick_primary_focus_and_override( + focus_t *out, + Window focusControlWindow, + const std::vector& vecPossibleFocusWindows, + bool globalFocus, + const std::vector& ctxFocusControlAppIDs, + uint64_t ulVirtualFocusKey = 0, + gamescope::VirtualConnectorStrategy eStrategy = gamescope::VirtualConnectorStrategies::PerWindow); bool env_to_bool(const char *env) { @@ -715,7 +727,7 @@ Window x11_win(steamcompmgr_win_t *w) { } static uint64_t s_ulFocusSerial = 0ul; -static inline void MakeFocusDirty() +void MakeFocusDirty() { s_ulFocusSerial++; } @@ -733,10 +745,40 @@ bool focus_t::IsDirty() struct global_focus_t : public focus_t { steamcompmgr_win_t *keyboardFocusWindow; - steamcompmgr_win_t *fadeWindow; + steamcompmgr_win_t *fadeWindow; MouseCursor *cursor; -} global_focus; + gamescope::VirtualConnectorKey_t ulVirtualFocusKey = 0; + std::shared_ptr pVirtualConnector; + + gamescope::INestedHints *GetNestedHints() + { + if ( pVirtualConnector ) + { + return pVirtualConnector->GetNestedHints(); + } + + return nullptr; + } +}; + +std::unordered_map g_VirtualConnectorFocuses; +global_focus_t *GetCurrentFocus() +{ + if ( GetBackend()->GetCurrentConnector() ) + { + uint64_t ulKey = GetBackend()->GetCurrentConnector()->GetVirtualConnectorKey(); + + auto iter = g_VirtualConnectorFocuses.find( ulKey ); + if ( iter != g_VirtualConnectorFocuses.end() ) + return &iter->second; + } + + if ( g_VirtualConnectorFocuses.size() > 0 ) + return &g_VirtualConnectorFocuses.begin()->second; + + return nullptr; +} uint32_t currentOutputWidth, currentOutputHeight; bool currentHDROutput = false; @@ -899,7 +941,7 @@ bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false; bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) { // VRR + FPS Limit needs another approach. - if ( GetBackend()->IsVRRActive() ) + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) return false; return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay; @@ -923,7 +965,7 @@ steamcompmgr_user_has_any_game_open() bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w ) { - if ( GetBackend()->IsVRRActive() ) + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) return false; if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive ) @@ -1678,8 +1720,10 @@ bool MouseCursor::getTexture() updateCursorFeedback(); if (m_imageEmpty) { +#if 0 // XXX(strategy) FIXME if ( GetBackend()->GetNestedHints() ) GetBackend()->GetNestedHints()->SetCursorImage( nullptr ); +#endif return false; } @@ -1692,6 +1736,8 @@ bool MouseCursor::getTexture() } m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data()); + +#if 0 // XXX(strategy) FIXME if ( GetBackend()->GetNestedHints() ) { auto info = std::make_shared( @@ -1705,6 +1751,7 @@ bool MouseCursor::getTexture() }); GetBackend()->GetNestedHints()->SetCursorImage( std::move( info ) ); } +#endif assert(m_texture); XFree(image); @@ -1908,12 +1955,6 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win // Base plane will stay as tex=0 if we don't have contents yet, which will // make us fall back to compositing and use the Vulkan null texture - steamcompmgr_win_t *mainOverlayWindow = global_focus.overlayWindow; - - const bool notificationMode = flags & PaintWindowFlag::NotificationMode; - if (notificationMode && !mainOverlayWindow) - return nullptr; - int curLayer = frameInfo->layerCount++; FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; @@ -1922,12 +1963,7 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win layer->tex = lastCommit->GetTexture( layer->filter, g_upscaleScaler ); - if (notificationMode) - { - sourceWidth = mainOverlayWindow->GetGeometry().nWidth; - sourceHeight = mainOverlayWindow->GetGeometry().nHeight; - } - else if ( flags & PaintWindowFlag::NoScale ) + if ( flags & PaintWindowFlag::NoScale ) { sourceWidth = currentOutputWidth; sourceHeight = currentOutputHeight; @@ -1991,22 +2027,6 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win layer->offset.x = -drawXOffset; layer->offset.y = -drawYOffset; } - else if (notificationMode) - { - int xOffset = 0, yOffset = 0; - - int width = w->GetGeometry().nWidth * currentScaleRatio_x; - int height = w->GetGeometry().nHeight * currentScaleRatio_y; - - if (globalScaleRatio != 1.0f) - { - xOffset = (currentOutputWidth - currentOutputWidth * globalScaleRatio) / 2.0; - yOffset = (currentOutputHeight - currentOutputHeight * globalScaleRatio) / 2.0; - } - - layer->offset.x = (currentOutputWidth - xOffset - width) * -1.0f; - layer->offset.y = (currentOutputHeight - yOffset - height) * -1.0f; - } else { layer->offset.x = -drawXOffset; @@ -2175,7 +2195,7 @@ static void paint_pipewire() } else { - pFocus = &global_focus; + pFocus = GetCurrentFocus(); } if ( !pFocus->focusWindow ) @@ -2239,12 +2259,19 @@ bool ShouldDrawCursor() if ( cv_cursor_composite == 0 ) return false; - return g_bForceRelativeMouse || !GetBackend()->GetNestedHints(); + return g_bForceRelativeMouse || !GetCurrentFocus()->GetNestedHints(); } static void -paint_all(bool async) +paint_all( global_focus_t *pFocus, bool async ) { + if ( !pFocus ) + return; + + gamescope::IBackendConnector *pConnector = pFocus->pVirtualConnector.get(); + if ( !pConnector ) + pConnector = GetBackend()->GetCurrentConnector(); + gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); xwayland_ctx_t *root_ctx = root_server->ctx.get(); @@ -2264,12 +2291,12 @@ paint_all(bool async) unsigned int currentTime = get_time_in_milliseconds(); bool fadingOut = ( currentTime - fadeOutStartTime < g_FadeOutDuration || g_bPendingFade ) && g_HeldCommits[HELD_COMMIT_FADE] != nullptr; - w = global_focus.focusWindow; - overlay = global_focus.overlayWindow; - externalOverlay = global_focus.externalOverlayWindow; - notification = global_focus.notificationWindow; - override = global_focus.overrideWindow; - input = global_focus.inputFocusWindow; + w = pFocus->focusWindow; + overlay = pFocus->overlayWindow; + externalOverlay = pFocus->externalOverlayWindow; + notification = pFocus->notificationWindow; + override = pFocus->overrideWindow; + input = pFocus->inputFocusWindow; if (++frameCounter == 300) { @@ -2312,7 +2339,7 @@ paint_all(bool async) if ( videow->isSteamStreamingClientVideo == true ) { // TODO: also check matching AppID so we can have several pairs - paint_window(videow, videow, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders); + paint_window(videow, videow, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders); bHasVideoUnderlay = true; break; } @@ -2324,8 +2351,9 @@ paint_all(bool async) uint32_t flags = 0; if ( !bHasVideoUnderlay ) flags |= PaintWindowFlag::BasePlane; - paint_window(w, w, &frameInfo, global_focus.cursor, flags); - update_touch_scaling( &frameInfo ); + paint_window(w, w, &frameInfo, pFocus->cursor, flags); + if ( pFocus == GetCurrentFocus() ) + update_touch_scaling( &frameInfo ); // paint UI unless it's fully hidden, which it communicates to us through opacity=0 // we paint it to extract scaling coefficients above, then remove the layer if one was added @@ -2341,7 +2369,7 @@ paint_all(bool async) : ((currentTime - fadeOutStartTime) / (float)g_FadeOutDuration); paint_cached_base_layer(g_HeldCommits[HELD_COMMIT_FADE], g_CachedPlanes[HELD_COMMIT_FADE], &frameInfo, 1.0f - opacityScale, false); - paint_window(w, w, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::FadeTarget | PaintWindowFlag::DrawBorders, opacityScale, override); + paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::FadeTarget | PaintWindowFlag::DrawBorders, opacityScale, override); } else { @@ -2351,17 +2379,18 @@ paint_all(bool async) g_HeldCommits[HELD_COMMIT_FADE] = nullptr; g_bPendingFade = false; fadeOutStartTime = 0; - global_focus.fadeWindow = None; + pFocus->fadeWindow = None; } } // Just draw focused window as normal, be it Steam or the game - paint_window(w, w, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override); + paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override); bool needsScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f; frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsScaling; frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsScaling; } - update_touch_scaling( &frameInfo ); + if ( pFocus == GetCurrentFocus() ) + update_touch_scaling( &frameInfo ); } } else @@ -2386,7 +2415,7 @@ paint_all(bool async) // as we will have too many layers. Better to be safe than sorry. if ( override && w && !w->isSteamStreamingClient ) { - paint_window(override, w, &frameInfo, global_focus.cursor, PaintWindowFlag::NoFilter, 1.0f, override); + paint_window(override, w, &frameInfo, pFocus->cursor, PaintWindowFlag::NoFilter, 1.0f, override); // Don't update touch scaling for frameInfo. We don't ever make it our // wlserver_mousefocus window. //update_touch_scaling( &frameInfo ); @@ -2399,18 +2428,18 @@ paint_all(bool async) { if (externalOverlay->opacity) { - paint_window(externalOverlay, externalOverlay, &frameInfo, global_focus.cursor, PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter); + paint_window(externalOverlay, externalOverlay, &frameInfo, pFocus->cursor, PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter); - if ( externalOverlay == global_focus.inputFocusWindow ) + if ( externalOverlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() ) update_touch_scaling( &frameInfo ); } } if (overlay && overlay->opacity) { - paint_window(overlay, overlay, &frameInfo, global_focus.cursor, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter); + paint_window(overlay, overlay, &frameInfo, pFocus->cursor, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter); - if ( overlay == global_focus.inputFocusWindow ) + if ( overlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() ) update_touch_scaling( &frameInfo ); } else if ( !GetBackend()->UsesVulkanSwapchain() && GetBackend()->IsSessionBased() ) @@ -2446,7 +2475,7 @@ paint_all(bool async) { if (notification->opacity) { - paint_window(notification, notification, &frameInfo, global_focus.cursor, PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter); + paint_window(notification, notification, &frameInfo, pFocus->cursor, PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter); } } @@ -2454,12 +2483,12 @@ paint_all(bool async) { // Make sure to un-dirty the texture before we do any painting logic. // We determine whether we are grabbed etc this way. - global_focus.cursor->undirty(); + pFocus->cursor->undirty(); } // Draw cursor if we need to if (input && ShouldDrawCursor()) { - global_focus.cursor->paint( + pFocus->cursor->paint( input, w == input ? override : nullptr, &frameInfo); } @@ -2499,14 +2528,14 @@ paint_all(bool async) update_app_target_refresh_cycle(); - const bool bSupportsDynamicRefresh = GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates().empty(); + const bool bSupportsDynamicRefresh = pConnector && !pConnector->GetValidDynamicRefreshRates().empty(); if ( bSupportsDynamicRefresh ) { - auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); + auto rates = pConnector->GetValidDynamicRefreshRates(); int nDynamicRefreshHz = g_nDynamicRefreshRate[GetBackend()->GetScreenType()]; - int nTargetRefreshHz = nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow + int nTargetRefreshHz = nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( pFocus->focusWindow ) ? nDynamicRefreshHz : int( rates[ rates.size() - 1 ] ); @@ -2561,7 +2590,7 @@ paint_all(bool async) } } - if ( GetBackend()->Present( &frameInfo, async ) != 0 ) + if ( pConnector->Present( &frameInfo, async ) != 0 ) { return; } @@ -2682,10 +2711,10 @@ paint_all(bool async) if ( !maxCLLNits && !maxFALLNits ) { - if ( GetBackend()->GetCurrentConnector() ) + if ( pConnector ) { - maxCLLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; - maxFALLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxFrameAverageLuminance; + maxCLLNits = pConnector->GetHDRInfo().uMaxContentLightLevel; + maxFALLNits = pConnector->GetHDRInfo().uMaxFrameAverageLuminance; } } @@ -3160,35 +3189,57 @@ static bool is_good_override_candidate( steamcompmgr_win_t *override, steamcompm } static bool -pick_primary_focus_and_override(focus_t *out, Window focusControlWindow, const std::vector& vecPossibleFocusWindows, bool globalFocus, const std::vector& ctxFocusControlAppIDs) +pick_primary_focus_and_override( + focus_t *out, + Window focusControlWindow, + const std::vector& vecPossibleFocusWindows, + bool globalFocus, + const std::vector& ctxFocusControlAppIDs, + uint64_t ulVirtualFocusKey, + gamescope::VirtualConnectorStrategy eStrategy ) { bool localGameFocused = false; steamcompmgr_win_t *focus = NULL, *override_focus = NULL; - bool controlledFocus = focusControlWindow != None || !ctxFocusControlAppIDs.empty(); + bool controlledFocus = eStrategy != gamescope::VirtualConnectorStrategies::SingleApplication || focusControlWindow != None || !ctxFocusControlAppIDs.empty(); if ( controlledFocus ) { - if ( focusControlWindow != None ) + if ( eStrategy == gamescope::VirtualConnectorStrategies::SteamControlled ) { - for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) + if ( focusControlWindow != None ) { - if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND ) - continue; + for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) + { + if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND ) + continue; - if ( focusable_window->xwayland().id == focusControlWindow ) + if ( focusable_window->xwayland().id == focusControlWindow ) + { + focus = focusable_window; + localGameFocused = true; + goto found; + } + } + } + + for ( auto focusable_appid : ctxFocusControlAppIDs ) + { + for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) { - focus = focusable_window; - localGameFocused = true; - goto found; + if ( focusable_window->appID == focusable_appid ) + { + focus = focusable_window; + localGameFocused = true; + goto found; + } } } } - - for ( auto focusable_appid : ctxFocusControlAppIDs ) + else { for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) { - if ( focusable_window->appID == focusable_appid ) + if ( focusable_window->GetVirtualConnectorKey( eStrategy ) == ulVirtualFocusKey ) { focus = focusable_window; localGameFocused = true; @@ -3416,9 +3467,12 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win } } - if ( w->isOverlay && w->inputFocusMode ) + if ( gamescope::VirtualConnectorIsSingleOutput() ) { - inputFocus = w; + if ( w->isOverlay && w->inputFocusMode ) + { + inputFocus = w; + } } } @@ -3456,8 +3510,11 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win steamcompmgr_win_t *keyboardFocusWin = inputFocus; - if ( inputFocus && inputFocus->inputFocusMode == 2 ) - keyboardFocusWin = ctx->focus.focusWindow; + if ( gamescope::VirtualConnectorIsSingleOutput() ) + { + if ( inputFocus && inputFocus->inputFocusMode == 2 ) + keyboardFocusWin = ctx->focus.focusWindow; + } Window keyboardFocusWindow = keyboardFocusWin ? keyboardFocusWin->xwayland().id : None; @@ -3650,14 +3707,16 @@ steamcompmgr_xdg_determine_and_apply_focus( const std::vector< steamcompmgr_win_ uint32_t g_focusedBaseAppId = 0; static void -determine_and_apply_focus() +determine_and_apply_focus( global_focus_t *pFocus ) { gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); xwayland_ctx_t *root_ctx = root_server->ctx.get(); - global_focus_t previous_focus = global_focus; - global_focus = global_focus_t{}; - global_focus.focusWindow = previous_focus.focusWindow; - global_focus.cursor = root_ctx->cursor.get(); + global_focus_t previousLocalFocus = *pFocus; + *pFocus = global_focus_t{}; + pFocus->focusWindow = previousLocalFocus.focusWindow; + pFocus->cursor = root_ctx->cursor.get(); + pFocus->ulVirtualFocusKey = previousLocalFocus.ulVirtualFocusKey; + pFocus->pVirtualConnector = previousLocalFocus.pVirtualConnector; gameFocused = false; std::vector< unsigned long > focusable_appids; @@ -3725,42 +3784,52 @@ determine_and_apply_focus() XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusableWindowsAtom, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)focusable_windows.data(), focusable_windows.size() ); - gameFocused = pick_primary_focus_and_override(&global_focus, root_ctx->focusControlWindow, vecPossibleFocusWindows, true, vecFocuscontrolAppIDs); + gameFocused = pick_primary_focus_and_override( pFocus, root_ctx->focusControlWindow, vecPossibleFocusWindows, true, vecFocuscontrolAppIDs, + pFocus->ulVirtualFocusKey, + gamescope::cv_backend_virtual_connector_strategy ); // Pick overlay/notifications from root ctx - global_focus.overlayWindow = root_ctx->focus.overlayWindow; - global_focus.externalOverlayWindow = root_ctx->focus.externalOverlayWindow; - global_focus.notificationWindow = root_ctx->focus.notificationWindow; + pFocus->overlayWindow = root_ctx->focus.overlayWindow; + pFocus->externalOverlayWindow = root_ctx->focus.externalOverlayWindow; + pFocus->notificationWindow = root_ctx->focus.notificationWindow; - if ( !global_focus.overlayWindow ) + if ( !pFocus->overlayWindow ) { - global_focus.overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow; + pFocus->overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow; } - if ( !global_focus.externalOverlayWindow ) + if ( !pFocus->externalOverlayWindow ) { - global_focus.externalOverlayWindow = g_steamcompmgr_xdg_focus.externalOverlayWindow; + pFocus->externalOverlayWindow = g_steamcompmgr_xdg_focus.externalOverlayWindow; } + bool bUseOverlay = gamescope::VirtualConnectorIsSingleOutput() || gamescope::VirtualConnectorKeyIsSteam( pFocus->ulVirtualFocusKey ); + if ( !bUseOverlay ) + { + pFocus->overlayWindow = nullptr; + pFocus->notificationWindow = nullptr; + } + // Pick inputFocusWindow - if (global_focus.overlayWindow && global_focus.overlayWindow->inputFocusMode) + if ( gamescope::VirtualConnectorIsSingleOutput() && + pFocus->overlayWindow && pFocus->overlayWindow->inputFocusMode ) { - global_focus.inputFocusWindow = global_focus.overlayWindow; - global_focus.keyboardFocusWindow = global_focus.overlayWindow; + pFocus->inputFocusWindow = pFocus->overlayWindow; + pFocus->keyboardFocusWindow = pFocus->overlayWindow; } else { - global_focus.inputFocusWindow = global_focus.focusWindow; - global_focus.keyboardFocusWindow = global_focus.overrideWindow ? global_focus.overrideWindow : global_focus.focusWindow; + pFocus->inputFocusWindow = pFocus->focusWindow; + pFocus->keyboardFocusWindow = pFocus->overrideWindow ? pFocus->overrideWindow : pFocus->focusWindow; } // Pick cursor from our input focus window // Initially pick cursor from the ctx of our input focus. - if (global_focus.inputFocusWindow) + if (pFocus->inputFocusWindow) { - if (global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND) - global_focus.cursor = global_focus.inputFocusWindow->xwayland().ctx->cursor.get(); + if (pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND) + pFocus->cursor = pFocus->inputFocusWindow->xwayland().ctx->cursor.get(); else { // TODO XDG: @@ -3777,79 +3846,89 @@ determine_and_apply_focus() } } - if (global_focus.inputFocusWindow) - global_focus.inputFocusMode = global_focus.inputFocusWindow->inputFocusMode; + if (pFocus->inputFocusWindow) + pFocus->inputFocusMode = pFocus->inputFocusWindow->inputFocusMode; - if ( global_focus.inputFocusMode == 2 ) + if ( gamescope::VirtualConnectorIsSingleOutput() && + pFocus->inputFocusMode == 2 ) { - global_focus.keyboardFocusWindow = global_focus.overrideWindow - ? global_focus.overrideWindow - : global_focus.focusWindow; + pFocus->keyboardFocusWindow = pFocus->overrideWindow + ? pFocus->overrideWindow + : pFocus->focusWindow; } - // Tell wlserver about our keyboard/mouse focus. - if ( global_focus.inputFocusWindow != previous_focus.inputFocusWindow || - global_focus.keyboardFocusWindow != previous_focus.keyboardFocusWindow || - global_focus.overrideWindow != previous_focus.overrideWindow ) + // TODO(strategy): multi-seat on Wayland side + if ( pFocus == GetCurrentFocus() ) { - if ( win_surface(global_focus.inputFocusWindow) != nullptr || - win_surface(global_focus.keyboardFocusWindow) != nullptr ) - { - wlserver_lock(); + static global_focus_t s_lastFocus; - wlserver_clear_dropdowns(); - if ( win_surface( global_focus.overrideWindow ) != nullptr ) - wlserver_notify_dropdown( global_focus.overrideWindow->main_surface(), global_focus.overrideWindow->xwayland().a.x, global_focus.overrideWindow->xwayland().a.y ); + // Tell wlserver about our keyboard/mouse focus. + if ( pFocus->inputFocusWindow != s_lastFocus.inputFocusWindow || + pFocus->keyboardFocusWindow != s_lastFocus.keyboardFocusWindow || + pFocus->overrideWindow != s_lastFocus.overrideWindow ) + { + if ( win_surface(pFocus->inputFocusWindow) != nullptr || + win_surface(pFocus->keyboardFocusWindow) != nullptr ) + { + wlserver_lock(); - if ( win_surface(global_focus.inputFocusWindow) != nullptr && global_focus.cursor ) - wlserver_mousefocus( global_focus.inputFocusWindow->main_surface(), global_focus.cursor->x(), global_focus.cursor->y() ); + wlserver_clear_dropdowns(); + if ( win_surface( pFocus->overrideWindow ) != nullptr ) + wlserver_notify_dropdown( pFocus->overrideWindow->main_surface(), pFocus->overrideWindow->xwayland().a.x, pFocus->overrideWindow->xwayland().a.y ); - if ( win_surface(global_focus.keyboardFocusWindow) != nullptr ) - wlserver_keyboardfocus( global_focus.keyboardFocusWindow->main_surface() ); - wlserver_unlock(); - } + if ( win_surface(pFocus->inputFocusWindow) != nullptr && pFocus->cursor ) + wlserver_mousefocus( pFocus->inputFocusWindow->main_surface(), pFocus->cursor->x(), pFocus->cursor->y() ); - // Hide cursor on transitioning between xwaylands - // We already do this when transitioning input focus inside of an - // xwayland ctx. - // don't care if we change kb focus window due to that happening when - // going from override -> focus and we don't want to hide then as it's probably a dropdown. - if ( global_focus.cursor && global_focus.inputFocusWindow != previous_focus.inputFocusWindow ) - global_focus.cursor->hide(); - } + if ( win_surface(pFocus->keyboardFocusWindow) != nullptr ) + wlserver_keyboardfocus( pFocus->keyboardFocusWindow->main_surface() ); + wlserver_unlock(); + } - if ( global_focus.inputFocusWindow ) - { - // Cannot simply XWarpPointer here as we immediately go on to - // do wlserver_mousefocus and need to update m_x and m_y of the cursor. - if ( global_focus.inputFocusWindow->GetFocus()->bResetToCorner ) - { - wlserver_lock(); - wlserver_mousewarp( global_focus.inputFocusWindow->GetGeometry().nWidth / 2, global_focus.inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_fake_mouse_pos( global_focus.inputFocusWindow->GetGeometry().nWidth - 1, global_focus.inputFocusWindow->GetGeometry().nHeight - 1 ); - wlserver_unlock(); + // Hide cursor on transitioning between xwaylands + // We already do this when transitioning input focus inside of an + // xwayland ctx. + // don't care if we change kb focus window due to that happening when + // going from override -> focus and we don't want to hide then as it's probably a dropdown. + if ( pFocus->cursor && pFocus->inputFocusWindow != s_lastFocus.inputFocusWindow ) + pFocus->cursor->hide(); } - else if ( global_focus.inputFocusWindow->GetFocus()->bResetToCenter ) + + if ( pFocus->inputFocusWindow ) { - wlserver_lock(); - wlserver_mousewarp( global_focus.inputFocusWindow->GetGeometry().nWidth / 2, global_focus.inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_unlock(); + // Cannot simply XWarpPointer here as we immediately go on to + // do wlserver_mousefocus and need to update m_x and m_y of the cursor. + if ( pFocus->inputFocusWindow->GetFocus()->bResetToCorner ) + { + wlserver_lock(); + wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); + wlserver_fake_mouse_pos( pFocus->inputFocusWindow->GetGeometry().nWidth - 1, pFocus->inputFocusWindow->GetGeometry().nHeight - 1 ); + wlserver_unlock(); + } + else if ( pFocus->inputFocusWindow->GetFocus()->bResetToCenter ) + { + wlserver_lock(); + wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); + wlserver_unlock(); + } + + pFocus->inputFocusWindow->GetFocus()->bResetToCorner = false; + pFocus->inputFocusWindow->GetFocus()->bResetToCenter = false; } - global_focus.inputFocusWindow->GetFocus()->bResetToCorner = false; - global_focus.inputFocusWindow->GetFocus()->bResetToCenter = false; + s_lastFocus = *pFocus; + s_lastFocus.pVirtualConnector = nullptr; // I don't want to keep a ref to this. } // Determine if we need to repaints - if (previous_focus.overlayWindow != global_focus.overlayWindow || - previous_focus.externalOverlayWindow != global_focus.externalOverlayWindow || - previous_focus.notificationWindow != global_focus.notificationWindow || - previous_focus.overrideWindow != global_focus.overrideWindow) + if (previousLocalFocus.overlayWindow != pFocus->overlayWindow || + previousLocalFocus.externalOverlayWindow != pFocus->externalOverlayWindow || + previousLocalFocus.notificationWindow != pFocus->notificationWindow || + previousLocalFocus.overrideWindow != pFocus->overrideWindow) { hasRepaintNonBasePlane = true; } - if (previous_focus.focusWindow != global_focus.focusWindow) + if (previousLocalFocus.focusWindow != pFocus->focusWindow) { hasRepaint = true; } @@ -3862,25 +3941,25 @@ determine_and_apply_focus() const char *focused_keyboard_display = root_ctx->xwayland_server->get_nested_display_name(); const char *focused_mouse_display = root_ctx->xwayland_server->get_nested_display_name(); - if ( global_focus.focusWindow ) + if ( pFocus->focusWindow ) { - focusedWindow = (unsigned long)global_focus.focusWindow->id(); - focusedBaseAppId = global_focus.focusWindow->appID; - focusedAppId = global_focus.inputFocusWindow->appID; - focused_display = get_win_display_name(global_focus.focusWindow); - focusWindow_pid = global_focus.focusWindow->pid; + focusedWindow = (unsigned long)pFocus->focusWindow->id(); + focusedBaseAppId = pFocus->focusWindow->appID; + focusedAppId = pFocus->inputFocusWindow->appID; + focused_display = get_win_display_name(pFocus->focusWindow); + focusWindow_pid = pFocus->focusWindow->pid; } g_focusedBaseAppId = (uint32_t)focusedAppId; - if ( global_focus.inputFocusWindow ) + if ( pFocus->inputFocusWindow ) { - focused_mouse_display = get_win_display_name(global_focus.inputFocusWindow); + focused_mouse_display = get_win_display_name(pFocus->inputFocusWindow); } - if ( global_focus.keyboardFocusWindow ) + if ( pFocus->keyboardFocusWindow ) { - focused_keyboard_display = get_win_display_name(global_focus.keyboardFocusWindow); + focused_keyboard_display = get_win_display_name(pFocus->keyboardFocusWindow); } if ( steamMode ) @@ -3907,25 +3986,25 @@ determine_and_apply_focus() XFlush( root_ctx->dpy ); // Sort out fading. - if (global_focus.focusWindow && previous_focus.focusWindow != global_focus.focusWindow) + if (pFocus->focusWindow && previousLocalFocus.focusWindow != pFocus->focusWindow) { if ( g_FadeOutDuration != 0 && !g_bFirstFrame ) { if ( g_HeldCommits[ HELD_COMMIT_FADE ] == nullptr ) { - global_focus.fadeWindow = previous_focus.focusWindow; + pFocus->fadeWindow = previousLocalFocus.focusWindow; g_HeldCommits[ HELD_COMMIT_FADE ] = g_HeldCommits[ HELD_COMMIT_BASE ]; g_bPendingFade = true; } else { // If we end up fading back to what we were going to fade to, cancel the fade. - if ( global_focus.fadeWindow != nullptr && global_focus.focusWindow == global_focus.fadeWindow ) + if ( pFocus->fadeWindow != nullptr && pFocus->focusWindow == pFocus->fadeWindow ) { g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; g_bPendingFade = false; fadeOutStartTime = 0; - global_focus.fadeWindow = nullptr; + pFocus->fadeWindow = nullptr; } } } @@ -3934,32 +4013,32 @@ determine_and_apply_focus() if ( !cv_paint_debug_pause_base_plane ) { // Update last focus commit - if ( global_focus.focusWindow && - previous_focus.focusWindow != global_focus.focusWindow && - !global_focus.focusWindow->isSteamStreamingClient ) + if ( pFocus->focusWindow && + previousLocalFocus.focusWindow != pFocus->focusWindow && + !pFocus->focusWindow->isSteamStreamingClient ) { - get_window_last_done_commit( global_focus.focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] ); + get_window_last_done_commit( pFocus->focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] ); } } // Set SDL window title - if ( GetBackend()->GetNestedHints() ) + if ( pFocus->GetNestedHints() ) { - if ( global_focus.focusWindow ) + if ( pFocus->focusWindow ) { - GetBackend()->GetNestedHints()->SetVisible( true ); - GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title ); - GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon ); + pFocus->GetNestedHints()->SetVisible( true ); + pFocus->GetNestedHints()->SetTitle( pFocus->focusWindow->title ); + pFocus->GetNestedHints()->SetIcon( pFocus->focusWindow->icon ); } else { - GetBackend()->GetNestedHints()->SetVisible( false ); + pFocus->GetNestedHints()->SetVisible( false ); } } // Some games such as Disgaea PC (405900) don't take controller input until // the window is first clicked on despite it having focus. - if ( global_focus.inputFocusWindow && global_focus.inputFocusWindow->appID == 405900 ) + if ( pFocus->inputFocusWindow && pFocus->inputFocusWindow->appID == 405900 ) { auto now = get_time_in_milliseconds(); @@ -3970,7 +4049,7 @@ determine_and_apply_focus() wlserver_unlock(); } - global_focus.ulCurrentFocusSerial = GetFocusSerial(); + pFocus->ulCurrentFocusSerial = GetFocusSerial(); } static void @@ -4649,19 +4728,23 @@ destroy_win(xwayland_ctx_t *ctx, Window id, bool gone, bool fade) if (ctx->currentKeyboardFocusWindow == id && gone) ctx->currentKeyboardFocusWindow = None; - // Global Focus - if (x11_win(global_focus.focusWindow) == id && gone) - global_focus.focusWindow = nullptr; - if (x11_win(global_focus.inputFocusWindow) == id && gone) - global_focus.inputFocusWindow = nullptr; - if (x11_win(global_focus.overlayWindow) == id && gone) - global_focus.overlayWindow = nullptr; - if (x11_win(global_focus.notificationWindow) == id && gone) - global_focus.notificationWindow = nullptr; - if (x11_win(global_focus.overrideWindow) == id && gone) - global_focus.overrideWindow = nullptr; - if (x11_win(global_focus.fadeWindow) == id && gone) - global_focus.fadeWindow = nullptr; + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + // Global Focus + if (x11_win(pFocus->focusWindow) == id && gone) + pFocus->focusWindow = nullptr; + if (x11_win(pFocus->inputFocusWindow) == id && gone) + pFocus->inputFocusWindow = nullptr; + if (x11_win(pFocus->overlayWindow) == id && gone) + pFocus->overlayWindow = nullptr; + if (x11_win(pFocus->notificationWindow) == id && gone) + pFocus->notificationWindow = nullptr; + if (x11_win(pFocus->overrideWindow) == id && gone) + pFocus->overrideWindow = nullptr; + if (x11_win(pFocus->fadeWindow) == id && gone) + pFocus->fadeWindow = nullptr; + } MakeFocusDirty(); @@ -4721,18 +4804,23 @@ handle_wl_surface_id(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, uint32_t surfac return; } - // If we already focused on our side and are handling this late, - // let wayland know now. - if ( w == global_focus.inputFocusWindow ) - wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX ); + global_focus_t *pCurrentFocus = GetCurrentFocus(); + if ( pCurrentFocus ) + { + // If we already focused on our side and are handling this late, + // let wayland know now. + if ( w == pCurrentFocus->inputFocusWindow ) + wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX ); - steamcompmgr_win_t *keyboardFocusWindow = global_focus.inputFocusWindow; + steamcompmgr_win_t *keyboardFocusWindow = pCurrentFocus->inputFocusWindow; - if ( keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 ) - keyboardFocusWindow = global_focus.focusWindow; + if ( gamescope::VirtualConnectorIsSingleOutput() && + keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 ) + keyboardFocusWindow = pCurrentFocus->focusWindow; - if ( w == keyboardFocusWindow ) - wlserver_keyboardfocus( main_surface ); + if ( w == keyboardFocusWindow ) + wlserver_keyboardfocus( main_surface ); + } // Pull the first buffer out of that window, if needed xwayland_surface_commit( current_surface ); @@ -4987,22 +5075,22 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) if (ev->selection == ctx->atoms.clipboard) { - if ( GetBackend()->GetNestedHints() ) - { - GetBackend()->GetNestedHints()->SetSelection( szContents, GAMESCOPE_SELECTION_CLIPBOARD ); - } - else + // if ( GetBackend()->GetNestedHints() ) + // { + // //GetBackend()->GetNestedHints()->SetSelection() + // } + // else { gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); } } else if (ev->selection == ctx->atoms.primarySelection) { - if ( GetBackend()->GetNestedHints() ) - { - GetBackend()->GetNestedHints()->SetSelection( szContents, GAMESCOPE_SELECTION_PRIMARY ); - } - else + // if ( GetBackend()->GetNestedHints() ) + // { + // //GetBackend()->GetNestedHints()->SetSelection() + // } + // else { gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); } @@ -5078,7 +5166,7 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w ) static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx ) { - if ( GetBackend()->IsVRRActive() ) + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) return true; bool bSendCallback = true; @@ -5309,9 +5397,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - if (global_focus.focusWindow) + for ( auto &iter : g_VirtualConnectorFocuses ) { - hasRepaint = true; + global_focus_t *pFocus = &iter.second; + if (pFocus->focusWindow) + { + hasRepaint = true; + } } MakeFocusDirty(); @@ -5322,9 +5414,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - if (global_focus.focusWindow) + for ( auto &iter : g_VirtualConnectorFocuses ) { - hasRepaint = true; + global_focus_t *pFocus = &iter.second; + if (pFocus->focusWindow) + { + hasRepaint = true; + } } MakeFocusDirty(); @@ -5356,10 +5452,14 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) { get_win_title(ctx, w, ev->atom); - if (ev->window == x11_win(global_focus.focusWindow)) + for ( auto &iter : g_VirtualConnectorFocuses ) { - if ( GetBackend()->GetNestedHints() ) - GetBackend()->GetNestedHints()->SetTitle( w->title ); + global_focus_t *pFocus = &iter.second; + if (ev->window == x11_win(pFocus->focusWindow)) + { + if ( pFocus->GetNestedHints() ) + pFocus->GetNestedHints()->SetTitle( w->title ); + } } } } @@ -5371,10 +5471,14 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) { get_win_icon(ctx, w); - if (ev->window == x11_win(global_focus.focusWindow)) + for ( auto &iter : g_VirtualConnectorFocuses ) { - if ( GetBackend()->GetNestedHints() ) - GetBackend()->GetNestedHints()->SetIcon( w->icon ); + global_focus_t *pFocus = &iter.second; + if (ev->window == x11_win(pFocus->focusWindow)) + { + if ( pFocus->GetNestedHints() ) + pFocus->GetNestedHints()->SetIcon( w->icon ); + } } } } @@ -5802,49 +5906,53 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(server_id); if (server) { - if (global_focus.focusWindow && - global_focus.focusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.focusWindow->xwayland().ctx == server->ctx.get()) - global_focus.focusWindow = nullptr; - - if (global_focus.inputFocusWindow && - global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.inputFocusWindow->xwayland().ctx == server->ctx.get()) - global_focus.inputFocusWindow = nullptr; - - if (global_focus.overlayWindow && - global_focus.overlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.overlayWindow->xwayland().ctx == server->ctx.get()) - global_focus.overlayWindow = nullptr; - - if (global_focus.externalOverlayWindow && - global_focus.externalOverlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.externalOverlayWindow->xwayland().ctx == server->ctx.get()) - global_focus.externalOverlayWindow = nullptr; - - if (global_focus.notificationWindow && - global_focus.notificationWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.notificationWindow->xwayland().ctx == server->ctx.get()) - global_focus.notificationWindow = nullptr; - - if (global_focus.overrideWindow && - global_focus.overrideWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.overrideWindow->xwayland().ctx == server->ctx.get()) - global_focus.overrideWindow = nullptr; - - if (global_focus.keyboardFocusWindow && - global_focus.keyboardFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.keyboardFocusWindow->xwayland().ctx == server->ctx.get()) - global_focus.keyboardFocusWindow = nullptr; - - if (global_focus.fadeWindow && - global_focus.fadeWindow->type == steamcompmgr_win_type_t::XWAYLAND && - global_focus.fadeWindow->xwayland().ctx == server->ctx.get()) - global_focus.fadeWindow = nullptr; - - if (global_focus.cursor && - global_focus.cursor->getCtx() == server->ctx.get()) - global_focus.cursor = nullptr; + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if (pFocus->focusWindow && + pFocus->focusWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->focusWindow->xwayland().ctx == server->ctx.get()) + pFocus->focusWindow = nullptr; + + if (pFocus->inputFocusWindow && + pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->inputFocusWindow->xwayland().ctx == server->ctx.get()) + pFocus->inputFocusWindow = nullptr; + + if (pFocus->overlayWindow && + pFocus->overlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->overlayWindow->xwayland().ctx == server->ctx.get()) + pFocus->overlayWindow = nullptr; + + if (pFocus->externalOverlayWindow && + pFocus->externalOverlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->externalOverlayWindow->xwayland().ctx == server->ctx.get()) + pFocus->externalOverlayWindow = nullptr; + + if (pFocus->notificationWindow && + pFocus->notificationWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->notificationWindow->xwayland().ctx == server->ctx.get()) + pFocus->notificationWindow = nullptr; + + if (pFocus->overrideWindow && + pFocus->overrideWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->overrideWindow->xwayland().ctx == server->ctx.get()) + pFocus->overrideWindow = nullptr; + + if (pFocus->keyboardFocusWindow && + pFocus->keyboardFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->keyboardFocusWindow->xwayland().ctx == server->ctx.get()) + pFocus->keyboardFocusWindow = nullptr; + + if (pFocus->fadeWindow && + pFocus->fadeWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pFocus->fadeWindow->xwayland().ctx == server->ctx.get()) + pFocus->fadeWindow = nullptr; + + if (pFocus->cursor && + pFocus->cursor->getCtx() == server->ctx.get()) + pFocus->cursor = nullptr; + } wlserver_lock(); g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get() ); @@ -6051,47 +6159,52 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co // Window just got a new available commit, determine if that's worth a repaint - // If this is an overlay that we're presenting, repaint - if ( w == global_focus.overlayWindow && w->opacity != TRANSLUCENT ) + for ( auto &iter : g_VirtualConnectorFocuses ) { - hasRepaintNonBasePlane = true; - } + global_focus_t *pFocus = &iter.second; - if ( w == global_focus.notificationWindow && w->opacity != TRANSLUCENT ) - { - hasRepaintNonBasePlane = true; - } + // If this is an overlay that we're presenting, repaint + if ( w == pFocus->overlayWindow && w->opacity != TRANSLUCENT ) + { + hasRepaintNonBasePlane = true; + } - // If this is an external overlay, repaint - if ( w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT ) - { - hasRepaintNonBasePlane = true; - } + if ( w == pFocus->notificationWindow && w->opacity != TRANSLUCENT ) + { + hasRepaintNonBasePlane = true; + } - if ( w->outdatedInteractiveFocus ) - { - MakeFocusDirty(); - w->outdatedInteractiveFocus = false; - } + // If this is an external overlay, repaint + if ( w == pFocus->externalOverlayWindow && w->opacity != TRANSLUCENT ) + { + hasRepaintNonBasePlane = true; + } - // If this is the main plane, repaint - if ( w == global_focus.focusWindow && !w->isSteamStreamingClient ) - { - if ( !cv_paint_debug_pause_base_plane ) - g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; - hasRepaint = true; - } + // If this is the main plane, repaint + if ( w == pFocus->focusWindow && !w->isSteamStreamingClient ) + { + if ( !cv_paint_debug_pause_base_plane ) + g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; + hasRepaint = true; + } - if ( w == global_focus.overrideWindow ) - { - hasRepaintNonBasePlane = true; + if ( w == pFocus->overrideWindow ) + { + hasRepaintNonBasePlane = true; + } + + if ( w->isSteamStreamingClientVideo && pFocus->focusWindow && pFocus->focusWindow->isSteamStreamingClient ) + { + if ( !cv_paint_debug_pause_base_plane ) + g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; + hasRepaint = true; + } } - if ( w->isSteamStreamingClientVideo && global_focus.focusWindow && global_focus.focusWindow->isSteamStreamingClient ) + if ( w->outdatedInteractiveFocus ) { - if ( !cv_paint_debug_pause_base_plane ) - g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; - hasRepaint = true; + MakeFocusDirty(); + w->outdatedInteractiveFocus = false; } break; @@ -6456,11 +6569,13 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re int fence = -1; if ( newCommit != nullptr ) { + global_focus_t *pCurrentFocus = GetCurrentFocus(); + // Whether or not to nudge mango app when this commit is done. - const bool mango_nudge = ( w == global_focus.focusWindow && !w->isSteamStreamingClient ) || - ( global_focus.focusWindow && global_focus.focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ); + const bool mango_nudge = pCurrentFocus && ( ( w == pCurrentFocus->focusWindow && !w->isSteamStreamingClient ) || + ( pCurrentFocus->focusWindow && pCurrentFocus->focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ) ); - bool bValidPreemptiveScale = reslistentry.pAcquirePoint && w == global_focus.focusWindow; + bool bValidPreemptiveScale = reslistentry.pAcquirePoint && pCurrentFocus && w == pCurrentFocus->focusWindow; bool bPreemptiveUpscale = bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale(); bool bKnownReady = false; @@ -7101,9 +7216,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ xwm_log.errorf("Failed to load mouse cursor: %s", g_customCursorPath); } else - { - std::shared_ptr pHostCursor; - if ( GetBackend()->GetNestedHints() && ( pHostCursor = GetBackend()->GetNestedHints()->GetHostCursor() ) ) + { + if ( std::shared_ptr pHostCursor = gamescope::GetX11HostCursor() ) { ctx->cursor->setCursorImage( reinterpret_cast( pHostCursor->pPixels.data() ), @@ -7149,7 +7263,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = *needs_flush = true; } - bool in_use = GetBackend()->IsVRRActive(); + bool in_use = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); if ( in_use != g_bVRRInUse_CachedValue || force ) { uint32_t in_use_value = in_use ? 1 : 0; @@ -7234,18 +7348,22 @@ void steamcompmgr_check_xdg(bool vblank, uint64_t vblank_idx) { if (wlserver_xdg_dirty()) { - if (global_focus.focusWindow && global_focus.focusWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.focusWindow = nullptr; - if (global_focus.inputFocusWindow && global_focus.inputFocusWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.inputFocusWindow = nullptr; - if (global_focus.overlayWindow && global_focus.overlayWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.overlayWindow = nullptr; - if (global_focus.notificationWindow && global_focus.notificationWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.notificationWindow = nullptr; - if (global_focus.overrideWindow && global_focus.overrideWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.overrideWindow = nullptr; - if (global_focus.fadeWindow && global_focus.fadeWindow->type == steamcompmgr_win_type_t::XDG) - global_focus.fadeWindow = nullptr; + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if (pFocus->focusWindow && pFocus->focusWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->focusWindow = nullptr; + if (pFocus->inputFocusWindow && pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->inputFocusWindow = nullptr; + if (pFocus->overlayWindow && pFocus->overlayWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->overlayWindow = nullptr; + if (pFocus->notificationWindow && pFocus->notificationWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->notificationWindow = nullptr; + if (pFocus->overrideWindow && pFocus->overrideWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->overrideWindow = nullptr; + if (pFocus->fadeWindow && pFocus->fadeWindow->type == steamcompmgr_win_type_t::XDG) + pFocus->fadeWindow = nullptr; + } g_steamcompmgr_xdg_wins = wlserver_get_xdg_shell_windows(); MakeFocusDirty(); } @@ -7520,7 +7638,12 @@ steamcompmgr_main(int argc, char **argv) globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - determine_and_apply_focus(); + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty() ) + determine_and_apply_focus( pFocus ); + } if ( readyPipeFD != -1 ) { @@ -7593,7 +7716,7 @@ steamcompmgr_main(int argc, char **argv) const bool bIsVBlankFromTimer = vblank; // We can always vblank if VRR. - const bool bVRR = GetBackend()->IsVRRActive(); + const bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); if ( bVRR ) vblank = true; @@ -7618,8 +7741,82 @@ steamcompmgr_main(int argc, char **argv) flush_root = true; } - if (global_focus.IsDirty()) - determine_and_apply_focus(); + static gamescope::VirtualConnectorStrategy s_eLastVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy; + gamescope::VirtualConnectorStrategy eVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy; + + if ( eVirtualConnectorStrategy != s_eLastVirtualConnectorStrategy ) + { + // If our virtual connector strategy changes, clear out our virtual connector + // global focuses. + g_VirtualConnectorFocuses.clear(); + s_eLastVirtualConnectorStrategy = eVirtualConnectorStrategy; + } + + bool bDirtyFocuses = false; + for ( auto &iter : g_VirtuaConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty() ) + { + bDirtyFocuses = true; + break; + } + } + + if ( bDirtyFocuses ) + { + // TODO(misyl): Improve this situation, it's kind of a mess. + // We could/should make this event driven rather than solving + // per-frame. + + std::vector newKeys; + + auto focusWindows = GetGlobalPossibleFocusWindows(); + for ( steamcompmgr_win_t *pWindow : focusWindows ) + { + gamescope::VirtualConnectorKey_t ulKey = pWindow->GetVirtualConnectorKey( eVirtualConnectorStrategy ); + if ( !gamescope::Algorithm::Contains( newKeys, ulKey ) ) + newKeys.emplace_back( ulKey ); + } + std::sort( newKeys.begin(), newKeys.end() ); + + std::vector oldKeys; + for ( const auto &iter : g_VirtualConnectorFocuses ) + oldKeys.emplace_back( iter.first ); + std::sort( oldKeys.begin(), oldKeys.end() ); + + std::vector diffKeys; + + std::set_symmetric_difference(oldKeys.begin(), oldKeys.end(), + newKeys.begin(), newKeys.end(), + std::back_inserter(diffKeys), + [](auto& a, auto& b) { return a < b; }); + + for ( gamescope::VirtualConnectorKey_t ulKey : diffKeys ) + { + bool bIsSteam = gamescope::VirtualConnectorKeyIsSteam( ulKey ); + + if ( gamescope::Algorithm::Contains( newKeys, ulKey ) ) + { + g_VirtualConnectorFocuses[ulKey] = global_focus_t + { + .ulVirtualFocusKey = ulKey, + .pVirtualConnector = GetBackend()->UsesVirtualConnectors() ? GetBackend()->CreateVirtualConnector( ulKey ) : nullptr, + }; + } + else if ( !bIsSteam ) // Never remove Steam's virtual conn ector. + { + g_VirtualConnectorFocuses.erase( ulKey ); + } + } + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty() ) + determine_and_apply_focus( pFocus ); + } + } // If our DRM state is out-of-date, refresh it. This might update // the output size. @@ -7832,7 +8029,7 @@ steamcompmgr_main(int argc, char **argv) XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback); } - g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata; + g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata; flush_root = true; } } @@ -7840,10 +8037,16 @@ steamcompmgr_main(int argc, char **argv) // Handles if we got a commit for the window we want to focus // to switch to it for painting (outdatedInteractiveFocus) // Doesn't realllly matter but avoids an extra frame of being on the wrong window. - if (global_focus.IsDirty()) - determine_and_apply_focus(); + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty() ) + determine_and_apply_focus( pFocus ); + } - if ( window_is_steam( global_focus.focusWindow ) ) + // XXX(misyl): This is bad! We shouldnt change the upscaler like this at all!!! + // We should move this to business logic in paint_window or something! + if ( GetCurrentFocus() && window_is_steam( GetCurrentFocus()->focusWindow ) ) { g_bSteamIsActiveWindow = true; g_upscaleScaler = GamescopeUpscaleScaler::FIT; @@ -7861,157 +8064,159 @@ steamcompmgr_main(int argc, char **argv) if ( is_fading_out() ) hasRepaint = true; - if ( vblank ) - { - if ( global_focus.cursor ) - global_focus.cursor->UpdatePosition(); - } + bool bPainted = false; - if ( GetBackend()->GetNestedHints() && !g_bForceRelativeMouse ) - { - const bool bImageEmpty = - ( global_focus.cursor && global_focus.cursor->imageEmpty() ) && - ( !window_is_steam( global_focus.inputFocusWindow ) ); + static int nIgnoredOverlayRepaints = 0; - const bool bHasPointerConstraint = global_focus.cursor->IsConstrained(); + if ( !hasRepaintNonBasePlane ) + nIgnoredOverlayRepaints = 0; - uint32_t uAppId = global_focus.inputFocusWindow - ? global_focus.inputFocusWindow->appID - : 0; + if ( cv_adaptive_sync_ignore_overlay ) + nIgnoredOverlayRepaints = 0; - const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId ); + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pPaintFocus = &iter.second; - const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId; + if ( vblank ) + { + if ( pPaintFocus->cursor ) + pPaintFocus->cursor->UpdatePosition(); + } - GetBackend()->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode ); - } + if ( pPaintFocus->GetNestedHints() && !g_bForceRelativeMouse ) + { + const bool bImageEmpty = + ( pPaintFocus->cursor && pPaintFocus->cursor->imageEmpty() ) && + ( !window_is_steam( pPaintFocus->inputFocusWindow ) ); - static int nIgnoredOverlayRepaints = 0; + const bool bHasPointerConstraint = pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained(); - if ( !hasRepaintNonBasePlane ) - nIgnoredOverlayRepaints = 0; + uint32_t uAppId = pPaintFocus->inputFocusWindow + ? pPaintFocus->inputFocusWindow->appID + : 0; - if ( cv_adaptive_sync_ignore_overlay ) - nIgnoredOverlayRepaints = 0; + const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId ); - // HACK: Disable tearing if we have an overlay to avoid stutters right now - // TODO: Fix properly. - const bool bHasOverlay = ( global_focus.overlayWindow && global_focus.overlayWindow->opacity ) || - ( global_focus.externalOverlayWindow && global_focus.externalOverlayWindow->opacity ) || - ( global_focus.overrideWindow && global_focus.focusWindow && !global_focus.focusWindow->isSteamStreamingClient && global_focus.overrideWindow->opacity ); + const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId; - // If we are running behind, allow tearing. + pPaintFocus->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode ); + } - const bool bForceRepaint = vblank && g_bForceRepaint.exchange(false); - const bool bForceSyncFlip = bForceRepaint || is_fading_out(); + // HACK: Disable tearing if we have an overlay to avoid stutters right now + // TODO: Fix properly. + const bool bHasOverlay = ( pPaintFocus->overlayWindow && pPaintFocus->overlayWindow->opacity ) || + ( pPaintFocus->externalOverlayWindow && pPaintFocus->externalOverlayWindow->opacity ) || + ( pPaintFocus->overrideWindow && pPaintFocus->focusWindow && !pPaintFocus->focusWindow->isSteamStreamingClient && pPaintFocus->overrideWindow->opacity ); - // If we are compositing, always force sync flips because we currently wait - // for composition to finish before submitting. - // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it. - const bool bSurfaceWantsAsync = (g_HeldCommits[HELD_COMMIT_BASE] != nullptr && g_HeldCommits[HELD_COMMIT_BASE]->async); - const bool bTearing = cv_tearing_enabled && GetBackend()->SupportsTearing() && bSurfaceWantsAsync; + // If we are running behind, allow tearing. - enum class FlipType - { - Normal, - Async, - VRR, - }; + const bool bForceRepaint = g_bForceRepaint.exchange(false); + const bool bForceSyncFlip = bForceRepaint || is_fading_out(); - FlipType eFlipType = FlipType::Normal; + // If we are compositing, always force sync flips because we currently wait + // for composition to finish before submitting. + // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it. + const bool bSurfaceWantsAsync = (g_HeldCommits[HELD_COMMIT_BASE] != nullptr && g_HeldCommits[HELD_COMMIT_BASE]->async); + const bool bTearing = cv_tearing_enabled && GetBackend()->SupportsTearing() && bSurfaceWantsAsync; - if ( bForceSyncFlip ) - eFlipType = FlipType::Normal; - else if ( bVRR ) - eFlipType = FlipType::VRR; - else if ( bTearing ) - { - eFlipType = FlipType::Async; + enum class FlipType + { + Normal, + Async, + VRR, + }; - if ( nIgnoredOverlayRepaints ) - eFlipType = FlipType::Normal; - if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is up atm. + FlipType eFlipType = FlipType::Normal; + + if ( bForceSyncFlip ) eFlipType = FlipType::Normal; - if ( GetVBlankTimer().WasCompositing() ) + else if ( bVRR ) + eFlipType = FlipType::VRR; + else if ( bTearing ) + { + eFlipType = FlipType::Async; + + if ( nIgnoredOverlayRepaints ) + eFlipType = FlipType::Normal; + if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is up atm. + eFlipType = FlipType::Normal; + if ( GetVBlankTimer().WasCompositing() ) + eFlipType = FlipType::Normal; + } + else eFlipType = FlipType::Normal; - } - else - eFlipType = FlipType::Normal; - if ( g_pLastReshadeEffect && ( g_pLastReshadeEffect->flags() & ReshadeEffectFlag::AlwaysScanout ) ) - { - eFlipType = FlipType::Normal; - } - - bool bShouldPaint = false; + bool bShouldPaint = false; - if ( GetBackend()->IsVisible() ) - { - switch ( eFlipType ) + if ( GetBackend()->IsVisible() ) { - case FlipType::Normal: + switch ( eFlipType ) { - bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip ); - break; - } - - case FlipType::Async: - { - bShouldPaint = hasRepaint; + case FlipType::Normal: + { + bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip ); + break; + } - if ( vblank && !bShouldPaint && hasRepaintNonBasePlane ) - nIgnoredOverlayRepaints++; + case FlipType::Async: + { + bShouldPaint = hasRepaint; - break; - } + if ( vblank && !bShouldPaint && hasRepaintNonBasePlane ) + nIgnoredOverlayRepaints++; - case FlipType::VRR: - { - bShouldPaint = hasRepaint; + break; + } - if ( bIsVBlankFromTimer ) + case FlipType::VRR: { - if ( hasRepaintNonBasePlane ) + bShouldPaint = hasRepaint; + + if ( bIsVBlankFromTimer ) { - if ( nIgnoredOverlayRepaints >= cv_adaptive_sync_overlay_cycles ) + if ( hasRepaintNonBasePlane ) { - // If we hit vblank and we previously punted on drawing an overlay - // we should go ahead and draw now. - bShouldPaint = true; - } - else if ( !bShouldPaint ) - { - // If we hit vblank (ie. fastest refresh cycle since last commit), - // and we aren't painting and we have a pending overlay, then: - // defer it until the next game update or next true vblank. - if ( !cv_adaptive_sync_ignore_overlay ) - nIgnoredOverlayRepaints++; + if ( nIgnoredOverlayRepaints >= cv_adaptive_sync_overlay_cycles ) + { + // If we hit vblank and we previously punted on drawing an overlay + // we should go ahead and draw now. + bShouldPaint = true; + } + else if ( !bShouldPaint ) + { + // If we hit vblank (ie. fastest refresh cycle since last commit), + // and we aren't painting and we have a pending overlay, then: + // defer it until the next game update or next true vblank. + if ( !cv_adaptive_sync_ignore_overlay ) + nIgnoredOverlayRepaints++; + } } } - } - // If we have a pending page flip and doing VRR, lets not do another... - if ( GetBackend()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) - bShouldPaint = false; + // If we have a pending page flip and doing VRR, lets not do another... + if ( GetBackend()->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) + bShouldPaint = false; - break; + break; + } } } - } - else - { - bShouldPaint = false; - } + else + { + bShouldPaint = false; + } - if ( g_pLastReshadeEffect && ( g_pLastReshadeEffect->flags() & ReshadeEffectFlag::AlwaysScanout ) ) - { - bShouldPaint = true; + if ( bShouldPaint ) + { + paint_all( pPaintFocus, eFlipType == FlipType::Async ); + + bPainted = true; + } } - if ( bShouldPaint ) + if ( bPainted ) { - paint_all( eFlipType == FlipType::Async ); - hasRepaint = false; hasRepaintNonBasePlane = false; nIgnoredOverlayRepaints = 0; @@ -8039,14 +8244,14 @@ steamcompmgr_main(int argc, char **argv) update_vrr_atoms(root_ctx, false, &flush_root); - if (global_focus.cursor) + if (GetCurrentFocus() && GetCurrentFocus()->cursor) { - global_focus.cursor->checkSuspension(); + GetCurrentFocus()->cursor->checkSuspension(); - if (global_focus.cursor->needs_server_flush()) + if (GetCurrentFocus()->cursor->needs_server_flush()) { flush_root = true; - global_focus.cursor->inform_flush(); + GetCurrentFocus()->cursor->inform_flush(); } } @@ -8063,34 +8268,6 @@ steamcompmgr_main(int argc, char **argv) steamcompmgr_exit(); } -void steamcompmgr_send_frame_done_to_focus_window() -{ - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - - if ( global_focus.focusWindow && global_focus.focusWindow->xwayland().surface.main_surface ) - { - wlserver_lock(); - wlserver_send_frame_done( global_focus.focusWindow->xwayland().surface.main_surface , &now ); - wlserver_unlock(); - } -} - -gamescope_xwayland_server_t *steamcompmgr_get_focused_server() -{ - if (global_focus.inputFocusWindow != nullptr) - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - if (server->ctx->focus.inputFocusWindow == global_focus.inputFocusWindow) - return server; - } - } - - return wlserver_get_xwayland_server(0); -} - struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx ) { gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); @@ -8121,7 +8298,7 @@ struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xw MouseCursor *steamcompmgr_get_current_cursor() { - return global_focus.cursor; + return GetCurrentFocus()->cursor; } MouseCursor *steamcompmgr_get_server_cursor(uint32_t idx) diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 9f384c461c..df537eb598 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -136,7 +136,6 @@ void nudge_steamcompmgr( void ); void force_repaint( void ); extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ); -gamescope_xwayland_server_t *steamcompmgr_get_focused_server(); struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx ); wlserver_vk_swapchain_feedback* steamcompmgr_get_base_layer_swapchain_feedback(); diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp index 095694e493..d82382959e 100644 --- a/src/steamcompmgr_shared.hpp +++ b/src/steamcompmgr_shared.hpp @@ -209,6 +209,21 @@ struct steamcompmgr_win_t { else return nullptr; } + + gamescope::VirtualConnectorKey_t GetVirtualConnectorKey( gamescope::VirtualConnectorStrategy eStrategy ) + { + switch ( eStrategy ) + { + default: + case gamescope::VirtualConnectorStrategies::SingleApplication: + case gamescope::VirtualConnectorStrategies::SteamControlled: + return 0; + case gamescope::VirtualConnectorStrategies::PerAppId: + return static_cast( this->appID ); + case gamescope::VirtualConnectorStrategies::PerWindow: + return static_cast( this->seq ); + } + } }; namespace gamescope diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp index 2fd0ec45ef..f036d000a8 100644 --- a/src/vblankmanager.cpp +++ b/src/vblankmanager.cpp @@ -100,7 +100,7 @@ namespace gamescope const int nRefreshRate = GetRefresh(); const uint64_t ulRefreshInterval = mHzToRefreshCycle( nRefreshRate ); - bool bVRR = GetBackend()->IsVRRActive(); + bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); uint64_t ulOffset = 0; if ( !bVRR ) { @@ -358,7 +358,22 @@ namespace gamescope if ( !m_bRunning ) return; - VBlankScheduleTime schedule = GetBackend()->FrameSync(); + VBlankScheduleTime schedule; + if ( GetBackend()->GetCurrentConnector() ) + { + schedule = GetBackend()->GetCurrentConnector()->FrameSync(); + } + else + { + // If we don't currently have a connector, make up some dummy refresh cycle. + sleep_for_nanos( mHzToRefreshCycle( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ) ); + uint64_t ulNow = get_time_in_nanos(); + schedule = VBlankScheduleTime + { + .ulTargetVBlank = ulNow + 3'000'000, + .ulScheduledWakeupPoint = ulNow, + }; + } const uint64_t ulWakeupTime = get_time_in_nanos(); { From ae445f6c9579fce94c1cf7b8b52ef3956d597224 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:39:33 +0000 Subject: [PATCH 02/28] main: Add --virtual-connector-strategy --- src/backend.h | 23 ++++++++++++++++++----- src/main.cpp | 15 ++++++++++++++- src/steamcompmgr.cpp | 5 +++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/backend.h b/src/backend.h index 99c2d37f75..3080ba54be 100644 --- a/src/backend.h +++ b/src/backend.h @@ -34,16 +34,16 @@ namespace gamescope { enum VirtualConnectorStrategy : uint32_t { - SingleApplication = 0x1, - SteamControlled = 0x2, - PerAppId = 0x4, - PerWindow = 0x8, + SingleApplication, + SteamControlled, + PerAppId, + PerWindow, Count, }; } using VirtualConnectorStrategy = VirtualConnectorStrategies::VirtualConnectorStrategy; - using VirtualConnectorKey_t = uint64_t; + using VirtualConnectorKey_t = uint64_t; extern ConVar cv_backend_virtual_connector_strategy; static constexpr bool VirtualConnectorStrategyIsSingleOutput( VirtualConnectorStrategy eStrategy ) @@ -66,6 +66,19 @@ namespace gamescope return VirtualConnectorInSteamPerAppState() && ulKey == 769; } + static inline std::string_view VirtualConnectorStrategyToString( VirtualConnectorStrategy eStrategy ) + { + switch ( eStrategy ) + { + case VirtualConnectorStrategies::SingleApplication: return "SingleApplication"; + case VirtualConnectorStrategies::SteamControlled: return "SteamControlled"; + case VirtualConnectorStrategies::PerAppId: return "PerAppId"; + case VirtualConnectorStrategies::PerWindow: return "PerWindow"; + default: + return "Unknown"; + } + } + namespace TouchClickModes { diff --git a/src/main.cpp b/src/main.cpp index 7230ebeb8a..349fa3d2d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -115,6 +115,7 @@ const struct option *gamescope_options = (struct option[]){ { "cursor", required_argument, nullptr, 0 }, { "cursor-hotspot", required_argument, nullptr, 0 }, { "cursor-scale-height", required_argument, nullptr, 0 }, + { "virtual-connector-strategy", required_argument, nullptr, 0 }, { "ready-fd", required_argument, nullptr, 'R' }, { "stats-path", required_argument, nullptr, 'T' }, { "hide-cursor-delay", required_argument, nullptr, 'C' }, @@ -192,6 +193,7 @@ const char usage[] = " --force-orientation rotate the internal display (left, right, normal, upsidedown)\n" " --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n" " --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n" + " --virtual-connector-strategy Specifies how we should make virtual connectors.\n" " --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n" " If this is not set, and there is a HDR client, it will be tonemapped SDR.\n" " --sdr-gamut-wideness Set the 'wideness' of the gamut for SDR comment. 0 - 1.\n" @@ -764,7 +766,7 @@ int main(int argc, char **argv) } else if (strcmp(opt_name, "force-grab-cursor") == 0) { g_bForceRelativeMouse = true; } else if (strcmp(opt_name, "display-index") == 0) { - g_nNestedDisplayIndex = atoi( optarg ); + g_nNestedDisplayIndex = atoi( optarg ); } else if (strcmp(opt_name, "adaptive-sync") == 0) { cv_adaptive_sync = true; } else if (strcmp(opt_name, "expose-wayland") == 0) { @@ -775,6 +777,17 @@ int main(int argc, char **argv) g_nCursorScaleHeight = atoi(optarg); } else if (strcmp(opt_name, "mangoapp") == 0) { g_bLaunchMangoapp = true; + } else if (strcmp(opt_name, "virtual-connector-strategy") == 0) { + for ( uint32_t i = 0; i < gamescope::VirtualConnectorStrategies::Count; i++ ) + { + gamescope::VirtualConnectorStrategy eStrategy = + static_cast( i ); + if ( optarg == gamescope::VirtualConnectorStrategyToString( eStrategy ) ) + { + gamescope::cv_backend_virtual_connector_strategy = eStrategy; + + } + } } break; case '?': diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 981d6e207a..aa0b6c44ed 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -7753,7 +7753,7 @@ steamcompmgr_main(int argc, char **argv) } bool bDirtyFocuses = false; - for ( auto &iter : g_VirtuaConnectorFocuses ) + for ( auto &iter : g_VirtualConnectorFocuses ) { global_focus_t *pFocus = &iter.second; if ( pFocus->IsDirty() ) @@ -7763,7 +7763,8 @@ steamcompmgr_main(int argc, char **argv) } } - if ( bDirtyFocuses ) + // XXX: Need to look into why this doesn't work. + // if ( bDirtyFocuses ) { // TODO(misyl): Improve this situation, it's kind of a mess. // We could/should make this event driven rather than solving From 75eb1790d6a10fbcf0267f3b6c2e655a2cfb783c Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:52:19 +0000 Subject: [PATCH 03/28] OpeNVRBackend: Add vr_nudge_to_visible_per_connector --- src/Backends/OpenVRBackend.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 34e11791cd..8ba9879ad2 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -65,6 +65,7 @@ gamescope::ConVar cv_vr_trackpad_sensitivity( "vr_trackpad_sensitivity", gamescope::ConVar cv_vr_trackpad_click_time( "vr_trackpad_click_time", 250'000'000ul, "Time to consider a 'click' vs a 'drag' when using trackpad mode. In nanoseconds." ); gamescope::ConVar cv_vr_trackpad_click_max_delta( "vr_trackpad_click_max_delta", 0.14f, "Max amount the cursor can move before not clicking." ); gamescope::ConVar cv_vr_debug_force_opaque( "vr_debug_force_opaque", false, "Force textures to be treated as opaque." ); +gamescope::ConVar cv_vr_nudge_to_visible_per_connector( "vr_nudge_to_visible_per_connector", false, "" ); // Just below half of 120Hz, so we always at least poll input once per frame, regardless of cadence/cycles. gamescope::ConVar cv_vr_poll_rate( "vr_poll_rate", 4'000'000ul, "Time between input polls. In nanoseconds." ); @@ -775,6 +776,7 @@ namespace gamescope float GetPhysicalPreCurvePitch() const { return m_flPhysicalPreCurvePitch; } float GetScrollSpeed() const { return m_flScrollSpeed; } + bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } bool ShouldNudgeToVisible() const { return m_bNudgeToVisible; } CVulkanTexture *GetBlackTexture() { return m_pBlackTexture.get(); } @@ -1515,9 +1517,21 @@ namespace gamescope vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); } - if ( !m_bIsSubview && m_pConnector->ConsumeNudgeToVisible() ) + if ( !m_bIsSubview ) { - vr::VROverlay()->ShowDashboard( m_sDashboardOverlayKey.c_str() ); + bool bNudgeToVisible = cv_vr_nudge_to_visible_per_connector + ? m_pConnector->ConsumeNudgeToVisible() + : m_pBackend->ConsumeNudgeToVisible(); + + if ( bNudgeToVisible ) + { + vr::VROverlay()->ShowDashboard( m_sDashboardOverlayKey.c_str() ); + + // Make sure we don't leave any nudges either side. + m_pConnector->ConsumeNudgeToVisible(); + if ( !cv_vr_nudge_to_visible_per_connector ) + m_pBackend->ConsumeNudgeToVisible(); + } } } else From a2e49de405f928816af571be70ee74ef2eb508a4 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 19:08:03 +0000 Subject: [PATCH 04/28] OpenVRBackend: Add logging for overlay visible count --- src/Backends/OpenVRBackend.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 8ba9879ad2..04035696af 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -993,11 +993,13 @@ namespace gamescope // or for other reasons. if ( !plane.IsSubview() ) { + int nNewOverlayVisibleCount; if ( vrEvent.eventType == vr::VREvent_OverlayShown ) - m_nOverlaysVisible++; + nNewOverlayVisibleCount = ++m_nOverlaysVisible; else - m_nOverlaysVisible--; + nNewOverlayVisibleCount = --m_nOverlaysVisible; + openvr_log.debugf( "nNewOverlayVisibleCount: %d", nNewOverlayVisibleCount ); m_nOverlaysVisible.notify_all(); assert( m_nOverlaysVisible >= 0 ); } From f787cc6975c172be4437f613cfd2a0e6638e21a5 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 21:43:45 +0000 Subject: [PATCH 05/28] OpenVRBackend: Consider visible when an appid's scene app is visible --- src/Backends/OpenVRBackend.cpp | 116 ++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 04035696af..e6952a000a 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -79,6 +79,8 @@ namespace vr const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); } +uint32_t get_appid_from_pid( pid_t pid ); + /////////////////////////////////////////////// // Josh: // GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( @@ -308,6 +310,28 @@ namespace gamescope bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } bool IsRelativeMouse() const { return m_bRelativeMouse; } + // Thread safe. + bool IsVisible() const + { + return m_bOverlayShown || m_bSceneAppVisible; + } + + // Only called from event thread + void MarkOverlayShown( bool bShown ) + { + m_bOverlayShown = bShown; + UpdateVisibility( "Overlay Visibility" ); + } + + // Only called from event thread + void MarkSceneAppShown( bool bShown ) + { + m_bSceneAppVisible = bShown; + UpdateVisibility( "Scene App Visibility" ); + } + + void UpdateVisibility( const char *pszReason ); + private: COpenVRBackend *m_pBackend = nullptr; COpenVRPlane m_Planes[8]; @@ -317,6 +341,10 @@ namespace gamescope bool m_bNudgeToVisible = false; std::atomic m_bRelativeMouse = false; + + bool m_bWasVisible = false; // Event thread only + std::atomic m_bOverlayShown = { false }; + std::atomic m_bSceneAppVisible = { false }; }; class COpenVRBackend final : public CBaseBackend @@ -844,6 +872,46 @@ namespace gamescope break; } + case vr::VREvent_SceneApplicationChanged: + { + if ( m_uCurrentScenePid != vrEvent.data.process.pid ) + { + m_uCurrentScenePid = vrEvent.data.process.pid; + m_uCurrentSceneAppId = get_appid_from_pid( m_uCurrentScenePid ); + + openvr_log.debugf( "SceneApplicationChanged -> pid: %u appid: %u", m_uCurrentScenePid, m_uCurrentSceneAppId ); + + std::optional oulNewSceneAppVirtualConnectorKey; + if ( cv_backend_virtual_connector_strategy == VirtualConnectorStrategies::PerAppId ) + { + oulNewSceneAppVirtualConnectorKey = m_uCurrentSceneAppId; + } + + if ( ( oulNewSceneAppVirtualConnectorKey || m_oulCurrentSceneVirtualConnectorKey ) && + ( oulNewSceneAppVirtualConnectorKey != m_oulCurrentSceneVirtualConnectorKey ) ) + { + for ( COpenVRConnector *pOtherConnector : m_pActiveConnectors ) + { + if ( oulNewSceneAppVirtualConnectorKey ) + { + if ( pOtherConnector->GetVirtualConnectorKey() == *oulNewSceneAppVirtualConnectorKey ) + pOtherConnector->MarkSceneAppShown( true ); + } + + if ( m_oulCurrentSceneVirtualConnectorKey ) + { + if ( pOtherConnector->GetVirtualConnectorKey() == *m_oulCurrentSceneVirtualConnectorKey ) + pOtherConnector->MarkSceneAppShown( false ); + } + } + } + + m_oulCurrentSceneVirtualConnectorKey = oulNewSceneAppVirtualConnectorKey; + } + + break; + } + case vr::VREvent_KeyboardCharInput: { if (m_pIME) @@ -993,15 +1061,7 @@ namespace gamescope // or for other reasons. if ( !plane.IsSubview() ) { - int nNewOverlayVisibleCount; - if ( vrEvent.eventType == vr::VREvent_OverlayShown ) - nNewOverlayVisibleCount = ++m_nOverlaysVisible; - else - nNewOverlayVisibleCount = --m_nOverlaysVisible; - - openvr_log.debugf( "nNewOverlayVisibleCount: %d", nNewOverlayVisibleCount ); - m_nOverlaysVisible.notify_all(); - assert( m_nOverlaysVisible >= 0 ); + pConnector->MarkOverlayShown( vrEvent.eventType == vr::VREvent_OverlayShown ); } break; } @@ -1050,6 +1110,10 @@ namespace gamescope glm::vec2 m_vScreenTrackpadPos{}; glm::vec2 m_vScreenStartTrackpadPos{}; + uint32_t m_uCurrentScenePid = -1; + uint32_t m_uCurrentSceneAppId = 0; + std::optional m_oulCurrentSceneVirtualConnectorKey; + friend COpenVRConnector; std::vector m_pActiveConnectors; std::mutex m_mutActiveConnectors; @@ -1072,6 +1136,9 @@ namespace gamescope { std::scoped_lock lock{ m_pBackend->m_mutActiveConnectors }; + MarkSceneAppShown( false ); + MarkOverlayShown( false ); + auto iter = m_pBackend->m_pActiveConnectors.begin(); for ( ; iter != m_pBackend->m_pActiveConnectors.end(); iter++ ) { @@ -1336,6 +1403,8 @@ namespace gamescope bool COpenVRConnector::Init() { + openvr_log.debugf( "New connector! -> ulKey: %lu", GetVirtualConnectorKey() ); + m_bNudgeToVisible = m_pBackend->ShouldNudgeToVisible(); for ( uint32_t i = 0; i < 8; i++ ) @@ -1350,10 +1419,39 @@ namespace gamescope if ( g_bForceRelativeMouse ) this->SetRelativeMouseMode( true ); + + if ( m_pBackend->m_oulCurrentSceneVirtualConnectorKey && + GetVirtualConnectorKey() == *m_pBackend->m_oulCurrentSceneVirtualConnectorKey ) + { + MarkSceneAppShown( true ); + } return true; } + void COpenVRConnector::UpdateVisibility( const char *pszReason ) + { + bool bVisible = IsVisible(); + if ( m_bWasVisible != bVisible ) + { + int nNewOverlayVisibleCount; + if ( bVisible ) + nNewOverlayVisibleCount = ++m_pBackend->m_nOverlaysVisible; + else + nNewOverlayVisibleCount = --m_pBackend->m_nOverlaysVisible; + + m_pBackend->m_nOverlaysVisible.notify_all(); + + m_bWasVisible = bVisible; + openvr_log.debugf( "[%s] ulKey: %lu nNewOverlayVisibleCount: %d -> m_bOverlayShown: %s m_bSceneAppVisible: %s", + pszReason, + GetVirtualConnectorKey(), + nNewOverlayVisibleCount, + m_bOverlayShown ? "true" : "false", + m_bSceneAppVisible ? "true" : "false" ); + } + } + ///////////////////////// // COpenVRFb ///////////////////////// From 0e2f5c7a9cd5ec9739e11baafe77027817d6be3b Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 22:35:02 +0000 Subject: [PATCH 06/28] OpenVRBackend: Add vr-app-overlay-key --- src/Backends/OpenVRBackend.cpp | 15 ++++++++++++++- src/main.cpp | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index e6952a000a..c51fccafd2 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -414,6 +414,8 @@ namespace gamescope opt_name = gamescope_options[opt_index].name; if (strcmp(opt_name, "vr-overlay-key") == 0) { m_szOverlayKey = optarg; + } else if (strcmp(opt_name, "vr-app-overlay-key") == 0) { + m_szAppOverlayKey = optarg; } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { m_pchOverlayName = optarg; m_bExplicitOverlayName = true; @@ -793,6 +795,7 @@ namespace gamescope } const char *GetOverlayKey() const { return m_szOverlayKey.c_str(); } + const char *GetAppOverlayKey() const { return m_szAppOverlayKey.c_str(); } const char *GetOverlayName() const { return m_pchOverlayName; } const char *GetOverlayIcon() const { return m_pchOverlayIcon; } bool ShouldEnableControlBar() const { return m_bEnableControlBar; } @@ -1078,6 +1081,7 @@ namespace gamescope } std::string m_szOverlayKey; + std::string m_szAppOverlayKey; const char *m_pchOverlayName = nullptr; const char *m_pchOverlayIcon = nullptr; bool m_bExplicitOverlayName = false; @@ -1506,7 +1510,16 @@ namespace gamescope bool bIsSteam = VirtualConnectorKeyIsSteam( ulKey ); if ( !bIsSteam ) { - sOverlayKey += ".app"; + const char *pszAppOverlayKey = m_pBackend->GetAppOverlayKey(); + if ( pszAppOverlayKey && *pszAppOverlayKey ) + { + sOverlayKey = pszAppOverlayKey; + sOverlayKey += "."; + } + else + { + sOverlayKey += ".app."; + } sOverlayKey += std::to_string( m_pConnector->GetVirtualConnectorKey() ); } } diff --git a/src/main.cpp b/src/main.cpp index 349fa3d2d8..649bc9d63f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,6 +94,7 @@ const struct option *gamescope_options = (struct option[]){ // openvr options #if HAVE_OPENVR { "vr-overlay-key", required_argument, nullptr, 0 }, + { "vr-app-overlay-key", required_argument, nullptr, 0 }, { "vr-overlay-explicit-name", required_argument, nullptr, 0 }, { "vr-overlay-default-name", required_argument, nullptr, 0 }, { "vr-overlay-icon", required_argument, nullptr, 0 }, @@ -224,6 +225,7 @@ const char usage[] = #if HAVE_OPENVR "VR mode options:\n" " --vr-overlay-key Sets the SteamVR overlay key to this string\n" + " --vr-app-overlay-key Sets the SteamVR overlay key to use for child apps\n" " --vr-overlay-explicit-name Force the SteamVR overlay name to always be this string\n" " --vr-overlay-default-name Sets the fallback SteamVR overlay name when there is no window title\n" " --vr-overlay-icon Sets the SteamVR overlay icon to this file\n" From 3967caca6676da4bfac2e65aa41f49733064186f Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:08:13 +0000 Subject: [PATCH 07/28] steamcompmgr: Clean up g_VirtualConnectorFocuses before closing backend off --- src/steamcompmgr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index aa0b6c44ed..47a94c6007 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -6046,6 +6046,8 @@ steamcompmgr_exit(void) } } + g_VirtualConnectorFocuses.clear(); + gamescope::IBackend::Set( nullptr ); wlserver_lock(); From fe44d885fc9be72b07f1609bd668d6d43a6222c7 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:13:12 +0000 Subject: [PATCH 08/28] OpenVRBackend: Add logging for creating a new dashboard overlay --- src/Backends/OpenVRBackend.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index c51fccafd2..323cd71800 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -1527,6 +1527,7 @@ namespace gamescope if ( !m_bIsSubview ) { m_sDashboardOverlayKey = sOverlayKey; + openvr_log.debugf( "Creating new dashboard overlay: %s", m_sDashboardOverlayKey.c_str() ); vr::VROverlay()->CreateDashboardOverlay( sOverlayKey.c_str(), From 4b86596139fdb81eb2ba8f5535c7b548ff570864 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:19:04 +0000 Subject: [PATCH 09/28] OpenVRBackend: Fix crash with SteamVR input thread --- src/Backends/OpenVRBackend.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 323cd71800..e68d1df92c 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -351,11 +351,18 @@ namespace gamescope { public: COpenVRBackend() + : m_Thread{ [this](){ this->VRInputThread(); } } { } virtual ~COpenVRBackend() { + m_bRunning = false; + + m_bInitted = true; + m_bInitted.notify_all(); + + m_Thread.join(); } ///////////// @@ -501,8 +508,10 @@ namespace gamescope // Setup misc. stuff g_nOutputRefresh = (int32_t) ConvertHztomHz( roundf( vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ) ) ); - std::thread input_thread_vrinput( [this](){ this->VRInputThread(); } ); - input_thread_vrinput.detach(); + m_bRunning = true; + + m_bInitted = true; + m_bInitted.notify_all(); return true; } @@ -839,9 +848,11 @@ namespace gamescope { pthread_setname_np( pthread_self(), "gamescope-vrinp" ); + m_bInitted.wait( false ); + // Josh: PollNextOverlayEvent sucks. // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. - while (true) + while ( m_bRunning ) { { std::scoped_lock lock{ m_mutActiveConnectors }; @@ -1122,6 +1133,10 @@ namespace gamescope std::vector m_pActiveConnectors; std::mutex m_mutActiveConnectors; std::atomic m_pFocusConnector; + + std::thread m_Thread; + std::atomic m_bInitted = { false }; + std::atomic m_bRunning = { false }; }; //////////////////// From 831705bdf8bba525a1dcfa85870d483baa4cc405 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Mon, 26 Aug 2024 23:00:57 +0000 Subject: [PATCH 10/28] SDLBackend: Fix with virtual connector backend --- src/Backends/SDLBackend.cpp | 73 +++++++++++++++++++++++++++++++------ src/steamcompmgr.cpp | 11 ++++-- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp index d79b60f67b..325f91f78d 100644 --- a/src/Backends/SDLBackend.cpp +++ b/src/Backends/SDLBackend.cpp @@ -55,7 +55,7 @@ namespace gamescope GAMESCOPE_SDL_EVENT_COUNT, }; - class CSDLConnector final : public CBaseBackendConnector + class CSDLConnector final : public CBaseBackendConnector, public INestedHints { public: CSDLConnector(); @@ -98,8 +98,24 @@ namespace gamescope return "Virtual Display"; } + virtual INestedHints *GetNestedHints() override + { + return this; + } + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + /////////////////// + // INestedHints + /////////////////// + + virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetRelativeMouseMode( bool bRelative ) override; + virtual void SetVisible( bool bVisible ) override; + virtual void SetTitle( std::shared_ptr szTitle ) override; + virtual void SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; + //-- SDL_Window *GetSDLWindow() const { return m_pWindow; } @@ -110,7 +126,7 @@ namespace gamescope BackendConnectorHDRInfo m_HDRInfo{}; }; - class CSDLBackend : public CBaseBackend, public INestedHints + class CSDLBackend : public CBaseBackend { public: CSDLBackend(); @@ -151,17 +167,16 @@ namespace gamescope virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; - /////////////////// - // INestedHints - /////////////////// + //////////////////////// + // INestedHints Compat + /////////////////////// - virtual void SetCursorImage( std::shared_ptr info ) override; - virtual void SetRelativeMouseMode( bool bRelative ) override; - virtual void SetVisible( bool bVisible ) override; - virtual void SetTitle( std::shared_ptr szTitle ) override; - virtual void SetIcon( std::shared_ptr> uIconPixels ) override; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; - virtual std::shared_ptr GetHostCursor() override; + void SetCursorImage( std::shared_ptr info ); + void SetRelativeMouseMode( bool bRelative ); + void SetVisible( bool bVisible ); + void SetTitle( std::shared_ptr szTitle ); + void SetIcon( std::shared_ptr> uIconPixels ); + void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ); protected: virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; private: @@ -339,6 +354,40 @@ namespace gamescope return 0; } + void CSDLConnector::SetCursorImage( std::shared_ptr info ) + { + CSDLBackend *pBackend = static_cast( GetBackend() ); + pBackend->SetCursorImage( std::move( info ) ); + } + void CSDLConnector::SetRelativeMouseMode( bool bRelative ) + { + CSDLBackend *pBackend = static_cast( GetBackend() ); + pBackend->SetRelativeMouseMode( bRelative ); + } + void CSDLConnector::SetVisible( bool bVisible ) + { + CSDLBackend *pBackend = static_cast( GetBackend() ); + pBackend->SetVisible( bVisible ); + } + void CSDLConnector::SetTitle( std::shared_ptr szTitle ) + { + CSDLBackend *pBackend = static_cast( GetBackend() ); + pBackend->SetTitle( std::move( szTitle ) ); + } + void CSDLConnector::SetIcon( std::shared_ptr> uIconPixels ) + { + CSDLBackend *pBackend = static_cast( GetBackend() ); + pBackend->SetIcon( std::move( uIconPixels ) ); + } + + void CSDLConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + { + if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) + SDL_SetClipboardText(szContents->c_str()); + else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) + SDL_SetPrimarySelectionText(szContents->c_str()); + } + //////////////// // CSDLBackend //////////////// diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 47a94c6007..282dacb932 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -753,9 +753,13 @@ struct global_focus_t : public focus_t gamescope::INestedHints *GetNestedHints() { - if ( pVirtualConnector ) + gamescope::IBackendConnector *pConnector = this->pVirtualConnector.get(); + if ( !pConnector ) + pConnector = GetBackend()->GetCurrentConnector(); + + if ( pConnector ) { - return pVirtualConnector->GetNestedHints(); + return pConnector->GetNestedHints(); } return nullptr; @@ -8152,7 +8156,8 @@ steamcompmgr_main(int argc, char **argv) bool bShouldPaint = false; - if ( GetBackend()->IsVisible() ) + //if ( GetBackend()->IsVisible() ) + if ( true ) { switch ( eFlipType ) { From 9886583f4c178726de333cf06bb07f023dbb72d5 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Tue, 27 Aug 2024 00:31:25 +0000 Subject: [PATCH 11/28] steamcompmgr: Fix Steam intergration with multiple virtual connectors --- src/steamcompmgr.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 282dacb932..26cfdc7d61 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -3966,28 +3966,31 @@ determine_and_apply_focus( global_focus_t *pFocus ) focused_keyboard_display = get_win_display_name(pFocus->keyboardFocusWindow); } - if ( steamMode ) + if ( pFocus == GetCurrentFocus() ) { - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedAppId, focusedAppId != 0 ? 1 : 0 ); + if ( steamMode ) + { + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppAtom, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&focusedAppId, focusedAppId != 0 ? 1 : 0 ); - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppGfxAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedBaseAppId, focusedBaseAppId != 0 ? 1 : 0 ); - } + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppGfxAtom, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&focusedBaseAppId, focusedBaseAppId != 0 ? 1 : 0 ); + } - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedWindowAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedWindow, focusedWindow != 0 ? 1 : 0 ); + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedWindowAtom, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&focusedWindow, focusedWindow != 0 ? 1 : 0 ); - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_display, strlen(focused_display) + 1 ); + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusDisplay, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)focused_display, strlen(focused_display) + 1 ); - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeMouseFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_mouse_display, strlen(focused_mouse_display) + 1 ); + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeMouseFocusDisplay, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)focused_mouse_display, strlen(focused_mouse_display) + 1 ); - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeKeyboardFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_keyboard_display, strlen(focused_keyboard_display) + 1 ); + XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeKeyboardFocusDisplay, XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)focused_keyboard_display, strlen(focused_keyboard_display) + 1 ); - XFlush( root_ctx->dpy ); + XFlush( root_ctx->dpy ); + } // Sort out fading. if (pFocus->focusWindow && previousLocalFocus.focusWindow != pFocus->focusWindow) From cf43175458a0a61a56e3fa0ab584451fd1eaf239 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 20:02:05 +0000 Subject: [PATCH 12/28] LibInputHandler: add, hook up to vr-session-manager --- src/Backends/OpenVRBackend.cpp | 18 ++++ src/LibInputHandler.cpp | 163 +++++++++++++++++++++++++++++++++ src/LibInputHandler.h | 27 ++++++ src/main.cpp | 1 + src/meson.build | 4 +- src/wlserver.cpp | 6 +- 6 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/LibInputHandler.cpp create mode 100644 src/LibInputHandler.h diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index e68d1df92c..cfb93f9a23 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -19,6 +19,7 @@ #include "refresh_rate.h" #include "edid.h" #include "Ratio.h" +#include "LibInputHandler.h" #include #include @@ -352,6 +353,7 @@ namespace gamescope public: COpenVRBackend() : m_Thread{ [this](){ this->VRInputThread(); } } + , m_LibInputWaiter{ "gamescope-libinput" } { } @@ -450,6 +452,19 @@ namespace gamescope m_flPhysicalPreCurvePitch = atof( optarg ); } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { m_flScrollSpeed = atof( optarg ); + } else if (strcmp(opt_name, "vr-session-manager") == 0) { + openvr_log.infof( "Becoming the VR session manager." ); + + std::unique_ptr pLibInput = std::make_unique(); + if ( pLibInput->Init() ) + { + m_pLibInput = std::move( pLibInput ); + m_LibInputWaiter.AddWaitable( m_pLibInput.get() ); + } + else + { + openvr_log.errorf( "Could not start libinput for being the vr session manager" ); + } } break; case '?': @@ -1137,6 +1152,9 @@ namespace gamescope std::thread m_Thread; std::atomic m_bInitted = { false }; std::atomic m_bRunning = { false }; + + std::shared_ptr m_pLibInput; + CAsyncWaiter, 16> m_LibInputWaiter; }; //////////////////// diff --git a/src/LibInputHandler.cpp b/src/LibInputHandler.cpp new file mode 100644 index 0000000000..ed5ff56021 --- /dev/null +++ b/src/LibInputHandler.cpp @@ -0,0 +1,163 @@ +#include "LibInputHandler.h" + +#include +#include +#include +#include + +#include "log.hpp" +#include "wlserver.hpp" +#include "Utils/Defer.h" + +// Handles libinput in contexts where we don't have a session +// and can't use the wlroots libinput stuff. +// +// eg. in VR where we want global access to the m + kb +// without doing any seat dance. +// +// That may change in the future... +// but for now, this solves that problem. + +namespace gamescope +{ + static LogScope log_input_stealer( "InputStealer" ); + + const libinput_interface CLibInputHandler::s_LibInputInterface = + { + .open_restricted = []( const char *pszPath, int nFlags, void *pUserData ) -> int + { + return open( pszPath, nFlags ); + }, + + .close_restricted = []( int nFd, void *pUserData ) -> void + { + close( nFd ); + }, + }; + + CLibInputHandler::CLibInputHandler() + { + } + + CLibInputHandler::~CLibInputHandler() + { + if ( m_pLibInput ) + { + libinput_unref( m_pLibInput ); + m_pLibInput = nullptr; + } + + if ( m_pUdev ) + { + udev_unref( m_pUdev ); + m_pUdev = nullptr; + } + } + + bool CLibInputHandler::Init() + { + m_pUdev = udev_new(); + if ( !m_pUdev ) + { + log_input_stealer.errorf( "Failed to create udev interface" ); + return false; + } + + m_pLibInput = libinput_udev_create_context( &s_LibInputInterface, nullptr, m_pUdev ); + if ( !m_pLibInput ) + { + log_input_stealer.errorf( "Failed to create libinput context" ); + return false; + } + + const char *pszSeatName = "seat0"; + if ( libinput_udev_assign_seat( m_pLibInput, pszSeatName ) == -1 ) + { + log_input_stealer.errorf( "Could not assign seat \"%s\"", pszSeatName ); + return false; + } + + return true; + } + + int CLibInputHandler::GetFD() + { + if ( !m_pLibInput ) + return -1; + + return libinput_get_fd( m_pLibInput ); + } + + void CLibInputHandler::OnPollIn() + { + static uint32_t s_uSequence = 0; + + libinput_dispatch( m_pLibInput ); + + while ( libinput_event *pEvent = libinput_get_event( m_pLibInput ) ) + { + defer( libinput_event_destroy( pEvent ) ); + + libinput_event_type eEventType = libinput_event_get_type( pEvent ); + + switch ( eEventType ) + { + case LIBINPUT_EVENT_POINTER_MOTION: + { + libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + + double flDx = libinput_event_pointer_get_dx( pPointerEvent ); + double flDy = libinput_event_pointer_get_dy( pPointerEvent ); + + wlserver_lock(); + wlserver_mousemotion( flDx, flDy, ++s_uSequence ); + wlserver_unlock(); + } + break; + + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + { + libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + + double flX = libinput_event_pointer_get_absolute_x( pPointerEvent ); + double flY = libinput_event_pointer_get_absolute_y( pPointerEvent ); + + wlserver_lock(); + wlserver_mousewarp( flX, flY, ++s_uSequence, true ); + wlserver_unlock(); + } + break; + + case LIBINPUT_EVENT_POINTER_BUTTON: + { + libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + + uint32_t uButton = libinput_event_pointer_get_button( pPointerEvent ); + libinput_button_state eButtonState = libinput_event_pointer_get_button_state( pPointerEvent ); + + wlserver_lock(); + wlserver_mousebutton( uButton, eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, ++s_uSequence ); + wlserver_unlock(); + } + break; + + // TODO: Scrolling. + + case LIBINPUT_EVENT_KEYBOARD_KEY: + { + libinput_event_keyboard *pKeyboardEvent = libinput_event_get_keyboard_event( pEvent ); + uint32_t uKey = libinput_event_keyboard_get_key( pKeyboardEvent ); + libinput_key_state eState = libinput_event_keyboard_get_key_state( pKeyboardEvent ); + + wlserver_lock(); + wlserver_key( uKey, eState == LIBINPUT_KEY_STATE_PRESSED, ++ s_uSequence ); + wlserver_unlock(); + } + break; + + default: + break; + } + } + } +} diff --git a/src/LibInputHandler.h b/src/LibInputHandler.h new file mode 100644 index 0000000000..de1c3132f5 --- /dev/null +++ b/src/LibInputHandler.h @@ -0,0 +1,27 @@ +#pragma once + +#include "waitable.h" + +struct libinput_interface; +struct udev; +struct libinput; + +namespace gamescope +{ + class CLibInputHandler final : public IWaitable + { + public: + CLibInputHandler(); + ~CLibInputHandler(); + + bool Init(); + + virtual int GetFD() override; + virtual void OnPollIn() override; + private: + udev *m_pUdev = nullptr; + libinput *m_pLibInput = nullptr; + + static const libinput_interface s_LibInputInterface; + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 649bc9d63f..7bd4f6f2ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,6 +107,7 @@ const struct option *gamescope_options = (struct option[]){ { "vr-overlay-physical-curvature", required_argument, nullptr, 0 }, { "vr-overlay-physical-pre-curve-pitch", required_argument, nullptr, 0 }, { "vr-scroll-speed", required_argument, nullptr, 0 }, + { "vr-session-manager", no_argument, nullptr, 0 }, #endif // wlserver options diff --git a/src/meson.build b/src/meson.build index 74fc0334d4..63897dd2a4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -119,9 +119,11 @@ src = [ 'backend.cpp', 'x11cursor.cpp', 'InputEmulation.cpp', + 'LibInputHandler.cpp', ] luajit_dep = dependency( 'luajit' ) +libinput_dep = dependency('libinput', required: true) gamescope_cpp_args = [] if drm_dep.found() @@ -194,7 +196,7 @@ gamescope_version = configure_file( xkbcommon, thread_dep, sdl2_dep, wlroots_dep, vulkan_dep, liftoff_dep, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep, stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi, - libdecor_dep, eis_dep, luajit_dep + libdecor_dep, eis_dep, luajit_dep, libinput_dep, ], install: true, cpp_args: gamescope_cpp_args, diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 78a86ee0e2..66e94541a1 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -1844,9 +1844,11 @@ bool wlserver_init( void ) { char szEISocket[ 64 ]; snprintf( szEISocket, sizeof( szEISocket ), "%s-ei", wlserver.wl_display_name ); - g_InputServer = std::make_unique(); - if ( g_InputServer->Init( szEISocket ) ) + std::unique_ptr pInputServer = std::make_unique(); + if ( pInputServer->Init( szEISocket ) ) { + g_InputServer = std::move( pInputServer ); + setenv( "LIBEI_SOCKET", szEISocket, 1 ); g_LibEisWaiter.AddWaitable( g_InputServer.get() ); wl_log.infof( "Successfully initialized libei for input emulation!" ); From 42e1fbd89f90b2a6d9ac3146d453f1e5d334da1d Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:21:21 +0000 Subject: [PATCH 13/28] LibInputHandler: Add support for scroll wheel --- src/LibInputHandler.cpp | 45 ++++++++++++++++++++++++++++++++++++++++- src/LibInputHandler.h | 2 ++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/LibInputHandler.cpp b/src/LibInputHandler.cpp index ed5ff56021..238dd66f48 100644 --- a/src/LibInputHandler.cpp +++ b/src/LibInputHandler.cpp @@ -141,7 +141,35 @@ namespace gamescope } break; - // TODO: Scrolling. + case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: + { + libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + + static constexpr libinput_pointer_axis eAxes[] = + { + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, + }; + + for ( uint32_t i = 0; i < std::size( eAxes ); i++ ) + { + libinput_pointer_axis eAxis = eAxes[i]; + + if ( !libinput_event_pointer_has_axis( pPointerEvent, eAxis ) ) + continue; + + double flScroll = libinput_event_pointer_get_scroll_value_v120( pPointerEvent, eAxis ); + m_flScrollAccum[i] += flScroll / 120.0; + } + + m_flScrollAccum[0] += eis_event_scroll_get_discrete_dx( pEisEvent ) / 120.0; + m_flScrollAccum[1] += eis_event_scroll_get_discrete_dy( pEisEvent ) / 120.0; + + wlserver_lock(); + wlserver_mousebutton( uButton, eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, ++s_uSequence ); + wlserver_unlock(); + } + break; case LIBINPUT_EVENT_KEYBOARD_KEY: { @@ -159,5 +187,20 @@ namespace gamescope break; } } + + // Handle scrolling + { + double flScrollX = m_flScrollAccum[0]; + double flScrollY = m_flScrollAccum[1]; + m_flScrollAccum[0] = 0.0; + m_flScrollAccum[1] = 0.0; + + if ( flScrollX != 0.0 || flScrollY != 0.0 ) + { + wlserver_lock(); + wlserver_mousewheel( flScrollX, flScrollY, ++s_uSequence ); + wlserver_unlock(); + } + } } } diff --git a/src/LibInputHandler.h b/src/LibInputHandler.h index de1c3132f5..d9853f34c7 100644 --- a/src/LibInputHandler.h +++ b/src/LibInputHandler.h @@ -22,6 +22,8 @@ namespace gamescope udev *m_pUdev = nullptr; libinput *m_pLibInput = nullptr; + double m_flScrollAccum[2]{}; + static const libinput_interface s_LibInputInterface; }; } \ No newline at end of file From 3508441556e3b7ec06a041cb53d98835f0e891d0 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:51:19 +0000 Subject: [PATCH 14/28] OpenVRBackend: Support for physical mouse input controlling cursor --- src/Backends/OpenVRBackend.cpp | 188 ++++++++++++++++++++++----------- src/InputEmulation.cpp | 5 + src/LibInputHandler.cpp | 12 +-- src/backend.h | 8 ++ 4 files changed, 143 insertions(+), 70 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index cfb93f9a23..4951f1ad55 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -333,6 +333,10 @@ namespace gamescope void UpdateVisibility( const char *pszReason ); + // XXX + std::atomic m_bUsingVRMouse = { true }; + bool m_bCurrentlyOverridingPosition = false; + private: COpenVRBackend *m_pBackend = nullptr; COpenVRPlane m_Planes[8]; @@ -346,6 +350,7 @@ namespace gamescope bool m_bWasVisible = false; // Event thread only std::atomic m_bOverlayShown = { false }; std::atomic m_bSceneAppVisible = { false }; + }; class COpenVRBackend final : public CBaseBackend @@ -808,6 +813,23 @@ namespace gamescope return pConnector; } + void NotifyPhysicalInput( InputType eInputType ) override + { + if ( eInputType == InputType::Mouse ) + { + // TODO: Avoid this lock someday. + // Can we make this a shared_mutex for r/w? + + std::scoped_lock lock{ m_mutActiveConnectors }; + + COpenVRConnector *pConnector = static_cast( GetCurrentConnector() ); + if ( pConnector ) + { + pConnector->m_bUsingVRMouse = false; + } + } + } + vr::IVRIPCResourceManagerClient *GetIPCResourceManager() { return m_pIPCResourceManager; @@ -952,43 +974,47 @@ namespace gamescope case vr::VREvent_MouseMove: { - SetFocus( pConnector ); - float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); - float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); - - TouchClickMode eMode = GetTouchClickMode(); - // Always warp a cursor, even if it's invisible, so we get hover events. - bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor; - - if ( eMode == TouchClickModes::Trackpad ) + if ( pConnector->m_bUsingVRMouse ) { - glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos; - m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + SetFocus( pConnector ); + float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); + float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); + + TouchClickMode eMode = GetTouchClickMode(); + // Always warp a cursor, even if it's invisible, so we get hover events. + bool bAlwaysMoveCursor = eMode == TouchClickModes::Passthrough && cv_vr_always_warp_cursor; - if ( m_bMouseDown ) + if ( eMode == TouchClickModes::Trackpad ) { - glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos ); - // We are based off normalized coords, so we need to fix the aspect ratio - // or we get different sensitivities on X and Y. - vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); + glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos; + m_vScreenTrackpadPos = glm::vec2{ flX, flY }; - vDelta *= float( cv_vr_trackpad_sensitivity ); + if ( m_bMouseDown ) + { + glm::vec2 vDelta = ( m_vScreenTrackpadPos - vOldTrackpadPos ); + // We are based off normalized coords, so we need to fix the aspect ratio + // or we get different sensitivities on X and Y. + vDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); + + vDelta *= float( cv_vr_trackpad_sensitivity ); + wlserver_lock(); + wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp ); + wlserver_unlock(); + } + } + else + { wlserver_lock(); - wlserver_mousemotion( vDelta.x, vDelta.y, ++m_uFakeTimestamp ); + wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor ); wlserver_unlock(); } } - else - { - wlserver_lock(); - wlserver_touchmotion( flX, flY , 0, ++m_uFakeTimestamp, bAlwaysMoveCursor ); - wlserver_unlock(); - } break; } case vr::VREvent_FocusEnter: { + pConnector->m_bUsingVRMouse = true; SetFocus( pConnector ); break; } @@ -997,59 +1023,67 @@ namespace gamescope { SetFocus( pConnector ); - float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); - float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); - - uint64_t ulNow = get_time_in_nanos(); - - if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) + if ( !pConnector->m_bUsingVRMouse ) { - m_ulMouseDownTime = ulNow; - m_bMouseDown = true; + pConnector->m_bUsingVRMouse = true; } else { - m_bMouseDown = false; - } - TouchClickMode eMode = GetTouchClickMode(); - if ( eMode == TouchClickModes::Trackpad ) - { - m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + float flX = vrEvent.data.mouse.x / float( g_nOutputWidth ); + float flY = ( g_nOutputHeight - vrEvent.data.mouse.y ) / float( g_nOutputHeight ); + + uint64_t ulNow = get_time_in_nanos(); - if ( vrEvent.eventType == vr::VREvent_MouseButtonUp ) + if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) { - glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos ); - vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); - float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) ); + m_ulMouseDownTime = ulNow; + m_bMouseDown = true; + } + else + { + m_bMouseDown = false; + } - uint64_t ulClickTime = ulNow - m_ulMouseDownTime; - if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta ) + TouchClickMode eMode = GetTouchClickMode(); + if ( eMode == TouchClickModes::Trackpad ) + { + m_vScreenTrackpadPos = glm::vec2{ flX, flY }; + + if ( vrEvent.eventType == vr::VREvent_MouseButtonUp ) { - wlserver_lock(); - wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp ); - wlserver_unlock(); + glm::vec2 vTotalDelta = ( m_vScreenTrackpadPos - m_vScreenStartTrackpadPos ); + vTotalDelta.y *= ( (float)g_nOutputHeight / (float)g_nOutputWidth ); + float flMaxAbsTotalDelta = std::max( std::abs( vTotalDelta.x ), std::abs( vTotalDelta.y ) ); - sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 ); + uint64_t ulClickTime = ulNow - m_ulMouseDownTime; + if ( ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta ) + { + wlserver_lock(); + wlserver_mousebutton( BTN_LEFT, true, ++m_uFakeTimestamp ); + wlserver_unlock(); - wlserver_lock(); - wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp ); - wlserver_unlock(); - } - else - { - m_vScreenStartTrackpadPos = m_vScreenTrackpadPos; + sleep_for_nanos( g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000 ); + + wlserver_lock(); + wlserver_mousebutton( BTN_LEFT, false, ++m_uFakeTimestamp ); + wlserver_unlock(); + } + else + { + m_vScreenStartTrackpadPos = m_vScreenTrackpadPos; + } } } - } - else - { - wlserver_lock(); - if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) - wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp ); else - wlserver_touchup( 0, ++m_uFakeTimestamp ); - wlserver_unlock(); + { + wlserver_lock(); + if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) + wlserver_touchdown( flX, flY, 0, ++m_uFakeTimestamp ); + else + wlserver_touchup( 0, ++m_uFakeTimestamp ); + wlserver_unlock(); + } } break; } @@ -1101,7 +1135,35 @@ namespace gamescope } } } + + // Process mouse input state. + for ( COpenVRConnector *pConnector : m_pActiveConnectors ) + { + bool bUsingPhysicalMouse = GetCurrentConnector() == pConnector && !pConnector->m_bUsingVRMouse; + + if ( bUsingPhysicalMouse ) + { + vr::HmdVector2_t vMousePos = + { + static_cast( wlserver.mouse_surface_cursorx ), + static_cast( static_cast( g_nOutputHeight ) - wlserver.mouse_surface_cursory ), + }; + + vr::VROverlay()->SetOverlayCursorPositionOverride( pConnector->GetPrimaryPlane()->GetOverlay(), &vMousePos ); + pConnector->m_bCurrentlyOverridingPosition = true; + } + else + { + if ( !pConnector->m_bCurrentlyOverridingPosition ) + continue; + + vr::VROverlay()->ClearOverlayCursorPositionOverride( pConnector->GetPrimaryPlane()->GetOverlay() ); + + pConnector->m_bCurrentlyOverridingPosition = false; + } + } } + sleep_for_nanos( cv_vr_poll_rate ); } } diff --git a/src/InputEmulation.cpp b/src/InputEmulation.cpp index 460d0b41a1..5412b01b93 100644 --- a/src/InputEmulation.cpp +++ b/src/InputEmulation.cpp @@ -5,6 +5,7 @@ #include #include +#include "backend.h" #include "InputEmulation.h" #include "wlserver.hpp" @@ -167,6 +168,8 @@ namespace gamescope case EIS_EVENT_POINTER_MOTION: { + GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + wlserver_lock(); wlserver_mousemotion( eis_event_pointer_get_dx( pEisEvent ), eis_event_pointer_get_dy( pEisEvent ), ++s_uSequence ); wlserver_unlock(); @@ -175,6 +178,8 @@ namespace gamescope case EIS_EVENT_POINTER_MOTION_ABSOLUTE: { + GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + wlserver_lock(); wlserver_mousewarp( eis_event_pointer_get_absolute_x( pEisEvent ), eis_event_pointer_get_absolute_y( pEisEvent ), ++s_uSequence, true ); wlserver_unlock(); diff --git a/src/LibInputHandler.cpp b/src/LibInputHandler.cpp index 238dd66f48..145a5be93d 100644 --- a/src/LibInputHandler.cpp +++ b/src/LibInputHandler.cpp @@ -6,6 +6,7 @@ #include #include "log.hpp" +#include "backend.h" #include "wlserver.hpp" #include "Utils/Defer.h" @@ -109,6 +110,8 @@ namespace gamescope double flDx = libinput_event_pointer_get_dx( pPointerEvent ); double flDy = libinput_event_pointer_get_dy( pPointerEvent ); + GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + wlserver_lock(); wlserver_mousemotion( flDx, flDy, ++s_uSequence ); wlserver_unlock(); @@ -122,6 +125,8 @@ namespace gamescope double flX = libinput_event_pointer_get_absolute_x( pPointerEvent ); double flY = libinput_event_pointer_get_absolute_y( pPointerEvent ); + GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + wlserver_lock(); wlserver_mousewarp( flX, flY, ++s_uSequence, true ); wlserver_unlock(); @@ -161,13 +166,6 @@ namespace gamescope double flScroll = libinput_event_pointer_get_scroll_value_v120( pPointerEvent, eAxis ); m_flScrollAccum[i] += flScroll / 120.0; } - - m_flScrollAccum[0] += eis_event_scroll_get_discrete_dx( pEisEvent ) / 120.0; - m_flScrollAccum[1] += eis_event_scroll_get_discrete_dy( pEisEvent ) / 120.0; - - wlserver_lock(); - wlserver_mousebutton( uButton, eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, ++s_uSequence ); - wlserver_unlock(); } break; diff --git a/src/backend.h b/src/backend.h index 3080ba54be..82db19c285 100644 --- a/src/backend.h +++ b/src/backend.h @@ -79,6 +79,10 @@ namespace gamescope } } + enum class InputType + { + Mouse, + }; namespace TouchClickModes { @@ -333,6 +337,8 @@ namespace gamescope virtual bool UsesVirtualConnectors() = 0; virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) = 0; + virtual void NotifyPhysicalInput( InputType eInputType ) = 0; + static IBackend *Get(); template static bool Set(); @@ -360,6 +366,8 @@ namespace gamescope virtual bool UsesVirtualConnectors() override; virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; + + virtual void NotifyPhysicalInput( InputType eInputType ) override {} }; // This is a blob of data that may be associated with From 1f3f118fa93bcc6b57b45caa455e67cdb7085f3a Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:53:47 +0000 Subject: [PATCH 15/28] OpenVRBackend: Only override cursor if not in relative mode. --- src/Backends/OpenVRBackend.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 4951f1ad55..f3d422118f 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -1141,7 +1141,9 @@ namespace gamescope { bool bUsingPhysicalMouse = GetCurrentConnector() == pConnector && !pConnector->m_bUsingVRMouse; - if ( bUsingPhysicalMouse ) + bool bShowCursor = !pConnector->IsRelativeMouse(); + + if ( bUsingPhysicalMouse && bShowCursor ) { vr::HmdVector2_t vMousePos = { From fe83b24b8ddd1687a509528697db7775b172c461 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 11 Sep 2024 04:21:07 +0100 Subject: [PATCH 16/28] OpenVRBackend: Fix OverlayClosed when not in steam mode --- src/Backends/OpenVRBackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index f3d422118f..190037cf5b 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -908,7 +908,7 @@ namespace gamescope case vr::VREvent_OverlayClosed: case vr::VREvent_Quit: { - if ( bIsSteam ) + if ( !steamMode || bIsSteam ) { if ( !plane.IsSubview() ) { From d76a94e7eac336ac2bb0c2af4db5d2c07da36210 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 11 Sep 2024 04:29:15 +0100 Subject: [PATCH 17/28] messagey: Add GAMESCOPE_ZENITY_DISABLE --- src/messagey.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/messagey.h b/src/messagey.h index 87a84d310c..640fb4d72f 100644 --- a/src/messagey.h +++ b/src/messagey.h @@ -112,6 +112,10 @@ namespace messagey { int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */ pid_t pid1; + const char *zenityDisableEnv = getenv("GAMESCOPE_ZENITY_DISABLE"); + if (zenityDisableEnv && *zenityDisableEnv && atoi(zenityDisableEnv) != 0) + return 0; + if (messageboxdata->numbuttons > MaxButtons) { return SetError("Too many buttons (%d max allowed)", MaxButtons); } From 7ac836a0dd6496f6b059839e69f804285a18032c Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 06:29:09 +0000 Subject: [PATCH 18/28] OpenVRBackend: Fix for dual/double frame jitter --- src/Backends/OpenVRBackend.cpp | 76 ++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 190037cf5b..1d443c1a8d 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -230,6 +230,8 @@ namespace gamescope COpenVRBackend *GetBackend() const { return m_pBackend; } + void OnPageFlip(); + private: COpenVRConnector *m_pConnector = nullptr; COpenVRBackend *m_pBackend = nullptr; @@ -240,6 +242,9 @@ namespace gamescope uint32_t m_uSortOrder = 0; vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; + + Rc m_pQueuedFbId; + Rc m_pVisibleFbId; }; class COpenVRConnector final : public CBaseBackendConnector, public INestedHints @@ -275,8 +280,6 @@ namespace gamescope virtual const char *GetMake() const override; virtual const char *GetModel() const override; - virtual VBlankScheduleTime FrameSync() override; - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; virtual INestedHints *GetNestedHints() override @@ -350,7 +353,6 @@ namespace gamescope bool m_bWasVisible = false; // Event thread only std::atomic m_bOverlayShown = { false }; std::atomic m_bSceneAppVisible = { false }; - }; class COpenVRBackend final : public CBaseBackend @@ -358,6 +360,7 @@ namespace gamescope public: COpenVRBackend() : m_Thread{ [this](){ this->VRInputThread(); } } + , m_FlipHandlerThread{ [this](){ this->FlipHandlerThread(); } } , m_LibInputWaiter{ "gamescope-libinput" } { } @@ -370,8 +373,44 @@ namespace gamescope m_bInitted.notify_all(); m_Thread.join(); + m_FlipHandlerThread.join(); } + void FlipHandlerThread() + { + pthread_setname_np( pthread_self(), "gamescope-vrflip" ); + + m_bInitted.wait( false ); + + while ( m_bRunning ) + { + if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) + openvr_log.errorf( "WaitFrameSync failed!" ); + + static constexpr uint64_t k_ulSchedulingFudge = 100'000; // 0.1ms + uint64_t ulNow = get_time_in_nanos() - k_ulSchedulingFudge; + + GetVBlankTimer().MarkVBlank( ulNow, true ); + + // Nudge so that steamcompmgr releases commits. + nudge_steamcompmgr(); + + // Flush out any pending commits -> visible + // and any visible commits -> release. + { + std::scoped_lock lock{ m_mutActiveConnectors }; + + for ( COpenVRConnector *pConnector : m_pActiveConnectors ) + { + for ( COpenVRPlane &plane : pConnector->GetPlanes() ) + { + plane.OnPageFlip(); + } + } + } + } + } + ///////////// // IBackend ///////////// @@ -762,7 +801,7 @@ namespace gamescope virtual bool NeedsFrameSync() const override { - return true; + return false; } virtual TouchClickMode GetTouchClickMode() override @@ -1214,6 +1253,7 @@ namespace gamescope std::atomic m_pFocusConnector; std::thread m_Thread; + std::thread m_FlipHandlerThread; std::atomic m_bInitted = { false }; std::atomic m_bRunning = { false }; @@ -1230,7 +1270,6 @@ namespace gamescope , m_pBackend{ pBackend } , m_Planes{ this, this, this, this, this, this, this, this } { - } COpenVRConnector::~COpenVRConnector() @@ -1320,21 +1359,6 @@ namespace gamescope return "Virtual Display"; } - VBlankScheduleTime COpenVRConnector::FrameSync() - { - m_pBackend->WaitUntilVisible(); - - if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) - openvr_log.errorf( "WaitFrameSync failed!" ); - - uint64_t ulNow = get_time_in_nanos(); - return VBlankScheduleTime - { - .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now. - .ulScheduledWakeupPoint = ulNow, - }; - } - int COpenVRConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) { bool bNeedsFullComposite = false; @@ -1672,6 +1696,8 @@ namespace gamescope vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); } + COpenVRFb *pFb = nullptr; + if ( oState ) { vr::VROverlay()->SetOverlayAlpha( m_hOverlay, oState->flAlpha ); @@ -1700,7 +1726,7 @@ namespace gamescope vr::VROverlay()->ShowOverlay( m_hOverlay ); } - COpenVRFb *pFb = static_cast( oState->pTexture->GetBackendFb() ); + pFb = static_cast( oState->pTexture->GetBackendFb() ); vr::SharedTextureHandle_t ulHandle = pFb->GetSharedTextureHandle(); vr::Texture_t texture = { (void *)&ulHandle, vr::TextureType_SharedTextureHandle, vr::ColorSpace_Gamma }; @@ -1752,6 +1778,8 @@ namespace gamescope vr::VROverlay()->HideOverlay( m_hOverlay ); } } + + m_pQueuedFbId = pFb; } void COpenVRPlane::Present( const FrameInfo_t::Layer_t *pLayer ) @@ -1781,6 +1809,12 @@ namespace gamescope } } + void COpenVRPlane::OnPageFlip() + { + m_pVisibleFbId = m_pQueuedFbId; + m_pQueuedFbId = nullptr; + } + ///////////////////////// // Backend Instantiator ///////////////////////// From 58abfaf1f8378fad4a2ddf7f15d96a455b327920 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 06:37:10 +0000 Subject: [PATCH 19/28] OpenVRBackend: Fix FPS limit not updating --- src/Backends/DRMBackend.cpp | 2 +- src/Backends/OpenVRBackend.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 4c1000b343..9e2e27bba8 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -583,7 +583,7 @@ static constexpr uint32_t s_kSteamDeckOLEDRates[] = 90, }; -static void update_connector_display_info_wl(struct drm_t *drm) +void update_connector_display_info_wl(struct drm_t *drm) { wlserver_lock(); for ( const auto &control : wlserver.gamescope_controls ) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 1d443c1a8d..441ec23ec1 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -50,6 +50,7 @@ extern gamescope::ConVar cv_hdr_enabled; extern uint64_t g_SteamCompMgrLimitedAppRefreshCycle; void MakeFocusDirty(); +void update_connector_display_info_wl(struct drm_t *drm); static LogScope openvr_log("openvr"); @@ -917,7 +918,10 @@ namespace gamescope { COpenVRConnector *pPreviousFocus = m_pFocusConnector.exchange( pFocus ); if ( pPreviousFocus != pFocus ) + { MakeFocusDirty(); + update_connector_display_info_wl( NULL ); + } } void VRInputThread() From 5cf6b915c87602ca036227509688235349368594 Mon Sep 17 00:00:00 2001 From: Gabe Rowe Date: Thu, 12 Sep 2024 00:04:15 -0700 Subject: [PATCH 20/28] GAMESCOPE_MANGOAPP_SOCKET_DISABLE added --- src/steamcompmgr.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 26cfdc7d61..cf24bcf309 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -6580,9 +6580,12 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re { global_focus_t *pCurrentFocus = GetCurrentFocus(); + static bool bMangoappSocketDisable = env_to_bool( getenv( "GAMESCOPE_MANGOAPP_SOCKET_DISABLE" )); + // Whether or not to nudge mango app when this commit is done. const bool mango_nudge = pCurrentFocus && ( ( w == pCurrentFocus->focusWindow && !w->isSteamStreamingClient ) || - ( pCurrentFocus->focusWindow && pCurrentFocus->focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ) ); + ( pCurrentFocus->focusWindow && pCurrentFocus->focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ) ) + && !bMangoappSocketDisable; bool bValidPreemptiveScale = reslistentry.pAcquirePoint && pCurrentFocus && w == pCurrentFocus->focusWindow; bool bPreemptiveUpscale = bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale(); From c7cf2fbc554b4fe29985a1274f022aebb170f386 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:17:31 +0000 Subject: [PATCH 21/28] OpenVRBackend: Disable explicit sync for now --- src/Backends/OpenVRBackend.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 441ec23ec1..be5a1badfa 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -758,9 +758,8 @@ namespace gamescope virtual bool SupportsExplicitSync() const override { - // We only forward done DMA-BUFs, so this should be fine. - // SteamVR does not do any wait/poll/sync on these. - return true; + // This branch, need to rebase stuff for it to actually be good. + return false; } virtual bool IsVisible() const override From 20ccd6fcbb32d058dc44932e70801c3d0a956293 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:18:22 +0000 Subject: [PATCH 22/28] Revert "steamcompmgr: Fix crash when using magnifier and game recording" This reverts commit 611a47683f8304ae7a128347a2237df345482fcd. --- src/steamcompmgr.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index cf24bcf309..974f1f7f7b 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -2014,7 +2014,7 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win drawYOffset += w->GetGeometry().nY * currentScaleRatio_y; } - if ( cursor && zoomScaleRatio != 1.0 ) + if ( zoomScaleRatio != 1.0 ) { drawXOffset += (((int)sourceWidth / 2) - cursor->x()) * currentScaleRatio_x; drawYOffset += (((int)sourceHeight / 2) - cursor->y()) * currentScaleRatio_y; @@ -2224,10 +2224,10 @@ static void paint_pipewire() s_ulLastOverrideCommitId = ulOverrideCommitId; // Paint the windows we have onto the Pipewire stream. - paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, 0, 1.0f, pFocus->overrideWindow ); + paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow ); if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient ) - paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); + paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr() ? vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, DRM_FORMAT_XRGB2101010 ) From f6da6a3a321862ac18ef971ee890a9044731c8e1 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:19:00 +0000 Subject: [PATCH 23/28] backend: Hack --- src/backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend.cpp b/src/backend.cpp index c7c1f0d419..7ac9b3e49a 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -58,7 +58,7 @@ namespace gamescope CBaseBackendFb::~CBaseBackendFb() { // I do not own the client buffer, but I released that in DecRef. - assert( !HasLiveReferences() ); + //assert( !HasLiveReferences() ); } uint32_t CBaseBackendFb::IncRef() From 214041a05f94fc62cbf507d5ce0cc2040b25e6ba Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 22 Nov 2024 01:52:12 +0000 Subject: [PATCH 24/28] OpenVRBackend: Add mutex around fb id tracking --- src/Backends/OpenVRBackend.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index be5a1badfa..f9e695cfa1 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -244,6 +244,7 @@ namespace gamescope vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; + std::mutex m_mutFbIds; Rc m_pQueuedFbId; Rc m_pVisibleFbId; }; @@ -1782,7 +1783,10 @@ namespace gamescope } } - m_pQueuedFbId = pFb; + { + std::scoped_lock lock{ m_mutFbIds }; + m_pQueuedFbId = pFb; + } } void COpenVRPlane::Present( const FrameInfo_t::Layer_t *pLayer ) @@ -1814,8 +1818,14 @@ namespace gamescope void COpenVRPlane::OnPageFlip() { - m_pVisibleFbId = m_pQueuedFbId; - m_pQueuedFbId = nullptr; + { + std::scoped_lock lock{ m_mutFbIds }; + + // XXX: We have no guarantee for WHAT the sequence is here. This could be total crap. + // but this is probably good enough for now? + m_pVisibleFbId = std::move( m_pQueuedFbId ); + m_pQueuedFbId = nullptr; + } } ///////////////////////// From 1c927413ff7bc0f0bb2ff9f564344f25c8b91aac Mon Sep 17 00:00:00 2001 From: Antonino Maniscalco Date: Wed, 4 Dec 2024 00:38:53 +0100 Subject: [PATCH 25/28] mangoapp: plumb engineName --- layer/VkLayer_FROG_gamescope_wsi.cpp | 9 ++++++++- protocol/gamescope-swapchain.xml | 1 + src/WaylandServer/WaylandServerLegacy.h | 1 + src/mangoapp.cpp | 6 ++++++ src/steamcompmgr.cpp | 6 ++++++ src/steamcompmgr.hpp | 1 + src/steamcompmgr_shared.hpp | 2 ++ src/wlserver.cpp | 4 +++- 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp index 718a2604f3..569bfd06eb 100644 --- a/layer/VkLayer_FROG_gamescope_wsi.cpp +++ b/layer/VkLayer_FROG_gamescope_wsi.cpp @@ -394,6 +394,7 @@ namespace GamescopeWSILayer { struct GamescopeInstanceData { wl_display* display; uint32_t appId = 0; + std::string engineName; GamescopeLayerClient::Flags flags = 0; }; VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeInstance, VkInstance); @@ -616,9 +617,14 @@ namespace GamescopeWSILayer { { uint32_t appId = clientAppId(); + std::string engineName; + if (pCreateInfo->pApplicationInfo && pCreateInfo->pApplicationInfo->pEngineName) + engineName = pCreateInfo->pApplicationInfo->pEngineName; + auto state = GamescopeInstance::create(*pInstance, GamescopeInstanceData { .display = display, .appId = appId, + .engineName = engineName, .flags = defaultLayerClientFlags(pCreateInfo->pApplicationInfo, appId), }); @@ -1251,7 +1257,8 @@ namespace GamescopeWSILayer { uint32_t(pCreateInfo->imageColorSpace), uint32_t(pCreateInfo->compositeAlpha), uint32_t(pCreateInfo->preTransform), - uint32_t(pCreateInfo->clipped)); + uint32_t(pCreateInfo->clipped), + gamescopeInstance->engineName.c_str()); return VK_SUCCESS; } diff --git a/protocol/gamescope-swapchain.xml b/protocol/gamescope-swapchain.xml index 91be3fc02d..58ac8463b7 100644 --- a/protocol/gamescope-swapchain.xml +++ b/protocol/gamescope-swapchain.xml @@ -89,6 +89,7 @@ + diff --git a/src/WaylandServer/WaylandServerLegacy.h b/src/WaylandServer/WaylandServerLegacy.h index 0facb7dc8b..63ee2ca17e 100644 --- a/src/WaylandServer/WaylandServerLegacy.h +++ b/src/WaylandServer/WaylandServerLegacy.h @@ -29,6 +29,7 @@ struct wlserver_vk_swapchain_feedback VkCompositeAlphaFlagBitsKHR vk_composite_alpha; VkSurfaceTransformFlagBitsKHR vk_pre_transform; VkBool32 vk_clipped; + std::shared_ptr vk_engine_name; std::shared_ptr hdr_metadata_blob; }; diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp index 91e01bc275..d8e1ce7eda 100644 --- a/src/mangoapp.cpp +++ b/src/mangoapp.cpp @@ -31,6 +31,7 @@ struct mangoapp_msg_v1 { uint16_t displayRefresh; bool bAppWantsHDR : 1; bool bSteamFocused : 1; + char engineName[40]; // WARNING: Always ADD fields, never remove or repurpose fields } __attribute__((packed)) mangoapp_msg_v1; @@ -60,6 +61,11 @@ void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uin mangoapp_msg_v1.displayRefresh = (uint16_t) gamescope::ConvertmHzToHz( g_nOutputRefresh ); mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached; mangoapp_msg_v1.bSteamFocused = g_focusedBaseAppId == 769; + memset(mangoapp_msg_v1.engineName, 0, sizeof(mangoapp_msg_v1.engineName)); + if (focusWindow_engine) + focusWindow_engine->copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); + else + std::string("gamescope").copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); msgsnd(msgid, &mangoapp_msg_v1, sizeof(mangoapp_msg_v1) - sizeof(mangoapp_msg_v1.hdr.msg_type), IPC_NOWAIT); } diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 974f1f7f7b..7180b2d0a3 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -930,6 +930,7 @@ int g_BlurRadius = 5; unsigned int g_BlurFadeStartTime = 0; pid_t focusWindow_pid; +std::shared_ptr focusWindow_engine = nullptr; focus_t g_steamcompmgr_xdg_focus; std::vector> g_steamcompmgr_xdg_wins; @@ -6158,6 +6159,9 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co uint32_t j; for ( j = 0; j < w->commit_queue.size(); j++ ) { + if (w->commit_queue[ j ]->feedback.has_value()) + w->engineName = w->commit_queue[ j ]->feedback->vk_engine_name; + if ( w->commit_queue[ j ]->commitID == commitID ) { gpuvis_trace_printf( "commit %lu done", w->commit_queue[ j ]->commitID ); @@ -6195,6 +6199,8 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co if ( !cv_paint_debug_pause_base_plane ) g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; hasRepaint = true; + + focusWindow_engine = w->engineName; } if ( w == pFocus->overrideWindow ) diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index df537eb598..4aa12f76a0 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -143,6 +143,7 @@ struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xw extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; extern pid_t focusWindow_pid; +extern std::shared_ptr focusWindow_engine; void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp index d82382959e..c4b616d65d 100644 --- a/src/steamcompmgr_shared.hpp +++ b/src/steamcompmgr_shared.hpp @@ -140,6 +140,8 @@ struct steamcompmgr_win_t { bool unlockedForFrameCallback = false; bool receivedDoneCommit = false; + std::shared_ptr engineName; + std::vector< gamescope::Rc > commit_queue; std::shared_ptr> icon; diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 66e94541a1..32554849a3 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -840,7 +840,8 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st uint32_t vk_colorspace, uint32_t vk_composite_alpha, uint32_t vk_pre_transform, - uint32_t vk_clipped) + uint32_t vk_clipped, + const char *vk_engine_name) { wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); if ( wl_info ) @@ -852,6 +853,7 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st .vk_composite_alpha = VkCompositeAlphaFlagBitsKHR(vk_composite_alpha), .vk_pre_transform = VkSurfaceTransformFlagBitsKHR(vk_pre_transform), .vk_clipped = VkBool32(vk_clipped), + .vk_engine_name = std::make_shared(vk_engine_name), .hdr_metadata_blob = nullptr, }); } From 67272653ce1f3078b3c13b55c06c8b19895e5df5 Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Sat, 14 Dec 2024 00:27:57 +0000 Subject: [PATCH 26/28] OpenVRBackend: Handle quit more robustly --- src/Backends/OpenVRBackend.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index f9e695cfa1..aa168bb0c9 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -948,8 +948,13 @@ namespace gamescope { switch( vrEvent.eventType ) { - case vr::VREvent_OverlayClosed: case vr::VREvent_Quit: + { + raise( SIGTERM ); + } + break; + + case vr::VREvent_OverlayClosed: { if ( !steamMode || bIsSteam ) { From 1e30e9d504159b7191027e6c3a4a2717d334ac91 Mon Sep 17 00:00:00 2001 From: Antonino Maniscalco Date: Fri, 10 Jan 2025 02:30:45 +0100 Subject: [PATCH 27/28] Revert "OpenVRBackend: Disable explicit sync for now" This reverts commit 21c54bd9939181cbd233b97cad87163c835da576. --- src/Backends/OpenVRBackend.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index aa168bb0c9..8998831867 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -759,8 +759,9 @@ namespace gamescope virtual bool SupportsExplicitSync() const override { - // This branch, need to rebase stuff for it to actually be good. - return false; + // We only forward done DMA-BUFs, so this should be fine. + // SteamVR does not do any wait/poll/sync on these. + return true; } virtual bool IsVisible() const override From 778d592663709e7dcdacf9ff12dfb2d2b0a95a8f Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Wed, 15 Jan 2025 05:01:51 +0000 Subject: [PATCH 28/28] steamcompmgr: Fix warning --- src/steamcompmgr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 7180b2d0a3..3b26f3919e 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -7770,6 +7770,7 @@ steamcompmgr_main(int argc, char **argv) s_eLastVirtualConnectorStrategy = eVirtualConnectorStrategy; } +#if 0 bool bDirtyFocuses = false; for ( auto &iter : g_VirtualConnectorFocuses ) { @@ -7780,6 +7781,7 @@ steamcompmgr_main(int argc, char **argv) break; } } +#endif // XXX: Need to look into why this doesn't work. // if ( bDirtyFocuses )