diff --git a/src/Backends/HeadlessBackend.cpp b/src/Backends/HeadlessBackend.cpp index 49987f69f..1bf4171a2 100644 --- a/src/Backends/HeadlessBackend.cpp +++ b/src/Backends/HeadlessBackend.cpp @@ -1,267 +1,11 @@ #include "backend.h" +#include "Backends/HeadlessBackend.hpp" #include "rendervulkan.hpp" #include "wlserver.hpp" #include "refresh_rate.h" -extern int g_nPreferredOutputWidth; -extern int g_nPreferredOutputHeight; - -namespace gamescope +namespace gamescope { - class CHeadlessConnector final : public IBackendConnector - { - public: - CHeadlessConnector() - { - } - virtual ~CHeadlessConnector() - { - } - - virtual gamescope::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{}; - } - 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 "Headless"; - } - virtual const char *GetMake() const override - { - return "Gamescope"; - } - virtual const char *GetModel() const override - { - return "Virtual Display"; - } - - private: - BackendConnectorHDRInfo m_HDRInfo{}; - }; - - class CHeadlessBackend final : public CBaseBackend - { - public: - CHeadlessBackend() - { - } - - virtual ~CHeadlessBackend() - { - } - - virtual bool Init() override - { - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; - g_nOutputRefresh = g_nNestedRefresh; - - if ( g_nOutputHeight == 0 ) - { - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); - return false; - } - g_nOutputHeight = 720; - } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; - if ( g_nOutputRefresh == 0 ) - g_nOutputRefresh = ConvertHztomHz( 60 ); - - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) - { - return false; - } - - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - return false; - } - - return true; - } - - virtual bool PostInit() override - { - return true; - } - - virtual std::span GetInstanceExtensions() const override - { - return std::span{}; - } - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return std::span{}; - } - virtual VkImageLayout GetPresentLayout() const override - { - return VK_IMAGE_LAYOUT_GENERAL; - } - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override - { - *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); - *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); - } - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return true; - } - - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override - { - return 0; - } - - virtual void DirtyState( bool bForce, bool bForceModeset ) override - { - } - - virtual bool PollState() override - { - return false; - } - - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - return std::make_shared( data ); - } - - virtual OwningRc ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override - { - return new CBaseBackendFb(); - } - - virtual bool UsesModifiers() const override - { - return false; - } - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override - { - return std::span{}; - } - - virtual IBackendConnector *GetCurrentConnector() override - { - return &m_Connector; - } - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override - { - if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return &m_Connector; - - return nullptr; - } - - virtual bool IsVRRActive() const override - { - return false; - } - - virtual bool SupportsPlaneHardwareCursor() const override - { - return false; - } - - virtual bool SupportsTearing() const override - { - return false; - } - - virtual bool UsesVulkanSwapchain() const override - { - return false; - } - - virtual bool IsSessionBased() const override - { - return false; - } - - virtual bool SupportsExplicitSync() const override - { - return true; - } - - virtual bool IsVisible() const override - { - return true; - } - - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override - { - return uvecSize; - } - - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override - { - return false; - } - - virtual void HackUpdatePatchedEdid() override - { - } - - protected: - - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override - { - } - - private: - - CHeadlessConnector m_Connector; - }; - ///////////////////////// // Backend Instantiator ///////////////////////// diff --git a/src/Backends/HeadlessBackend.hpp b/src/Backends/HeadlessBackend.hpp new file mode 100644 index 000000000..4d1abb915 --- /dev/null +++ b/src/Backends/HeadlessBackend.hpp @@ -0,0 +1,269 @@ +VkInstance vulkan_get_instance( void ); +enum VkFormat; +uint32_t VulkanFormatToDRM( VkFormat vkFormat, std::optional obHasAlphaOverride ); +bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ); +bool wlsession_init( void ); +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; +extern uint32_t g_nOutputWidth; +extern uint32_t g_nOutputHeight; +extern int g_nOutputRefresh; +extern int g_nNestedRefresh; +#include "refresh_rate.h" + +namespace gamescope +{ + class CHeadlessConnector final : public IBackendConnector + { + public: + CHeadlessConnector() + { + } + virtual ~CHeadlessConnector() + { + } + + virtual gamescope::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{}; + } + 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 "Headless"; + } + virtual const char *GetMake() const override + { + return "Gamescope"; + } + virtual const char *GetModel() const override + { + return "Virtual Display"; + } + + private: + BackendConnectorHDRInfo m_HDRInfo{}; + }; + + class CHeadlessBackend final : public CBaseBackend + { + public: + CHeadlessBackend() + { + } + + virtual ~CHeadlessBackend() + { + } + + virtual bool Init() override + { + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; + g_nOutputRefresh = g_nNestedRefresh; + + if ( g_nOutputHeight == 0 ) + { + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); + return false; + } + g_nOutputHeight = 720; + } + if ( g_nOutputWidth == 0 ) + g_nOutputWidth = g_nOutputHeight * 16 / 9; + if ( g_nOutputRefresh == 0 ) + g_nOutputRefresh = ConvertHztomHz( 60 ); + + if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) + { + return false; + } + + if ( !wlsession_init() ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + return false; + } + + return true; + } + + virtual bool PostInit() override + { + return true; + } + + virtual std::span GetInstanceExtensions() const override + { + return std::span{}; + } + virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { + return std::span{}; + } + virtual VkImageLayout GetPresentLayout() const override + { + return VK_IMAGE_LAYOUT_GENERAL; + } + virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32, std::nullopt ); + *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM, std::nullopt ); + } + virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { + return true; + } + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + { + return 0; + } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override + { + } + + virtual bool PollState() override + { + return false; + } + + virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override + { + return std::make_shared( data ); + } + + virtual OwningRc ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override + { + return new CBaseBackendFb(); + } + + virtual bool UsesModifiers() const override + { + return false; + } + virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override + { + return std::span{}; + } + + virtual IBackendConnector *GetCurrentConnector() override + { + return &m_Connector; + } + virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override + { + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return &m_Connector; + + return nullptr; + } + + virtual bool IsVRRActive() const override + { + return false; + } + + virtual bool SupportsPlaneHardwareCursor() const override + { + return false; + } + + virtual bool SupportsTearing() const override + { + return false; + } + + virtual bool UsesVulkanSwapchain() const override + { + return false; + } + + virtual bool IsSessionBased() const override + { + return false; + } + + virtual bool SupportsExplicitSync() const override + { + return true; + } + + virtual bool IsVisible() const override + { + return true; + } + + virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { + return uvecSize; + } + + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { + return false; + } + + virtual void HackUpdatePatchedEdid() override + { + } + + protected: + + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override + { + } + + private: + + CHeadlessConnector m_Connector; + }; +} \ No newline at end of file diff --git a/src/backend.cpp b/src/backend.cpp index 8a6bbe8ed..809d0ac94 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -1,4 +1,5 @@ #include "backend.h" +#include "Backends/HeadlessBackend.hpp" #include "vblankmanager.hpp" #include "convar.h" #include "wlserver.hpp" @@ -15,35 +16,84 @@ namespace gamescope ///////////// // IBackend ///////////// - - static IBackend *s_pBackend = nullptr; - + + + //ubsan seemed to complain about bad vptr when not using __attribute__((visibility("default"))) + inline std::atomic __attribute__((visibility("default"))) s_pBackend = nullptr; IBackend *IBackend::Get() { - return s_pBackend; + return s_pBackend.load(); } - + + static void ReplaceBackend(IBackend* pNewBackend); + bool IBackend::Set( IBackend *pBackend ) { + AcquireExclusive(); if ( s_pBackend ) { - delete s_pBackend; - s_pBackend = nullptr; + GetBackend()->DestroyBackend(); //we're intentionally *not* setting s_pBackend to nullptr after deletion. + //the DestroyBackend() method ensures that + //IBackend::Get will still point to a safe memory region (a CHeadlessBackend object) after it is run } if ( pBackend ) { - s_pBackend = pBackend; - if ( !s_pBackend->Init() ) + ReplaceBackend(pBackend); + if ( !GetBackend()->Init() ) { - delete s_pBackend; - s_pBackend = nullptr; + pBackend->DestroyBackend(); return false; } } return true; } + + /////////////////////////////////////////// + // IBackend Internal Book-keeping bits: // + /////////////////////////////////////////// + + static inline gamescope::CHeadlessBackend* s_pHeadless; + static inline IBackend *s_pOldBackend = nullptr; //for internal book-keeping only + + //order of usage (B Backend obj): + // operator new B1 -> ReplaceBackend(B1) -> operator new B2 -> B1->DestroyBackend() -> ReplaceBackend(B2) + void* IBackend::operator new(size_t size) { + console_log.infof("operator new()"); + AcquireExclusive(); + auto* ptr = ::operator new(size); + s_pHeadless = std::construct_at( + (CHeadlessBackend*)(::operator new (sizeof(CHeadlessBackend))) + ); + return ptr; + } + + void IBackend::DestroyBackend() { + console_log.infof("IBackend::DestroyBackend()"); + AcquireExclusive(); + + assert(s_pOldBackend == nullptr); + + s_pOldBackend = this; + this->~IBackend(); //manually call the backend's destructor, without deallocating the memory + s_pBackend = s_pHeadless; + } + + static void ReplaceBackend(IBackend* pNewBackend) + { + s_pBackend = pNewBackend; + if (auto* pOld = std::exchange(s_pOldBackend, nullptr); + pOld != nullptr) + { //delete the old backend for realsies + ::operator delete(s_pHeadless); + s_pHeadless=nullptr; + ::operator delete(pOld); + } + } + + /////////////////////////////////////////// + /////////////////////////////////////////// ///////////////// // CBaseBackendFb @@ -184,4 +234,4 @@ namespace gamescope GetBackend()->DumpDebugInfo(); }); -} +} \ No newline at end of file diff --git a/src/backend.h b/src/backend.h index dd295f4ac..fc690c21d 100644 --- a/src/backend.h +++ b/src/backend.h @@ -170,10 +170,13 @@ namespace gamescope wlr_buffer *m_pClientBuffer = nullptr; std::shared_ptr m_pReleasePoint; }; - + inline static constinit thread_local bool s_tl_bIsLockHeld = false; + inline static std::mutex s_backendModifyMut; + class IBackend { public: + void* operator new(size_t size); virtual ~IBackend() {} virtual bool Init() = 0; @@ -206,6 +209,7 @@ namespace gamescope virtual bool UsesModifiers() const = 0; virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; + inline bool SupportsFormat( uint32_t uDrmFormat ) const { return Algorithm::Contains( this->GetSupportedModifiers( uDrmFormat ), DRM_FORMAT_MOD_INVALID ); @@ -259,10 +263,33 @@ namespace gamescope static bool Set( IBackend *pBackend ); protected: + constexpr void operator delete(void*) { + //this shouldn't be getting called directly + } friend BackendBlob; virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) = 0; private: + struct LockWrapper { + static inline std::optional> CheckedLock() { + if (std::exchange(s_tl_bIsLockHeld, true)) { + return std::nullopt; + } else { + return std::optional>{std::in_place_t{}, s_backendModifyMut}; + } + } + LockWrapper() : m_oLock{CheckedLock()} {} + ~LockWrapper() { + s_tl_bIsLockHeld = false; + } + + std::optional> m_oLock; + }; + static LockWrapper AcquireExclusive() { + return LockWrapper{}; + } + + void DestroyBackend(); }; @@ -347,5 +374,4 @@ namespace gamescope inline gamescope::IBackend *GetBackend() { return gamescope::IBackend::Get(); -} - +} \ No newline at end of file diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 11a7cade6..8d15bda9d 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -5896,7 +5896,7 @@ error(Display *dpy, XErrorEvent *ev) } [[noreturn]] static void -steamcompmgr_exit(void) +steamcompmgr_exit(std::optional> lock = std::nullopt) { g_ImageWaiter.Shutdown(); @@ -5936,7 +5936,9 @@ steamcompmgr_exit(void) wlserver_lock(); wlserver_shutdown(); wlserver_unlock(false); - + + if (lock) + lock->unlock(); pthread_exit(NULL); } @@ -8042,7 +8044,7 @@ steamcompmgr_main(int argc, char **argv) vblank = false; } - steamcompmgr_exit(); + steamcompmgr_exit(std::optional> {std::in_place_t{}, std::move(xwayland_server_guard)} ); } void steamcompmgr_send_frame_done_to_focus_window()