From d75b122cb0dc36ded36c1d96de0b1327a4fb487a Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:28:44 +0000 Subject: [PATCH 01/34] 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 179b5fe5db88f4dfea81ec41a37aad018a048937 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:39:33 +0000 Subject: [PATCH 02/34] 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 a3e25a6ba1b3dc2ad52b9d0819c94392011be15e Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 22 Aug 2024 22:52:19 +0000 Subject: [PATCH 03/34] 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 b97a60d867b6f9fdc13fdcba1950ee88c009ede5 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 19:08:03 +0000 Subject: [PATCH 04/34] 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 ca5b82957729ea3d69f7052d771f3bd473e9ad2b Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 21:43:45 +0000 Subject: [PATCH 05/34] 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 1a7488199be7b77d16874e0b1c9adea4e42c44b0 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 22:35:02 +0000 Subject: [PATCH 06/34] 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 8feb1f7daaa54b56905730713a57c64a2ce6cf7e Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:08:13 +0000 Subject: [PATCH 07/34] 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 775e5b85510712633d731cd19e26dd31135b9b2e Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:13:12 +0000 Subject: [PATCH 08/34] 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 ed07c3260b3677116c2b8e38a5598112bad4a6d2 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 23 Aug 2024 23:19:04 +0000 Subject: [PATCH 09/34] 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 32deb5f9aee1f59191d6aa810e36dcbecdcb914b Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Mon, 26 Aug 2024 23:00:57 +0000 Subject: [PATCH 10/34] 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 95b5d5ae2f412093685c30842a52b59dc2dfa941 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Tue, 27 Aug 2024 00:31:25 +0000 Subject: [PATCH 11/34] 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 93a5a7514b8b534f97c899773a8504a98e643bb5 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 20:02:05 +0000 Subject: [PATCH 12/34] 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 5aa710e2d35c1e91ff161ca3c714643354ade80a Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:21:21 +0000 Subject: [PATCH 13/34] 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 c30a8fc7731d876f88e109c294d59dfabc740820 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:51:19 +0000 Subject: [PATCH 14/34] 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 b2cd2fa35b2d669fa2d01d0ee1b4df151eae65b6 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 28 Aug 2024 21:53:47 +0000 Subject: [PATCH 15/34] 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 b708345fc91a6b7078071ea01b5eed4692434b58 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 11 Sep 2024 04:21:07 +0100 Subject: [PATCH 16/34] 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 6b292e44f7fa08823230088a766a84cf253e1820 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 11 Sep 2024 04:29:15 +0100 Subject: [PATCH 17/34] 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 0dd2ba10cfa3fd02da8d8462f1c95c5526bd7144 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 06:29:09 +0000 Subject: [PATCH 18/34] 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 2f88849ac9dc7da5c678d5d7d3e9b58f38add1bf Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 06:37:10 +0000 Subject: [PATCH 19/34] 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 5431f9a053a2a0d2e9288a132d90ee0ec2506a1e Mon Sep 17 00:00:00 2001 From: Gabe Rowe Date: Thu, 12 Sep 2024 00:04:15 -0700 Subject: [PATCH 20/34] 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 0060cf10163f612622a11c3f098e0c17c3d8c6fb Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:17:31 +0000 Subject: [PATCH 21/34] 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 b0699634013259aee5a194438138bba629f1f632 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:18:22 +0000 Subject: [PATCH 22/34] 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 6e7e1e61ae7bd165b75f2d18afaaa34925d6b57c Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Thu, 21 Nov 2024 07:19:00 +0000 Subject: [PATCH 23/34] 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 672c8121516ece5afcea35516f8c7c3722fb1bd1 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 22 Nov 2024 01:52:12 +0000 Subject: [PATCH 24/34] 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 6a4d150dacb46ca74e22fc83e1c259cb2c3128c2 Mon Sep 17 00:00:00 2001 From: Antonino Maniscalco Date: Wed, 4 Dec 2024 00:38:53 +0100 Subject: [PATCH 25/34] 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 24bedb3661d930949871c32695fa1bfaccd91cc0 Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Sat, 14 Dec 2024 00:27:57 +0000 Subject: [PATCH 26/34] 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 10509c515712502020d0f4904c037d9d123a197f Mon Sep 17 00:00:00 2001 From: Antonino Maniscalco Date: Fri, 10 Jan 2025 02:30:45 +0100 Subject: [PATCH 27/34] 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 1248f3ae87a04f6c1c1808e7dcbf8abb268eeed4 Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Wed, 15 Jan 2025 05:01:51 +0000 Subject: [PATCH 28/34] 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 ) From 3d230c945945b4981f73e513d3d957f149706dc9 Mon Sep 17 00:00:00 2001 From: Jeremy Selan Date: Thu, 16 Jan 2025 16:30:21 -0800 Subject: [PATCH 29/34] OpenVRBackend: disable special steamui controlbar. This functionality did not ship in steamvr. --- src/Backends/OpenVRBackend.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 8998831867..742002c7b9 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -75,8 +75,6 @@ gamescope::ConVar cv_vr_poll_rate( "vr_poll_rate", 4'000'000ul, "Time // Not in public headers yet. namespace vr { - const VROverlayFlags VROverlayFlags_EnableControlBarSteamUI = (VROverlayFlags)(1 << 26); - const EVRButtonId k_EButton_Steam = (EVRButtonId)(50); const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); } @@ -1701,11 +1699,6 @@ namespace gamescope void COpenVRPlane::Present( std::optional oState ) { - if ( !m_bIsSubview ) - { - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); - } - COpenVRFb *pFb = nullptr; if ( oState ) From 328f06b0b5e4492cf26c7b2a6e3fe628718caddd Mon Sep 17 00:00:00 2001 From: Jeremy Selan Date: Thu, 16 Jan 2025 17:16:44 -0800 Subject: [PATCH 30/34] OpenVRBackend: Add command-line support for overlay click stabilization This works on all overlay types (subview, dashboard, etc) --- src/Backends/OpenVRBackend.cpp | 5 +++++ src/main.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index 742002c7b9..1f26fe6c71 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -484,6 +484,8 @@ namespace gamescope m_bEnableControlBarKeyboard = true; } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { m_bEnableControlBarClose = true; + } else if (strcmp(opt_name, "vr-overlay-enable-click-stabilization") == 0) { + m_bEnableClickStabilization = true; } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { m_bModal = true; } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { @@ -886,6 +888,7 @@ namespace gamescope bool ShouldEnableControlBar() const { return m_bEnableControlBar; } bool ShouldEnableControlBarKeyboard() const { return m_bEnableControlBarKeyboard; } bool ShouldEnableControlBarClose() const { return m_bEnableControlBarClose; } + bool ShouldEnableClickStabilization() const { return m_bEnableClickStabilization; } bool IsModal() const { return m_bModal; } float GetPhysicalWidth() const { return m_flPhysicalWidth; } float GetPhysicalCurvature() const { return m_flPhysicalCurvature; } @@ -1226,6 +1229,7 @@ namespace gamescope bool m_bEnableControlBar = false; bool m_bEnableControlBarKeyboard = false; bool m_bEnableControlBarClose = false; + bool m_bEnableClickStabilization = false; bool m_bModal = false; float m_flPhysicalWidth = 2.0f; float m_flPhysicalCurvature = 0.0f; @@ -1690,6 +1694,7 @@ namespace gamescope vr::VROverlay()->CreateSubviewOverlay( pParent->GetOverlay(), szSubviewName.c_str(), "Gamescope Layer", &m_hOverlay ); } + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableClickStabilization, m_pBackend->ShouldEnableClickStabilization() ); vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IsPremultiplied, true ); vr::VROverlay()->SetOverlayInputMethod( m_hOverlay, vr::VROverlayInputMethod_Mouse ); vr::VROverlay()->SetOverlaySortOrder( m_hOverlay, m_uSortOrder ); diff --git a/src/main.cpp b/src/main.cpp index 7bd4f6f2ef..161a9793e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,6 +102,7 @@ const struct option *gamescope_options = (struct option[]){ { "vr-overlay-enable-control-bar", no_argument, nullptr, 0 }, { "vr-overlay-enable-control-bar-keyboard", no_argument, nullptr, 0 }, { "vr-overlay-enable-control-bar-close", no_argument, nullptr, 0 }, + { "vr-overlay-enable-click-stabilization", no_argument, nullptr, 0 }, { "vr-overlay-modal", no_argument, nullptr, 0 }, { "vr-overlay-physical-width", required_argument, nullptr, 0 }, { "vr-overlay-physical-curvature", required_argument, nullptr, 0 }, @@ -234,6 +235,7 @@ const char usage[] = " --vr-overlay-enable-control-bar Enables the SteamVR control bar\n" " --vr-overlay-enable-control-bar-keyboard Enables the SteamVR keyboard button on the control bar\n" " --vr-overlay-enable-control-bar-close Enables the SteamVR close button on the control bar\n" + " --vr-overlay-enable-click-stabilization Enables the SteamVR click stabilization\n" " --vr-overlay-modal Makes our VR overlay appear as a modal\n" " --vr-overlay-physical-width Sets the physical width of our VR overlay in metres\n" " --vr-overlay-physical-curvature Sets the curvature of our VR overlay\n" From 19dc18e81a0b4ea5fc340b225150ac4b8ee729fc Mon Sep 17 00:00:00 2001 From: Autumn Ashton Date: Tue, 21 Jan 2025 16:30:06 +0000 Subject: [PATCH 31/34] WaylandBackend: Fix grabbing cursor since virtual connector refactor --- src/Backends/WaylandBackend.cpp | 160 ++++++++++++++++---------------- src/backend.h | 13 +++ src/steamcompmgr.cpp | 22 +++-- src/steamcompmgr.hpp | 1 + 4 files changed, 110 insertions(+), 86 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index ec28d28153..051a07dcb5 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -130,6 +130,32 @@ namespace gamescope return outState; } + static int CreateShmBuffer( uint32_t uSize, void *pData ) + { + char szShmBufferPath[ PATH_MAX ]; + int nFd = MakeTempFile( szShmBufferPath, k_szGamescopeTempShmTemplate ); + if ( nFd < 0 ) + return -1; + + if ( ftruncate( nFd, uSize ) < 0 ) + { + close( nFd ); + return -1; + } + + if ( pData ) + { + void *pMappedData = mmap( nullptr, uSize, PROT_READ | PROT_WRITE, MAP_SHARED, nFd, 0 ); + if ( pMappedData == MAP_FAILED ) + return -1; + defer( munmap( pMappedData, uSize ) ); + + memcpy( pMappedData, pData, uSize ); + } + + return nFd; + } + class CWaylandPlane { public: @@ -591,6 +617,9 @@ namespace gamescope wl_surface *CursorInfoToSurface( const std::shared_ptr &info ); bool SupportsColorManagement() const; + + void SetCursorImage( std::shared_ptr info ); + void SetRelativeMouseMode( wl_surface *pSurface, bool bRelative ); void UpdateCursor(); friend CWaylandConnector; @@ -1075,53 +1104,12 @@ namespace gamescope 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 + m_pBackend->SetCursorImage( std::move( info ) ); } 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 + // TODO: Do more tracking across multiple connectors, and activity here if we ever want to use this. + m_pBackend->SetRelativeMouseMode( m_Planes[0].GetSurface(), bRelative ); } void CWaylandConnector::SetVisible( bool bVisible ) { @@ -1140,14 +1128,12 @@ namespace gamescope } void CWaylandConnector::SetIcon( std::shared_ptr> uIconPixels ) { - // XXX(strategy): FIXME FIXME FIXME -#if 0 - if ( !m_pToplevelIconManager ) + if ( !m_pBackend->GetToplevelIconManager() ) return; if ( uIconPixels && uIconPixels->size() >= 3 ) { - xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pToplevelIconManager ); + xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pBackend->GetToplevelIconManager() ); if ( !pIcon ) { xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" ); @@ -1168,7 +1154,7 @@ namespace gamescope } defer( close( nFd ) ); - wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize ); + wl_shm_pool *pPool = wl_shm_create_pool( m_pBackend->GetShm(), 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 ); @@ -1176,13 +1162,12 @@ namespace gamescope xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 ); - xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), pIcon ); + xdg_toplevel_icon_manager_v1_set_icon( m_pBackend->GetToplevelIconManager(), m_Planes[0].GetXdgToplevel(), pIcon ); } else { - xdg_toplevel_icon_manager_v1_set_icon( m_pToplevelIconManager, m_Planes[0].GetXdgToplevel(), nullptr ); + xdg_toplevel_icon_manager_v1_set_icon( m_pBackend->GetToplevelIconManager(), m_Planes[0].GetXdgToplevel(), nullptr ); } -#endif } void CWaylandConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) @@ -2086,32 +2071,6 @@ namespace gamescope // INestedHints /////////////////// - static int CreateShmBuffer( uint32_t uSize, void *pData ) - { - char szShmBufferPath[ PATH_MAX ]; - int nFd = MakeTempFile( szShmBufferPath, k_szGamescopeTempShmTemplate ); - if ( nFd < 0 ) - return -1; - - if ( ftruncate( nFd, uSize ) < 0 ) - { - close( nFd ); - return -1; - } - - if ( pData ) - { - void *pMappedData = mmap( nullptr, uSize, PROT_READ | PROT_WRITE, MAP_SHARED, nFd, 0 ); - if ( pMappedData == MAP_FAILED ) - return -1; - defer( munmap( pMappedData, uSize ) ); - - memcpy( pMappedData, pData, uSize ); - } - - return nFd; - } - void CWaylandBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) { // Do nothing. @@ -2149,6 +2108,51 @@ namespace gamescope return m_pFrogColorMgmtFactory != nullptr || ( m_pXXColorManager != nullptr && m_XXColorManagerFeatures.bSupportsGamescopeColorManagement ); } + 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( wl_surface *pSurface, 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, pSurface, 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::UpdateCursor() { bool bUseHostCursor = false; diff --git a/src/backend.h b/src/backend.h index 82db19c285..adfb103a73 100644 --- a/src/backend.h +++ b/src/backend.h @@ -160,6 +160,8 @@ namespace gamescope public: virtual ~IBackendConnector() {} + virtual uint64_t GetConnectorID() const = 0; + virtual GamescopeScreenType GetScreenType() const = 0; virtual GamescopePanelOrientation GetCurrentOrientation() const = 0; virtual bool SupportsHDR() const = 0; @@ -196,10 +198,12 @@ namespace gamescope public: CBaseBackendConnector() { + AssignConnectorId(); } CBaseBackendConnector( uint64_t ulVirtualConnectorKey ) : m_ulVirtualConnectorKey{ ulVirtualConnectorKey } { + AssignConnectorId(); } virtual ~CBaseBackendConnector() @@ -207,13 +211,22 @@ namespace gamescope } + virtual uint64_t GetConnectorID() const override { return m_ulConnectorId; } 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_ulConnectorId = 0; uint64_t m_ulVirtualConnectorKey = 0; BackendPresentFeedback m_PresentFeedback{}; + + private: + void AssignConnectorId() + { + static uint64_t s_ulLastConnectorKey = 0; + m_ulVirtualConnectorKey = ++s_ulLastConnectorKey; + } }; class INestedHints diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 3b26f3919e..eb09ae6a37 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -1623,6 +1623,16 @@ int MouseCursor::y() const bool MouseCursor::getTexture() { + uint64_t ulConnectorId = 0; + if ( GetBackend()->GetCurrentConnector() ) + ulConnectorId = GetBackend()->GetCurrentConnector()->GetConnectorID(); + + if ( ulConnectorId != m_ulLastConnectorId ) + { + m_ulLastConnectorId = ulConnectorId; + m_dirty = true; + } + if (!m_dirty) { return !m_imageEmpty; } @@ -1725,10 +1735,8 @@ bool MouseCursor::getTexture() updateCursorFeedback(); if (m_imageEmpty) { -#if 0 // XXX(strategy) FIXME - if ( GetBackend()->GetNestedHints() ) - GetBackend()->GetNestedHints()->SetCursorImage( nullptr ); -#endif + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetNestedHints() ) + GetBackend()->GetCurrentConnector()->GetNestedHints()->SetCursorImage( nullptr ); return false; } @@ -1742,8 +1750,7 @@ 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() ) + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetNestedHints() ) { auto info = std::make_shared( gamescope::INestedHints::CursorInfo @@ -1754,9 +1761,8 @@ bool MouseCursor::getTexture() .uXHotspot = image->xhot, .uYHotspot = image->yhot, }); - GetBackend()->GetNestedHints()->SetCursorImage( std::move( info ) ); + GetBackend()->GetCurrentConnector()->GetNestedHints()->SetCursorImage( std::move( info ) ); } -#endif assert(m_texture); XFree(image); diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 4aa12f76a0..98e9272964 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -109,6 +109,7 @@ class MouseCursor gamescope::OwningRc m_texture; bool m_dirty; + uint64_t m_ulLastConnectorId = 0; bool m_imageEmpty; xwayland_ctx_t *m_ctx; From e78e5da20b08544e0c517b6f5544f40edf90e395 Mon Sep 17 00:00:00 2001 From: MithicSpirit Date: Wed, 22 Jan 2025 16:28:57 -0500 Subject: [PATCH 32/34] nested: fix forwarding selections to the host --- src/steamcompmgr.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index eb09ae6a37..c462464d79 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -5087,24 +5087,28 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) auto szContents = std::make_shared(contents); defer( XFree( data ); ); + gamescope::INestedHints *hints = nullptr; + if (auto connector = GetBackend()->GetCurrentConnector()) + hints = connector->GetNestedHints(); + if (ev->selection == ctx->atoms.clipboard) { - // if ( GetBackend()->GetNestedHints() ) - // { - // //GetBackend()->GetNestedHints()->SetSelection() - // } - // else + if ( hints ) + { + hints->SetSelection( szContents, GAMESCOPE_SELECTION_CLIPBOARD ); + } + else { gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); } } else if (ev->selection == ctx->atoms.primarySelection) { - // if ( GetBackend()->GetNestedHints() ) - // { - // //GetBackend()->GetNestedHints()->SetSelection() - // } - // else + if ( hints ) + { + hints->SetSelection( szContents, GAMESCOPE_SELECTION_PRIMARY ); + } + else { gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); } From e17aec44733c152b432bc9d4a32d450e84eb1f43 Mon Sep 17 00:00:00 2001 From: MithicSpirit Date: Wed, 22 Jan 2025 16:37:55 -0500 Subject: [PATCH 33/34] WaylandBackend: copying to clipboard This forwards the internal clipboard to the host, but does not handle the primary selection nor accessing the host's clipboard internally. The source is recreated for every new copy, rather than being reused when it is still valid. This doesn't seem to be necessary, but doing it differently leads to clipboard managers not becoming aware of the reused copies (under Sway, `wl-paste --watch` doesn't notice them), which at least suggests that not reusing is more common. --- src/Backends/WaylandBackend.cpp | 53 ++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index 051a07dcb5..bfba09f3ab 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -688,6 +688,10 @@ namespace gamescope void Wayland_XXColorManager_SupportedPrimariesNamed( xx_color_manager_v3 *pXXColorManager, uint32_t uPrimaries ); static const xx_color_manager_v3_listener s_XXColorManagerListener; + void Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ); + void Wayland_DataSource_Cancelled( struct wl_data_source *pSource ); + static const wl_data_source_listener s_DataSourceListener; + CWaylandInputThread m_InputThread; wl_display *m_pDisplay = nullptr; @@ -713,6 +717,10 @@ namespace gamescope // TODO: Restructure and remove the need for this. std::shared_ptr m_pFocusConnector; + wl_data_device_manager *m_pDataDeviceManager = nullptr; + wl_data_device *m_pDataDevice = nullptr; + std::shared_ptr m_pClipboard = nullptr; + struct { std::vector ePrimaries; @@ -740,6 +748,7 @@ namespace gamescope uint32_t m_uPointerEnterSerial = 0; bool m_bMouseEntered = false; + uint32_t m_uKeyboardEnterSerial = 0; bool m_bKeyboardEntered = false; std::shared_ptr m_pCursorInfo; @@ -795,6 +804,11 @@ namespace gamescope .supported_tf_named = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_XXColorManager_SupportedTFNamed ), .supported_primaries_named = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_XXColorManager_SupportedPrimariesNamed ), }; + const wl_data_source_listener CWaylandBackend::s_DataSourceListener = + { + .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Send ), + .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Cancelled ), + }; ////////////////// // CWaylandFb @@ -1172,7 +1186,25 @@ namespace gamescope void CWaylandConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) { - // Do nothing + if ( m_pBackend->m_pDataDeviceManager && !m_pBackend->m_pDataDevice ) + m_pBackend->m_pDataDevice = wl_data_device_manager_get_data_device( m_pBackend->m_pDataDeviceManager, m_pBackend->m_pSeat ); + + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD && m_pBackend->m_pDataDevice ) + { + m_pBackend->m_pClipboard = szContents; + wl_data_source *source = wl_data_device_manager_create_data_source( m_pBackend->m_pDataDeviceManager ); + wl_data_source_add_listener( source, &m_pBackend->s_DataSourceListener, m_pBackend ); + wl_data_source_offer( source, "text/plain" ); + wl_data_source_offer( source, "text/plain;charset=utf-8" ); + wl_data_source_offer( source, "TEXT" ); + wl_data_source_offer( source, "STRING" ); + wl_data_source_offer( source, "UTF8_STRING" ); + wl_data_device_set_selection( m_pBackend->m_pDataDevice, source, m_pBackend->m_uKeyboardEnterSerial ); + } + else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY ) + { + // Do nothing + } } ////////////////// @@ -2265,6 +2297,10 @@ namespace gamescope { m_pToplevelIconManager = (xdg_toplevel_icon_manager_v1 *)wl_registry_bind( pRegistry, uName, &xdg_toplevel_icon_manager_v1_interface, 1u ); } + else if ( !strcmp( pInterface, wl_data_device_manager_interface.name ) ) + { + m_pDataDeviceManager = (wl_data_device_manager *)wl_registry_bind( pRegistry, uName, &wl_data_device_manager_interface, 3u ); + } } void CWaylandBackend::Wayland_Modifier( zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) @@ -2370,6 +2406,7 @@ namespace gamescope if ( !IsSurfacePlane( pSurface ) ) return; + m_uKeyboardEnterSerial = uSerial; m_bKeyboardEntered = true; UpdateCursor(); @@ -2403,6 +2440,20 @@ namespace gamescope m_XXColorManagerFeatures.ePrimaries.push_back( static_cast( uPrimaries ) ); } + // Data Source + + void CWaylandBackend::Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ) + { + ssize_t len = m_pClipboard->length(); + if ( write( nFd, m_pClipboard->c_str(), len ) != len ) + xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); + close( nFd ); + } + void CWaylandBackend::Wayland_DataSource_Cancelled( struct wl_data_source *pSource ) + { + wl_data_source_destroy( pSource ); + } + /////////////////////// // CWaylandInputThread /////////////////////// From ef1e8dbe49da7ee87120c243f7d1d6935590cc76 Mon Sep 17 00:00:00 2001 From: MithicSpirit Date: Wed, 22 Jan 2025 16:43:57 -0500 Subject: [PATCH 34/34] WaylandBackend: copying to primary selection This is an extension of the previous commit, also forwarding the primary selection to the host. This does not add support for accessing the host's primary selection internally. --- protocol/meson.build | 1 + src/Backends/WaylandBackend.cpp | 47 +++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/protocol/meson.build b/protocol/meson.build index dbce92edce..6c0fb5c719 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -19,6 +19,7 @@ protocols = [ wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', wl_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', + wl_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index bfba09f3ab..e924ac865c 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include "wlr_end.hpp" @@ -692,6 +693,10 @@ namespace gamescope void Wayland_DataSource_Cancelled( struct wl_data_source *pSource ); static const wl_data_source_listener s_DataSourceListener; + void Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ); + void Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource ); + static const zwp_primary_selection_source_v1_listener s_PrimarySelectionSourceListener; + CWaylandInputThread m_InputThread; wl_display *m_pDisplay = nullptr; @@ -721,6 +726,10 @@ namespace gamescope wl_data_device *m_pDataDevice = nullptr; std::shared_ptr m_pClipboard = nullptr; + zwp_primary_selection_device_manager_v1 *m_pPrimarySelectionDeviceManager = nullptr; + zwp_primary_selection_device_v1 *m_pPrimarySelectionDevice = nullptr; + std::shared_ptr m_pPrimarySelection = nullptr; + struct { std::vector ePrimaries; @@ -809,6 +818,11 @@ namespace gamescope .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Send ), .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Cancelled ), }; + const zwp_primary_selection_source_v1_listener CWaylandBackend::s_PrimarySelectionSourceListener = + { + .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Send ), + .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Cancelled ), + }; ////////////////// // CWaylandFb @@ -1189,6 +1203,9 @@ namespace gamescope if ( m_pBackend->m_pDataDeviceManager && !m_pBackend->m_pDataDevice ) m_pBackend->m_pDataDevice = wl_data_device_manager_get_data_device( m_pBackend->m_pDataDeviceManager, m_pBackend->m_pSeat ); + if ( m_pBackend->m_pPrimarySelectionDeviceManager && !m_pBackend->m_pPrimarySelectionDevice ) + m_pBackend->m_pPrimarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device( m_pBackend->m_pPrimarySelectionDeviceManager, m_pBackend->m_pSeat ); + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD && m_pBackend->m_pDataDevice ) { m_pBackend->m_pClipboard = szContents; @@ -1201,9 +1218,17 @@ namespace gamescope wl_data_source_offer( source, "UTF8_STRING" ); wl_data_device_set_selection( m_pBackend->m_pDataDevice, source, m_pBackend->m_uKeyboardEnterSerial ); } - else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY ) + else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY && m_pBackend->m_pPrimarySelectionDevice ) { - // Do nothing + m_pBackend->m_pPrimarySelection = szContents; + zwp_primary_selection_source_v1 *source = zwp_primary_selection_device_manager_v1_create_source( m_pBackend->m_pPrimarySelectionDeviceManager ); + zwp_primary_selection_source_v1_add_listener( source, &m_pBackend->s_PrimarySelectionSourceListener, m_pBackend ); + zwp_primary_selection_source_v1_offer( source, "text/plain" ); + zwp_primary_selection_source_v1_offer( source, "text/plain;charset=utf-8" ); + zwp_primary_selection_source_v1_offer( source, "TEXT" ); + zwp_primary_selection_source_v1_offer( source, "STRING" ); + zwp_primary_selection_source_v1_offer( source, "UTF8_STRING" ); + zwp_primary_selection_device_v1_set_selection( m_pBackend->m_pPrimarySelectionDevice, source, m_pBackend->m_uPointerEnterSerial ); } } @@ -2301,6 +2326,10 @@ namespace gamescope { m_pDataDeviceManager = (wl_data_device_manager *)wl_registry_bind( pRegistry, uName, &wl_data_device_manager_interface, 3u ); } + else if ( !strcmp( pInterface, zwp_primary_selection_device_manager_v1_interface.name ) ) + { + m_pPrimarySelectionDeviceManager = (zwp_primary_selection_device_manager_v1 *)wl_registry_bind( pRegistry, uName, &zwp_primary_selection_device_manager_v1_interface, 1u ); + } } void CWaylandBackend::Wayland_Modifier( zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) @@ -2454,6 +2483,20 @@ namespace gamescope wl_data_source_destroy( pSource ); } + // Primary Selection Source + + void CWaylandBackend::Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ) + { + ssize_t len = m_pPrimarySelection->length(); + if ( write( nFd, m_pPrimarySelection->c_str(), len ) != len ) + xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); + close( nFd ); + } + void CWaylandBackend::Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource) + { + zwp_primary_selection_source_v1_destroy( pSource ); + } + /////////////////////// // CWaylandInputThread ///////////////////////