diff --git a/src/backend.cpp b/src/backend.cpp new file mode 100644 index 000000000..b8eeaa9a9 --- /dev/null +++ b/src/backend.cpp @@ -0,0 +1,60 @@ +#include "backend.h" +#include "vblankmanager.hpp" + +extern void sleep_until_nanos(uint64_t nanos); +extern bool env_to_bool(const char *env); + +namespace gamescope +{ + ///////////// + // IBackend + ///////////// + + static IBackend *s_pBackend = nullptr; + + IBackend *IBackend::Get() + { + return s_pBackend; + } + + bool IBackend::Set( IBackend *pBackend ) + { + if ( s_pBackend ) + { + delete s_pBackend; + s_pBackend = nullptr; + } + + s_pBackend = pBackend; + if ( !s_pBackend->Init() ) + { + delete s_pBackend; + s_pBackend = nullptr; + return false; + } + + return true; + } + + ///////////////// + // CBaseBackend + ///////////////// + + bool CBaseBackend::NeedsFrameSync() const + { + const bool bForceTimerFd = env_to_bool( getenv( "GAMESCOPE_DISABLE_TIMERFD" ) ); + return bForceTimerFd; + } + + INestedHints *CBaseBackend::GetNestedHints() + { + return nullptr; + } + + VBlankScheduleTime CBaseBackend::FrameSync() + { + VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); + sleep_until_nanos( schedule.ulScheduledWakeupPoint ); + return schedule; + } +} diff --git a/src/backend.h b/src/backend.h new file mode 100644 index 000000000..37e93345b --- /dev/null +++ b/src/backend.h @@ -0,0 +1,284 @@ +#pragma once + +#include "color_helpers.h" +#include "gamescope_shared.h" + +#include "vulkan/vulkan_core.h" +#include +#include +#include +#include +#include +#include + +struct wlr_buffer; +struct wlr_dmabuf_attributes; + +struct FrameInfo_t; + +namespace gamescope +{ + struct VBlankScheduleTime; + class BackendBlob; + + struct BackendConnectorHDRInfo + { + // We still want to set up HDR info for Steam Deck LCD with some good + // target/mapping values for the display brightness for undocking from a HDR display, + // but don't want to expose HDR there as it is not good. + bool bExposeHDRSupport = false; + + // The output encoding to use for HDR output. + // For typical HDR10 displays, this will be PQ. + // For displays doing "traditional HDR" such as Steam Deck OLED, this is Gamma 2.2. + EOTF eOutputEncodingEOTF = EOTF_Gamma22; + + uint16_t uMaxContentLightLevel = 500; // Nits + uint16_t uMaxFrameAverageLuminance = 500; // Nits + uint16_t uMinContentLightLevel = 0; // Nits / 10000 + std::shared_ptr pDefaultMetadataBlob; + + bool IsHDRG22() const + { + return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; + } + + bool ShouldPatchEDID() const + { + return IsHDRG22(); + } + + bool IsHDR10() const + { + // PQ output encoding is always HDR10 (PQ + 2020) for us. + // If that assumption changes, update me. + return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; + } + }; + + struct BackendMode + { + uint32_t uWidth; + uint32_t uHeight; + uint32_t uRefresh; // Hz + }; + + class IBackendConnector + { + public: + virtual ~IBackendConnector() {} + + virtual GamescopeScreenType GetScreenType() const = 0; + virtual GamescopePanelOrientation GetCurrentOrientation() const = 0; + virtual bool SupportsHDR() const = 0; + virtual bool IsHDRActive() const = 0; + virtual const BackendConnectorHDRInfo &GetHDRInfo() const = 0; + virtual std::span GetModes() const = 0; + + virtual bool SupportsVRR() const = 0; + + virtual std::span GetRawEDID() const = 0; + virtual std::span GetValidDynamicRefreshRates() const = 0; + + virtual void GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const = 0; + + virtual const char *GetName() const = 0; + virtual const char *GetMake() const = 0; + virtual const char *GetModel() const = 0; + }; + + class INestedHints + { + public: + virtual ~INestedHints() {} + + struct CursorInfo + { + std::vector pPixels; + uint32_t uWidth; + uint32_t uHeight; + uint32_t uXHotspot; + uint32_t uYHotspot; + }; + + virtual void SetCursorImage( std::shared_ptr info ) = 0; + virtual void SetRelativeMouseMode( bool bRelative ) = 0; + virtual void SetVisible( bool bVisible ) = 0; + virtual void SetTitle( std::shared_ptr szTitle ) = 0; + virtual void SetIcon( std::shared_ptr> uIconPixels ) = 0; + virtual std::optional 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 IBackend + { + public: + virtual ~IBackend() {} + + virtual bool Init() = 0; + virtual bool PostInit() = 0; + virtual std::span GetInstanceExtensions() const = 0; + virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const = 0; + virtual VkImageLayout GetPresentLayout() const = 0; + virtual void GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *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; + + virtual std::shared_ptr CreateBackendBlob( std::span data ) = 0; + template + std::shared_ptr CreateBackendBlob( const T& thing ) + { + const uint8_t *pBegin = reinterpret_cast( &thing ); + const uint8_t *pEnd = pBegin + sizeof( T ); + return CreateBackendBlob( std::span( pBegin, pEnd ) ); + } + + // For DRM, this is + // dmabuf -> fb_id. + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) = 0; + virtual void LockBackendFb( uint32_t uFbId ) = 0; + virtual void UnlockBackendFb( uint32_t uFbId ) = 0; + virtual void DropBackendFb( uint32_t uFbId ) = 0; + + virtual bool UsesModifiers() const = 0; + virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; + + 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; + + virtual bool UsesVulkanSwapchain() const = 0; + virtual bool IsSessionBased() const = 0; + + // Dumb helper we should remove to support multi display someday. + gamescope::GamescopeScreenType GetScreenType() + { + if ( GetCurrentConnector() ) + return GetCurrentConnector()->GetScreenType(); + + return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + } + + 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; + + static IBackend *Get(); + template + static bool Set(); + protected: + friend BackendBlob; + + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) = 0; + private: + static bool Set( IBackend *pBackend ); + }; + + + 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; } + protected: + BackendPresentFeedback m_PresentFeedback{}; + }; + + // This is a blob of data that may be associated with + // a backend if it needs to be. + // Currently on non-DRM backends this is basically a + // no-op. + class BackendBlob + { + public: + BackendBlob() + { + } + + BackendBlob( std::span data ) + : m_Data( data.begin(), data.end() ) + { + } + + BackendBlob( std::span data, uint32_t uBlob, bool bOwned ) + : m_Data( data.begin(), data.end() ) + , m_uBlob( uBlob ) + , m_bOwned( bOwned ) + { + } + + ~BackendBlob() + { + if ( m_bOwned ) + IBackend::Get()->OnBackendBlobDestroyed( this ); + } + + // No copy constructor, because we can't duplicate the blob handle. + BackendBlob( const BackendBlob& ) = delete; + BackendBlob& operator=( const BackendBlob& ) = delete; + // No move constructor, because we use shared_ptr anyway, but can be added if necessary. + BackendBlob( BackendBlob&& ) = delete; + BackendBlob& operator=( BackendBlob&& ) = delete; + + std::span GetData() const { return std::span( m_Data.begin(), m_Data.end() ); } + template + const T& View() const + { + assert( sizeof( T ) == m_Data.size() ); + return *reinterpret_cast( m_Data.data() ); + } + uint32_t GetBlobValue() const { return m_uBlob; } + + private: + std::vector m_Data; + uint32_t m_uBlob = 0; + bool m_bOwned = false; + }; +} + +inline gamescope::IBackend *GetBackend() +{ + return gamescope::IBackend::Get(); +} + diff --git a/src/backends.h b/src/backends.h new file mode 100644 index 000000000..11a54bd04 --- /dev/null +++ b/src/backends.h @@ -0,0 +1,20 @@ +#pragma once + +namespace gamescope +{ + // Backend enum. + enum GamescopeBackend + { + Auto, + DRM, + SDL, + OpenVR, + Headless, + }; + + // Backend forward declarations. + class CSDLBackend; + class CDRMBackend; + class COpenVRBackend; + class CHeadlessBackend; +} diff --git a/src/color_bench.cpp b/src/color_bench.cpp index bde6dd19c..33dff78c0 100644 --- a/src/color_bench.cpp +++ b/src/color_bench.cpp @@ -40,16 +40,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) colorMapping, nightmode, tonemapping, nullptr, flGain ); for ( size_t i=0, end = lut1d_float.dataR.size(); i // glm::vec2 #include // glm::vec3 #include // glm::mat3 +#include // Color utils inline int quantize( float fVal, float fMaxVal ) @@ -17,7 +18,7 @@ inline int quantize( float fVal, float fMaxVal ) return std::max( 0.f, std::min( fMaxVal, rintf( fVal * fMaxVal ) ) ); } -inline uint16_t drm_quantize_lut_value( float flValue ) +inline uint16_t quantize_lut_value_16bit( float flValue ) { return (uint16_t)quantize( flValue, (float)UINT16_MAX ); } diff --git a/src/color_tests.cpp b/src/color_tests.cpp index 61da1b74b..66aae90d7 100644 --- a/src/color_tests.cpp +++ b/src/color_tests.cpp @@ -41,16 +41,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) colorMapping, nightmode, tonemapping, nullptr, flGain ); for ( size_t i=0, end = lut1d_float.dataR.size(); i -#include -#include -#include -#include - #include #include #include @@ -14,33 +8,430 @@ #include #include -#include "wlr_begin.hpp" -#include -#include "wlr_end.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "drm.hpp" +#include "backend.h" +#include "color_helpers.h" #include "defer.hpp" +#include "drm_include.h" +#include "edid.h" +#include "gamescope_shared.h" +#include "gpuvis_trace_utils.h" +#include "log.hpp" #include "main.hpp" #include "modegen.hpp" +#include "rendervulkan.hpp" +#include "steamcompmgr.hpp" #include "vblankmanager.hpp" #include "wlserver.hpp" -#include "log.hpp" - -#include "gpuvis_trace_utils.h" -#include "steamcompmgr.hpp" -#include -#include -#include - -extern "C" { +#include "wlr_begin.hpp" +#include +#include #include "libdisplay-info/info.h" #include "libdisplay-info/edid.h" #include "libdisplay-info/cta.h" -} +#include "wlr_end.hpp" #include "gamescope-control-protocol.h" + +namespace gamescope +{ + template + using CAutoDeletePtr = std::unique_ptr; + + //////////////////////////////////////// + // DRM Object Wrappers + State Trackers + //////////////////////////////////////// + struct DRMObjectRawProperty + { + uint32_t uPropertyId = 0ul; + uint64_t ulValue = 0ul; + }; + using DRMObjectRawProperties = std::unordered_map; + + class CDRMAtomicObject + { + public: + CDRMAtomicObject( uint32_t ulObjectId ); + uint32_t GetObjectId() const { return m_ulObjectId; } + + // No copy or move constructors. + CDRMAtomicObject( const CDRMAtomicObject& ) = delete; + CDRMAtomicObject& operator=( const CDRMAtomicObject& ) = delete; + + CDRMAtomicObject( CDRMAtomicObject&& ) = delete; + CDRMAtomicObject& operator=( CDRMAtomicObject&& ) = delete; + protected: + uint32_t m_ulObjectId = 0ul; + }; + + template < uint32_t DRMObjectType > + class CDRMAtomicTypedObject : public CDRMAtomicObject + { + public: + CDRMAtomicTypedObject( uint32_t ulObjectId ); + protected: + std::optional GetRawProperties(); + }; + + class CDRMAtomicProperty + { + public: + CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); + + static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); + + uint64_t GetPendingValue() const { return m_ulPendingValue; } + uint64_t GetCurrentValue() const { return m_ulCurrentValue; } + uint64_t GetInitialValue() const { return m_ulInitialValue; } + int SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); + + void OnCommit(); + void Rollback(); + private: + CDRMAtomicObject *m_pObject = nullptr; + uint32_t m_uPropertyId = 0u; + + uint64_t m_ulPendingValue = 0ul; + uint64_t m_ulCurrentValue = 0ul; + uint64_t m_ulInitialValue = 0ul; + }; + + class CDRMPlane final : public CDRMAtomicTypedObject + { + public: + // Takes ownership of pPlane. + CDRMPlane( drmModePlane *pPlane ); + + void RefreshState(); + + drmModePlane *GetModePlane() const { return m_pPlane.get(); } + + struct PlaneProperties + { + std::optional *begin() { return &FB_ID; } + std::optional *end() { return &DUMMY_END; } + + std::optional type; // Immutable + std::optional IN_FORMATS; // Immutable + + std::optional FB_ID; + std::optional CRTC_ID; + std::optional SRC_X; + std::optional SRC_Y; + std::optional SRC_W; + std::optional SRC_H; + std::optional CRTC_X; + std::optional CRTC_Y; + std::optional CRTC_W; + std::optional CRTC_H; + std::optional zpos; + std::optional alpha; + std::optional rotation; + std::optional COLOR_ENCODING; + std::optional COLOR_RANGE; + std::optional VALVE1_PLANE_DEGAMMA_TF; + std::optional VALVE1_PLANE_DEGAMMA_LUT; + std::optional VALVE1_PLANE_CTM; + std::optional VALVE1_PLANE_HDR_MULT; + std::optional VALVE1_PLANE_SHAPER_LUT; + std::optional VALVE1_PLANE_SHAPER_TF; + std::optional VALVE1_PLANE_LUT3D; + std::optional VALVE1_PLANE_BLEND_TF; + std::optional VALVE1_PLANE_BLEND_LUT; + std::optional DUMMY_END; + }; + PlaneProperties &GetProperties() { return m_Props; } + const PlaneProperties &GetProperties() const { return m_Props; } + private: + CAutoDeletePtr m_pPlane; + PlaneProperties m_Props; + }; + + class CDRMCRTC final : public CDRMAtomicTypedObject + { + public: + // Takes ownership of pCRTC. + CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ); + + void RefreshState(); + uint32_t GetCRTCMask() const { return m_uCRTCMask; } + + struct CRTCProperties + { + std::optional *begin() { return &ACTIVE; } + std::optional *end() { return &DUMMY_END; } + + std::optional ACTIVE; + std::optional MODE_ID; + std::optional GAMMA_LUT; + std::optional DEGAMMA_LUT; + std::optional CTM; + std::optional VRR_ENABLED; + std::optional OUT_FENCE_PTR; + std::optional VALVE1_CRTC_REGAMMA_TF; + std::optional DUMMY_END; + }; + CRTCProperties &GetProperties() { return m_Props; } + const CRTCProperties &GetProperties() const { return m_Props; } + private: + CAutoDeletePtr m_pCRTC; + uint32_t m_uCRTCMask = 0u; + CRTCProperties m_Props; + }; + + class CDRMConnector final : public IBackendConnector, public CDRMAtomicTypedObject + { + public: + CDRMConnector( drmModeConnector *pConnector ); + + void RefreshState(); + + struct ConnectorProperties + { + std::optional *begin() { return &CRTC_ID; } + std::optional *end() { return &DUMMY_END; } + + std::optional CRTC_ID; + std::optional Colorspace; + std::optional content_type; // "content type" with space! + std::optional panel_orientation; // "panel orientation" with space! + std::optional HDR_OUTPUT_METADATA; + std::optional vrr_capable; + std::optional EDID; + std::optional DUMMY_END; + }; + ConnectorProperties &GetProperties() { return m_Props; } + const ConnectorProperties &GetProperties() const { return m_Props; } + + drmModeConnector *GetModeConnector() { return m_pConnector.get(); } + const char *GetName() const override { return m_Mutable.szName; } + const char *GetMake() const override { return m_Mutable.pszMake; } + const char *GetModel() const override { return m_Mutable.szModel; } + uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } + std::span GetValidDynamicRefreshRates() const override { return m_Mutable.ValidDynamicRefreshRates; } + GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } + const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } + + std::span GetRawEDID() const override { return std::span{ m_Mutable.EdidData.begin(), m_Mutable.EdidData.end() }; } + + bool SupportsHDR10() const + { + return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); + } + + bool SupportsHDRG22() const + { + return GetHDRInfo().IsHDRG22(); + } + + ////////////////////////////////////// + // IBackendConnector implementation + ////////////////////////////////////// + + GamescopeScreenType GetScreenType() const override + { + if ( m_pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || + m_pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || + m_pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) + return GAMESCOPE_SCREEN_TYPE_INTERNAL; + + return GAMESCOPE_SCREEN_TYPE_EXTERNAL; + } + + GamescopePanelOrientation GetCurrentOrientation() const override + { + return m_ChosenOrientation; + } + + bool SupportsHDR() const override + { + return SupportsHDR10() || SupportsHDRG22(); + } + + bool IsHDRActive() const override + { + if ( SupportsHDR10() ) + { + return GetProperties().Colorspace->GetCurrentValue() == DRM_MODE_COLORIMETRY_BT2020_RGB; + } + else if ( SupportsHDRG22() ) + { + return true; + } + + return false; + } + + const BackendConnectorHDRInfo &GetHDRInfo() const override { return m_Mutable.HDR; } + + virtual std::span GetModes() const override { return m_Mutable.BackendModes; } + + bool SupportsVRR() const override + { + return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); + } + + void GetNativeColorimetry( + bool bHDR, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override + { + *displayColorimetry = GetDisplayColorimetry(); + *displayEOTF = EOTF_Gamma22; + + if ( bHDR && GetHDRInfo().IsHDR10() ) + { + // For HDR10 output, expected content colorspace != native colorspace. + *outputEncodingColorimetry = displaycolorimetry_2020; + *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF; + } + else + { + *outputEncodingColorimetry = GetDisplayColorimetry(); + *outputEncodingEOTF = EOTF_Gamma22; + } + } + + void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); + + private: + void ParseEDID(); + + static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); + + CAutoDeletePtr m_pConnector; + + struct MutableConnectorState + { + int nDefaultRefresh = 0; + + uint32_t uPossibleCRTCMask = 0u; + char szName[32]{}; + char szMakePNP[4]{}; + char szModel[16]{}; + const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. + GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; + std::span ValidDynamicRefreshRates{}; + std::vector EdidData; // Raw, unmodified. + std::vector BackendModes; + + displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; + BackendConnectorHDRInfo HDR; + } m_Mutable; + + GamescopePanelOrientation m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; + + ConnectorProperties m_Props; + }; +} + +struct saved_mode { + int width; + int height; + int refresh; +}; + +struct fb { + uint32_t id; + /* Client buffer, if any */ + struct wlr_buffer *buf; + /* A FB is held if it's being used by steamcompmgr + * doesn't need to be atomic as it's only ever + * modified/read from the steamcompmgr thread */ + int held_refs; + /* Number of page-flips using the FB */ + std::atomic< uint32_t > n_refs; +}; + +struct drm_t { + bool bUseLiftoff; + + int fd; + + 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; + + std::map< uint32_t, drmModePropertyRes * > props; + + gamescope::CDRMPlane *pPrimaryPlane; + gamescope::CDRMCRTC *pCRTC; + gamescope::CDRMConnector *pConnector; + int kms_in_fence_fd; + int kms_out_fence_fd; + + 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 { + std::shared_ptr mode_id; + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; + drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; + } current, pending; + + /* FBs in the atomic request, but not yet submitted to KMS */ + std::vector < uint32_t > fbids_in_req; + /* FBs submitted to KMS, but not yet displayed on screen */ + std::vector < uint32_t > fbids_queued; + /* FBs currently on screen */ + std::vector < uint32_t > fbids_on_screen; + + std::unordered_map< uint32_t, struct fb > fb_map; + std::mutex fb_map_mutex; + + std::mutex free_queue_lock; + std::vector< uint32_t > fbid_unlock_queue; + std::vector< uint32_t > fbid_free_queue; + + std::mutex flip_lock; + + std::atomic < bool > paused; + std::atomic < int > out_of_date; + std::atomic < bool > needs_modeset; + + 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 = {}; @@ -48,8 +439,17 @@ 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; -bool g_bDebugLayers = false; -const char *g_sOutputName = nullptr; +extern bool g_bDebugLayers; + +struct DRMPresentCtx +{ + uint64_t ulPendingFlipCount = 0; +}; + +extern bool alwaysComposite; +extern bool g_bColorSliderInUse; +extern bool fadingOut; +extern std::string g_reshade_effect; #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 @@ -69,11 +469,10 @@ struct drm_color_ctm2 { bool g_bSupportsAsyncFlips = false; -gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; -enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; -std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; +extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; +extern GamescopePanelOrientation g_DesiredInternalOrientation; -bool g_bForceDisableColorMgmt = false; +extern bool g_bForceDisableColorMgmt; static LogScope drm_log("drm"); static LogScope drm_verbose_log("drm", LOG_SILENT); @@ -100,51 +499,12 @@ static constexpr uint32_t s_kSteamDeckOLEDRates[] = 90, }; -static uint32_t get_conn_display_info_flags( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) -{ - if ( !pConnector ) - return 0; - - uint32_t flags = 0; - if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; - if ( pConnector->IsVRRCapable() ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; - if ( pConnector->GetHDRInfo().bExposeHDRSupport ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; - - return flags; -} - -void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm) -{ - // assumes wlserver_lock HELD! - - if ( !drm->pConnector ) - return; - - auto& conn = drm->pConnector; - - uint32_t flags = get_conn_display_info_flags( drm, drm->pConnector ); - - struct wl_array display_rates; - wl_array_init(&display_rates); - if ( conn->GetValidDynamicRefreshRates().size() ) - { - size_t size = conn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); - uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); - memcpy( ptr, conn->GetValidDynamicRefreshRates().data(), size ); - } - gamescope_control_send_active_display_info( control, drm->pConnector->GetName(), drm->pConnector->GetMake(), drm->pConnector->GetModel(), flags, &display_rates ); - wl_array_release(&display_rates); -} - static void update_connector_display_info_wl(struct drm_t *drm) { wlserver_lock(); for ( const auto &control : wlserver.gamescope_controls ) { - drm_send_gamescope_control(control, drm); + wlserver_send_gamescope_control( control ); } wlserver_unlock(); } @@ -264,13 +624,13 @@ static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); -std::atomic g_nCompletedPageFlipCount = { 0u }; - extern void mangoapp_output_update( uint64_t vblanktime ); static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *data) { - uint64_t flipcount = (uint64_t)data; - g_nCompletedPageFlipCount = flipcount; + DRMPresentCtx *pCtx = reinterpret_cast( data ); + + // Make this const when we move into CDRMBackend. + GetBackend()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; if ( !g_DRM.pCRTC ) return; @@ -280,12 +640,12 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi // This is the last vblank time uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; - g_VBlankTimer.MarkVBlank( vblanktime, true ); + GetVBlankTimer().MarkVBlank( vblanktime, true ); // TODO: get the fbids_queued instance from data if we ever have more than one in flight - drm_verbose_log.debugf("page_flip_handler %" PRIu64, flipcount); - gpuvis_trace_printf("page_flip_handler %" PRIu64, flipcount); + drm_verbose_log.debugf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); + gpuvis_trace_printf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); for ( uint32_t i = 0; i < g_DRM.fbids_on_screen.size(); i++ ) { @@ -360,362 +720,98 @@ void flip_handler_thread_run(void) } } -static constexpr uint32_t EDID_MAX_BLOCK_COUNT = 256; -static constexpr uint32_t EDID_BLOCK_SIZE = 128; -static constexpr uint32_t EDID_MAX_STANDARD_TIMING_COUNT = 8; -static constexpr uint32_t EDID_BYTE_DESCRIPTOR_COUNT = 4; -static constexpr uint32_t EDID_BYTE_DESCRIPTOR_SIZE = 18; -static constexpr uint32_t EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT = 6; -static constexpr uint32_t EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT = 2; -static constexpr uint32_t EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT = 44; -static constexpr uint32_t EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT = 4; - -static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) -{ - size_t n; - uint8_t bitmask; - - assert(high <= 7 && high >= low); - - n = high - low + 1; - bitmask = (uint8_t) ((1 << n) - 1); - return (uint8_t) (val >> low) & bitmask; -} - -static inline void set_bit_range(uint8_t *val, size_t high, size_t low, uint8_t bits) +static bool refresh_state( drm_t *drm ) { - size_t n; - uint8_t bitmask; - - assert(high <= 7 && high >= low); + drmModeRes *pResources = drmModeGetResources( drm->fd ); + if ( pResources == nullptr ) + { + drm_log.errorf_errno( "drmModeGetResources failed" ); + return false; + } + defer( drmModeFreeResources( pResources ) ); - n = high - low + 1; - bitmask = (uint8_t) ((1 << n) - 1); - assert((bits & ~bitmask) == 0); + // Add connectors which appeared + for ( int i = 0; i < pResources->count_connectors; i++ ) + { + uint32_t uConnectorId = pResources->connectors[i]; - *val |= (uint8_t)(bits << low); -} + drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); + if ( !pConnector ) + continue; + if ( !drm->connectors.contains( uConnectorId ) ) + { + drm->connectors.emplace( + std::piecewise_construct, + std::forward_as_tuple( uConnectorId ), + std::forward_as_tuple( pConnector ) ); + } + } -static inline void patch_edid_checksum(uint8_t* block) -{ - uint8_t sum = 0; - for (uint32_t i = 0; i < EDID_BLOCK_SIZE - 1; i++) - sum += block[i]; + // Remove connectors which disappeared + for ( auto iter = drm->connectors.begin(); iter != drm->connectors.end(); ) + { + gamescope::CDRMConnector *pConnector = &iter->second; - uint8_t checksum = uint32_t(256) - uint32_t(sum); + const bool bFound = std::any_of( + pResources->connectors, + pResources->connectors + pResources->count_connectors, + std::bind_front( std::equal_to{}, pConnector->GetObjectId() ) ); - block[127] = checksum; -} + if ( !bFound ) + { + drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); -static bool validate_block_checksum(const uint8_t* data) -{ - uint8_t sum = 0; - size_t i; + if ( drm->pConnector == pConnector ) + { + drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); + drm->pConnector = nullptr; + } - for (i = 0; i < EDID_BLOCK_SIZE; i++) { - sum += data[i]; + iter = drm->connectors.erase( iter ); + } + else + iter++; } - return sum == 0; -} + // Re-probe connectors props and status) + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + pConnector->RefreshState(); + } -const char *drm_get_patched_edid_path() -{ - return getenv("GAMESCOPE_PATCHED_EDID_FILE"); -} + for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) + pCRTC->RefreshState(); -static uint8_t encode_max_luminance(float nits) -{ - if (nits == 0.0f) - return 0; + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + pPlane->RefreshState(); - return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); + return true; } -static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, gamescope::CDRMConnector *conn ) +static bool get_resources(struct drm_t *drm) { - // A zero length indicates that the edid parsing failed. - if (orig_size == 0) { - return; - } - - std::vector edid(orig_data, orig_data + orig_size); - - if ( g_bRotated ) { - // Patch width, height. - drm_log.infof("[patched edid] Patching dims %ux%u -> %ux%u", edid[0x15], edid[0x16], edid[0x16], edid[0x15]); - std::swap(edid[0x15], edid[0x16]); - - for (uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) + drmModeRes *pResources = drmModeGetResources( drm->fd ); + if ( !pResources ) { - uint8_t *byte_desc_data = &edid[0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE]; - if (byte_desc_data[0] || byte_desc_data[1]) - { - uint32_t horiz = (get_bit_range(byte_desc_data[4], 7, 4) << 8) | byte_desc_data[2]; - uint32_t vert = (get_bit_range(byte_desc_data[7], 7, 4) << 8) | byte_desc_data[5]; - drm_log.infof("[patched edid] Patching res %ux%u -> %ux%u", horiz, vert, vert, horiz); - std::swap(byte_desc_data[4], byte_desc_data[7]); - std::swap(byte_desc_data[2], byte_desc_data[5]); - break; - } + drm_log.errorf_errno( "drmModeGetResources failed" ); + return false; } + defer( drmModeFreeResources( pResources ) ); - patch_edid_checksum(&edid[0]); + for ( int i = 0; i < pResources->count_crtcs; i++ ) + { + drmModeCrtc *pCRTC = drmModeGetCrtc( drm->fd, pResources->crtcs[ i ] ); + if ( pCRTC ) + drm->crtcs.emplace_back( std::make_unique( pCRTC, 1u << i ) ); + } } - // If we are debugging HDR support lazily on a regular Deck, - // just hotpatch the edid for the game so we get values we want as if we had - // an external display attached. - // (Allows for debugging undocked fallback without undocking/redocking) - if ( conn->GetHDRInfo().ShouldPatchEDID() ) { - // TODO: Allow for override of min luminance - float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? - g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : - g_ColorMgmt.pending.flInternalDisplayBrightness; - drm_log.infof("[edid] Patching HDR static metadata. max peak luminance/max frame avg luminance = %f nits", flMaxPeakLuminance ); - const uint8_t new_hdr_static_metadata_block[] - { - (1 << HDMI_EOTF_SDR) | (1 << HDMI_EOTF_TRADITIONAL_HDR) | (1 << HDMI_EOTF_ST2084), /* supported eotfs */ - 1, /* type 1 */ - encode_max_luminance(flMaxPeakLuminance), /* desired content max peak luminance */ - encode_max_luminance(flMaxPeakLuminance * 0.8f), /* desired content max frame avg luminance */ - 0, /* desired content min luminance -- 0 is technically "undefined" */ - }; - - int ext_count = int(edid.size() / EDID_BLOCK_SIZE) - 1; - assert(ext_count == edid[0x7E]); - bool has_cta_block = false; - bool has_hdr_metadata_block = false; - - for (int i = 0; i < ext_count; i++) - { - uint8_t *ext_data = &edid[EDID_BLOCK_SIZE + i * EDID_BLOCK_SIZE]; - uint8_t tag = ext_data[0]; - if (tag == DI_EDID_EXT_CEA) - { - has_cta_block = true; - uint8_t dtd_start = ext_data[2]; - uint8_t flags = ext_data[3]; - if (dtd_start == 0) - { - drm_log.infof("[josh edid] Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); - continue; - } - if (flags != 0) - { - drm_log.infof("[josh edid] Hmmmm.... non-zero CTA flags. Interesting... Not going further! :-("); - continue; - } - - const int CTA_HEADER_SIZE = 4; - int j = CTA_HEADER_SIZE; - while (j < dtd_start) - { - uint8_t data_block_header = ext_data[j]; - uint8_t data_block_tag = get_bit_range(data_block_header, 7, 5); - uint8_t data_block_size = get_bit_range(data_block_header, 4, 0); - - if (j + 1 + data_block_size > dtd_start) - { - drm_log.infof("[josh edid] Hmmmm.... CTA malformatted. Interesting... Not going further! :-("); - break; - } - - uint8_t *data_block = &ext_data[j + 1]; - if (data_block_tag == 7) // extended - { - uint8_t extended_tag = data_block[0]; - uint8_t *extended_block = &data_block[1]; - uint8_t extended_block_size = data_block_size - 1; - - if (extended_tag == 6) // hdr static - { - if (extended_block_size >= sizeof(new_hdr_static_metadata_block)) - { - drm_log.infof("[josh edid] Patching existing HDR Metadata with our own!"); - memcpy(extended_block, new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); - has_hdr_metadata_block = true; - } - } - } - - j += 1 + data_block_size; // account for header size. - } - - if (!has_hdr_metadata_block) - { - const int hdr_metadata_block_size_plus_headers = sizeof(new_hdr_static_metadata_block) + 2; // +1 for header, +1 for extended header -> +2 - drm_log.infof("[josh edid] No HDR metadata block to patch... Trying to insert one."); - - // Assert that the end of the data blocks == dtd_start - if (dtd_start != j) - { - drm_log.infof("[josh edid] dtd_start != end of blocks. Giving up patching. I'm too scared to attempt it."); - } - - // Move back the dtd to make way for our block at the end. - uint8_t *dtd = &ext_data[dtd_start]; - memmove(dtd + hdr_metadata_block_size_plus_headers, dtd, hdr_metadata_block_size_plus_headers); - dtd_start += hdr_metadata_block_size_plus_headers; - - // Data block is where the dtd was. - uint8_t *data_block = dtd; - - // header - data_block[0] = 0; - set_bit_range(&data_block[0], 7, 5, 7); // extended tag - set_bit_range(&data_block[0], 4, 0, sizeof(new_hdr_static_metadata_block) + 1); // size (+1 for extended header, does not include normal header) - - // extended header - data_block[1] = 6; // hdr metadata extended tag - memcpy(&data_block[2], new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); - } - - patch_edid_checksum(ext_data); - bool sum_valid = validate_block_checksum(ext_data); - drm_log.infof("[josh edid] CTA Checksum valid? %s", sum_valid ? "Y" : "N"); - } - } - - if (!has_cta_block) - { - drm_log.infof("[josh edid] Couldn't patch for HDR metadata as we had no CTA block! Womp womp =c"); - } - } - - bool sum_valid = validate_block_checksum(&edid[0]); - drm_log.infof("[josh edid] BASE Checksum valid? %s", sum_valid ? "Y" : "N"); - - // Write it out then flip it over atomically. - - const char *filename = drm_get_patched_edid_path(); - if (!filename) - { - drm_log.errorf("[josh edid] Couldn't write patched edid. No Path."); - return; - } - - char filename_tmp[PATH_MAX]; - snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", filename); - - FILE *file = fopen(filename_tmp, "wb"); - if (!file) - { - drm_log.errorf("[josh edid] Couldn't open file: %s", filename_tmp); - return; - } - - fwrite(edid.data(), 1, edid.size(), file); - fflush(file); - fclose(file); - - rename(filename_tmp, filename); - drm_log.infof("[josh edid] Wrote new edid to: %s", filename); -} - -void drm_update_patched_edid( drm_t *drm ) -{ - if (!drm || !drm->pConnector) - return; - - create_patched_edid(drm->pConnector->GetRawEDID().data(), drm->pConnector->GetRawEDID().size(), drm, drm->pConnector); -} - -static bool refresh_state( drm_t *drm ) -{ - drmModeRes *pResources = drmModeGetResources( drm->fd ); - if ( pResources == nullptr ) - { - drm_log.errorf_errno( "drmModeGetResources failed" ); - return false; - } - defer( drmModeFreeResources( pResources ) ); - - // Add connectors which appeared - for ( int i = 0; i < pResources->count_connectors; i++ ) - { - uint32_t uConnectorId = pResources->connectors[i]; - - drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); - if ( !pConnector ) - continue; - - if ( !drm->connectors.contains( uConnectorId ) ) - { - drm->connectors.emplace( - std::piecewise_construct, - std::forward_as_tuple( uConnectorId ), - std::forward_as_tuple( pConnector ) ); - } - } - - // Remove connectors which disappeared - for ( auto iter = drm->connectors.begin(); iter != drm->connectors.end(); ) - { - gamescope::CDRMConnector *pConnector = &iter->second; - - const bool bFound = std::any_of( - pResources->connectors, - pResources->connectors + pResources->count_connectors, - std::bind_front( std::equal_to{}, pConnector->GetObjectId() ) ); - - if ( !bFound ) - { - drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); - - if ( drm->pConnector == pConnector ) - { - drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); - drm->pConnector = nullptr; - } - - iter = drm->connectors.erase( iter ); - } - else - iter++; - } - - // Re-probe connectors props and status) - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - pConnector->RefreshState(); - } - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - pCRTC->RefreshState(); - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - pPlane->RefreshState(); - - return true; -} - -static bool get_resources(struct drm_t *drm) -{ - { - drmModeRes *pResources = drmModeGetResources( drm->fd ); - if ( !pResources ) - { - drm_log.errorf_errno( "drmModeGetResources failed" ); - return false; - } - defer( drmModeFreeResources( pResources ) ); - - for ( int i = 0; i < pResources->count_crtcs; i++ ) - { - drmModeCrtc *pCRTC = drmModeGetCrtc( drm->fd, pResources->crtcs[ i ] ); - if ( pCRTC ) - drm->crtcs.emplace_back( std::make_unique( pCRTC, 1u << i ) ); - } - } - - { - drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); - if ( !pPlaneResources ) + drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); + if ( !pPlaneResources ) { drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); return false; @@ -845,7 +941,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) continue; - if ( drm->force_internal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) + if ( g_bForceInternal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) continue; int nPriority = get_connector_priority( drm, pConnector->GetName() ); @@ -912,8 +1008,6 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) return false; } - best->SetBaseRefresh( mode->vrefresh ); - if (!drm_set_mode(drm, mode)) { return false; } @@ -928,7 +1022,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) wlserver_unlock(); if (!initial) - create_patched_edid(best->GetRawEDID().data(), best->GetRawEDID().size(), drm, best); + WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo() ); update_connector_display_info_wl( drm ); @@ -1132,9 +1226,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) hdr_output_metadata sdr_metadata; memset(&sdr_metadata, 0, sizeof(sdr_metadata)); - drm->sdr_static_metadata = drm_create_hdr_metadata_blob(drm, &sdr_metadata); + drm->sdr_static_metadata = GetBackend()->CreateBackendBlob( sdr_metadata ); - drm->flipcount = 0; drm->needs_modeset = true; return true; @@ -1161,7 +1254,7 @@ void finish_drm(struct drm_t *drm) if ( pConnector->GetProperties().HDR_OUTPUT_METADATA ) { if ( drm->sdr_static_metadata && pConnector->GetHDRInfo().IsHDR10() ) - pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, drm->sdr_static_metadata->blob, true ); + pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, drm->sdr_static_metadata->GetBlobValue(), true ); else pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, 0, true ); } @@ -1259,164 +1352,6 @@ void finish_drm(struct drm_t *drm) // page-flip handler thread. } -int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) -{ - int ret; - - assert( drm->req != nullptr ); - -// if (drm->kms_in_fence_fd != -1) { -// add_plane_property(req, plane_id, "IN_FENCE_FD", drm->kms_in_fence_fd); -// } - -// drm->kms_out_fence_fd = -1; - -// add_crtc_property(req, drm->crtc_id, "OUT_FENCE_PTR", -// (uint64_t)(unsigned long)&drm->kms_out_fence_fd); - - - assert( drm->fbids_queued.size() == 0 ); - - bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; - - if ( isPageFlip ) { - drm->flip_lock.lock(); - - // Do it before the commit, as otherwise the pageflip handler could - // potentially beat us to the refcount checks. - for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) - { - struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); - assert( fb.held_refs ); - fb.n_refs++; - } - - drm->fbids_queued = drm->fbids_in_req; - } - - g_DRM.flipcount++; - - drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)g_DRM.flipcount); - gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)g_DRM.flipcount ); - - ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, (void*)(uint64_t)g_DRM.flipcount ); - if ( ret != 0 ) - { - drm_log.errorf_errno( "flip error" ); - - if ( ret != -EBUSY && ret != -EACCES ) - { - drm_log.errorf( "fatal flip error, aborting" ); - if ( isPageFlip ) - drm->flip_lock.unlock(); - abort(); - } - - drm->pending = drm->current; - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - for ( std::optional &oProperty : pCRTC->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - for ( std::optional &oProperty : pPlane->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } - - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - for ( std::optional &oProperty : pConnector->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } - - // Undo refcount if the commit didn't actually work - for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) - { - get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; - } - - drm->fbids_queued.clear(); - - g_DRM.flipcount--; - - if ( isPageFlip ) - drm->flip_lock.unlock(); - - goto out; - } else { - drm->fbids_in_req.clear(); - - drm->current = drm->pending; - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - for ( std::optional &oProperty : pCRTC->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - for ( std::optional &oProperty : pPlane->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - for ( std::optional &oProperty : pConnector->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - } - - // Update the draw time - // Ideally this would be updated by something right before the page flip - // is queued and would end up being the new page flip, rather than here. - // However, the page flip handler is called when the page flip occurs, - // not when it is successfully queued. - g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - if ( isPageFlip ) { - // Wait for flip handler to unlock - drm->flip_lock.lock(); - drm->flip_lock.unlock(); - } - -// if (drm->kms_in_fence_fd != -1) { -// close(drm->kms_in_fence_fd); -// drm->kms_in_fence_fd = -1; -// } -// -// drm->kms_in_fence_fd = drm->kms_out_fence_fd; - -out: - drmModeAtomicFree( drm->req ); - drm->req = nullptr; - - return ret; -} - uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ) { uint32_t fb_id = 0; @@ -1571,93 +1506,35 @@ void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ) drm_unlock_fb_internal( drm, &fb ); } -static uint64_t determine_drm_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) +static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) { - if ( pConnector && pConnector->GetProperties().panel_orientation ) + gamescope::IBackendConnector *pInternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); + if ( pInternalConnector ) { - switch ( pConnector->GetProperties().panel_orientation->GetCurrentValue() ) - { - case DRM_MODE_PANEL_ORIENTATION_NORMAL: - return DRM_MODE_ROTATE_0; - case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: - return DRM_MODE_ROTATE_180; - case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: - return DRM_MODE_ROTATE_90; - case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: - return DRM_MODE_ROTATE_270; - } + gamescope::CDRMConnector *pDRMInternalConnector = static_cast( pInternalConnector ); + const drmModeModeInfo *pInternalMode = pMode; + if ( pDRMInternalConnector != drm->pConnector ) + pInternalMode = find_mode( pDRMInternalConnector->GetModeConnector(), 0, 0, 0 ); + + pDRMInternalConnector->UpdateEffectiveOrientation( pInternalMode ); } - if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && mode ) + gamescope::IBackendConnector *pExternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); + if ( pExternalConnector ) { - // Auto-detect portait mode for internal displays - return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; - } + gamescope::CDRMConnector *pDRMExternalConnector = static_cast( pExternalConnector ); + const drmModeModeInfo *pExternalMode = pMode; + if ( pDRMExternalConnector != drm->pConnector ) + pExternalMode = find_mode( pDRMExternalConnector->GetModeConnector(), 0, 0, 0 ); - return DRM_MODE_ROTATE_0; + pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode ); + } } -/* Handle the orientation of the display */ -static void update_drm_effective_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) +// Only used for NV12 buffers +static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) { - gamescope::GamescopeScreenType eScreenType = pConnector->GetScreenType(); - - if ( eScreenType == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - switch ( g_drmModeOrientation ) - { - case PANEL_ORIENTATION_0: - g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_0; - break; - case PANEL_ORIENTATION_90: - g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_90; - break; - case PANEL_ORIENTATION_180: - g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_180; - break; - case PANEL_ORIENTATION_270: - g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_270; - break; - case PANEL_ORIENTATION_AUTO: - g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); - break; - } - } - else - { - g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); - } -} - -static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) -{ - gamescope::CDRMConnector *pInternalConnector = nullptr; - if ( drm->pConnector && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - pInternalConnector = drm->pConnector; - - if ( !pInternalConnector ) - { - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - pInternalConnector = pConnector; - // Find mode for internal connector instead. - pMode = find_mode(pInternalConnector->GetModeConnector(), 0, 0, 0); - break; - } - } - } - - if ( pInternalConnector ) - update_drm_effective_orientation( drm, pInternalConnector, pMode ); -} - -// Only used for NV12 buffers -static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) -{ - switch (colorspace) + switch (colorspace) { default: case k_EStreamColorspace_Unknown: @@ -2095,6 +1972,17 @@ namespace gamescope snprintf( m_Mutable.szName, sizeof( m_Mutable.szName ), "%s-%d", pszTypeStr, GetModeConnector()->connector_type_id ); m_Mutable.szName[ sizeof( m_Mutable.szName ) - 1 ] = '\0'; + for ( int i = 0; i < m_pConnector->count_modes; i++ ) + { + drmModeModeInfo *pMode = &m_pConnector->modes[i]; + m_Mutable.BackendModes.emplace_back( BackendMode + { + .uWidth = pMode->hdisplay, + .uHeight = pMode->vdisplay, + .uRefresh = pMode->vrefresh, + }); + } + auto rawProperties = GetRawProperties(); if ( rawProperties ) { @@ -2110,6 +1998,49 @@ namespace gamescope ParseEDID(); } + void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode ) + { + if ( this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL && g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO ) + { + m_ChosenOrientation = g_DesiredInternalOrientation; + } + else + { + if ( this->GetProperties().panel_orientation ) + { + switch ( this->GetProperties().panel_orientation->GetCurrentValue() ) + { + case DRM_MODE_PANEL_ORIENTATION_NORMAL: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; + return; + case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_180; + return; + case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_90; + return; + case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_270; + return; + default: + break; + } + } + + if ( this->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && pMode ) + { + // Auto-detect portait mode for internal displays + m_ChosenOrientation = pMode->hdisplay < pMode->vdisplay + ? GAMESCOPE_PANEL_ORIENTATION_270 + : GAMESCOPE_PANEL_ORIENTATION_0; + } + else + { + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; + } + } + } + void CDRMConnector::ParseEDID() { if ( !GetProperties().EDID ) @@ -2240,7 +2171,7 @@ namespace gamescope ///////////////////// // Parse HDR stuff. ///////////////////// - std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); + std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); if ( oKnownHDRInfo ) { m_Mutable.HDR = *oKnownHDRInfo; @@ -2319,7 +2250,7 @@ namespace gamescope pInfoframe->max_fall = uDefaultInfoframeLuminances; pInfoframe->eotf = HDMI_EOTF_ST2084; - m_Mutable.HDR.pDefaultMetadataBlob = drm_create_hdr_metadata_blob( &g_DRM, &defaultHDRMetadata ); + m_Mutable.HDR.pDefaultMetadataBlob = GetBackend()->CreateBackendBlob( defaultHDRMetadata ); } else { @@ -2328,14 +2259,14 @@ namespace gamescope } } - /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) + /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) { if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE || eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) { // The stuff in the EDID for the HDR metadata does not fully // reflect what we can achieve on the display by poking at more // things out-of-band. - return HDRInfo + return BackendConnectorHDRInfo { .bExposeHDRSupport = true, .eOutputEncodingEOTF = EOTF_Gamma22, @@ -2347,7 +2278,7 @@ namespace gamescope else if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) { // Set up some HDR fallbacks for undocking - return HDRInfo + return BackendConnectorHDRInfo { .bExposeHDRSupport = false, .eOutputEncodingEOTF = EOTF_Gamma22, @@ -2364,7 +2295,6 @@ namespace gamescope static int drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) { - gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); // If we are modesetting, reset the state cache, we might @@ -2402,7 +2332,24 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_W", entry.layerState[i].srcW ); liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_H", entry.layerState[i].srcH ); - liftoff_layer_set_property( drm->lo_layers[ i ], "rotation", g_drmEffectiveOrientation[screenType] ); + uint64_t ulOrientation = DRM_MODE_ROTATE_0; + switch ( drm->pConnector->GetCurrentOrientation() ) + { + default: + case GAMESCOPE_PANEL_ORIENTATION_0: + ulOrientation = DRM_MODE_ROTATE_0; + break; + case GAMESCOPE_PANEL_ORIENTATION_270: + ulOrientation = DRM_MODE_ROTATE_270; + break; + case GAMESCOPE_PANEL_ORIENTATION_90: + ulOrientation = DRM_MODE_ROTATE_90; + break; + case GAMESCOPE_PANEL_ORIENTATION_180: + ulOrientation = DRM_MODE_ROTATE_180; + break; + } + liftoff_layer_set_property( drm->lo_layers[ i ], "rotation", ulOrientation ); liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_X", entry.layerState[i].crtcX); liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_Y", entry.layerState[i].crtcY); @@ -2449,9 +2396,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo if ( !g_bDisableShaperAnd3DLUT ) { - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); // Josh: See shaders/colorimetry.h colorspace_blend_tf if you have questions as to why we start doing sRGB for BLEND_TF despite potentially working in Gamma 2.2 space prior. } else @@ -2482,7 +2429,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); if (frameInfo->layers[i].ctm != nullptr) - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->blob ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); else liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); } @@ -2552,13 +2499,15 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; - wlserver_hdr_metadata *pHDRMetadata = nullptr; + gamescope::BackendBlob *pHDRMetadata = nullptr; if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) { if ( bWantsHDR10 ) { wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); - pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); + pHDRMetadata = pFeedback + ? pFeedback->hdr_metadata_blob.get() + : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); uColorimetry = DRM_MODE_COLORIMETRY_BT2020_RGB; } else @@ -2675,7 +2624,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI if ( drm->pCRTC ) { drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true ); - drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->blob : 0lu, true ); + drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->GetBlobValue() : 0lu, true ); if ( drm->pCRTC->GetProperties().VRR_ENABLED ) drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, bVRREnabled, true ); @@ -2685,7 +2634,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI if ( drm->pConnector ) { if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) - drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->blob : 0lu, bForceInRequest ); + drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->GetBlobValue() : 0lu, bForceInRequest ); if ( drm->pConnector->GetProperties().content_type ) drm->pConnector->GetProperties().content_type->SetPendingValue( drm->req, DRM_MODE_CONTENT_TYPE_GAME, bForceInRequest ); @@ -2839,19 +2788,8 @@ bool drm_update_color_mgmt(struct drm_t *drm) if ( !g_ColorMgmtLuts[i].HasLuts() ) continue; - uint32_t shaper_blob_id = 0; - if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut1d, sizeof(g_ColorMgmtLuts[i].lut1d), &shaper_blob_id) != 0) { - drm_log.errorf_errno("Unable to create SHAPERLUT property blob"); - return false; - } - drm->pending.shaperlut_id[ i ] = std::make_shared( shaper_blob_id ); - - uint32_t lut3d_blob_id = 0; - if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut3d, sizeof(g_ColorMgmtLuts[i].lut3d), &lut3d_blob_id) != 0) { - drm_log.errorf_errno("Unable to create LUT3D property blob"); - return false; - } - drm->pending.lut3d_id[ i ] = std::make_shared( lut3d_blob_id ); + drm->pending.shaperlut_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut1d ); + drm->pending.lut3d_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut3d ); } return true; @@ -2873,8 +2811,6 @@ static void drm_unset_mode( struct drm_t *drm ) if (g_nOutputRefresh == 0) g_nOutputRefresh = 60; - g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; - g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; g_bRotated = false; } @@ -2883,31 +2819,26 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) if (!drm->pConnector || !drm->pConnector->GetModeConnector()) return false; - uint32_t mode_id = 0; - if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), &mode_id) != 0) - return false; - - gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); - drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); - drm->pending.mode_id = std::make_shared(mode_id); + drm->pending.mode_id = GetBackend()->CreateBackendBlob( *mode ); drm->needs_modeset = true; g_nOutputRefresh = mode->vrefresh; update_drm_effective_orientations(drm, mode); - switch ( g_drmEffectiveOrientation[screenType] ) + switch ( drm->pConnector->GetCurrentOrientation() ) { - case DRM_MODE_ROTATE_0: - case DRM_MODE_ROTATE_180: + default: + case GAMESCOPE_PANEL_ORIENTATION_0: + case GAMESCOPE_PANEL_ORIENTATION_180: g_bRotated = false; g_nOutputWidth = mode->hdisplay; g_nOutputHeight = mode->vdisplay; break; - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: + case GAMESCOPE_PANEL_ORIENTATION_90: + case GAMESCOPE_PANEL_ORIENTATION_270: g_bRotated = true; g_nOutputWidth = mode->vdisplay; g_nOutputHeight = mode->hdisplay; @@ -2974,37 +2905,10 @@ bool drm_set_resolution( struct drm_t *drm, int width, int height ) return drm_set_mode(drm, mode); } -int drm_get_default_refresh(struct drm_t *drm) -{ - if ( drm->preferred_refresh ) - return drm->preferred_refresh; - - if ( drm->pConnector && drm->pConnector->GetBaseRefresh() ) - return drm->pConnector->GetBaseRefresh(); - - if ( drm->pConnector && drm->pConnector->GetModeConnector() ) - { - int width = g_nOutputWidth; - int height = g_nOutputHeight; - if ( g_bRotated ) { - int tmp = width; - width = height; - height = tmp; - } - - drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *mode = find_mode( connector, width, height, 0); - if ( mode ) - return mode->vrefresh; - } - - return 60; -} - bool drm_get_vrr_capable(struct drm_t *drm) { if ( drm->pConnector ) - return drm->pConnector->IsVRRCapable(); + return drm->pConnector->SupportsVRR(); return false; } @@ -3044,102 +2948,662 @@ std::pair drm_get_connector_identifier(struct drm_t *drm) return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); } -std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata) +bool drm_supports_color_mgmt(struct drm_t *drm) { - uint32_t blob = 0; - if (!BIsNested()) - { - int ret = drmModeCreatePropertyBlob(drm->fd, metadata, sizeof(*metadata), &blob); - - if (ret != 0) { - drm_log.errorf("Failed to create blob for HDR_OUTPUT_METADATA. (%s) Falling back to null blob.", strerror(-ret)); - blob = 0; - } - + if ( g_bForceDisableColorMgmt ) + return false; - if (!blob) - return nullptr; - } + if ( !drm->pPrimaryPlane ) + return false; - return std::make_shared(metadata, blob); + return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); } -void drm_destroy_blob(struct drm_t *drm, uint32_t blob) + +std::span drm_get_valid_refresh_rates( struct drm_t *drm ) { - drmModeDestroyPropertyBlob(drm->fd, blob); + if ( drm && drm->pConnector ) + return drm->pConnector->GetValidDynamicRefreshRates(); + + return std::span{}; } -std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm) +namespace gamescope { - uint32_t blob = 0; - if (!BIsNested()) + class CDRMBackend; + + class CDRMBackend final : public CBaseBackend { - drm_color_ctm2 ctm2; - for (uint32_t i = 0; i < 12; i++) + public: + CDRMBackend() + { + } + + virtual ~CDRMBackend() + { + } + + virtual bool Init() override { - float *data = (float*)&ctm; - ctm2.matrix[i] = drm_calc_s31_32(data[i]); + if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) + { + fprintf( stderr, "Failed to initialize Vulkan\n" ); + return false; + } + + if ( !wlsession_init() ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + return false; + } + + return init_drm( &g_DRM, 0, 0, 0 ); } - int ret = drmModeCreatePropertyBlob(drm->fd, &ctm2, sizeof(ctm2), &blob); + virtual bool PostInit() override + { + if ( g_DRM.pConnector ) + WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo() ); + return true; + } - if (ret != 0) { - drm_log.errorf("Failed to create blob for CTM. (%s) Falling back to null blob.", strerror(-ret)); - blob = 0; + 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 + { + // Does not matter, as this has a queue family transition + // to VK_QUEUE_FAMILY_FOREIGN_EXT queue, + // thus: newLayout is ignored. + return VK_IMAGE_LAYOUT_GENERAL; } + virtual void GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = DRMFormatToVulkan( g_nDRMFormat, false ); + *pOverlayPlaneFormat = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); + } + virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { + return true; + } + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + { + bool bWantsPartialComposite = pFrameInfo->layerCount >= 3 && !kDisablePartialComposition; + + static bool s_bWasFirstFrame = true; + bool bWasFirstFrame = s_bWasFirstFrame; + s_bWasFirstFrame = false; + + bool bDrewCursor = false; + for ( uint32_t i = 0; i < k_nMaxLayers; i++ ) + { + if ( pFrameInfo->layers[i].zpos == g_zposCursor ) + { + bDrewCursor = true; + break; + } + } + + 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; + + bool bNeedsFullComposite = false; + bNeedsFullComposite |= alwaysComposite; + bNeedsFullComposite |= bWasFirstFrame; + bNeedsFullComposite |= pFrameInfo->useFSRLayer0; + bNeedsFullComposite |= pFrameInfo->useNISLayer0; + bNeedsFullComposite |= pFrameInfo->blurLayer0; + bNeedsFullComposite |= bNeedsCompositeFromFilter; + bNeedsFullComposite |= bDrewCursor; + bNeedsFullComposite |= g_bColorSliderInUse; + bNeedsFullComposite |= pFrameInfo->bFadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty(); + + if ( g_bOutputHDREnabled ) + { + bNeedsFullComposite |= g_bHDRItmEnable; + if ( !SupportsColorManagement() ) + bNeedsFullComposite |= ( pFrameInfo->layerCount > 1 || pFrameInfo->layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); + } + + bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); + + bool bDoComposite = true; + if ( !bNeedsFullComposite && !bWantsPartialComposite ) + { + int ret = drm_prepare( &g_DRM, bAsync, pFrameInfo ); + if ( ret == 0 ) + bDoComposite = false; + else if ( ret == -EACCES ) + return 0; + } + + // Update to let the vblank manager know we are currently compositing. + GetVBlankTimer().UpdateWasCompositing( bDoComposite ); + + if ( !bDoComposite ) + { + // Scanout + Planes Path + m_bWasPartialCompsiting = false; + m_bWasCompositing = false; + if ( pFrameInfo->layerCount == 2 ) + m_nLastSingleOverlayZPos = pFrameInfo->layers[1].zpos; + + return Commit( pFrameInfo ); + } + + // Composition Path + if ( kDisablePartialComposition ) + bNeedsFullComposite = true; + + FrameInfo_t compositeFrameInfo = *pFrameInfo; + + if ( compositeFrameInfo.layerCount == 1 ) + { + // If we failed to flip a single plane then + // we definitely need to composite for some reason... + bNeedsFullComposite = true; + } + + if ( !bNeedsFullComposite ) + { + // If we want to partial composite, fallback to full + // composite if we have mismatching colorspaces in our overlays. + // This is 2, and we do i-1 so 1...layerCount. So AFTER we have removed baseplane. + // Overlays only. + // + // Josh: + // We could handle mismatching colorspaces for partial composition + // but I want to keep overlay -> partial composition promotion as simple + // as possible, using the same 3D + SHAPER LUTs + BLEND in DRM + // as changing them is incredibly expensive!! It takes forever. + // We can't just point it to random BDA or whatever, it has to be uploaded slowly + // thru registers which is SUPER SLOW. + // This avoids stutter. + for ( int i = 2; i < compositeFrameInfo.layerCount; i++ ) + { + if ( pFrameInfo->layers[i - 1].colorspace != pFrameInfo->layers[i].colorspace ) + { + bNeedsFullComposite = true; + break; + } + } + } + + // If we ever promoted from partial -> full, for the first frame + // do NOT defer this partial composition. + // We were already stalling for the full composition before, so it's not an issue + // for latency, we just need to make sure we get 1 partial frame that isn't deferred + // in time so we don't lose layers. + bool bDefer = !bNeedsFullComposite && ( !m_bWasCompositing || m_bWasPartialCompsiting ); + + // If doing a partial composition then remove the baseplane + // from our frameinfo to composite. + if ( !bNeedsFullComposite ) + { + for ( int i = 1; i < compositeFrameInfo.layerCount; i++ ) + compositeFrameInfo.layers[i - 1] = compositeFrameInfo.layers[i]; + compositeFrameInfo.layerCount -= 1; + + // When doing partial composition, apply the shaper + 3D LUT stuff + // at scanout. + for ( uint32_t nEOTF = 0; nEOTF < EOTF_Count; nEOTF++ ) { + compositeFrameInfo.shaperLut[ nEOTF ] = nullptr; + compositeFrameInfo.lut3D[ nEOTF ] = nullptr; + } + } + + // If using composite debug markers, make sure we mark them as partial + // so we know! + if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) + g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; + + std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); + + m_bWasCompositing = true; + + g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; + + if ( !oCompositeResult ) + { + xwm_log.errorf("vulkan_composite failed"); + return -EINVAL; + } + + vulkan_wait( *oCompositeResult, true ); + + FrameInfo_t presentCompFrameInfo = {}; + + if ( bNeedsFullComposite ) + { + presentCompFrameInfo.applyOutputColorMgmt = false; + presentCompFrameInfo.layerCount = 1; + + FrameInfo_t::Layer_t *baseLayer = &presentCompFrameInfo.layers[ 0 ]; + baseLayer->scale.x = 1.0; + baseLayer->scale.y = 1.0; + baseLayer->opacity = 1.0; + baseLayer->zpos = g_zposBase; + + baseLayer->tex = vulkan_get_last_output_image( false, false ); + baseLayer->fbid = baseLayer->tex->fbid(); + baseLayer->applyColorMgmt = false; + + baseLayer->filter = GamescopeUpscaleFilter::NEAREST; + baseLayer->ctm = nullptr; + baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + m_bWasPartialCompsiting = false; + } + else + { + if ( m_bWasPartialCompsiting || !bDefer ) + { + presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; + presentCompFrameInfo.layerCount = 2; + + presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; + presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; + + FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; + overlayLayer->scale.x = 1.0; + overlayLayer->scale.y = 1.0; + overlayLayer->opacity = 1.0; + overlayLayer->zpos = g_zposOverlay; + + overlayLayer->tex = vulkan_get_last_output_image( true, bDefer ); + overlayLayer->fbid = overlayLayer->tex->fbid(); + overlayLayer->applyColorMgmt = g_ColorMgmt.pending.enabled; + + overlayLayer->filter = GamescopeUpscaleFilter::NEAREST; + // Partial composition stuff has the same colorspace. + // So read that from the composite frame info + overlayLayer->ctm = nullptr; + overlayLayer->colorspace = compositeFrameInfo.layers[0].colorspace; + } + else + { + // Use whatever overlay we had last while waiting for the + // partial composition to have anything queued. + presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; + presentCompFrameInfo.layerCount = 1; + + presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; + presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; + + const FrameInfo_t::Layer_t *lastPresentedOverlayLayer = nullptr; + for (int i = 0; i < pFrameInfo->layerCount; i++) + { + if ( pFrameInfo->layers[i].zpos == m_nLastSingleOverlayZPos ) + { + lastPresentedOverlayLayer = &pFrameInfo->layers[i]; + break; + } + } + + if ( lastPresentedOverlayLayer ) + { + FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; + *overlayLayer = *lastPresentedOverlayLayer; + overlayLayer->zpos = g_zposOverlay; + + presentCompFrameInfo.layerCount = 2; + } + } + + m_bWasPartialCompsiting = true; + } + + int ret = drm_prepare( &g_DRM, bAsync, &presentCompFrameInfo ); + + // Happens when we're VT-switched away + if ( ret == -EACCES ) + return 0; + + if ( ret != 0 ) + { + if ( g_DRM.current.mode_id == 0 ) + { + xwm_log.errorf("We failed our modeset and have no mode to fall back to! (Initial modeset failed?): %s", strerror(-ret)); + abort(); + } + + xwm_log.errorf("Failed to prepare 1-layer flip (%s), trying again with previous mode if modeset needed", strerror( -ret )); + + // Try once again to in case we need to fall back to another mode. + ret = drm_prepare( &g_DRM, bAsync, &compositeFrameInfo ); + + // Happens when we're VT-switched away + if ( ret == -EACCES ) + return 0; + + if ( ret != 0 ) + { + xwm_log.errorf("Failed to prepare 1-layer flip entirely: %s", strerror( -ret )); + // We should always handle a 1-layer flip, this used to abort, + // but lets be more friendly and just avoid a commit and try again later. + // Let's re-poll our state, and force grab the best connector again. + // + // Some intense connector hotplugging could be occuring and the + // connector could become destroyed before we had a chance to use it + // as we hadn't reffed it in a commit yet. + this->DirtyState( true, false ); + this->PollState(); + return ret; + } + } + + return Commit( &compositeFrameInfo ); + } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override + { + if ( bForceModeset ) + g_DRM.needs_modeset = true; + g_DRM.out_of_date = std::max( g_DRM.out_of_date, bForce ? 2 : 1 ); + g_DRM.paused = !wlsession_active(); + } + + virtual bool PollState() override + { + return drm_poll_state( &g_DRM ); + } + + virtual std::shared_ptr CreateBackendBlob( std::span data ) override + { + uint32_t uBlob = 0; + if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) + return nullptr; + + return std::make_shared( data, uBlob, true ); + } + + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override + { + return drm_fbid_from_dmabuf( &g_DRM, pBuffer, pDmaBuf ); + } + + virtual void LockBackendFb( uint32_t uFbId ) override + { + drm_lock_fbid( &g_DRM, uFbId ); + } + virtual void UnlockBackendFb( uint32_t uFbId ) override + { + drm_unlock_fbid( &g_DRM, uFbId ); + } + virtual void DropBackendFb( uint32_t uFbId ) override + { + drm_drop_fbid( &g_DRM, uFbId ); + } + + virtual bool UsesModifiers() const override + { + return true; + } + virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override + { + const wlr_drm_format *pFormat = wlr_drm_format_set_get( &g_DRM.formats, uDrmFormat ); + if ( !pFormat ) + return std::span{}; + + return std::span{ pFormat->modifiers, pFormat->modifiers + pFormat->len }; + } + + virtual IBackendConnector *GetCurrentConnector() override + { + return g_DRM.pConnector; + } + + virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override + { + if ( GetCurrentConnector() && GetCurrentConnector()->GetScreenType() == eScreenType ) + return GetCurrentConnector(); + + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + for ( auto &iter : g_DRM.connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + if ( pConnector->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return pConnector; + } + } - if (!blob) return nullptr; - } + } - return std::make_shared(ctm, blob); -} + 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(); + } -bool drm_supports_color_mgmt(struct drm_t *drm) -{ - if ( g_bForceDisableColorMgmt ) - return false; + virtual bool SupportsPlaneHardwareCursor() const override + { + return true; + } - if ( !drm->pPrimaryPlane ) - return false; + virtual bool SupportsTearing() const override + { + return g_bSupportsAsyncFlips; + } - return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); -} + virtual bool UsesVulkanSwapchain() const override + { + return false; + } -void drm_get_native_colorimetry( struct drm_t *drm, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) -{ - if ( !drm || !drm->pConnector ) - { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; - return; - } + virtual bool IsSessionBased() const override + { + return true; + } - *displayColorimetry = drm->pConnector->GetDisplayColorimetry(); - *displayEOTF = EOTF_Gamma22; + virtual bool IsVisible() const override + { + return !g_DRM.paused; + } - // For HDR10 output, expected content colorspace != native colorspace. - if ( g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ) - { - *outputEncodingColorimetry = displaycolorimetry_2020; - *outputEncodingEOTF = drm->pConnector->GetHDRInfo().eOutputEncodingEOTF; - } - else - { - *outputEncodingColorimetry = drm->pConnector->GetDisplayColorimetry(); - *outputEncodingEOTF = EOTF_Gamma22; - } -} + virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { + return glm::uvec2{ g_DRM.cursor_width, g_DRM.cursor_height }; + } + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { + return drm_set_refresh( &g_DRM, nRefresh ); + } -std::span drm_get_valid_refresh_rates( struct drm_t *drm ) -{ - if ( drm && drm->pConnector ) - return drm->pConnector->GetValidDynamicRefreshRates(); + virtual void HackUpdatePatchedEdid() override + { + if ( !GetCurrentConnector() ) + return; - return std::span{}; + WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo() ); + } + + protected: + + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override + { + if ( pBlob->GetBlobValue() ) + drmModeDestroyPropertyBlob( g_DRM.fd, pBlob->GetBlobValue() ); + } + + private: + bool m_bWasCompositing = false; + bool m_bWasPartialCompsiting = false; + int m_nLastSingleOverlayZPos = 0; + + uint32_t m_uNextPresentCtx = 0; + DRMPresentCtx m_PresentCtxs[3]; + + bool SupportsColorManagement() const + { + return drm_supports_color_mgmt( &g_DRM ); + } + + int Commit( const FrameInfo_t *pFrameInfo ) + { + drm_t *drm = &g_DRM; + int ret = 0; + + assert( drm->req != nullptr ); + assert( drm->fbids_queued.size() == 0 ); + + defer( if ( drm->req != nullptr ) { drmModeAtomicFree( drm->req ); drm->req = nullptr; } ); + + bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; + + if ( isPageFlip ) + { + drm->flip_lock.lock(); + + // Do it before the commit, as otherwise the pageflip handler could + // potentially beat us to the refcount checks. + for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) + { + struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); + assert( fb.held_refs ); + fb.n_refs++; + } + + drm->fbids_queued = drm->fbids_in_req; + } + + m_PresentFeedback.m_uQueuedPresents++; + + uint32_t uCurrentPresentCtx = m_uNextPresentCtx; + m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; + m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = m_PresentFeedback.m_uQueuedPresents; + + drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents); + gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents ); + + ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, &m_PresentCtxs[uCurrentPresentCtx] ); + if ( ret != 0 ) + { + drm_log.errorf_errno( "flip error" ); + + if ( ret != -EBUSY && ret != -EACCES ) + { + drm_log.errorf( "fatal flip error, aborting" ); + if ( isPageFlip ) + drm->flip_lock.unlock(); + abort(); + } + + drm->pending = drm->current; + + for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) + { + for ( std::optional &oProperty : pCRTC->GetProperties() ) + { + if ( oProperty ) + oProperty->Rollback(); + } + } + + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + for ( std::optional &oProperty : pPlane->GetProperties() ) + { + if ( oProperty ) + oProperty->Rollback(); + } + } + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + for ( std::optional &oProperty : pConnector->GetProperties() ) + { + if ( oProperty ) + oProperty->Rollback(); + } + } + + // Undo refcount if the commit didn't actually work + for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) + { + get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; + } + + drm->fbids_queued.clear(); + + m_PresentFeedback.m_uQueuedPresents--; + + if ( isPageFlip ) + drm->flip_lock.unlock(); + + return ret; + } else { + drm->fbids_in_req.clear(); + + drm->current = drm->pending; + + for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) + { + for ( std::optional &oProperty : pCRTC->GetProperties() ) + { + if ( oProperty ) + oProperty->OnCommit(); + } + } + + for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) + { + for ( std::optional &oProperty : pPlane->GetProperties() ) + { + if ( oProperty ) + oProperty->OnCommit(); + } + } + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + for ( std::optional &oProperty : pConnector->GetProperties() ) + { + if ( oProperty ) + oProperty->OnCommit(); + } + } + } + + // Update the draw time + // Ideally this would be updated by something right before the page flip + // is queued and would end up being the new page flip, rather than here. + // However, the page flip handler is called when the page flip occurs, + // not when it is successfully queued. + GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + if ( isPageFlip ) + { + // Wait for flip handler to unlock + drm->flip_lock.lock(); + drm->flip_lock.unlock(); + } + + return ret; + } + + }; + + ///////////////////////// + // Backend Instantiator + ///////////////////////// + + template <> + bool IBackend::Set() + { + return Set( new CDRMBackend{} ); + } } diff --git a/src/drm.hpp b/src/drm.hpp deleted file mode 100644 index 79c88050a..000000000 --- a/src/drm.hpp +++ /dev/null @@ -1,526 +0,0 @@ -#pragma once - -#include "drm_include.h" -#include "color_helpers.h" -#include "gamescope_shared.h" -#include "rendervulkan.hpp" - -#include "wlr_begin.hpp" -#include -#include -#include -#include "wlr_end.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern struct drm_t g_DRM; -void drm_destroy_blob(struct drm_t *drm, uint32_t blob); - -class drm_blob -{ -public: - drm_blob() : blob( 0 ), owned( false ) - { - } - - drm_blob(uint32_t blob, bool owned = true) - : blob( blob ), owned( owned ) - { - } - - ~drm_blob() - { - if (blob && owned) - drm_destroy_blob( &g_DRM, blob ); - } - - // No copy constructor, because we can't duplicate the blob handle. - drm_blob(const drm_blob&) = delete; - drm_blob& operator=(const drm_blob&) = delete; - // No move constructor, because we use shared_ptr anyway, but can be added if necessary. - drm_blob(drm_blob&&) = delete; - drm_blob& operator=(drm_blob&&) = delete; - - uint32_t blob; - bool owned; -}; - -struct wlserver_hdr_metadata : drm_blob -{ - wlserver_hdr_metadata() - { - } - - wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) - : drm_blob( blob, owned ) - { - if (_metadata) - this->metadata = *_metadata; - } - - hdr_output_metadata metadata = {}; -}; - -struct wlserver_ctm : drm_blob -{ - wlserver_ctm() - { - } - - wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) - : drm_blob( blob, owned ), matrix( ctm ) - { - } - - glm::mat3x4 matrix{}; -}; -namespace gamescope -{ - template - using CAutoDeletePtr = std::unique_ptr; - - //////////////////////////////////////// - // DRM Object Wrappers + State Trackers - //////////////////////////////////////// - struct DRMObjectRawProperty - { - uint32_t uPropertyId = 0ul; - uint64_t ulValue = 0ul; - }; - using DRMObjectRawProperties = std::unordered_map; - - class CDRMAtomicObject - { - public: - CDRMAtomicObject( uint32_t ulObjectId ); - uint32_t GetObjectId() const { return m_ulObjectId; } - - // No copy or move constructors. - CDRMAtomicObject( const CDRMAtomicObject& ) = delete; - CDRMAtomicObject& operator=( const CDRMAtomicObject& ) = delete; - - CDRMAtomicObject( CDRMAtomicObject&& ) = delete; - CDRMAtomicObject& operator=( CDRMAtomicObject&& ) = delete; - protected: - uint32_t m_ulObjectId = 0ul; - }; - - template < uint32_t DRMObjectType > - class CDRMAtomicTypedObject : public CDRMAtomicObject - { - public: - CDRMAtomicTypedObject( uint32_t ulObjectId ); - protected: - std::optional GetRawProperties(); - }; - - class CDRMAtomicProperty - { - public: - CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); - - static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); - - uint64_t GetPendingValue() const { return m_ulPendingValue; } - uint64_t GetCurrentValue() const { return m_ulCurrentValue; } - int SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); - - void OnCommit(); - void Rollback(); - private: - CDRMAtomicObject *m_pObject = nullptr; - uint32_t m_uPropertyId = 0u; - - uint64_t m_ulPendingValue = 0ul; - uint64_t m_ulCurrentValue = 0ul; - uint64_t m_ulInitialValue = 0ul; - }; - - class CDRMPlane final : public CDRMAtomicTypedObject - { - public: - // Takes ownership of pPlane. - CDRMPlane( drmModePlane *pPlane ); - - void RefreshState(); - - drmModePlane *GetModePlane() const { return m_pPlane.get(); } - - struct PlaneProperties - { - std::optional *begin() { return &FB_ID; } - std::optional *end() { return &DUMMY_END; } - - std::optional type; // Immutable - std::optional IN_FORMATS; // Immutable - - std::optional FB_ID; - std::optional CRTC_ID; - std::optional SRC_X; - std::optional SRC_Y; - std::optional SRC_W; - std::optional SRC_H; - std::optional CRTC_X; - std::optional CRTC_Y; - std::optional CRTC_W; - std::optional CRTC_H; - std::optional zpos; - std::optional alpha; - std::optional rotation; - std::optional COLOR_ENCODING; - std::optional COLOR_RANGE; - std::optional VALVE1_PLANE_DEGAMMA_TF; - std::optional VALVE1_PLANE_DEGAMMA_LUT; - std::optional VALVE1_PLANE_CTM; - std::optional VALVE1_PLANE_HDR_MULT; - std::optional VALVE1_PLANE_SHAPER_LUT; - std::optional VALVE1_PLANE_SHAPER_TF; - std::optional VALVE1_PLANE_LUT3D; - std::optional VALVE1_PLANE_BLEND_TF; - std::optional VALVE1_PLANE_BLEND_LUT; - std::optional DUMMY_END; - }; - PlaneProperties &GetProperties() { return m_Props; } - const PlaneProperties &GetProperties() const { return m_Props; } - private: - CAutoDeletePtr m_pPlane; - PlaneProperties m_Props; - }; - - class CDRMCRTC final : public CDRMAtomicTypedObject - { - public: - // Takes ownership of pCRTC. - CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ); - - void RefreshState(); - uint32_t GetCRTCMask() const { return m_uCRTCMask; } - - struct CRTCProperties - { - std::optional *begin() { return &ACTIVE; } - std::optional *end() { return &DUMMY_END; } - - std::optional ACTIVE; - std::optional MODE_ID; - std::optional GAMMA_LUT; - std::optional DEGAMMA_LUT; - std::optional CTM; - std::optional VRR_ENABLED; - std::optional OUT_FENCE_PTR; - std::optional VALVE1_CRTC_REGAMMA_TF; - std::optional DUMMY_END; - }; - CRTCProperties &GetProperties() { return m_Props; } - const CRTCProperties &GetProperties() const { return m_Props; } - private: - CAutoDeletePtr m_pCRTC; - uint32_t m_uCRTCMask = 0u; - CRTCProperties m_Props; - }; - - class CDRMConnector final : public CDRMAtomicTypedObject - { - public: - CDRMConnector( drmModeConnector *pConnector ); - - void RefreshState(); - - struct ConnectorProperties - { - std::optional *begin() { return &CRTC_ID; } - std::optional *end() { return &DUMMY_END; } - - std::optional CRTC_ID; - std::optional Colorspace; - std::optional content_type; // "content type" with space! - std::optional panel_orientation; // "panel orientation" with space! - std::optional HDR_OUTPUT_METADATA; - std::optional vrr_capable; - std::optional EDID; - std::optional DUMMY_END; - }; - ConnectorProperties &GetProperties() { return m_Props; } - const ConnectorProperties &GetProperties() const { return m_Props; } - - struct HDRInfo - { - // We still want to set up HDR info for Steam Deck LCD with some good - // target/mapping values for the display brightness for undocking from a HDR display, - // but don't want to expose HDR there as it is not good. - bool bExposeHDRSupport = false; - - // The output encoding to use for HDR output. - // For typical HDR10 displays, this will be PQ. - // For displays doing "traditional HDR" such as Steam Deck OLED, this is Gamma 2.2. - EOTF eOutputEncodingEOTF = EOTF_Gamma22; - - uint16_t uMaxContentLightLevel = 500; // Nits - uint16_t uMaxFrameAverageLuminance = 500; // Nits - uint16_t uMinContentLightLevel = 0; // Nits / 10000 - std::shared_ptr pDefaultMetadataBlob; - - bool IsHDRG22() const - { - return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; - } - - bool ShouldPatchEDID() const - { - return IsHDRG22(); - } - - bool IsHDR10() const - { - // PQ output encoding is always HDR10 (PQ + 2020) for us. - // If that assumption changes, update me. - return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; - } - }; - - drmModeConnector *GetModeConnector() { return m_pConnector.get(); } - const char *GetName() const { return m_Mutable.szName; } - const char *GetMake() const { return m_Mutable.pszMake; } - const char *GetModel() const { return m_Mutable.szModel; } - uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } - const HDRInfo &GetHDRInfo() const { return m_Mutable.HDR; } - std::span GetValidDynamicRefreshRates() const { return m_Mutable.ValidDynamicRefreshRates; } - GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } - GamescopeScreenType GetScreenType() const - { - if ( m_pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || - m_pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || - m_pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) - return GAMESCOPE_SCREEN_TYPE_INTERNAL; - - return GAMESCOPE_SCREEN_TYPE_EXTERNAL; - } - bool IsVRRCapable() const - { - return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); - } - const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } - - const std::vector &GetRawEDID() const { return m_Mutable.EdidData; } - - // TODO: Remove - void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } - int GetBaseRefresh() const { return m_nBaseRefresh; } - - bool SupportsHDR10() const - { - return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); - } - - bool SupportsHDRG22() const - { - return GetHDRInfo().IsHDRG22(); - } - - bool SupportsHDR() const - { - return SupportsHDR10() || SupportsHDRG22(); - } - private: - void ParseEDID(); - - static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); - - CAutoDeletePtr m_pConnector; - - struct MutableConnectorState - { - int nDefaultRefresh = 0; - - uint32_t uPossibleCRTCMask = 0u; - char szName[32]{}; - char szMakePNP[4]{}; - char szModel[16]{}; - const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. - GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; - std::span ValidDynamicRefreshRates{}; - std::vector EdidData; // Raw, unmodified. - - displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; - HDRInfo HDR; - } m_Mutable; - - // TODO: Remove - int m_nBaseRefresh = 0; - - ConnectorProperties m_Props; - }; -} - -struct saved_mode { - int width; - int height; - int refresh; -}; - -struct fb { - uint32_t id; - /* Client buffer, if any */ - struct wlr_buffer *buf; - /* A FB is held if it's being used by steamcompmgr - * doesn't need to be atomic as it's only ever - * modified/read from the steamcompmgr thread */ - int held_refs; - /* Number of page-flips using the FB */ - std::atomic< uint32_t > n_refs; -}; - -struct drm_t { - bool bUseLiftoff; - - int fd; - - 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; - - std::map< uint32_t, drmModePropertyRes * > props; - - gamescope::CDRMPlane *pPrimaryPlane; - gamescope::CDRMCRTC *pCRTC; - gamescope::CDRMConnector *pConnector; - int kms_in_fence_fd; - int kms_out_fence_fd; - - 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 { - std::shared_ptr mode_id; - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; - drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; - } current, pending; - - /* FBs in the atomic request, but not yet submitted to KMS */ - std::vector < uint32_t > fbids_in_req; - /* FBs submitted to KMS, but not yet displayed on screen */ - std::vector < uint32_t > fbids_queued; - /* FBs currently on screen */ - std::vector < uint32_t > fbids_on_screen; - - std::unordered_map< uint32_t, struct fb > fb_map; - std::mutex fb_map_mutex; - - std::mutex free_queue_lock; - std::vector< uint32_t > fbid_unlock_queue; - std::vector< uint32_t > fbid_free_queue; - - std::mutex flip_lock; - - std::atomic < uint64_t > flipcount; - - std::atomic < bool > paused; - std::atomic < int > out_of_date; - std::atomic < bool > needs_modeset; - - std::unordered_map< std::string, int > connector_priorities; - - bool force_internal = false; - - char *device_name = nullptr; -}; - -extern struct drm_t g_DRM; - -extern uint32_t g_nDRMFormat; -extern uint32_t g_nDRMFormatOverlay; - -extern bool g_bRotated; -extern bool g_bFlipped; -extern bool g_bDebugLayers; -extern const char *g_sOutputName; - -enum g_panel_orientation { - PANEL_ORIENTATION_0, /* NORMAL */ - PANEL_ORIENTATION_270, /* RIGHT */ - PANEL_ORIENTATION_90, /* LEFT */ - PANEL_ORIENTATION_180, /* UPSIDE DOWN */ - PANEL_ORIENTATION_AUTO, -}; - -enum drm_panel_orientation { - DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, - DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, - DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, - DRM_MODE_PANEL_ORIENTATION_LEFT_UP, - DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, -}; - -extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; -extern enum g_panel_orientation g_drmModeOrientation; - -extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* - -extern bool g_bForceDisableColorMgmt; - -bool init_drm(struct drm_t *drm, int width, int height, int refresh); -void finish_drm(struct drm_t *drm); -int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); -int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); -bool drm_poll_state(struct drm_t *drm); -uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); -void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); -void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); -void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); -bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); -bool drm_set_refresh( struct drm_t *drm, int refresh ); -bool drm_set_resolution( struct drm_t *drm, int width, int height ); -gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); - -char *find_drm_node_by_devid(dev_t devid); -int drm_get_default_refresh(struct drm_t *drm); -bool drm_get_vrr_capable(struct drm_t *drm); -bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); -bool drm_get_vrr_in_use(struct drm_t *drm); -bool drm_supports_color_mgmt(struct drm_t *drm); -std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata); -std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm); -void drm_destroy_blob(struct drm_t *drm, uint32_t blob); - -const char *drm_get_connector_name(struct drm_t *drm); -const char *drm_get_device_name(struct drm_t *drm); - -std::pair drm_get_connector_identifier(struct drm_t *drm); - -void drm_get_native_colorimetry( struct drm_t *drm, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); - -std::span drm_get_valid_refresh_rates( struct drm_t *drm ); - -extern bool g_bSupportsAsyncFlips; - -const char* drm_get_patched_edid_path(); -void drm_update_patched_edid(drm_t *drm); - -void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm); diff --git a/src/drm_include.h b/src/drm_include.h index cf4a7cb5c..500c3040a 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -5,6 +5,13 @@ #include #include +#include "wlr_begin.hpp" +#include +#include +#include "wlr_end.hpp" + +#include "hdmi.h" + // Josh: Okay whatever, this header isn't // available for whatever stupid reason. :v //#include @@ -36,11 +43,13 @@ enum drm_valve1_transfer_function { DRM_VALVE1_TRANSFER_FUNCTION_MAX, }; -/* from CTA-861-G */ -#define HDMI_EOTF_SDR 0 -#define HDMI_EOTF_TRADITIONAL_HDR 1 -#define HDMI_EOTF_ST2084 2 -#define HDMI_EOTF_HLG 3 +enum drm_panel_orientation { + DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, + DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, + DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, + DRM_MODE_PANEL_ORIENTATION_LEFT_UP, + DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, +}; /* For Default case, driver will set the colorspace */ #define DRM_MODE_COLORIMETRY_DEFAULT 0 diff --git a/src/edid.cpp b/src/edid.cpp new file mode 100644 index 000000000..3f499fc98 --- /dev/null +++ b/src/edid.cpp @@ -0,0 +1,296 @@ +#include "edid.h" + +#include "backend.h" +#include "log.hpp" +#include "hdmi.h" + +#include +#include +#include + +extern "C" +{ +#include "libdisplay-info/info.h" +#include "libdisplay-info/edid.h" +#include "libdisplay-info/cta.h" +} + +extern bool g_bRotated; + +static LogScope edid_log("josh edid"); + +namespace gamescope +{ + static constexpr uint32_t EDID_MAX_BLOCK_COUNT = 256; + static constexpr uint32_t EDID_BLOCK_SIZE = 128; + static constexpr uint32_t EDID_MAX_STANDARD_TIMING_COUNT = 8; + static constexpr uint32_t EDID_BYTE_DESCRIPTOR_COUNT = 4; + static constexpr uint32_t EDID_BYTE_DESCRIPTOR_SIZE = 18; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT = 6; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT = 2; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT = 44; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT = 4; + + static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) + { + size_t n; + uint8_t bitmask; + + assert(high <= 7 && high >= low); + + n = high - low + 1; + bitmask = (uint8_t) ((1 << n) - 1); + return (uint8_t) (val >> low) & bitmask; + } + + static inline void set_bit_range(uint8_t *val, size_t high, size_t low, uint8_t bits) + { + size_t n; + uint8_t bitmask; + + assert(high <= 7 && high >= low); + + n = high - low + 1; + bitmask = (uint8_t) ((1 << n) - 1); + assert((bits & ~bitmask) == 0); + + *val |= (uint8_t)(bits << low); + } + + + static inline void patch_edid_checksum(uint8_t* block) + { + uint8_t sum = 0; + for (uint32_t i = 0; i < EDID_BLOCK_SIZE - 1; i++) + sum += block[i]; + + uint8_t checksum = uint32_t(256) - uint32_t(sum); + + block[127] = checksum; + } + + static bool validate_block_checksum(const uint8_t* data) + { + uint8_t sum = 0; + size_t i; + + for (i = 0; i < EDID_BLOCK_SIZE; i++) { + sum += data[i]; + } + + return sum == 0; + } + + static uint8_t encode_max_luminance(float nits) + { + if (nits == 0.0f) + return 0; + + return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); + } + + std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) + { + // A zero length indicates that the edid parsing failed. + if ( pEdid.empty() ) + return std::nullopt; + + std::vector edid( pEdid.begin(), pEdid.end() ); + + if ( g_bRotated ) + { + // Patch width, height. + edid_log.infof("Patching dims %ux%u -> %ux%u", edid[0x15], edid[0x16], edid[0x16], edid[0x15]); + std::swap(edid[0x15], edid[0x16]); + + for (uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) + { + uint8_t *byte_desc_data = &edid[0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE]; + if (byte_desc_data[0] || byte_desc_data[1]) + { + uint32_t horiz = (get_bit_range(byte_desc_data[4], 7, 4) << 8) | byte_desc_data[2]; + uint32_t vert = (get_bit_range(byte_desc_data[7], 7, 4) << 8) | byte_desc_data[5]; + edid_log.infof("Patching res %ux%u -> %ux%u", horiz, vert, vert, horiz); + std::swap(byte_desc_data[4], byte_desc_data[7]); + std::swap(byte_desc_data[2], byte_desc_data[5]); + break; + } + } + + patch_edid_checksum(&edid[0]); + } + + // If we are debugging HDR support lazily on a regular Deck, + // just hotpatch the edid for the game so we get values we want as if we had + // an external display attached. + // (Allows for debugging undocked fallback without undocking/redocking) + if ( !hdrInfo.ShouldPatchEDID() ) + return std::nullopt; + + // TODO: Allow for override of min luminance +#if 0 + float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? + g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : + g_ColorMgmt.pending.flInternalDisplayBrightness; +#endif + // TODO(JoshA): Need to resolve flInternalDisplayBrightness vs new connector hdrinfo mechanism. + + edid_log.infof("[edid] Patching HDR static metadata:\n" + " - Max peak luminance = %u nits\n" + " - Max frame average luminance = %u nits", + hdrInfo.uMaxContentLightLevel, hdrInfo.uMaxFrameAverageLuminance ); + const uint8_t new_hdr_static_metadata_block[] + { + (1 << HDMI_EOTF_SDR) | (1 << HDMI_EOTF_TRADITIONAL_HDR) | (1 << HDMI_EOTF_ST2084), /* supported eotfs */ + 1, /* type 1 */ + encode_max_luminance( float( hdrInfo.uMaxContentLightLevel ) ), /* desired content max peak luminance */ + encode_max_luminance( float( hdrInfo.uMaxFrameAverageLuminance ) ), /* desired content max frame avg luminance */ + 0, /* desired content min luminance -- 0 is technically "undefined" */ + }; + + int ext_count = int(edid.size() / EDID_BLOCK_SIZE) - 1; + assert(ext_count == edid[0x7E]); + bool has_cta_block = false; + bool has_hdr_metadata_block = false; + + for (int i = 0; i < ext_count; i++) + { + uint8_t *ext_data = &edid[EDID_BLOCK_SIZE + i * EDID_BLOCK_SIZE]; + uint8_t tag = ext_data[0]; + if (tag == DI_EDID_EXT_CEA) + { + has_cta_block = true; + uint8_t dtd_start = ext_data[2]; + uint8_t flags = ext_data[3]; + if (dtd_start == 0) + { + edid_log.infof("Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); + continue; + } + if (flags != 0) + { + edid_log.infof("Hmmmm.... non-zero CTA flags. Interesting... Not going further! :-("); + continue; + } + + const int CTA_HEADER_SIZE = 4; + int j = CTA_HEADER_SIZE; + while (j < dtd_start) + { + uint8_t data_block_header = ext_data[j]; + uint8_t data_block_tag = get_bit_range(data_block_header, 7, 5); + uint8_t data_block_size = get_bit_range(data_block_header, 4, 0); + + if (j + 1 + data_block_size > dtd_start) + { + edid_log.infof("Hmmmm.... CTA malformatted. Interesting... Not going further! :-("); + break; + } + + uint8_t *data_block = &ext_data[j + 1]; + if (data_block_tag == 7) // extended + { + uint8_t extended_tag = data_block[0]; + uint8_t *extended_block = &data_block[1]; + uint8_t extended_block_size = data_block_size - 1; + + if (extended_tag == 6) // hdr static + { + if (extended_block_size >= sizeof(new_hdr_static_metadata_block)) + { + edid_log.infof("Patching existing HDR Metadata with our own!"); + memcpy(extended_block, new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); + has_hdr_metadata_block = true; + } + } + } + + j += 1 + data_block_size; // account for header size. + } + + if (!has_hdr_metadata_block) + { + const int hdr_metadata_block_size_plus_headers = sizeof(new_hdr_static_metadata_block) + 2; // +1 for header, +1 for extended header -> +2 + edid_log.infof("No HDR metadata block to patch... Trying to insert one."); + + // Assert that the end of the data blocks == dtd_start + if (dtd_start != j) + { + edid_log.infof("dtd_start != end of blocks. Giving up patching. I'm too scared to attempt it."); + } + + // Move back the dtd to make way for our block at the end. + uint8_t *dtd = &ext_data[dtd_start]; + memmove(dtd + hdr_metadata_block_size_plus_headers, dtd, hdr_metadata_block_size_plus_headers); + dtd_start += hdr_metadata_block_size_plus_headers; + + // Data block is where the dtd was. + uint8_t *data_block = dtd; + + // header + data_block[0] = 0; + set_bit_range(&data_block[0], 7, 5, 7); // extended tag + set_bit_range(&data_block[0], 4, 0, sizeof(new_hdr_static_metadata_block) + 1); // size (+1 for extended header, does not include normal header) + + // extended header + data_block[1] = 6; // hdr metadata extended tag + memcpy(&data_block[2], new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); + } + + patch_edid_checksum(ext_data); + bool sum_valid = validate_block_checksum(ext_data); + edid_log.infof("CTA Checksum valid? %s", sum_valid ? "Y" : "N"); + } + } + + if (!has_cta_block) + { + edid_log.infof("Couldn't patch for HDR metadata as we had no CTA block! Womp womp =c"); + } + + bool sum_valid = validate_block_checksum(&edid[0]); + edid_log.infof("BASE Checksum valid? %s", sum_valid ? "Y" : "N"); + + return edid; + } + + const char *GetPatchedEdidPath() + { + const char *pszPatchedEdidPath = getenv( "GAMESCOPE_PATCHED_EDID_FILE" ); + if ( !pszPatchedEdidPath || !*pszPatchedEdidPath ) + return nullptr; + + return pszPatchedEdidPath; + } + + void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) + { + const char *pszPatchedEdidPath = GetPatchedEdidPath(); + if ( !pszPatchedEdidPath ) + return; + + std::span pEdidToWrite = pEdid; + + auto oPatchedEdid = PatchEdid( pEdid, hdrInfo ); + if ( oPatchedEdid ) + pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; + + char szTmpFilename[PATH_MAX]; + snprintf( szTmpFilename, sizeof( szTmpFilename ), "%s.tmp", pszPatchedEdidPath ); + + FILE *pFile = fopen( szTmpFilename, "wb" ); + if ( !pFile ) + { + edid_log.errorf( "Couldn't open file: %s", szTmpFilename ); + return; + } + + fwrite( pEdidToWrite.data(), 1, pEdidToWrite.size(), pFile ); + fflush( pFile ); + fclose( pFile ); + + // Flip it over. + rename( szTmpFilename, pszPatchedEdidPath ); + edid_log.infof( "Wrote new edid to: %s", pszPatchedEdidPath ); + } +} diff --git a/src/edid.h b/src/edid.h new file mode 100644 index 000000000..e6ab74688 --- /dev/null +++ b/src/edid.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include + +namespace gamescope +{ + struct BackendConnectorHDRInfo; + + const char *GetPatchedEdidPath(); + void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); + + std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); +} \ No newline at end of file diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h index fdbcfa481..f34174e59 100644 --- a/src/gamescope_shared.h +++ b/src/gamescope_shared.h @@ -2,6 +2,8 @@ namespace gamescope { + class BackendBlob; + enum GamescopeKnownDisplays { GAMESCOPE_KNOWN_DISPLAY_UNKNOWN, @@ -41,3 +43,25 @@ inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; } +enum GamescopeSelection +{ + GAMESCOPE_SELECTION_CLIPBOARD, + GAMESCOPE_SELECTION_PRIMARY, + + GAMESCOPE_SELECTION_COUNT, +}; + +enum GamescopePanelOrientation +{ + GAMESCOPE_PANEL_ORIENTATION_0, // normal + GAMESCOPE_PANEL_ORIENTATION_270, // right + GAMESCOPE_PANEL_ORIENTATION_90, // left + GAMESCOPE_PANEL_ORIENTATION_180, // upside down + + GAMESCOPE_PANEL_ORIENTATION_AUTO, +}; + +// Disable partial composition for now until we get +// composite priorities working in libliftoff + also +// use the proper libliftoff composite plane system. +static constexpr bool kDisablePartialComposition = true; diff --git a/src/hdmi.h b/src/hdmi.h new file mode 100644 index 000000000..57d5257e2 --- /dev/null +++ b/src/hdmi.h @@ -0,0 +1,7 @@ +#pragma once + +/* from CTA-861-G */ +#define HDMI_EOTF_SDR 0 +#define HDMI_EOTF_TRADITIONAL_HDR 1 +#define HDMI_EOTF_ST2084 2 +#define HDMI_EOTF_HLG 3 diff --git a/src/headless.cpp b/src/headless.cpp new file mode 100644 index 000000000..493aea13e --- /dev/null +++ b/src/headless.cpp @@ -0,0 +1,248 @@ +#include "backend.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 + { + 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + *pOverlayPlaneFormat = 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( std::span data ) override + { + return std::make_shared( data ); + } + + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override + { + return 0; + } + + virtual void LockBackendFb( uint32_t uFbId ) override + { + abort(); + } + virtual void UnlockBackendFb( uint32_t uFbId ) override + { + abort(); + } + virtual void DropBackendFb( uint32_t uFbId ) override + { + abort(); + } + + 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 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 + ///////////////////////// + + template <> + bool IBackend::Set() + { + return Set( new CHeadlessBackend{} ); + } + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 02cc5ce3a..42110675d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,15 +20,11 @@ #include "main.hpp" #include "steamcompmgr.hpp" -#include "drm.hpp" #include "rendervulkan.hpp" -#include "sdlwindow.hpp" #include "wlserver.hpp" #include "gpuvis_trace_utils.h" -#if HAVE_OPENVR -#include "vr_session.hpp" -#endif +#include "backends.h" #if HAVE_PIPEWIRE #include "pipewire.hpp" @@ -267,9 +263,6 @@ bool g_bOutputHDREnabled = false; bool g_bFullscreen = false; bool g_bForceRelativeMouse = false; -bool g_bIsNested = false; -bool g_bHeadless = false; - bool g_bGrabbed = false; float g_mouseSensitivity = 1.0; @@ -281,6 +274,8 @@ GamescopeUpscaleFilter g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; GamescopeUpscaleScaler g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; int g_upscaleFilterSharpness = 2; +gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; + bool g_bBorderlessOutputWindow = false; int g_nXWaylandCount = 1; @@ -300,36 +295,6 @@ uint32_t g_preferDeviceID = 0; pthread_t g_mainThread; -bool BIsNested() -{ - return g_bIsNested; -} - -bool BIsHeadless() -{ - return g_bHeadless; -} - -#if HAVE_OPENVR -bool g_bUseOpenVR = false; -bool BIsVRSession( void ) -{ - return g_bUseOpenVR; -} -#else -bool BIsVRSession( void ) -{ - return false; -} -#endif - -bool BIsSDLSession( void ) -{ - return g_bIsNested && !g_bHeadless && !BIsVRSession(); -} - - -static bool initOutput(int preferredWidth, int preferredHeight, int preferredRefresh); static void steamCompMgrThreadRun(int argc, char **argv); static std::string build_optstring(const struct option *options) @@ -367,16 +332,17 @@ static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const } } -static enum g_panel_orientation force_orientation(const char *str) +GamescopePanelOrientation g_DesiredInternalOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; +static GamescopePanelOrientation force_orientation(const char *str) { if (strcmp(str, "normal") == 0) { - return PANEL_ORIENTATION_0; + return GAMESCOPE_PANEL_ORIENTATION_0; } else if (strcmp(str, "right") == 0) { - return PANEL_ORIENTATION_270; + return GAMESCOPE_PANEL_ORIENTATION_270; } else if (strcmp(str, "left") == 0) { - return PANEL_ORIENTATION_90; + return GAMESCOPE_PANEL_ORIENTATION_90; } else if (strcmp(str, "upsidedown") == 0) { - return PANEL_ORIENTATION_180; + return GAMESCOPE_PANEL_ORIENTATION_180; } else { fprintf( stderr, "gamescope: invalid value for --force-orientation\n" ); exit(1); @@ -545,19 +511,62 @@ static bool CheckWaylandPresentationTime() return g_bSupportsWaylandPresentationTime; } +#if 0 +static bool IsInDebugSession() +{ + static FILE *fp; + if ( !fp ) + { + fp = fopen( "/proc/self/status", "r" ); + } + + char rgchLine[256]; rgchLine[0] = '\0'; + int nTracePid = 0; + if ( fp ) + { + const char *pszSearchString = "TracerPid:"; + const uint cchSearchString = strlen( pszSearchString ); + rewind( fp ); + fflush( fp ); + while ( fgets( rgchLine, sizeof(rgchLine), fp ) ) + { + if ( !strncasecmp( pszSearchString, rgchLine, cchSearchString ) ) + { + char *pszVal = rgchLine+cchSearchString+1; + nTracePid = atoi( pszVal ); + break; + } + } + } + return nTracePid != 0; +} +#endif int g_nPreferredOutputWidth = 0; int g_nPreferredOutputHeight = 0; bool g_bExposeWayland = false; +const char *g_sOutputName = nullptr; +bool g_bDebugLayers = false; +bool g_bForceDisableColorMgmt = false; + +// This will go away when we remove the getopt stuff from vr session. +// For now... +int g_argc; +char **g_argv; int main(int argc, char **argv) { + g_argc = argc; + g_argv = argv; + // Force disable this horrible broken layer. setenv("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1", "1", 1); static std::string optstring = build_optstring(gamescope_options); gamescope_optstring = optstring.c_str(); + gamescope::GamescopeBackend eCurrentBackend = gamescope::GamescopeBackend::Auto; + int o; int opt_index = -1; while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) @@ -628,7 +637,7 @@ int main(int argc, char **argv) } else if (strcmp(opt_name, "generate-drm-mode") == 0) { g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg ); } else if (strcmp(opt_name, "force-orientation") == 0) { - g_drmModeOrientation = force_orientation( optarg ); + g_DesiredInternalOrientation = force_orientation( optarg ); } else if (strcmp(opt_name, "sharpness") == 0 || strcmp(opt_name, "fsr-sharpness") == 0) { g_upscaleFilterSharpness = atoi( optarg ); @@ -651,15 +660,13 @@ int main(int argc, char **argv) } else if (strcmp(opt_name, "expose-wayland") == 0) { g_bExposeWayland = true; } else if (strcmp(opt_name, "headless") == 0) { - g_bHeadless = true; - g_bIsNested = true; + eCurrentBackend = gamescope::GamescopeBackend::Headless; } else if (strcmp(opt_name, "cursor-scale-height") == 0) { g_nCursorScaleHeight = atoi(optarg); } #if HAVE_OPENVR else if (strcmp(opt_name, "openvr") == 0) { - g_bUseOpenVR = true; - g_bIsNested = true; + eCurrentBackend = gamescope::GamescopeBackend::OpenVR; } #endif break; @@ -722,6 +729,13 @@ int main(int argc, char **argv) setenv( "XCURSOR_SIZE", "256", 1 ); } +#if 0 + while( !IsInDebugSession() ) + { + usleep( 100 ); + } +#endif + raise_fd_limit(); if ( gpuvis_trace_init() != -1 ) @@ -734,9 +748,16 @@ int main(int argc, char **argv) g_pOriginalDisplay = getenv("DISPLAY"); g_pOriginalWaylandDisplay = getenv("WAYLAND_DISPLAY"); - g_bIsNested = g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL; - if ( BIsSDLSession() && g_pOriginalWaylandDisplay != NULL ) + if ( eCurrentBackend == gamescope::GamescopeBackend::Auto ) + { + if ( g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL ) + eCurrentBackend = gamescope::GamescopeBackend::SDL; + else + eCurrentBackend = gamescope::GamescopeBackend::DRM; + } + + if ( g_pOriginalWaylandDisplay != NULL ) { if (CheckWaylandPresentationTime()) { @@ -755,40 +776,29 @@ int main(int argc, char **argv) } } - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - return 1; - } - - if ( BIsSDLSession() ) + switch ( eCurrentBackend ) { - if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) - { - fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); - return 1; - } - } - - if ( !BIsNested() ) - { - g_bForceRelativeMouse = false; - } - + case gamescope::GamescopeBackend::DRM: + gamescope::IBackend::Set(); + break; + case gamescope::GamescopeBackend::SDL: + gamescope::IBackend::Set(); + break; #if HAVE_OPENVR - if ( BIsVRSession() ) - { - if ( !vr_init( argc, argv ) ) - { - fprintf( stderr, "Failed to initialize OpenVR runtime\n" ); - return 1; - } - } + case gamescope::GamescopeBackend::OpenVR: + gamescope::IBackend::Set(); + break; #endif + case gamescope::GamescopeBackend::Headless: + gamescope::IBackend::Set(); + break; + default: + abort(); + } - if ( !initOutput( g_nPreferredOutputWidth, g_nPreferredOutputHeight, g_nNestedRefresh ) ) + if ( !GetBackend() ) { - fprintf( stderr, "Failed to initialize output\n" ); + fprintf( stderr, "Failed to create backend.\n" ); return 1; } @@ -846,13 +856,6 @@ int main(int argc, char **argv) return 1; } -#if HAVE_OPENVR - if ( BIsVRSession() ) - { - vrsession_ime_init(); - } -#endif - gamescope_xwayland_server_t *base_server = wlserver_get_xwayland_server(0); setenv("DISPLAY", base_server->get_nested_display_name(), 1); @@ -909,73 +912,3 @@ static void steamCompMgrThreadRun(int argc, char **argv) pthread_kill( g_mainThread, SIGINT ); } - -static bool initOutput( int preferredWidth, int preferredHeight, int preferredRefresh ) -{ - VkInstance instance = vulkan_create_instance(); - - if ( BIsNested() ) - { - g_nOutputWidth = preferredWidth; - g_nOutputHeight = preferredHeight; - g_nOutputRefresh = preferredRefresh; - - 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 = 60; - - if ( BIsVRSession() ) - { -#if HAVE_OPENVR - if ( !vrsession_init() ) - return false; -#else - return false; -#endif - } - else if ( BIsSDLSession() ) - { - if ( !sdlwindow_init() ) - return false; - } - - VkSurfaceKHR surface = VK_NULL_HANDLE; - - if ( BIsSDLSession() ) - { - if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) - { - fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); - return false; - } - } - - if ( !vulkan_init( instance, surface ) ) - { - fprintf( stderr, "Failed to initialize Vulkan\n" ); - return false; - } - - return true; - } - else - { - if ( !vulkan_init( instance, VK_NULL_HANDLE ) ) - { - fprintf( stderr, "Failed to initialize Vulkan\n" ); - return false; - } - - return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh ); - } -} diff --git a/src/main.hpp b/src/main.hpp index b3de6b847..a87030b0f 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -17,14 +17,17 @@ extern int g_nNestedDisplayIndex; extern uint32_t g_nOutputWidth; extern uint32_t g_nOutputHeight; +extern bool g_bForceRelativeMouse; extern int g_nOutputRefresh; // Hz extern bool g_bOutputHDREnabled; +extern bool g_bForceInternal; extern bool g_bFullscreen; extern bool g_bGrabbed; extern float g_mouseSensitivity; +extern const char *g_sOutputName; enum class GamescopeUpscaleFilter : uint32_t { @@ -70,7 +73,3 @@ extern uint32_t g_preferVendorID; extern uint32_t g_preferDeviceID; void restore_fd_limit( void ); -bool BIsNested( void ); -bool BIsHeadless( void ); -bool BIsSDLSession( void ); -bool BIsVRSession( void ); diff --git a/src/meson.build b/src/meson.build index 4f88d6ecb..a9e64990c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -105,6 +105,8 @@ src = [ 'steamcompmgr.cpp', 'color_helpers.cpp', 'main.cpp', + 'edid.cpp', + 'headless.cpp', 'wlserver.cpp', 'drm.cpp', 'modegen.cpp', @@ -115,6 +117,7 @@ src = [ 'ime.cpp', 'mangoapp.cpp', 'reshade_effect_manager.cpp', + 'backend.cpp', ] src += spirv_shaders diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index 357018607..51a5d5c7f 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -21,14 +21,12 @@ // NIS_Config needs to be included before the X11 headers because of conflicting defines introduced by X11 #include "shaders/NVIDIAImageScaling/NIS/NIS_Config.h" +#include "drm_include.h" + #include "rendervulkan.hpp" #include "main.hpp" #include "steamcompmgr.hpp" -#include "sdlwindow.hpp" #include "log.hpp" -#if HAVE_OPENVR -#include "vr_session.hpp" -#endif #include "cs_composite_blit.h" #include "cs_composite_blur.h" @@ -47,6 +45,9 @@ #include "reshade_effect_manager.hpp" +#include "SDL.h" +#include "SDL_vulkan.h" + extern bool g_bWasPartialComposite; static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ @@ -100,6 +101,12 @@ VulkanOutput_t g_output; uint32_t g_uCompositeDebug = 0u; +template +static bool Contains( const std::span x, T value ) +{ + return std::ranges::any_of( x, std::bind_front(std::equal_to{}, value) ); +} + static std::map< VkFormat, std::map< uint64_t, VkDrmFormatModifierPropertiesEXT > > DRMModifierProps = {}; static std::vector< uint32_t > sampledShmFormats{}; static struct wlr_drm_format_set sampledDRMFormats = {}; @@ -409,6 +416,11 @@ bool CVulkanDevice::createDevice() vk_log.infof( "physical device %s DRM format modifiers", m_bSupportsModifiers ? "supports" : "does not support" ); + if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) + return false; + + // XXX(JoshA): Move this to ValidPhysicalDevice. + // We need to refactor some Vulkan stuff to do that though. if ( hasDrmProps ) { VkPhysicalDeviceDrmPropertiesEXT drmProps = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, @@ -419,7 +431,7 @@ bool CVulkanDevice::createDevice() }; vk.GetPhysicalDeviceProperties2( physDev(), &props2 ); - if ( !BIsNested() && !drmProps.hasPrimary ) { + if ( !GetBackend()->UsesVulkanSwapchain() && !drmProps.hasPrimary ) { vk_log.errorf( "physical device has no primary node" ); return false; } @@ -500,7 +512,7 @@ bool CVulkanDevice::createDevice() std::vector< const char * > enabledExtensions; - if ( BIsNested() == true ) + if ( GetBackend()->UsesVulkanSwapchain() ) { enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME ); @@ -526,12 +538,8 @@ bool CVulkanDevice::createDevice() if ( supportsHDRMetadata ) enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME ); - if ( BIsVRSession() ) - { -#if HAVE_OPENVR - vrsession_append_device_exts( physDev(), enabledExtensions ); -#endif - } + for ( auto& extension : GetBackend()->GetDeviceExtensions( physDev() ) ) + enabledExtensions.push_back( extension ); #if 0 VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5 = { @@ -1609,7 +1617,7 @@ void CVulkanCmdBuffer::prepareSrcImage(CVulkanTexture *image) if (!result.second) return; // using the swapchain image as a source without writing to it doesn't make any sense - assert(image->swapchainImage() == false); + assert(image->outputImage() == false); result.first->second.needsImport = image->externalImage(); result.first->second.needsExport = image->externalImage(); } @@ -1622,7 +1630,7 @@ void CVulkanCmdBuffer::prepareDestImage(CVulkanTexture *image) return; result.first->second.discarded = true; result.first->second.needsExport = image->externalImage(); - result.first->second.needsPresentLayout = image->swapchainImage(); + result.first->second.needsPresentLayout = image->outputImage(); } void CVulkanCmdBuffer::discardImage(CVulkanTexture *image) @@ -1663,8 +1671,6 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) bool isExport = flush && state.needsExport; bool isPresent = flush && state.needsPresentLayout; - VkImageLayout presentLayout = BIsVRSession() ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - if (!state.discarded && !state.dirty && !state.needsImport && !isExport && !isPresent) continue; @@ -1680,7 +1686,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) .srcAccessMask = state.dirty ? write_bits : 0u, .dstAccessMask = flush ? 0u : read_bits | write_bits, .oldLayout = (state.discarded || state.needsImport) ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL, - .newLayout = isPresent ? presentLayout : VK_IMAGE_LAYOUT_GENERAL, + .newLayout = isPresent ? GetBackend()->GetPresentLayout() : VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = isExport ? image->queueFamily : state.needsImport ? externalQueue : image->queueFamily, .dstQueueFamilyIndex = isExport ? externalQueue : state.needsImport ? m_queueFamily : m_queueFamily, .image = image->vkImage(), @@ -1699,7 +1705,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) 0, 0, nullptr, 0, nullptr, barriers.size(), barriers.data()); } -static CVulkanDevice g_device; +CVulkanDevice g_device; static bool allDMABUFsEqual( wlr_dmabuf_attributes *pDMA ) { @@ -1827,9 +1833,9 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } - if ( flags.bSwapchain == true ) + if ( flags.bOutputImage == true ) { - m_bSwapchain = true; + m_bOutputImage = true; } m_bExternal = pDMA || flags.bExportable == true; @@ -1916,7 +1922,8 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin } std::vector modifiers = {}; - if ( flags.bFlippable == true && g_device.supportsModifiers() && !pDMA ) + // TODO(JoshA): Move this code to backend for making flippable image. + if ( GetBackend()->UsesModifiers() && flags.bFlippable && g_device.supportsModifiers() && !pDMA ) { assert( drmFormat != DRM_FORMAT_INVALID ); @@ -1931,10 +1938,10 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin } else { - const struct wlr_drm_format *drmFormatDesc = wlr_drm_format_set_get( &g_DRM.primary_formats, drmFormat ); - assert( drmFormatDesc != nullptr ); - possibleModifiers = drmFormatDesc->modifiers; - numPossibleModifiers = drmFormatDesc->len; + std::span modifiers = GetBackend()->GetSupportedModifiers( drmFormat ); + assert( !modifiers.empty() ); + possibleModifiers = modifiers.data(); + numPossibleModifiers = modifiers.size(); } for ( size_t i = 0; i < numPossibleModifiers; i++ ) @@ -1976,7 +1983,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; } - if ( flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) + if ( GetBackend()->UsesModifiers() && flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) { // We want to scan-out the image wsiImageCreateInfo = { @@ -2240,11 +2247,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin if ( flags.bFlippable == true ) { - m_FBID = drm_fbid_from_dmabuf( &g_DRM, nullptr, &m_dmabuf ); - if ( m_FBID == 0 ) { - vk_log.errorf( "drm_fbid_from_dmabuf failed" ); - return false; - } + m_FBID = GetBackend()->ImportDmabufToBackend( nullptr, &m_dmabuf ); } bool bHasAlpha = pDMA ? DRMFormatHasAlpha( pDMA->format ) : true; @@ -2357,7 +2360,7 @@ bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t m_format = format; m_contentWidth = width; m_contentHeight = height; - m_bSwapchain = true; + m_bOutputImage = true; VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -2430,7 +2433,7 @@ CVulkanTexture::~CVulkanTexture( void ) if ( m_FBID != 0 ) { - drm_drop_fbid( &g_DRM, m_FBID ); + GetBackend()->DropBackendFb( m_FBID ); m_FBID = 0; } @@ -2560,10 +2563,12 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) if ( !g_device.supportsModifiers() ) { - if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, DRM_FORMAT_MOD_INVALID ) ) + if ( GetBackend()->UsesModifiers() ) { - return false; + if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), DRM_FORMAT_MOD_INVALID ) ) + return false; } + wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID ); return false; } @@ -2604,18 +2609,13 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) { continue; } - if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, modifier ) ) - { - continue; - } - if ( BIsNested() == false && drmFormat == DRM_FORMAT_NV12 && modifier == DRM_FORMAT_MOD_LINEAR && g_bRotated ) + + if ( GetBackend()->UsesModifiers() ) { - // If embedded and rotated, blacklist NV12 LINEAR because - // amdgpu won't support direct scan-out. Since only pure - // Wayland clients can submit NV12 buffers, this should only - // affect streaming_client. - continue; + if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), modifier ) ) + continue; } + wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, modifier ); } @@ -2684,7 +2684,7 @@ static void present_wait_thread_func( void ) { g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); uint64_t vblanktime = get_time_in_nanos(); - g_VBlankTimer.MarkVBlank( vblanktime, true ); + GetVBlankTimer().MarkVBlank( vblanktime, true ); mangoapp_output_update( vblanktime ); } } @@ -2707,7 +2707,7 @@ void vulkan_update_swapchain_hdr_metadata( VulkanOutput_t *pOutput ) return; } - hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->metadata.hdmi_metadata_type1; + const hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->View().hdmi_metadata_type1; VkHdrMetadataEXT metadata = { .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT, @@ -2830,7 +2830,7 @@ std::shared_ptr vulkan_get_hacky_blank_texture() std::shared_ptr vulkan_create_debug_blank_texture() { CVulkanTexture::createFlags flags; - flags.bFlippable = !BIsNested(); + flags.bFlippable = true; flags.bSampled = true; flags.bTransferDst = true; @@ -2853,46 +2853,6 @@ std::shared_ptr vulkan_create_debug_blank_texture() return texture; } -#if HAVE_OPENVR -std::shared_ptr vulkan_create_debug_white_texture() -{ - CVulkanTexture::createFlags flags; - flags.bMappable = true; - flags.bSampled = true; - flags.bTransferSrc = true; - flags.bLinear = true; - - auto texture = std::make_shared(); - bool bRes = texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags); - assert( bRes ); - - memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 ); - - return texture; -} - -void vulkan_present_to_openvr( void ) -{ - //static auto texture = vulkan_create_debug_white_texture(); - auto texture = vulkan_get_last_output_image( false, false ); - - vr::VRVulkanTextureData_t data = - { - .m_nImage = (uint64_t)(uintptr_t)texture->vkImage(), - .m_pDevice = g_device.device(), - .m_pPhysicalDevice = g_device.physDev(), - .m_pInstance = g_device.instance(), - .m_pQueue = g_device.queue(), - .m_nQueueFamilyIndex = g_device.queueFamily(), - .m_nWidth = texture->width(), - .m_nHeight = texture->height(), - .m_nFormat = texture->format(), - .m_nSampleCount = 1, - }; - vrsession_present(&data); -} -#endif - bool vulkan_supports_hdr10() { for ( auto& format : g_output.surfaceFormats ) @@ -3038,11 +2998,11 @@ bool vulkan_remake_swapchain( void ) static bool vulkan_make_output_images( VulkanOutput_t *pOutput ) { CVulkanTexture::createFlags outputImageflags; - outputImageflags.bFlippable = !BIsNested(); + outputImageflags.bFlippable = true; outputImageflags.bStorage = true; outputImageflags.bTransferSrc = true; // for screenshots outputImageflags.bSampled = true; // for pipewire blits - outputImageflags.bSwapchain = BIsVRSession(); + outputImageflags.bOutputImage = true; pOutput->outputImages.resize(3); // extra image for partial composition. pOutput->outputImagesPartialOverlay.resize(3); @@ -3137,12 +3097,7 @@ bool vulkan_make_output() VkResult result; - if ( BIsVRSession() || BIsHeadless() ) - { - pOutput->outputFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; - vulkan_make_output_images( pOutput ); - } - else if ( BIsSDLSession() ) + if ( GetBackend()->UsesVulkanSwapchain() ) { result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); if ( result != VK_SUCCESS ) @@ -3195,9 +3150,8 @@ bool vulkan_make_output() } else { - pOutput->outputFormat = DRMFormatToVulkan( g_nDRMFormat, false ); - pOutput->outputFormatOverlay = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); - + GetBackend()->GetPreferredOutputFormat( &pOutput->outputFormat, &pOutput->outputFormatOverlay ); + if ( pOutput->outputFormat == VK_FORMAT_UNDEFINED ) { vk_log.errorf( "failed to find Vulkan format suitable for KMS" ); @@ -3261,55 +3215,41 @@ static bool init_nis_data() return true; } -VkInstance vulkan_create_instance( void ) +VkInstance vulkan_get_instance( void ) { - VkResult result = VK_ERROR_INITIALIZATION_FAILED; - - std::vector< const char * > sdlExtensions; - if ( BIsVRSession() ) - { -#if HAVE_OPENVR - vrsession_append_instance_exts( sdlExtensions ); -#endif - } - else if ( BIsSDLSession() ) + static VkInstance s_pVkInstance = []() -> VkInstance { - if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) - { - fprintf(stderr, "SDL_Vulkan_LoadLibrary failed: %s\n", SDL_GetError()); - return nullptr; - } + VkResult result = VK_ERROR_INITIALIZATION_FAILED; - unsigned int extCount = 0; - SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, nullptr ); - sdlExtensions.resize( extCount ); - SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, sdlExtensions.data() ); - } + auto instanceExtensions = GetBackend()->GetInstanceExtensions(); - const VkApplicationInfo appInfo = { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "gamescope", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "hopefully not just some code", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_3, - }; + const VkApplicationInfo appInfo = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "gamescope", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "hopefully not just some code", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_3, + }; - const VkInstanceCreateInfo createInfo = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &appInfo, - .enabledExtensionCount = (uint32_t)sdlExtensions.size(), - .ppEnabledExtensionNames = sdlExtensions.data(), - }; + const VkInstanceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledExtensionCount = (uint32_t)instanceExtensions.size(), + .ppEnabledExtensionNames = instanceExtensions.data(), + }; - VkInstance instance = nullptr; - result = vkCreateInstance(&createInfo, 0, &instance); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkCreateInstance failed" ); - } + VkInstance instance = nullptr; + result = vkCreateInstance(&createInfo, 0, &instance); + if ( result != VK_SUCCESS ) + { + vk_errorf( result, "vkCreateInstance failed" ); + } + + return instance; + }(); - return instance; + return s_pVkInstance; } bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) @@ -3320,7 +3260,7 @@ bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) if (!init_nis_data()) return false; - if (BIsNested() && !BIsVRSession()) + if ( GetBackend()->UsesVulkanSwapchain() ) { std::thread present_wait_thread( present_wait_thread_func ); present_wait_thread.detach(); @@ -3451,7 +3391,7 @@ struct BlitPushData_t if (layer->ctm) { - ctm[i] = layer->ctm->matrix; + ctm[i] = layer->ctm->View(); } else { @@ -3586,7 +3526,7 @@ struct RcasPushData_t if (layer->ctm) { - ctm[i] = layer->ctm->matrix; + ctm[i] = layer->ctm->View(); } else { @@ -3903,7 +3843,7 @@ std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::sh uint64_t sequence = g_device.submit(std::move(cmdBuffer)); - if ( !BIsSDLSession() && pOutputOverride == nullptr && increment ) + if ( !GetBackend()->UsesVulkanSwapchain() && pOutputOverride == nullptr && increment ) { g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; } diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index f090344ae..e07da598c 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -20,9 +20,6 @@ class CVulkanCmdBuffer; -struct wlserver_ctm; -struct wlserver_hdr_metadata; - // 1: Fade Plane (Fade outs between switching focus) // 2: Video Underlay (The actual video) // 3: Video Streaming UI (Game, App) @@ -141,7 +138,7 @@ class CVulkanTexture bTransferDst = false; bLinear = false; bExportable = false; - bSwapchain = false; + bOutputImage = false; bColorAttachment = false; imageType = VK_IMAGE_TYPE_2D; } @@ -154,7 +151,7 @@ class CVulkanTexture bool bTransferDst : 1; bool bLinear : 1; bool bExportable : 1; - bool bSwapchain : 1; + bool bOutputImage : 1; bool bColorAttachment : 1; VkImageType imageType; }; @@ -178,7 +175,7 @@ class CVulkanTexture inline VkFormat format() const { return m_format; } inline const struct wlr_dmabuf_attributes& dmabuf() { return m_dmabuf; } inline VkImage vkImage() { return m_vkImage; } - inline bool swapchainImage() { return m_bSwapchain; } + inline bool outputImage() { return m_bOutputImage; } inline bool externalImage() { return m_bExternal; } inline VkDeviceSize totalSize() const { return m_size; } inline uint32_t drmFormat() const { return m_drmFormat; } @@ -206,7 +203,7 @@ class CVulkanTexture private: bool m_bInitialized = false; bool m_bExternal = false; - bool m_bSwapchain = false; + bool m_bOutputImage = false; uint32_t m_drmFormat = DRM_FORMAT_INVALID; @@ -264,6 +261,7 @@ struct FrameInfo_t { bool useFSRLayer0; bool useNISLayer0; + bool bFadingOut; BlurMode blurLayer0; int blurRadius; @@ -291,7 +289,7 @@ struct FrameInfo_t bool blackBorder; bool applyColorMgmt; // drm only - std::shared_ptr ctm; + std::shared_ptr ctm; GamescopeAppTextureColorspace colorspace; @@ -367,7 +365,7 @@ namespace CompositeDebugFlag static constexpr uint32_t Tonemap_Reinhard = 1u << 7; }; -VkInstance vulkan_create_instance(void); +VkInstance vulkan_get_instance(void); bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); bool vulkan_init_formats(void); bool vulkan_make_output(); @@ -382,9 +380,6 @@ std::shared_ptr vulkan_get_last_output_image( bool partial, bool std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); void vulkan_present_to_window( void ); -#if HAVE_OPENVR -void vulkan_present_to_openvr( void ); -#endif void vulkan_garbage_collect( void ); bool vulkan_remake_swapchain( void ); @@ -438,7 +433,7 @@ struct gamescope_color_mgmt_t glm::vec2 outputVirtualWhite = { 0.f, 0.f }; EChromaticAdaptationMethod chromaticAdaptationMode = k_EChromaticAdapatationMethod_Bradford; - std::shared_ptr appHDRMetadata = nullptr; + std::shared_ptr appHDRMetadata; bool operator == (const gamescope_color_mgmt_t&) const = default; bool operator != (const gamescope_color_mgmt_t&) const = default; @@ -487,7 +482,7 @@ struct VulkanOutput_t std::vector< VkPresentModeKHR > presentModes; - std::shared_ptr swapchainHDRMetadata; + std::shared_ptr swapchainHDRMetadata; VkSwapchainKHR swapChain; VkFence acquireFence; @@ -918,3 +913,5 @@ uint32_t DRMFormatGetBPP( uint32_t nDRMFormat ); bool vulkan_supports_hdr10(); void vulkan_wait_idle(); + +extern CVulkanDevice g_device; diff --git a/src/sdlscancodetable.hpp b/src/sdlscancodetable.hpp index 17aa90256..9ae0a38ad 100644 --- a/src/sdlscancodetable.hpp +++ b/src/sdlscancodetable.hpp @@ -1,5 +1,5 @@ -static uint32_t s_ScancodeTable[] = +static const uint32_t s_ScancodeTable[] = { KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 0 */ KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 1 */ @@ -287,3 +287,25 @@ static uint32_t s_ScancodeTable[] = KEY_PROG1, /* SDL_SCANCODE_APP1 283 */ KEY_RESERVED, /* SDL_SCANCODE_APP2 284 */ }; + +inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) +{ + if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) + { + return s_ScancodeTable[ nScancode ]; + } + return KEY_RESERVED; +} + +inline int SDLButtonToLinuxButton( int SDLButton ) +{ + switch ( SDLButton ) + { + case SDL_BUTTON_LEFT: return BTN_LEFT; + case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; + case SDL_BUTTON_RIGHT: return BTN_RIGHT; + case SDL_BUTTON_X1: return BTN_SIDE; + case SDL_BUTTON_X2: return BTN_EXTRA; + default: return 0; + } +} diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp index 1e51eab6c..1974bc229 100644 --- a/src/sdlwindow.cpp +++ b/src/sdlwindow.cpp @@ -13,587 +13,963 @@ #include "SDL_events.h" #include "main.hpp" #include "wlserver.hpp" -#include "sdlwindow.hpp" +#include +#include #include "rendervulkan.hpp" #include "steamcompmgr.hpp" +#include "defer.hpp" #include "sdlscancodetable.hpp" -#define DEFAULT_TITLE "gamescope" - -static bool g_bSDLInitOK = false; -static std::mutex g_SDLInitLock; - -static bool g_bWindowShown = false; - static int g_nOldNestedRefresh = 0; static bool g_bWindowFocused = true; static int g_nOutputWidthPts = 0; static int g_nOutputHeightPts = 0; - +extern const char *g_pOriginalDisplay; +extern bool g_bForceHDR10OutputDebug; extern bool steamMode; extern bool g_bFirstFrame; +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; -SDL_Window *g_SDLWindow; - -enum UserEvents +namespace gamescope { - USER_EVENT_TITLE, - USER_EVENT_VISIBLE, - USER_EVENT_GRAB, - USER_EVENT_CURSOR, + enum class SDLInitState + { + SDLInit_Waiting, + SDLInit_Success, + SDLInit_Failure, + }; - USER_EVENT_COUNT -}; + enum SDLCustomEvents + { + GAMESCOPE_SDL_EVENT_TITLE, + GAMESCOPE_SDL_EVENT_ICON, + GAMESCOPE_SDL_EVENT_VISIBLE, + GAMESCOPE_SDL_EVENT_GRAB, + GAMESCOPE_SDL_EVENT_CURSOR, -static uint32_t g_unSDLUserEventID; + GAMESCOPE_SDL_EVENT_COUNT, + }; -static std::mutex g_SDLWindowTitleLock; -static std::shared_ptr g_SDLWindowTitle; -static std::shared_ptr> g_SDLWindowIcon; -static bool g_bUpdateSDLWindowTitle = false; -static bool g_bUpdateSDLWindowIcon = false; + class CSDLConnector final : public IBackendConnector + { + public: + CSDLConnector(); + virtual bool Init(); -struct SDLPendingCursor -{ - uint32_t width, height, xhot, yhot; - std::shared_ptr> data; -}; -static std::mutex g_SDLCursorLock; -static SDLPendingCursor g_SDLPendingCursorData; -static bool g_bUpdateSDLCursor = false; - -static void set_gamescope_selections(); - -//----------------------------------------------------------------------------- -// Purpose: Convert from the remote scancode to a Linux event keycode -//----------------------------------------------------------------------------- -static inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) -{ - if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) + virtual ~CSDLConnector(); + + ///////////////////// + // 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 "SDLWindow"; + } + virtual const char *GetMake() const override + { + return "Gamescope"; + } + virtual const char *GetModel() const override + { + return "Virtual Display"; + } + + //-- + + SDL_Window *GetSDLWindow() const { return m_pWindow; } + VkSurfaceKHR GetVulkanSurface() const { return m_pVkSurface; } + private: + SDL_Window *m_pWindow = nullptr; + VkSurfaceKHR m_pVkSurface = VK_NULL_HANDLE; + BackendConnectorHDRInfo m_HDRInfo{}; + }; + + class CSDLBackend : public CBaseBackend, public INestedHints + { + public: + CSDLBackend(); + + ///////////// + // IBackend + ///////////// + + virtual bool Init() override; + virtual bool PostInit() override; + virtual std::span GetInstanceExtensions() const override; + virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override; + virtual VkImageLayout GetPresentLayout() const override; + virtual void GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *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; + + virtual std::shared_ptr CreateBackendBlob( std::span data ) override; + + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override; + virtual void LockBackendFb( uint32_t uFbId ) override; + virtual void UnlockBackendFb( uint32_t uFbId ) override; + virtual void DropBackendFb( uint32_t uFbId ) override; + virtual bool UsesModifiers() const override; + virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override; + + 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; + virtual bool UsesVulkanSwapchain() const override; + + virtual bool IsSessionBased() const override; + + virtual bool IsVisible() const override; + + virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const 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 std::optional GetHostCursor() override; + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; + private: + void SDLThreadFunc(); + + uint32_t GetUserEventIndex( SDLCustomEvents eEvent ) const; + void PushUserEvent( SDLCustomEvents eEvent ); + + bool m_bShown = false; + CSDLConnector m_Connector; // Window. + uint32_t m_uUserEventIdBase = 0u; + std::vector m_pszInstanceExtensions; + + std::thread m_SDLThread; + std::atomic m_eSDLInit = { SDLInitState::SDLInit_Waiting }; + + std::atomic m_bApplicationGrabbed = { false }; + std::atomic m_bApplicationVisible = { false }; + std::atomic> m_pApplicationCursor; + std::atomic> m_pApplicationTitle; + std::atomic>> m_pApplicationIcon; + SDL_Surface *m_pIconSurface = nullptr; + SDL_Surface *m_pCursorSurface = nullptr; + SDL_Cursor *m_pCursor = nullptr; + }; + + ////////////////// + // CSDLConnector + ////////////////// + + CSDLConnector::CSDLConnector() { - return s_ScancodeTable[ nScancode ]; } - return KEY_RESERVED; -} -static inline int SDLButtonToLinuxButton( int SDLButton ) -{ - switch ( SDLButton ) + CSDLConnector::~CSDLConnector() { - case SDL_BUTTON_LEFT: return BTN_LEFT; - case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; - case SDL_BUTTON_RIGHT: return BTN_RIGHT; - case SDL_BUTTON_X1: return BTN_SIDE; - case SDL_BUTTON_X2: return BTN_EXTRA; - default: return 0; + if ( m_pWindow ) + SDL_DestroyWindow( m_pWindow ); } -} -void updateOutputRefresh( void ) -{ - int display_index = 0; - SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; + bool CSDLConnector::Init() + { + 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 = 60; + + uint32_t uSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; + + if ( g_bBorderlessOutputWindow == true ) + uSDLWindowFlags |= SDL_WINDOW_BORDERLESS; + + if ( g_bFullscreen == true ) + uSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + if ( g_bGrabbed == true ) + uSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; + + m_pWindow = SDL_CreateWindow( + "gamescope", + SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), + SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), + g_nOutputWidth, + g_nOutputHeight, + uSDLWindowFlags ); + + if ( m_pWindow == nullptr ) + return false; + + if ( !SDL_Vulkan_CreateSurface( m_pWindow, vulkan_get_instance(), &m_pVkSurface ) ) + { + fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); + return false; + } - display_index = SDL_GetWindowDisplayIndex( g_SDLWindow ); - if ( SDL_GetDesktopDisplayMode( display_index, &mode ) == 0 ) + return true; + } + + GamescopeScreenType CSDLConnector::GetScreenType() const { - g_nOutputRefresh = mode.refresh_rate; + return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + } + GamescopePanelOrientation CSDLConnector::GetCurrentOrientation() const + { + return GAMESCOPE_PANEL_ORIENTATION_0; + } + bool CSDLConnector::SupportsHDR() const + { + return GetHDRInfo().IsHDR10(); + } + bool CSDLConnector::IsHDRActive() const + { + // XXX: blah + return false; + } + const BackendConnectorHDRInfo &CSDLConnector::GetHDRInfo() const + { + return m_HDRInfo; + } + std::span CSDLConnector::GetModes() const + { + return std::span{}; } -} -extern bool g_bForceRelativeMouse; + bool CSDLConnector::SupportsVRR() const + { + return false; + } -static std::string gamescope_str = DEFAULT_TITLE; + std::span CSDLConnector::GetRawEDID() const + { + return std::span{}; + } + std::span CSDLConnector::GetValidDynamicRefreshRates() const + { + return std::span{}; + } -void inputSDLThreadRun( void ) -{ - pthread_setname_np( pthread_self(), "gamescope-sdl" ); + void CSDLConnector::GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + { + if ( g_bForceHDR10OutputDebug ) + { + *displayColorimetry = displaycolorimetry_2020; + *displayEOTF = EOTF_PQ; + *outputEncodingColorimetry = displaycolorimetry_2020; + *outputEncodingEOTF = EOTF_PQ; + } + else + { + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; + } + } - SDL_Event event; - uint32_t key; - bool bRelativeMouse = false; + //////////////// + // CSDLBackend + //////////////// - g_unSDLUserEventID = SDL_RegisterEvents( USER_EVENT_COUNT ); + CSDLBackend::CSDLBackend() + : m_SDLThread{ [this](){ this->SDLThreadFunc(); } } + { + } - uint32_t nSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; + bool CSDLBackend::Init() + { + m_eSDLInit.wait( SDLInitState::SDLInit_Waiting ); - if ( g_bBorderlessOutputWindow == true ) + return m_eSDLInit == SDLInitState::SDLInit_Success; + } + + bool CSDLBackend::PostInit() { - nSDLWindowFlags |= SDL_WINDOW_BORDERLESS; + return true; } - if ( g_bFullscreen == true ) + std::span CSDLBackend::GetInstanceExtensions() const { - nSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + return std::span{ m_pszInstanceExtensions.begin(), m_pszInstanceExtensions.end() }; } - if ( g_bGrabbed == true ) + std::span CSDLBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const { - nSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; + return std::span{}; } - g_SDLWindow = SDL_CreateWindow( DEFAULT_TITLE, - SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), - SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), - g_nOutputWidth, - g_nOutputHeight, - nSDLWindowFlags ); + VkImageLayout CSDLBackend::GetPresentLayout() const + { + return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + } - if ( g_SDLWindow == nullptr ) + void CSDLBackend::GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const { - fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); - g_SDLInitLock.unlock(); - return; + *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; } - // Update g_nOutputWidthPts. + bool CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const { - int width, height; - SDL_GetWindowSize( g_SDLWindow, &width, &height ); - g_nOutputWidthPts = width; - g_nOutputHeightPts = height; + 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(); - #if SDL_VERSION_ATLEAST(2, 26, 0) - SDL_GetWindowSizeInPixels( g_SDLWindow, &width, &height ); - #endif - g_nOutputWidth = width; - g_nOutputHeight = height; + // 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 ) + { + } + bool CSDLBackend::PollState() + { + return false; + } + + std::shared_ptr CSDLBackend::CreateBackendBlob( std::span data ) + { + return std::make_shared( data ); + } + + uint32_t CSDLBackend::ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) + { + return 0; + } + void CSDLBackend::LockBackendFb( uint32_t uFbId ) + { + abort(); + } + void CSDLBackend::UnlockBackendFb( uint32_t uFbId ) + { + abort(); + } + void CSDLBackend::DropBackendFb( uint32_t uFbId ) + { + abort(); + } + + bool CSDLBackend::UsesModifiers() const + { + return false; + } + std::span CSDLBackend::GetSupportedModifiers( uint32_t uDrmFormat ) const + { + return std::span{}; + } + + IBackendConnector *CSDLBackend::GetCurrentConnector() + { + return &m_Connector; + } + IBackendConnector *CSDLBackend::GetConnector( GamescopeScreenType eScreenType ) + { + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return &m_Connector; + + return nullptr; + } + bool CSDLBackend::IsVRRActive() const + { + return false; + } + + bool CSDLBackend::SupportsPlaneHardwareCursor() const + { + // We use the nested hints cursor stuff. + // Not our own plane. + return false; + } + + bool CSDLBackend::SupportsTearing() const + { + return false; + } + bool CSDLBackend::UsesVulkanSwapchain() const + { + return true; + } + + bool CSDLBackend::IsSessionBased() const + { + return false; + } + + bool CSDLBackend::IsVisible() const + { + return true; + } + + glm::uvec2 CSDLBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const + { + return uvecSize; } - if ( g_bForceRelativeMouse ) + INestedHints *CSDLBackend::GetNestedHints() { - SDL_SetRelativeMouseMode( SDL_TRUE ); - bRelativeMouse = true; + return this; } - SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + /////////////////// + // INestedHints + /////////////////// - g_nOldNestedRefresh = g_nNestedRefresh; + void CSDLBackend::SetCursorImage( std::shared_ptr info ) + { + m_pApplicationCursor = info; + PushUserEvent( GAMESCOPE_SDL_EVENT_CURSOR ); + } + void CSDLBackend::SetRelativeMouseMode( bool bRelative ) + { + m_bApplicationGrabbed = bRelative; + PushUserEvent( GAMESCOPE_SDL_EVENT_GRAB ); + } + void CSDLBackend::SetVisible( bool bVisible ) + { + m_bApplicationVisible = bVisible; + PushUserEvent( GAMESCOPE_SDL_EVENT_VISIBLE ); + } + void CSDLBackend::SetTitle( std::shared_ptr szTitle ) + { + m_pApplicationTitle = szTitle; + PushUserEvent( GAMESCOPE_SDL_EVENT_TITLE ); + } + void CSDLBackend::SetIcon( std::shared_ptr> uIconPixels ) + { + m_pApplicationIcon = uIconPixels; + PushUserEvent( GAMESCOPE_SDL_EVENT_ICON ); + } + + std::optional CSDLBackend::GetHostCursor() + { + if ( !g_pOriginalDisplay ) + return std::nullopt; + + Display *display = XOpenDisplay( g_pOriginalDisplay ); + if ( !display ) + return std::nullopt; + defer( XCloseDisplay( display ) ); + + int xfixes_event, xfixes_error; + if ( !XFixesQueryExtension( display, &xfixes_event, &xfixes_error ) ) + { + xwm_log.errorf("No XFixes extension on current compositor"); + return std::nullopt; + } + + XFixesCursorImage *image = XFixesGetCursorImage( display ); + if ( !image ) + return std::nullopt; + defer( XFree( image ) ); + + // image->pixels is `unsigned long*` :/ + // Thanks X11. + std::vector cursorData = std::vector( image->width * image->height ); + for (uint32_t y = 0; y < image->height; y++) + { + for (uint32_t x = 0; x < image->width; x++) + { + cursorData[y * image->width + x] = static_cast( image->pixels[image->height * y + x] ); + } + } - g_bSDLInitOK = true; - g_SDLInitLock.unlock(); + return CursorInfo + { + .pPixels = std::move( cursorData ), + .uWidth = image->width, + .uHeight = image->height, + .uXHotspot = image->xhot, + .uYHotspot = image->yhot, + }; + } - static uint32_t fake_timestamp = 0; - SDL_Surface *cursor_surface = nullptr; - SDL_Surface *icon_surface = nullptr; - SDL_Cursor *cursor = nullptr; + void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) + { + // Do nothing. + } - while( SDL_WaitEvent( &event ) ) + void CSDLBackend::SDLThreadFunc() { - fake_timestamp++; + pthread_setname_np( pthread_self(), "gamescope-sdl" ); + + m_uUserEventIdBase = SDL_RegisterEvents( GAMESCOPE_SDL_EVENT_COUNT ); + + if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all(); + return; + } + + if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) + { + fprintf(stderr, "SDL_Vulkan_LoadLibrary failed: %s\n", SDL_GetError()); + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all(); + return; + } + + unsigned int uExtCount = 0; + SDL_Vulkan_GetInstanceExtensions( nullptr, &uExtCount, nullptr ); + m_pszInstanceExtensions.resize( uExtCount ); + SDL_Vulkan_GetInstanceExtensions( nullptr, &uExtCount, m_pszInstanceExtensions.data() ); + + if ( !m_Connector.Init() ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all(); + return; + } + + if ( !vulkan_init( vulkan_get_instance(), m_Connector.GetVulkanSurface() ) ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all(); + return; + } + + if ( !wlsession_init() ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all(); + return; + } + + // Update g_nOutputWidthPts. + { + int width, height; + SDL_GetWindowSize( m_Connector.GetSDLWindow(), &width, &height ); + g_nOutputWidthPts = width; + g_nOutputHeightPts = height; + + #if SDL_VERSION_ATLEAST(2, 26, 0) + SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); + #endif + g_nOutputWidth = width; + g_nOutputHeight = height; + } + + bool bRelativeMouse = false; + if ( g_bForceRelativeMouse ) + { + SDL_SetRelativeMouseMode( SDL_TRUE ); + bRelativeMouse = true; + } + + SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + + g_nOldNestedRefresh = g_nNestedRefresh; + + m_eSDLInit = SDLInitState::SDLInit_Success; + m_eSDLInit.notify_all(); + + static uint32_t fake_timestamp = 0; - switch( event.type ) + SDL_Event event; + while( SDL_WaitEvent( &event ) ) { - case SDL_CLIPBOARDUPDATE: - set_gamescope_selections(); + fake_timestamp++; + + switch( event.type ) + { + case SDL_CLIPBOARDUPDATE: + { + char *pClipBoard = SDL_GetClipboardText(); + char *pPrimarySelection = SDL_GetPrimarySelectionText(); + + gamescope_set_selection(pClipBoard, GAMESCOPE_SELECTION_CLIPBOARD); + gamescope_set_selection(pPrimarySelection, GAMESCOPE_SELECTION_PRIMARY); + + SDL_free(pClipBoard); + SDL_free(pPrimarySelection); + } break; - case SDL_MOUSEMOTION: - if ( bRelativeMouse ) + + case SDL_MOUSEMOTION: { - if ( g_bWindowFocused ) + if ( bRelativeMouse ) + { + if ( g_bWindowFocused ) + { + wlserver_lock(); + wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); + wlserver_unlock(); + } + } + else { wlserver_lock(); - wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); + wlserver_touchmotion( + event.motion.x / float(g_nOutputWidthPts), + event.motion.y / float(g_nOutputHeightPts), + 0, + fake_timestamp ); wlserver_unlock(); } } - else + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { wlserver_lock(); - wlserver_touchmotion( - event.motion.x / float(g_nOutputWidthPts), - event.motion.y / float(g_nOutputHeightPts), - 0, - fake_timestamp ); + wlserver_mousebutton( SDLButtonToLinuxButton( event.button.button ), + event.button.state == SDL_PRESSED, + fake_timestamp ); wlserver_unlock(); } break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - wlserver_lock(); - wlserver_mousebutton( SDLButtonToLinuxButton( event.button.button ), - event.button.state == SDL_PRESSED, - fake_timestamp ); - wlserver_unlock(); - break; - case SDL_MOUSEWHEEL: - wlserver_lock(); - wlserver_mousewheel( -event.wheel.x, -event.wheel.y, fake_timestamp ); - wlserver_unlock(); - break; - case SDL_FINGERMOTION: - wlserver_lock(); - wlserver_touchmotion( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); + + case SDL_MOUSEWHEEL: + { + wlserver_lock(); + wlserver_mousewheel( -event.wheel.x, -event.wheel.y, fake_timestamp ); + wlserver_unlock(); + } break; - case SDL_FINGERDOWN: - wlserver_lock(); - wlserver_touchdown( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); + + case SDL_FINGERMOTION: + { + wlserver_lock(); + wlserver_touchmotion( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); + wlserver_unlock(); + } break; - case SDL_FINGERUP: - wlserver_lock(); - wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); + + case SDL_FINGERDOWN: + { + wlserver_lock(); + wlserver_touchdown( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); + wlserver_unlock(); + } break; - case SDL_KEYDOWN: - // If this keydown event is super + one of the shortcut keys, consume the keydown event, since the corresponding keyup - // event will be consumed by the next case statement when the user releases the key - if ( event.key.keysym.mod & KMOD_LGUI ) + + case SDL_FINGERUP: { - key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); - const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G}; - const bool isShortcutKey = std::find(std::begin(shortcutKeys), std::end(shortcutKeys), key) != std::end(shortcutKeys); - if ( isShortcutKey ) - { - break; - } + wlserver_lock(); + wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); + wlserver_unlock(); } - [[fallthrough]]; - case SDL_KEYUP: - key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); + break; - if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) + case SDL_KEYDOWN: { - bool handled = true; - switch ( key ) + // If this keydown event is super + one of the shortcut keys, consume the keydown event, since the corresponding keyup + // event will be consumed by the next case statement when the user releases the key + if ( event.key.keysym.mod & KMOD_LGUI ) { - case KEY_F: - g_bFullscreen = !g_bFullscreen; - SDL_SetWindowFullscreen( g_SDLWindow, g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 ); - break; - case KEY_N: - g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL; - break; - case KEY_B: - g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; - break; - case KEY_U: - g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR; - break; - case KEY_Y: - g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS; - break; - case KEY_I: - g_upscaleFilterSharpness = std::min(20, g_upscaleFilterSharpness + 1); - break; - case KEY_O: - g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1); - break; - case KEY_S: - gamescope::CScreenshotManager::Get().TakeScreenshot( true ); - break; - case KEY_G: - g_bGrabbed = !g_bGrabbed; - SDL_SetWindowKeyboardGrab( g_SDLWindow, g_bGrabbed ? SDL_TRUE : SDL_FALSE ); - g_bUpdateSDLWindowTitle = true; - - SDL_Event event; - event.type = g_unSDLUserEventID + USER_EVENT_TITLE; - SDL_PushEvent( &event ); + uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); + const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G}; + const bool isShortcutKey = std::find(std::begin(shortcutKeys), std::end(shortcutKeys), key) != std::end(shortcutKeys); + if ( isShortcutKey ) + { break; - default: - handled = false; + } } - if ( handled ) + } + [[fallthrough]]; + case SDL_KEYUP: + { + uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); + + if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) { - break; + bool handled = true; + switch ( key ) + { + case KEY_F: + g_bFullscreen = !g_bFullscreen; + SDL_SetWindowFullscreen( m_Connector.GetSDLWindow(), g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 ); + break; + case KEY_N: + g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL; + break; + case KEY_B: + g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; + break; + case KEY_U: + g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR) ? + GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR; + break; + case KEY_Y: + g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS) ? + GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS; + break; + case KEY_I: + g_upscaleFilterSharpness = std::min(20, g_upscaleFilterSharpness + 1); + break; + case KEY_O: + g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1); + break; + case KEY_S: + gamescope::CScreenshotManager::Get().TakeScreenshot( true ); + break; + case KEY_G: + g_bGrabbed = !g_bGrabbed; + SDL_SetWindowKeyboardGrab( m_Connector.GetSDLWindow(), g_bGrabbed ? SDL_TRUE : SDL_FALSE ); + + SDL_Event event; + event.type = GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ); + SDL_PushEvent( &event ); + break; + default: + handled = false; + } + if ( handled ) + { + break; + } } - } - // On Wayland, clients handle key repetition - if ( event.key.repeat ) - break; + // On Wayland, clients handle key repetition + if ( event.key.repeat ) + break; - wlserver_lock(); - wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); - wlserver_unlock(); + wlserver_lock(); + wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); + wlserver_unlock(); + } break; - case SDL_WINDOWEVENT: - switch( event.window.event ) + + case SDL_WINDOWEVENT: { - case SDL_WINDOWEVENT_CLOSE: - raise( SIGTERM ); - break; - default: - break; - case SDL_WINDOWEVENT_MOVED: - case SDL_WINDOWEVENT_SHOWN: - updateOutputRefresh(); - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - int width, height; - SDL_GetWindowSize( g_SDLWindow, &width, &height ); - g_nOutputWidthPts = width; - g_nOutputHeightPts = height; + switch( event.window.event ) + { + case SDL_WINDOWEVENT_CLOSE: + raise( SIGTERM ); + break; + default: + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + int width, height; + SDL_GetWindowSize( m_Connector.GetSDLWindow(), &width, &height ); + g_nOutputWidthPts = width; + g_nOutputHeightPts = height; #if SDL_VERSION_ATLEAST(2, 26, 0) - SDL_GetWindowSizeInPixels( g_SDLWindow, &width, &height ); + SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); #endif - g_nOutputWidth = width; - g_nOutputHeight = height; - - updateOutputRefresh(); - - break; - case SDL_WINDOWEVENT_FOCUS_LOST: - g_nNestedRefresh = g_nNestedUnfocusedRefresh; - g_bWindowFocused = false; - break; - case SDL_WINDOWEVENT_FOCUS_GAINED: - g_nNestedRefresh = g_nOldNestedRefresh; - g_bWindowFocused = true; - break; - case SDL_WINDOWEVENT_EXPOSED: - force_repaint(); - break; + g_nOutputWidth = width; + g_nOutputHeight = height; + + [[fallthrough]]; + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_SHOWN: + { + int display_index = 0; + SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; + + display_index = SDL_GetWindowDisplayIndex( m_Connector.GetSDLWindow() ); + if ( SDL_GetDesktopDisplayMode( display_index, &mode ) == 0 ) + { + g_nOutputRefresh = mode.refresh_rate; + } + } + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + g_nNestedRefresh = g_nNestedUnfocusedRefresh; + g_bWindowFocused = false; + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + g_nNestedRefresh = g_nOldNestedRefresh; + g_bWindowFocused = true; + break; + case SDL_WINDOWEVENT_EXPOSED: + force_repaint(); + break; + } } break; - default: - if ( event.type == g_unSDLUserEventID + USER_EVENT_TITLE ) + + default: { - g_SDLWindowTitleLock.lock(); - if ( g_bUpdateSDLWindowTitle ) + if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_VISIBLE ) ) { - std::string tmp_title; + bool bVisible = m_bApplicationVisible; - const std::string *window_title = g_SDLWindowTitle.get(); - if (!window_title) - window_title = &gamescope_str; + // If we are Steam Mode in nested, show the window + // whenever we have had a first frame to match + // what we do in embedded with Steam for testing + // held commits, etc. + if ( steamMode ) + bVisible |= !g_bFirstFrame; - g_bUpdateSDLWindowTitle = false; - if ( g_bGrabbed ) + if ( m_bShown != bVisible ) { - tmp_title = *window_title; - tmp_title += " (grabbed)"; - - window_title = &tmp_title; + m_bShown = bVisible; + + if ( m_bShown ) + { + SDL_ShowWindow( m_Connector.GetSDLWindow() ); + } + else + { + SDL_HideWindow( m_Connector.GetSDLWindow() ); + } } - SDL_SetWindowTitle( g_SDLWindow, window_title->c_str() ); } - - if ( g_bUpdateSDLWindowIcon ) + else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) ) { - if ( icon_surface ) + std::shared_ptr pAppTitle = m_pApplicationTitle; + + std::string szTitle = pAppTitle ? *pAppTitle : "gamescope"; + if ( g_bGrabbed ) + szTitle += " (grabbed)"; + SDL_SetWindowTitle( m_Connector.GetSDLWindow(), szTitle.c_str() ); + } + else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_ICON ) ) + { + std::shared_ptr> pIcon = m_pApplicationIcon; + + if ( m_pIconSurface ) { - SDL_FreeSurface( icon_surface ); - icon_surface = nullptr; + SDL_FreeSurface( m_pIconSurface ); + m_pIconSurface = nullptr; } - if ( g_SDLWindowIcon && g_SDLWindowIcon->size() >= 3 ) + if ( pIcon && pIcon->size() >= 3 ) { - const uint32_t width = (*g_SDLWindowIcon)[0]; - const uint32_t height = (*g_SDLWindowIcon)[1]; + const uint32_t uWidth = (*pIcon)[0]; + const uint32_t uHeight = (*pIcon)[1]; - icon_surface = SDL_CreateRGBSurfaceFrom( - &(*g_SDLWindowIcon)[2], - width, height, - 32, width * sizeof(uint32_t), + m_pIconSurface = SDL_CreateRGBSurfaceFrom( + &(*pIcon)[2], + uWidth, uHeight, + 32, uWidth * sizeof(uint32_t), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); } - SDL_SetWindowIcon( g_SDLWindow, icon_surface ); + SDL_SetWindowIcon( m_Connector.GetSDLWindow(), m_pIconSurface ); } - g_SDLWindowTitleLock.unlock(); - } - if ( event.type == g_unSDLUserEventID + USER_EVENT_VISIBLE ) - { - bool should_show = !!event.user.code; - - // If we are Steam Mode in nested, show the window - // whenever we have had a first frame to match - // what we do in embedded with Steam for testing - // held commits, etc. - if ( steamMode ) - should_show |= !g_bFirstFrame; - - if ( g_bWindowShown != should_show ) + else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_GRAB ) ) { - g_bWindowShown = should_show; + SDL_SetRelativeMouseMode( m_bApplicationGrabbed ? SDL_TRUE : SDL_FALSE ); + } + else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_CURSOR ) ) + { + std::shared_ptr pCursorInfo = m_pApplicationCursor; + + if ( m_pCursorSurface ) + { + SDL_FreeSurface( m_pCursorSurface ); + m_pCursorSurface = nullptr; + } - if ( g_bWindowShown ) + if ( m_pCursor ) { - SDL_ShowWindow( g_SDLWindow ); + SDL_FreeCursor( m_pCursor ); + m_pCursor = nullptr; } - else + + if ( pCursorInfo ) { - SDL_HideWindow( g_SDLWindow ); + m_pCursorSurface = SDL_CreateRGBSurfaceFrom( + pCursorInfo->pPixels.data(), + pCursorInfo->uWidth, + pCursorInfo->uHeight, + 32, + pCursorInfo->uWidth * sizeof(uint32_t), + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + + m_pCursor = SDL_CreateColorCursor( m_pCursorSurface, pCursorInfo->uXHotspot, pCursorInfo->uYHotspot ); } - } - } - if ( event.type == g_unSDLUserEventID + USER_EVENT_GRAB ) - { - bool grab = !!event.user.code; - if ( grab != bRelativeMouse ) - { - SDL_SetRelativeMouseMode( grab ? SDL_TRUE : SDL_FALSE ); - bRelativeMouse = grab; - } - } - if ( event.type == g_unSDLUserEventID + USER_EVENT_CURSOR ) - { - std::unique_lock lock(g_SDLCursorLock); - if ( g_bUpdateSDLCursor ) - { - if (cursor_surface) - SDL_FreeSurface(cursor_surface); - - cursor_surface = SDL_CreateRGBSurfaceFrom( - g_SDLPendingCursorData.data->data(), - g_SDLPendingCursorData.width, - g_SDLPendingCursorData.height, - 32, - g_SDLPendingCursorData.width * sizeof(uint32_t), - 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - - if (cursor) - SDL_FreeCursor(cursor); - - cursor = SDL_CreateColorCursor( cursor_surface, g_SDLPendingCursorData.xhot, g_SDLPendingCursorData.yhot ); - SDL_SetCursor( cursor ); - g_bUpdateSDLCursor = false; + + SDL_SetCursor( m_pCursor ); } } break; + } } } -} - -std::optional sdlwindow_thread; - -bool sdlwindow_init( void ) -{ - g_SDLInitLock.lock(); - - std::thread inputSDLThread( inputSDLThreadRun ); - sdlwindow_thread = inputSDLThread.native_handle(); - inputSDLThread.detach(); - - // When this returns SDL_Init should be over - g_SDLInitLock.lock(); - - return g_bSDLInitOK; -} -void sdlwindow_shutdown( void ) -{ - if ( sdlwindow_thread ) + uint32_t CSDLBackend::GetUserEventIndex( SDLCustomEvents eEvent ) const { - pthread_cancel(*sdlwindow_thread); - sdlwindow_thread = std::nullopt; + return m_uUserEventIdBase + uint32_t( eEvent ); } -} - -void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ) -{ - if ( !BIsSDLSession() ) - return; + void CSDLBackend::PushUserEvent( SDLCustomEvents eEvent ) { - std::unique_lock lock(g_SDLWindowTitleLock); - - if ( g_SDLWindowTitle != title ) + SDL_Event event = { - g_SDLWindowTitle = title; - g_bUpdateSDLWindowTitle = true; - } - - if ( g_SDLWindowIcon != icon ) - { - g_SDLWindowIcon = icon; - g_bUpdateSDLWindowIcon = true; - } - - if ( g_bUpdateSDLWindowTitle || g_bUpdateSDLWindowIcon ) - { - SDL_Event event; - event.type = g_unSDLUserEventID + USER_EVENT_TITLE; - SDL_PushEvent( &event ); - } - } -} - -void sdlwindow_set_selection(std::string contents, int selection) -{ - if (selection == CLIPBOARD) - { - SDL_SetClipboardText(contents.c_str()); + .user = + { + .type = GetUserEventIndex( eEvent ), + }, + }; + SDL_PushEvent( &event ); } - else if (selection == PRIMARYSELECTION) - { - SDL_SetPrimarySelectionText(contents.c_str()); - } -} -static void set_gamescope_selections() -{ - char *_clipboard = SDL_GetClipboardText(); - - char *_primarySelection = SDL_GetPrimarySelectionText(); - - gamescope_set_selection(_clipboard, CLIPBOARD); - gamescope_set_selection(_primarySelection, PRIMARYSELECTION); - - SDL_free(_clipboard); - SDL_free(_primarySelection); -} - -void sdlwindow_visible( bool bVisible ) -{ - if ( !BIsSDLSession() ) - return; - - SDL_Event event; - event.type = g_unSDLUserEventID + USER_EVENT_VISIBLE; - event.user.code = bVisible ? 1 : 0; - SDL_PushEvent( &event ); -} - -void sdlwindow_grab( bool bGrab ) -{ - if ( !BIsSDLSession() ) - return; - - if ( g_bForceRelativeMouse ) - return; - - static bool s_bWasGrabbed = false; - - if ( s_bWasGrabbed == bGrab ) - return; - - s_bWasGrabbed = bGrab; - - SDL_Event event; - event.type = g_unSDLUserEventID + USER_EVENT_GRAB; - event.user.code = bGrab ? 1 : 0; - SDL_PushEvent( &event ); -} - -void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot) -{ - if ( !BIsSDLSession() ) - return; - - if ( g_bForceRelativeMouse ) - return; + ///////////////////////// + // Backend Instantiator + ///////////////////////// + template <> + bool IBackend::Set() { - std::unique_lock lock( g_SDLCursorLock ); - g_SDLPendingCursorData.width = width; - g_SDLPendingCursorData.height = height; - g_SDLPendingCursorData.xhot = xhot; - g_SDLPendingCursorData.yhot = yhot; - g_SDLPendingCursorData.data = pixels; - g_bUpdateSDLCursor = true; + return Set( new CSDLBackend{} ); } - - SDL_Event event; - event.type = g_unSDLUserEventID + USER_EVENT_CURSOR; - SDL_PushEvent( &event ); } diff --git a/src/sdlwindow.hpp b/src/sdlwindow.hpp deleted file mode 100644 index bb88fd30a..000000000 --- a/src/sdlwindow.hpp +++ /dev/null @@ -1,23 +0,0 @@ -// For the nested case, manages SDL window for input/output - -#pragma once - -#include -#include - -#define CLIPBOARD 0 -#define PRIMARYSELECTION 1 - -bool sdlwindow_init( void ); -void sdlwindow_shutdown( void ); - -void sdlwindow_update( void ); -void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ); -void sdlwindow_set_selection(std::string, int selection); - -// called from other threads with interesting things have happened with clients that might warrant updating the nested window -void sdlwindow_visible( bool bVisible ); -void sdlwindow_grab( bool bGrab ); -void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot); - -extern SDL_Window *g_SDLWindow; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 49aa6fbd3..e3dae6154 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -78,19 +78,16 @@ #include #include "waitable.h" -#include "steamcompmgr_shared.hpp" - #include "main.hpp" #include "wlserver.hpp" -#include "drm.hpp" #include "rendervulkan.hpp" #include "steamcompmgr.hpp" #include "vblankmanager.hpp" -#include "sdlwindow.hpp" #include "log.hpp" #include "defer.hpp" #include "win32_styles.h" #include "mwm_hints.h" +#include "edid.h" #include "avif/avif.h" @@ -100,10 +97,6 @@ static const int g_nBaseCursorScale = 36; #include "pipewire.hpp" #endif -#if HAVE_OPENVR -#include "vr_session.hpp" -#endif - #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include @@ -144,7 +137,7 @@ extern float g_flHDRItmTargetNits; uint64_t g_lastWinSeq = 0; -static std::shared_ptr s_scRGB709To2020Matrix; +static std::shared_ptr s_scRGB709To2020Matrix; std::string clipboard; std::string primarySelection; @@ -153,6 +146,7 @@ std::string g_reshade_effect{}; uint32_t g_reshade_technique_idx = 0; bool g_bSteamIsActiveWindow = false; +bool g_bForceInternal = false; uint64_t timespec_to_nanos(struct timespec& spec) { @@ -304,17 +298,17 @@ create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_col // Create quantized output luts for ( size_t i=0, end = g_tmpLut1d.dataR.size(); iGetCurrentConnector() ) + return; + + GetBackend()->GetCurrentConnector()->GetNativeColorimetry( + g_bHDREnabled, + &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, + &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); #ifdef COLOR_MGMT_MICROBENCH struct timespec t0, t1; @@ -508,13 +488,16 @@ bool set_color_mgmt_enabled( bool bEnabled ) } static std::shared_ptr s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; -static std::shared_ptr s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; +static std::shared_ptr s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; static float g_flMuraScale = 1.0f; static bool g_bMuraCompensationDisabled = false; bool is_mura_correction_enabled() { - return s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )] != nullptr && !g_bMuraCompensationDisabled; + if ( !GetBackend()->GetCurrentConnector() ) + return false; + + return s_MuraCorrectionImage[GetBackend()->GetCurrentConnector()->GetScreenType()] != nullptr && !g_bMuraCompensationDisabled; } void update_mura_ctm() @@ -535,7 +518,7 @@ void update_mura_ctm() 0, flScale, 0, kMuraOffset * flScale, 0, 0, 0, 0, // No mura comp for blue channel. }; - s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); + s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = GetBackend()->CreateBackendBlob( mura_scale_offset ); } bool g_bMuraDebugFullColor = false; @@ -577,7 +560,7 @@ bool set_mura_overlay( const char *path ) free(green_data); CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bFlippable = !BIsNested(); + texCreateFlags.bFlippable = true; texCreateFlags.bSampled = true; s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = vulkan_create_texture_from_bits(w, h, w, h, DRM_FORMAT_ABGR8888, texCreateFlags, (void*)data); free(data); @@ -703,7 +686,7 @@ struct commit_t : public gamescope::IWaitable if ( fb_id != 0 ) { - drm_unlock_fbid( &g_DRM, fb_id ); + GetBackend()->UnlockBackendFb( fb_id ); fb_id = 0; } @@ -922,18 +905,14 @@ static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_C static void _update_app_target_refresh_cycle() { - if ( BIsNested() ) - { - g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = 0; - g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ]; + if ( !GetBackend()->GetCurrentConnector() ) return; - } static gamescope::GamescopeScreenType last_type; static int last_target_fps; static bool first = true; - gamescope::GamescopeScreenType type = drm_get_screen_type( &g_DRM ); + gamescope::GamescopeScreenType type = GetBackend()->GetCurrentConnector()->GetScreenType(); int target_fps = g_nCombinedAppRefreshCycleOverride[type]; if ( !first && type == last_type && last_target_fps == target_fps ) @@ -952,7 +931,7 @@ static void _update_app_target_refresh_cycle() return; } - auto rates = drm_get_valid_refresh_rates( &g_DRM ); + auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); g_nDynamicRefreshRate[ type ] = 0; g_nSteamCompMgrTargetFPS = target_fps; @@ -1081,7 +1060,7 @@ static bool debugFocus = false; static bool drawDebugInfo = false; static bool debugEvents = false; bool steamMode = false; -static bool alwaysComposite = false; +bool alwaysComposite = false; static bool useXRes = true; struct wlr_buffer_map_entry { @@ -1355,7 +1334,7 @@ destroy_buffer( struct wl_listener *listener, void * ) if ( entry->fb_id != 0 ) { - drm_drop_fbid( &g_DRM, entry->fb_id ); + GetBackend()->DropBackendFb( entry->fb_id ); } wl_list_remove( &entry->listener.link ); @@ -1400,7 +1379,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff if (commit->fb_id) { - drm_lock_fbid( &g_DRM, commit->fb_id ); + GetBackend()->LockBackendFb( commit->fb_id ); } return commit; @@ -1425,20 +1404,17 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff commit->vulkanTex = vulkan_create_texture_from_wlr_buffer( buf ); assert( commit->vulkanTex ); + commit->fb_id = 0; struct wlr_dmabuf_attributes dmabuf = {0}; - if ( BIsNested() == false && wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) { - commit->fb_id = drm_fbid_from_dmabuf( &g_DRM, buf, &dmabuf ); + commit->fb_id = GetBackend()->ImportDmabufToBackend( buf, &dmabuf ); if ( commit->fb_id ) { - drm_lock_fbid( &g_DRM, commit->fb_id ); + GetBackend()->LockBackendFb( commit->fb_id ); } } - else - { - commit->fb_id = 0; - } entry.listener.notify = destroy_buffer; entry.buf = buf; @@ -1875,23 +1851,16 @@ bool MouseCursor::getTexture() uint32_t surfaceWidth; uint32_t surfaceHeight; - if ( BIsNested() == false && alwaysComposite == false ) - { - surfaceWidth = g_DRM.cursor_width; - surfaceHeight = g_DRM.cursor_height; - } - else - { - surfaceWidth = nDesiredWidth; - surfaceHeight = nDesiredHeight; - } + glm::uvec2 surfaceSize = GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nDesiredWidth, (uint32_t)nDesiredHeight } ); + surfaceWidth = surfaceSize.x; + surfaceHeight = surfaceSize.y; m_texture = nullptr; // Assume the cursor is fully translucent unless proven otherwise. bool bNoCursor = true; - std::shared_ptr> cursorBuffer = nullptr; + std::vector cursorBuffer; int nContentWidth = image->width; int nContentHeight = image->height; @@ -1913,14 +1882,12 @@ bool MouseCursor::getTexture() (unsigned char *)resizeBuffer.data(), nDesiredWidth, nDesiredHeight, 0, 4, 3, STBIR_FLAG_ALPHA_PREMULTIPLIED ); - cursorBuffer = std::make_shared>(surfaceWidth * surfaceHeight); - for (int i = 0; i < nDesiredHeight; i++) { - for (int j = 0; j < nDesiredWidth; j++) { - (*cursorBuffer)[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; - - if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { - bNoCursor = false; - } + cursorBuffer = std::vector(surfaceWidth * surfaceHeight); + for (int i = 0; i < nDesiredHeight; i++) + { + for (int j = 0; j < nDesiredWidth; j++) + { + cursorBuffer[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; } } @@ -1932,29 +1899,39 @@ bool MouseCursor::getTexture() } else { - cursorBuffer = std::make_shared>(surfaceWidth * surfaceHeight); - for (int i = 0; i < image->height; i++) { - for (int j = 0; j < image->width; j++) { - (*cursorBuffer)[i * surfaceWidth + j] = image->pixels[i * image->width + j]; - - if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { - bNoCursor = false; - } + cursorBuffer = std::vector(surfaceWidth * surfaceHeight); + for (int i = 0; i < image->height; i++) + { + for (int j = 0; j < image->width; j++) + { + cursorBuffer[i * surfaceWidth + j] = image->pixels[i * image->width + j]; } } } } + for (int i = 0; i < image->height; i++) + { + for (int j = 0; j < image->width; j++) + { + if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) + { + bNoCursor = false; + break; + } + } + } if (bNoCursor) - cursorBuffer = nullptr; + cursorBuffer.clear(); m_imageEmpty = bNoCursor; - if ( !g_bForceRelativeMouse ) + if ( !GetBackend()->GetNestedHints() || !g_bForceRelativeMouse ) { - sdlwindow_grab( m_imageEmpty ); - bSteamCompMgrGrab = BIsNested() && m_imageEmpty; + if ( GetBackend()->GetNestedHints() ) + GetBackend()->GetNestedHints()->SetRelativeMouseMode( m_imageEmpty ); + bSteamCompMgrGrab = GetBackend()->GetNestedHints() && m_imageEmpty; } m_dirty = false; @@ -1969,15 +1946,28 @@ bool MouseCursor::getTexture() UpdatePosition(); CVulkanTexture::createFlags texCreateFlags; - if ( BIsNested() == false ) + texCreateFlags.bFlippable = true; + if ( GetBackend()->SupportsPlaneHardwareCursor() ) { - texCreateFlags.bFlippable = true; texCreateFlags.bLinear = true; // cursor buffer needs to be linear // TODO: choose format & modifiers from cursor plane } - m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer->data()); - sdlwindow_cursor(std::move(cursorBuffer), nDesiredWidth, nDesiredHeight, image->xhot, image->yhot); + m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data()); + if ( GetBackend()->GetNestedHints() ) + { + auto info = std::make_shared( + gamescope::INestedHints::CursorInfo + { + .pPixels = std::move( cursorBuffer ), + .uWidth = (uint32_t) nDesiredWidth, + .uHeight = (uint32_t) nDesiredHeight, + .uXHotspot = image->xhot, + .uYHotspot = image->yhot, + }); + GetBackend()->GetNestedHints()->SetCursorImage( std::move( info ) ); + } + assert(m_texture); XFree(image); @@ -1993,11 +1983,7 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); } - if ( BIsNested() == false && alwaysComposite == false ) - { - nSize = std::min( nSize, g_DRM.cursor_width ); - nSize = std::min( nSize, g_DRM.cursor_height ); - } + nSize = std::min( nSize, glm::compMin( GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nSize, (uint32_t)nSize } ) ) ); nWidth = nSize; nHeight = nSize; @@ -2108,7 +2094,7 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str layer->applyColorMgmt = false; layer->tex = m_texture; - layer->fbid = BIsNested() ? 0 : m_texture->fbid(); + layer->fbid = m_texture->fbid(); layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NEAREST; layer->blackBorder = false; @@ -2466,6 +2452,7 @@ paint_all(bool async) frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; frameInfo.allowVRR = g_bAllowVRR; + frameInfo.bFadingOut = fadingOut; // If the window we'd paint as the base layer is the streaming client, // find the video underlay and put it up first in the scenegraph @@ -2578,7 +2565,7 @@ paint_all(bool async) else { auto tex = vulkan_get_hacky_blank_texture(); - if ( !BIsNested() && tex != nullptr ) + if ( !GetBackend()->UsesVulkanSwapchain() && tex != nullptr ) { // HACK! HACK HACK HACK // To avoid stutter when toggling the overlay on @@ -2620,21 +2607,16 @@ paint_all(bool async) global_focus.cursor->undirty(); } - bool bForceHideCursor = BIsSDLSession() && !bSteamCompMgrGrab; - - bool bDrewCursor = false; + bool bForceHideCursor = GetBackend()->GetNestedHints() && !bSteamCompMgrGrab; // Draw cursor if we need to if (input && !bForceHideCursor) { - int nLayerCountBefore = frameInfo.layerCount; global_focus.cursor->paint( input, w == input ? override : nullptr, &frameInfo); - int nLayerCountAfter = frameInfo.layerCount; - bDrewCursor = nLayerCountAfter > nLayerCountBefore; } - if ( !bValidContents || ( BIsNested() == false && g_DRM.paused == true ) ) + if ( !bValidContents || !GetBackend()->IsVisible() ) { return; } @@ -2665,35 +2647,34 @@ paint_all(bool async) g_bFSRActive = frameInfo.useFSRLayer0; - bool bWasFirstFrame = g_bFirstFrame; g_bFirstFrame = false; - bool bDoComposite = true; - update_app_target_refresh_cycle(); - int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; - - int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow - ? nDynamicRefresh - : drm_get_default_refresh( &g_DRM ); + const bool bSupportsDynamicRefresh = GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates().empty(); + if ( bSupportsDynamicRefresh ) + { + auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); - uint64_t now = get_time_in_nanos(); + int nDynamicRefresh = g_nDynamicRefreshRate[GetBackend()->GetScreenType()]; - if ( g_nOutputRefresh == nTargetRefresh ) - g_uDynamicRefreshEqualityTime = now; + int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow + ? nDynamicRefresh + : int( rates[ rates.size() - 1 ] ); - if ( !BIsNested() && g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) - drm_set_refresh( &g_DRM, nTargetRefresh ); + uint64_t now = get_time_in_nanos(); - bool bLayer0ScreenSize = close_enough(frameInfo.layers[0].scale.x, 1.0f) && close_enough(frameInfo.layers[0].scale.y, 1.0f); + if ( g_nOutputRefresh == nTargetRefresh ) + g_uDynamicRefreshEqualityTime = now; - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; + if ( g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) + GetBackend()->HackTemporarySetDynamicRefresh( nTargetRefresh ); + } bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount; if ( bDoMuraCompensation ) { - auto& MuraCorrectionImage = s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )]; + auto& MuraCorrectionImage = s_MuraCorrectionImage[GetBackend()->GetScreenType()]; int curLayer = frameInfo.layerCount++; FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; @@ -2708,292 +2689,24 @@ paint_all(bool async) layer->zpos = g_zposMuraCorrection; layer->filter = GamescopeUpscaleFilter::NEAREST; layer->tex = MuraCorrectionImage; - layer->ctm = s_MuraCTMBlob[drm_get_screen_type( &g_DRM )]; + layer->ctm = s_MuraCTMBlob[GetBackend()->GetScreenType()]; // Blending needs to be done in Gamma 2.2 space for mura correction to work. frameInfo.applyOutputColorMgmt = false; } - bool bWantsPartialComposite = frameInfo.layerCount >= 3 && !kDisablePartialComposition; - - bool bNeedsFullComposite = BIsNested(); - bNeedsFullComposite |= alwaysComposite; - bNeedsFullComposite |= bWasFirstFrame; - bNeedsFullComposite |= frameInfo.useFSRLayer0; - bNeedsFullComposite |= frameInfo.useNISLayer0; - bNeedsFullComposite |= frameInfo.blurLayer0; - bNeedsFullComposite |= bNeedsCompositeFromFilter; - bNeedsFullComposite |= bDrewCursor; - bNeedsFullComposite |= g_bColorSliderInUse; - bNeedsFullComposite |= fadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - for (uint32_t i = 0; i < EOTF_Count; i++) { - if (g_ColorMgmtLuts[i].HasLuts()) + if ( g_ColorMgmtLuts[i].HasLuts() ) { frameInfo.shaperLut[i] = g_ColorMgmtLuts[i].vk_lut1d; frameInfo.lut3D[i] = g_ColorMgmtLuts[i].vk_lut3d; } } - if ( !BIsNested() && g_bOutputHDREnabled ) + if ( GetBackend()->Present( &frameInfo, async ) != 0 ) { - bNeedsFullComposite |= g_bHDRItmEnable; - if ( !drm_supports_color_mgmt(&g_DRM) ) - bNeedsFullComposite |= ( frameInfo.layerCount > 1 || frameInfo.layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); - } - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); - - static int g_nLastSingleOverlayZPos = 0; - static bool g_bWasCompositing = false; - - if ( !bNeedsFullComposite && !bWantsPartialComposite ) - { - int ret = drm_prepare( &g_DRM, async, &frameInfo ); - if ( ret == 0 ) - { - bDoComposite = false; - g_bWasPartialComposite = false; - g_bWasCompositing = false; - if ( frameInfo.layerCount == 2 ) - g_nLastSingleOverlayZPos = frameInfo.layers[1].zpos; - } - else if ( ret == -EACCES ) - return; - } - - // Update to let the vblank manager know we are currently compositing. - g_VBlankTimer.UpdateWasCompositing( bDoComposite ); - - if ( bDoComposite == true ) - { - if ( kDisablePartialComposition ) - bNeedsFullComposite = true; - - struct FrameInfo_t compositeFrameInfo = frameInfo; - - if ( compositeFrameInfo.layerCount == 1 ) - { - // If we failed to flip a single plane then - // we definitely need to composite for some reason... - bNeedsFullComposite = true; - } - - if ( !bNeedsFullComposite ) - { - // If we want to partial composite, fallback to full - // composite if we have mismatching colorspaces in our overlays. - // This is 2, and we do i-1 so 1...layerCount. So AFTER we have removed baseplane. - // Overlays only. - // - // Josh: - // We could handle mismatching colorspaces for partial composition - // but I want to keep overlay -> partial composition promotion as simple - // as possible, using the same 3D + SHAPER LUTs + BLEND in DRM - // as changing them is incredibly expensive!! It takes forever. - // We can't just point it to random BDA or whatever, it has to be uploaded slowly - // thru registers which is SUPER SLOW. - // This avoids stutter. - for ( int i = 2; i < compositeFrameInfo.layerCount; i++ ) - { - if ( frameInfo.layers[i - 1].colorspace != frameInfo.layers[i].colorspace ) - { - bNeedsFullComposite = true; - break; - } - } - } - - // If we ever promoted from partial -> full, for the first frame - // do NOT defer this partial composition. - // We were already stalling for the full composition before, so it's not an issue - // for latency, we just need to make sure we get 1 partial frame that isn't deferred - // in time so we don't lose layers. - bool bDefer = !bNeedsFullComposite && ( !g_bWasCompositing || g_bWasPartialComposite ); - - // If doing a partial composition then remove the baseplane - // from our frameinfo to composite. - if ( !bNeedsFullComposite ) - { - for ( int i = 1; i < compositeFrameInfo.layerCount; i++ ) - compositeFrameInfo.layers[i - 1] = compositeFrameInfo.layers[i]; - compositeFrameInfo.layerCount -= 1; - - // When doing partial composition, apply the shaper + 3D LUT stuff - // at scanout. - for ( uint32_t nEOTF = 0; nEOTF < EOTF_Count; nEOTF++ ) { - compositeFrameInfo.shaperLut[ nEOTF ] = nullptr; - compositeFrameInfo.lut3D[ nEOTF ] = nullptr; - } - } - - // If using composite debug markers, make sure we mark them as partial - // so we know! - if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) - g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; - - std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); - - g_bWasCompositing = true; - - g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; - - if ( !oCompositeResult ) - { - xwm_log.errorf("vulkan_composite failed"); - return; - } - - vulkan_wait( *oCompositeResult, true ); - - if ( BIsNested() == true ) - { -#if HAVE_OPENVR - if ( BIsVRSession() ) - { - vulkan_present_to_openvr(); - } - else if ( BIsSDLSession() ) -#endif - { - vulkan_present_to_window(); - } - - // Update the time it took us to commit - g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - } - else - { - struct FrameInfo_t presentCompFrameInfo = {}; - - if ( bNeedsFullComposite ) - { - presentCompFrameInfo.applyOutputColorMgmt = false; - presentCompFrameInfo.layerCount = 1; - - FrameInfo_t::Layer_t *baseLayer = &presentCompFrameInfo.layers[ 0 ]; - baseLayer->scale.x = 1.0; - baseLayer->scale.y = 1.0; - baseLayer->opacity = 1.0; - baseLayer->zpos = g_zposBase; - - baseLayer->tex = vulkan_get_last_output_image( false, false ); - baseLayer->fbid = baseLayer->tex->fbid(); - baseLayer->applyColorMgmt = false; - - baseLayer->filter = GamescopeUpscaleFilter::NEAREST; - baseLayer->ctm = nullptr; - baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - g_bWasPartialComposite = false; - } - else - { - if ( g_bWasPartialComposite || !bDefer ) - { - presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - presentCompFrameInfo.layerCount = 2; - - presentCompFrameInfo.layers[ 0 ] = frameInfo.layers[ 0 ]; - presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; - - FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; - overlayLayer->scale.x = 1.0; - overlayLayer->scale.y = 1.0; - overlayLayer->opacity = 1.0; - overlayLayer->zpos = g_zposOverlay; - - overlayLayer->tex = vulkan_get_last_output_image( true, bDefer ); - overlayLayer->fbid = overlayLayer->tex->fbid(); - overlayLayer->applyColorMgmt = g_ColorMgmt.pending.enabled; - - overlayLayer->filter = GamescopeUpscaleFilter::NEAREST; - // Partial composition stuff has the same colorspace. - // So read that from the composite frame info - overlayLayer->ctm = nullptr; - overlayLayer->colorspace = compositeFrameInfo.layers[0].colorspace; - } - else - { - // Use whatever overlay we had last while waiting for the - // partial composition to have anything queued. - presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - presentCompFrameInfo.layerCount = 1; - - presentCompFrameInfo.layers[ 0 ] = frameInfo.layers[ 0 ]; - presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; - - FrameInfo_t::Layer_t *lastPresentedOverlayLayer = nullptr; - for (int i = 0; i < frameInfo.layerCount; i++) - { - if (frameInfo.layers[i].zpos == g_nLastSingleOverlayZPos) - { - lastPresentedOverlayLayer = &frameInfo.layers[i]; - break; - } - } - - if (lastPresentedOverlayLayer) - { - FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; - *overlayLayer = *lastPresentedOverlayLayer; - overlayLayer->zpos = g_zposOverlay; - - presentCompFrameInfo.layerCount = 2; - } - } - - g_bWasPartialComposite = true; - } - - int ret = drm_prepare( &g_DRM, async, &presentCompFrameInfo ); - - // Happens when we're VT-switched away - if ( ret == -EACCES ) - return; - - if ( ret != 0 ) - { - if ( g_DRM.current.mode_id == 0 ) - { - xwm_log.errorf("We failed our modeset and have no mode to fall back to! (Initial modeset failed?): %s", strerror(-ret)); - abort(); - } - - xwm_log.errorf("Failed to prepare 1-layer flip (%s), trying again with previous mode if modeset needed", strerror( -ret )); - - // Try once again to in case we need to fall back to another mode. - ret = drm_prepare( &g_DRM, async, &compositeFrameInfo ); - - // Happens when we're VT-switched away - if ( ret == -EACCES ) - return; - - if ( ret != 0 ) - { - xwm_log.errorf("Failed to prepare 1-layer flip entirely: %s", strerror( -ret )); - // We should always handle a 1-layer flip, this used to abort, - // but lets be more friendly and just avoid a commit and try again later. - // Let's re-poll our state, and force grab the best connector again. - // - // Some intense connector hotplugging could be occuring and the - // connector could become destroyed before we had a chance to use it - // as we hadn't reffed it in a commit yet. - g_DRM.out_of_date = 2; - drm_poll_state( &g_DRM ); - return; - } - } - - drm_commit( &g_DRM, &compositeFrameInfo ); - } - } - else - { - assert( BIsNested() == false ); - - drm_commit( &g_DRM, &frameInfo ); + return; } #if HAVE_PIPEWIRE @@ -3155,7 +2868,11 @@ paint_all(bool async) if ( !maxCLLNits && !maxFALLNits ) { - drm_supports_hdr( &g_DRM, &maxCLLNits, &maxFALLNits ); + if ( GetBackend()->GetCurrentConnector() ) + { + maxCLLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; + maxFALLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxFrameAverageLuminance; + } } if ( !maxCLLNits && !maxFALLNits ) @@ -3347,7 +3064,7 @@ paint_all(bool async) gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); - gpuvis_trace_printf( "paint_all %i layers, composite %i", (int)frameInfo.layerCount, bDoComposite ); + gpuvis_trace_printf( "paint_all %i layers", (int)frameInfo.layerCount ); } /* Get prop from window @@ -4308,35 +4025,17 @@ determine_and_apply_focus() } // Set SDL window title - if ( global_focus.focusWindow ) + if ( GetBackend()->GetNestedHints() ) { -#if HAVE_OPENVR - if ( BIsVRSession() ) - { - const char *title = global_focus.focusWindow->title - ? global_focus.focusWindow->title->c_str() - : nullptr; - vrsession_title( title, global_focus.focusWindow->icon ); - } -#endif - - if ( BIsSDLSession() ) + if ( global_focus.focusWindow ) { - sdlwindow_title( global_focus.focusWindow->title, global_focus.focusWindow->icon ); + GetBackend()->GetNestedHints()->SetVisible( true ); + GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title ); + GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon ); } - } - -#if HAVE_OPENVR - if ( BIsVRSession() ) - { - vrsession_set_dashboard_visible( global_focus.focusWindow != nullptr ); - } - else -#endif - { - if ( BIsSDLSession() ) + else { - sdlwindow_visible( global_focus.focusWindow != nullptr ); + GetBackend()->GetNestedHints()->SetVisible( false ); } } @@ -5263,14 +4962,14 @@ handle_client_message(xwayland_ctx_t *ctx, XClientMessageEvent *ev) } } -static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, int selectionTarget) +static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, GamescopeSelection eSelectionTarget) { Atom target; - if (selectionTarget == CLIPBOARD) + if (eSelectionTarget == GAMESCOPE_SELECTION_CLIPBOARD) { target = ctx->atoms.clipboard; } - else if (selectionTarget == PRIMARYSELECTION) + else if (eSelectionTarget == GAMESCOPE_SELECTION_PRIMARY) { target = ctx->atoms.primarySelection; } @@ -5282,13 +4981,13 @@ static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, i XSetSelectionOwner(ctx->dpy, target, ctx->ourWindow, CurrentTime); } -void gamescope_set_selection(std::string contents, int selection) +void gamescope_set_selection(std::string contents, GamescopeSelection eSelection) { - if (selection == CLIPBOARD) + if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) { clipboard = contents; } - else if (selection == PRIMARYSELECTION) + else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) { primarySelection = contents; } @@ -5296,7 +4995,7 @@ void gamescope_set_selection(std::string contents, int selection) gamescope_xwayland_server_t *server = NULL; for (int i = 0; (server = wlserver_get_xwayland_server(i)); i++) { - x11_set_selection_owner(server->ctx.get(), contents, selection); + x11_set_selection_owner(server->ctx.get(), contents, eSelection); } } @@ -5356,8 +5055,6 @@ handle_selection_request(xwayland_ctx_t *ctx, XSelectionRequestEvent *ev) static void handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) { - int selection; - Atom actual_type; int actual_format; unsigned long nitems; @@ -5375,37 +5072,34 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) &actual_type, &actual_format, &nitems, &bytes_after, &data); if (data) { const char *contents = (const char *) data; + defer( XFree( data ); ); if (ev->selection == ctx->atoms.clipboard) { - selection = CLIPBOARD; + if ( GetBackend()->GetNestedHints() ) + { + //GetBackend()->GetNestedHints()->SetSelection() + } + else + { + gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); + } } else if (ev->selection == ctx->atoms.primarySelection) { - selection = PRIMARYSELECTION; + if ( GetBackend()->GetNestedHints() ) + { + //GetBackend()->GetNestedHints()->SetSelection() + } + else + { + gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); + } } else { xwm_log.errorf( "Selection '%s' not supported. Ignoring", XGetAtomName(ctx->dpy, ev->selection) ); - goto done; - } - - if (BIsNested()) - { - /* - * gamescope_set_selection() doesn't need to be called here. - * sdlwindow_set_selection triggers a clipboard update, which - * then indirectly ccalls gamescope_set_selection() - */ - sdlwindow_set_selection(contents, selection); - } - else - { - gamescope_set_selection(contents, selection); } - -done: - XFree(data); } } } @@ -5578,10 +5272,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if (ev->atom == ctx->atoms.steamTouchClickModeAtom ) { g_nTouchClickMode = (enum wlserver_touch_click_mode) get_prop(ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, g_nDefaultTouchClickMode ); -#if HAVE_OPENVR - if (BIsVRSession()) - vrsession_update_touch_mode(); -#endif } if (ev->atom == ctx->atoms.steamStreamingClientAtom) { @@ -5750,7 +5440,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if (ev->window == x11_win(global_focus.focusWindow)) { - sdlwindow_title( w->title, w->icon ); + if ( GetBackend()->GetNestedHints() ) + GetBackend()->GetNestedHints()->SetTitle( w->title ); } } } @@ -5764,7 +5455,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if (ev->window == x11_win(global_focus.focusWindow)) { - sdlwindow_title( w->title, w->icon ); + if ( GetBackend()->GetNestedHints() ) + GetBackend()->GetNestedHints()->SetIcon( w->icon ); } } } @@ -5924,19 +5616,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) } if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) { - if ( !BIsNested() ) - { - g_DRM.force_internal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); - g_DRM.out_of_date = 1; - } + g_bForceInternal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); + GetBackend()->DirtyState(); } if ( ev->atom == ctx->atoms.gamescopeDisplayModeNudge ) { - if ( !BIsNested() ) - { - g_DRM.out_of_date = 2; - XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); - } + GetBackend()->DirtyState( true ); + XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); } if ( ev->atom == ctx->atoms.gamescopeNewScalingFilter ) { @@ -5969,7 +5655,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if ( ev->atom == ctx->atoms.gamescopeDebugForceHDRSupport ) { g_bForceHDRSupportDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDRSupport, 0 ); - drm_update_patched_edid(&g_DRM); + GetBackend()->HackUpdatePatchedEdid(); hasRepaint = true; } if ( ev->atom == ctx->atoms.gamescopeDebugHDRHeatmap ) @@ -6079,15 +5765,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) } hasRepaint = true; } - if ( ev->atom == ctx->atoms.gamescopeInternalDisplayBrightness ) - { - uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeInternalDisplayBrightness, 0 ); - if ( set_internal_display_brightness( bit_cast(val) ) ) - { - drm_update_patched_edid(&g_DRM); - hasRepaint = true; - } - } if ( ev->atom == ctx->atoms.gamescopeHDRInputGain ) { uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRInputGain, 0 ); @@ -6398,14 +6075,12 @@ steamcompmgr_exit(void) statsThreadSem.signal(); } - sdlwindow_shutdown(); + //sdlwindow_shutdown(); wlserver_lock(); wlserver_force_shutdown(); wlserver_unlock(false); - finish_drm( &g_DRM ); - pthread_exit(NULL); } @@ -7246,46 +6921,6 @@ load_mouse_cursor( MouseCursor *cursor, const char *path, int hx, int hy ) return cursor->setCursorImage((char *)data, w, h, hx, hy); } -static bool -load_host_cursor( MouseCursor *cursor ) -{ - extern const char *g_pOriginalDisplay; - - if ( !g_pOriginalDisplay ) - return false; - - Display *display = XOpenDisplay( g_pOriginalDisplay ); - if ( !display ) - return false; - defer( XCloseDisplay( display ) ); - - int xfixes_event, xfixes_error; - if (!XFixesQueryExtension(display, &xfixes_event, &xfixes_error)) - { - xwm_log.errorf("No XFixes extension on current compositor"); - return false; - } - - XFixesCursorImage *image = XFixesGetCursorImage( display ); - if ( !image ) - return false; - defer( XFree( image ) ); - - // image->pixels is `unsigned long*` :/ - // Thanks X11. - std::vector cursorData; - for (uint32_t y = 0; y < image->height; y++) - { - for (uint32_t x = 0; x < image->width; x++) - { - cursorData.push_back((uint32_t)image->pixels[image->height * y + x]); - } - } - - cursor->setCursorImage((char *)cursorData.data(), image->width, image->height, image->xhot, image->yhot); - return true; -} - const char* g_customCursorPath = nullptr; int g_customCursorHotspotX = 0; int g_customCursorHotspotY = 0; @@ -7493,7 +7128,6 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ ctx->atoms.gamescopeDebugHDRHeatmap = XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_HDR_HEATMAP", false ); ctx->atoms.gamescopeHDROutputFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_OUTPUT_FEEDBACK", false ); ctx->atoms.gamescopeSDROnHDRContentBrightness = XInternAtom( ctx->dpy, "GAMESCOPE_SDR_ON_HDR_CONTENT_BRIGHTNESS", false ); - ctx->atoms.gamescopeInternalDisplayBrightness = XInternAtom( ctx->dpy, "GAMESCOPE_INTERNAL_DISPLAY_BRIGHTNESS", false ); ctx->atoms.gamescopeHDRInputGain = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_INPUT_GAIN", false ); ctx->atoms.gamescopeSDRInputGain = XInternAtom( ctx->dpy, "GAMESCOPE_SDR_INPUT_GAIN", false ); ctx->atoms.gamescopeHDRItmEnable = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_ENABLE", false ); @@ -7588,20 +7222,21 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ } else { - if ( BIsNested() ) + std::optional oHostCursor = std::nullopt; + if ( GetBackend()->GetNestedHints() && ( oHostCursor = GetBackend()->GetNestedHints()->GetHostCursor() ) ) { - if ( !load_host_cursor( ctx->cursor.get() ) ) - { - xwm_log.errorf("Failed to load host cursor. Falling back to left_ptr."); - if (!ctx->cursor->setCursorImageByName("left_ptr")) - xwm_log.errorf("Failed to load mouse cursor: left_ptr"); - } + ctx->cursor->setCursorImage( + reinterpret_cast( oHostCursor->pPixels.data() ), + oHostCursor->uWidth, + oHostCursor->uHeight, + oHostCursor->uXHotspot, + oHostCursor->uYHotspot ); } else { - xwm_log.infof("Embedded, no cursor set. Using left_ptr by default."); - if (!ctx->cursor->setCursorImageByName("left_ptr")) - xwm_log.errorf("Failed to load mouse cursor: left_ptr"); + xwm_log.infof( "Embedded, no cursor set. Using left_ptr by default." ); + if ( !ctx->cursor->setCursorImageByName( "left_ptr" ) ) + xwm_log.errorf( "Failed to load mouse cursor: left_ptr" ); } } @@ -7613,7 +7248,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = nullptr) { - bool capable = drm_get_vrr_capable( &g_DRM ); + bool capable = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsVRR(); if ( capable != g_bVRRCapable_CachedValue || force ) { uint32_t capable_value = capable ? 1 : 0; @@ -7624,7 +7259,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = *needs_flush = true; } - bool HDR = BIsNested() ? vulkan_supports_hdr10() : drm_supports_hdr( &g_DRM ); + bool HDR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsHDR(); if ( HDR != g_bSupportsHDR_CachedValue || force ) { uint32_t hdr_value = HDR ? 1 : 0; @@ -7635,7 +7270,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = *needs_flush = true; } - bool in_use = drm_get_vrr_in_use( &g_DRM ); + bool in_use = GetBackend()->IsVRRActive(); if ( in_use != g_bVRRInUse_CachedValue || force ) { uint32_t in_use_value = in_use ? 1 : 0; @@ -7673,7 +7308,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) if (needs_flush) *needs_flush = true; - if ( drm_get_screen_type(&g_DRM) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) { XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); @@ -7683,20 +7318,20 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) return; } - if ( !g_DRM.pConnector || !g_DRM.pConnector->GetModeConnector() ) - { + if ( !GetBackend()->GetCurrentConnector() ) return; - } + + auto connectorModes = GetBackend()->GetCurrentConnector()->GetModes(); char modes[4096] = ""; int remaining_size = sizeof(modes) - 1; int len = 0; - for (int i = 0; remaining_size > 0 && i < g_DRM.pConnector->GetModeConnector()->count_modes; i++) + for (int i = 0; remaining_size > 0 && i < (int)connectorModes.size(); i++) { - const auto& mode = g_DRM.pConnector->GetModeConnector()->modes[i]; + const auto& mode = connectorModes[i]; int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", i == 0 ? "" : " ", - int(mode.hdisplay), int(mode.vdisplay), int(mode.vrefresh)); + int(mode.uWidth), int(mode.uHeight), int(mode.uRefresh)); len += mode_len; remaining_size -= mode_len; } @@ -7713,8 +7348,6 @@ extern int g_nPreferredOutputHeight; static bool g_bWasFSRActive = false; -extern std::atomic g_nCompletedPageFlipCount; - void steamcompmgr_check_xdg(bool vblank) { if (wlserver_xdg_dirty()) @@ -7741,25 +7374,22 @@ void steamcompmgr_check_xdg(bool vblank) void update_edid_prop() { - if ( !BIsNested() ) - { - const char *filename = drm_get_patched_edid_path(); - if (!filename) - return; + const char *filename = gamescope::GetPatchedEdidPath(); + if (!filename) + return; - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) + gamescope_xwayland_server_t *server = NULL; + for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) + { + XTextProperty text_property = { - XTextProperty text_property = - { - .value = (unsigned char *)filename, - .encoding = server->ctx->atoms.utf8StringAtom, - .format = 8, - .nitems = strlen(filename), - }; + .value = (unsigned char *)filename, + .encoding = server->ctx->atoms.utf8StringAtom, + .format = 8, + .nitems = strlen(filename), + }; - XSetTextProperty( server->ctx->dpy, server->ctx->root, &text_property, server->ctx->atoms.gamescopeDisplayEdidPath ); - } + XSetTextProperty( server->ctx->dpy, server->ctx->root, &text_property, server->ctx->atoms.gamescopeDisplayEdidPath ); } } @@ -7771,7 +7401,7 @@ steamcompmgr_main(int argc, char **argv) // Reset getopt() state optind = 1; - bSteamCompMgrGrab = BIsNested() && g_bForceRelativeMouse; + bSteamCompMgrGrab = GetBackend()->GetNestedHints() && g_bForceRelativeMouse; int o; int opt_index = -1; @@ -7872,10 +7502,6 @@ steamcompmgr_main(int argc, char **argv) currentOutputHeight = g_nPreferredOutputHeight; init_runtime_info(); -#if HAVE_OPENVR - if ( BIsVRSession() ) - vrsession_steam_mode( steamMode ); -#endif std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); @@ -7910,8 +7536,8 @@ steamcompmgr_main(int argc, char **argv) } bool vblank = false; - g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); - g_VBlankTimer.ArmNextVBlank( true ); + g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer() ); + GetVBlankTimer().ArmNextVBlank( true ); { gamescope_xwayland_server_t *pServer = NULL; @@ -7928,18 +7554,16 @@ steamcompmgr_main(int argc, char **argv) update_mode_atoms(root_ctx); XFlush(root_ctx->dpy); - if ( !BIsNested() ) - { - drm_update_patched_edid(&g_DRM); - update_edid_prop(); - } + GetBackend()->PostInit(); + + update_edid_prop(); update_screenshot_color_mgmt(); // Transpose to get this 3x3 matrix into the right state for applying as a 3x4 // on DRM + the Vulkan side. // ie. color.rgb = color.rgba * u_ctm[offsetLayerIdx]; - s_scRGB709To2020Matrix = drm_create_ctm(&g_DRM, glm::mat3x4(glm::transpose(k_2020_from_709))); + s_scRGB709To2020Matrix = GetBackend()->CreateBackendBlob( glm::mat3x4( glm::transpose( k_2020_from_709 ) ) ); for (;;) { @@ -7957,7 +7581,7 @@ steamcompmgr_main(int argc, char **argv) g_SteamCompMgrWaiter.PollEvents(); - if ( std::optional pendingVBlank = g_VBlankTimer.ProcessVBlank() ) + if ( std::optional pendingVBlank = GetVBlankTimer().ProcessVBlank() ) { g_SteamCompMgrVBlankTime = *pendingVBlank; vblank = true; @@ -7994,14 +7618,11 @@ steamcompmgr_main(int argc, char **argv) // If our DRM state is out-of-date, refresh it. This might update // the output size. - if ( BIsNested() == false ) + if ( GetBackend()->PollState() ) { - if ( drm_poll_state( &g_DRM ) ) - { - hasRepaint = true; + hasRepaint = true; - update_mode_atoms(root_ctx, &flush_root); - } + update_mode_atoms(root_ctx, &flush_root); } g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; @@ -8023,7 +7644,8 @@ steamcompmgr_main(int argc, char **argv) wlserver_unlock(); } - if ( BIsSDLSession() ) + // XXX(JoshA): Remake this. It sucks. + if ( GetBackend()->UsesVulkanSwapchain() ) { vulkan_remake_swapchain(); @@ -8162,7 +7784,7 @@ steamcompmgr_main(int argc, char **argv) { GamescopeAppTextureColorspace current_app_colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - std::shared_ptr app_hdr_metadata = nullptr; + std::shared_ptr app_hdr_metadata = nullptr; if ( g_HeldCommits[HELD_COMMIT_BASE] ) { current_app_colorspace = g_HeldCommits[HELD_COMMIT_BASE]->colorspace(); @@ -8192,7 +7814,7 @@ steamcompmgr_main(int argc, char **argv) std::vector app_hdr_metadata_blob; app_hdr_metadata_blob.resize((sizeof(hdr_metadata_infoframe) + (sizeof(uint32_t) - 1)) / sizeof(uint32_t)); memset(app_hdr_metadata_blob.data(), 0, sizeof(uint32_t) * app_hdr_metadata_blob.size()); - memcpy(app_hdr_metadata_blob.data(), &app_hdr_metadata->metadata, sizeof(hdr_metadata_infoframe)); + memcpy(app_hdr_metadata_blob.data(), &app_hdr_metadata->View(), sizeof(hdr_metadata_infoframe)); XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)app_hdr_metadata_blob.data(), (int)app_hdr_metadata_blob.size() ); @@ -8235,7 +7857,7 @@ steamcompmgr_main(int argc, char **argv) static int nIgnoredOverlayRepaints = 0; - const bool bVRR = drm_get_vrr_in_use( &g_DRM ); + const bool bVRR = GetBackend()->IsVRRActive(); // HACK: Disable tearing if we have an overlay to avoid stutters right now // TODO: Fix properly. @@ -8252,13 +7874,13 @@ steamcompmgr_main(int argc, char **argv) // 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 bNeedsSyncFlip = bForceSyncFlip || g_VBlankTimer.WasCompositing() || nIgnoredOverlayRepaints; - const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && g_bSupportsAsyncFlips && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; + const bool bNeedsSyncFlip = bForceSyncFlip || GetVBlankTimer().WasCompositing() || nIgnoredOverlayRepaints; + const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && GetBackend()->SupportsTearing() && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; bool bShouldPaint = false; if ( bDoAsyncFlip ) { - if ( hasRepaint && !g_VBlankTimer.WasCompositing() ) + if ( hasRepaint && !GetVBlankTimer().WasCompositing() ) bShouldPaint = true; } else @@ -8267,16 +7889,14 @@ steamcompmgr_main(int argc, char **argv) } // If we have a pending page flip and doing VRR, lets not do another... - if ( bVRR && g_nCompletedPageFlipCount != g_DRM.flipcount ) + if ( bVRR && GetBackend()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) bShouldPaint = false; if ( !bShouldPaint && hasRepaintNonBasePlane && vblank ) nIgnoredOverlayRepaints++; -#if HAVE_OPENVR - if ( BIsVRSession() && !vrsession_visible() ) + if ( !GetBackend()->IsVisible() ) bShouldPaint = false; -#endif if ( bShouldPaint ) { @@ -8294,7 +7914,7 @@ steamcompmgr_main(int argc, char **argv) // // Juuust in case pageflip handler doesn't happen // so we don't stop vblanking forever. - g_VBlankTimer.ArmNextVBlank( true ); + GetVBlankTimer().ArmNextVBlank( true ); } update_vrr_atoms(root_ctx, false, &flush_root); diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 2429ee066..69ed2cb4c 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -43,11 +43,6 @@ extern bool g_bForceHDRSupportDebug; extern EStreamColorspace g_ForcedNV12ColorSpace; -// Disable partial composition for now until we get -// composite priorities working in libliftoff + also -// use the proper libliftoff composite plane system. -static constexpr bool kDisablePartialComposition = true; - struct CursorBarrierInfo { int x1 = 0; @@ -164,7 +159,7 @@ extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; extern pid_t focusWindow_pid; void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); -void gamescope_set_selection(std::string contents, int selection); +void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); MouseCursor *steamcompmgr_get_current_cursor(); MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp index 7de514583..7f7e6ea93 100644 --- a/src/steamcompmgr_shared.hpp +++ b/src/steamcompmgr_shared.hpp @@ -1,8 +1,10 @@ #pragma once -#include "xwayland_ctx.hpp" #include #include +#include + +#include "xwayland_ctx.hpp" #include "gamescope-control-protocol.h" struct commit_t; diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp index 4b617584e..74cfe3dca 100644 --- a/src/vblankmanager.cpp +++ b/src/vblankmanager.cpp @@ -16,20 +16,12 @@ #include "vblankmanager.hpp" #include "steamcompmgr.hpp" -#include "wlserver.hpp" #include "main.hpp" -#include "drm.hpp" - -#if HAVE_OPENVR -#include "vr_session.hpp" -#endif LogScope g_VBlankLog("vblank"); // #define VBLANK_DEBUG -extern bool env_to_bool(const char *env); - namespace gamescope { CVBlankTimer::CVBlankTimer() @@ -37,10 +29,10 @@ namespace gamescope m_ulTargetVBlank = get_time_in_nanos(); m_ulLastVBlank = m_ulTargetVBlank; - const bool bShouldUseTimerFD = !BIsVRSession() || env_to_bool( "GAMESCOPE_DISABLE_TIMERFD" ); - - if ( bShouldUseTimerFD ) + if ( !GetBackend()->NeedsFrameSync() ) { + // Majority of backends fall down this optimal + // timerfd path, vs nudge thread. g_VBlankLog.infof( "Using timerfd." ); } else @@ -53,18 +45,8 @@ namespace gamescope abort(); } -#if HAVE_OPENVR - if ( BIsVRSession() ) - { - std::thread vblankThread( [this]() { this->VRNudgeThread(); } ); - vblankThread.detach(); - } - else -#endif - { - std::thread vblankThread( [this]() { this->NudgeThread(); } ); - vblankThread.detach(); - } + std::thread vblankThread( [this]() { this->NudgeThread(); } ); + vblankThread.detach(); } } @@ -112,7 +94,7 @@ namespace gamescope VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) { - const GamescopeScreenType eScreenType = drm_get_screen_type( &g_DRM ); + const GamescopeScreenType eScreenType = GetBackend()->GetScreenType(); const int nRefreshRate = GetRefresh(); const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; @@ -129,7 +111,7 @@ namespace gamescope ? m_ulVBlankDrawBufferRedZone : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); - bool bVRR = drm_get_vrr_in_use( &g_DRM ); + bool bVRR = GetBackend()->IsVRRActive(); uint64_t ulOffset = 0; if ( !bVRR ) { @@ -368,43 +350,6 @@ namespace gamescope #endif } -#if HAVE_OPENVR - void CVBlankTimer::VRNudgeThread() - { - pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); - - for ( ;; ) - { - vrsession_wait_until_visible(); - - // Includes redzone. - vrsession_framesync( ~0u ); - - uint64_t ulWakeupTime = get_time_in_nanos(); - - VBlankTime timeInfo = - { - .schedule = - { - .ulTargetVBlank = ulWakeupTime + 3'000'000, // Not right. just a stop-gap for now. - .ulScheduledWakeupPoint = ulWakeupTime, - }, - .ulWakeupTime = ulWakeupTime, - }; - - ssize_t ret = write( m_nNudgePipe[ 1 ], &timeInfo, sizeof( timeInfo ) ); - if ( ret <= 0 ) - { - g_VBlankLog.errorf_errno( "Nudge write failed" ); - } - else - { - gpuvis_trace_printf( "sent vblank (nudge thread)" ); - } - } - } -#endif - void CVBlankTimer::NudgeThread() { pthread_setname_np( pthread_self(), "gamescope-vblk" ); @@ -416,10 +361,9 @@ namespace gamescope if ( !m_bRunning ) return; - VBlankScheduleTime schedule = CalcNextWakeupTime( false ); - sleep_until_nanos( schedule.ulScheduledWakeupPoint ); - const uint64_t ulWakeupTime = get_time_in_nanos(); + VBlankScheduleTime schedule = GetBackend()->FrameSync(); + const uint64_t ulWakeupTime = get_time_in_nanos(); { std::unique_lock lock( m_ScheduleMutex ); @@ -446,5 +390,9 @@ namespace gamescope } } -gamescope::CVBlankTimer g_VBlankTimer{}; +gamescope::CVBlankTimer &GetVBlankTimer() +{ + static gamescope::CVBlankTimer s_VBlankTimer; + return s_VBlankTimer; +} diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp index 0a7f1c428..a08af1700 100644 --- a/src/vblankmanager.hpp +++ b/src/vblankmanager.hpp @@ -128,11 +128,9 @@ namespace gamescope // 93% by default. (kDefaultVBlankRateOfDecayPercentage) uint64_t m_ulVBlankRateOfDecayPercentage = kDefaultVBlankRateOfDecayPercentage; -#if HAVE_OPENVR - void VRNudgeThread(); -#endif void NudgeThread(); }; } -extern gamescope::CVBlankTimer g_VBlankTimer; +gamescope::CVBlankTimer &GetVBlankTimer(); + diff --git a/src/vr_session.cpp b/src/vr_session.cpp index 8696b62d2..9bf8085cd 100644 --- a/src/vr_session.cpp +++ b/src/vr_session.cpp @@ -1,7 +1,16 @@ -#include "vr_session.hpp" +#include +#include +#define VK_NO_PROTOTYPES +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#include +#pragma GCC diagnostic pop + +#include "backend.h" #include "main.hpp" #include "openvr.h" -#include "rendervulkan.hpp" #include "steamcompmgr.hpp" #include "wlserver.hpp" #include "log.hpp" @@ -11,107 +20,18 @@ #include #include #include -#include + +struct wlserver_input_method; + +extern bool steamMode; +extern int g_argc; +extern char **g_argv; static LogScope openvr_log("openvr"); static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, std::vector< std::string > &outDeviceExtensionList ); -static void vrsession_input_thread(); - -struct OpenVRSession -{ - const char *pchOverlayKey = nullptr; - const char *pchOverlayName = nullptr; - const char *pchOverlayIcon = nullptr; - bool bExplicitOverlayName = false; - bool bNudgeToVisible = false; - bool bEnableControlBar = false; - bool bEnableControlBarKeyboard = false; - bool bEnableControlBarClose = false; - bool bModal = false; - float flPhysicalWidth = 2.0f; - float flPhysicalCurvature = 0.0f; - float flPhysicalPreCurvePitch = 0.0f; - float flScrollSpeed = 8.0f; - float flScrollAccum[2] = { 0.0f, 0.0f }; - vr::VROverlayHandle_t hOverlay = vr::k_ulOverlayHandleInvalid; - vr::VROverlayHandle_t hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; - struct wlserver_input_method *pIME = nullptr; -}; - -OpenVRSession &GetVR() -{ - static OpenVRSession s_Global; - return s_Global; -} - -bool vr_init(int argc, char **argv) -{ - vr::EVRInitError error = vr::VRInitError_None; - VR_Init(&error, vr::VRApplication_Background); - - if ( error != vr::VRInitError_None ) - { - openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); - return false; - } - - // Reset getopt() state - optind = 1; - - int o; - int opt_index = -1; - while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) - { - const char *opt_name; - switch (o) { - case 0: // long options without a short option - opt_name = gamescope_options[opt_index].name; - if (strcmp(opt_name, "vr-overlay-key") == 0) { - GetVR().pchOverlayKey = optarg; - } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { - GetVR().pchOverlayName = optarg; - GetVR().bExplicitOverlayName = true; - } else if (strcmp(opt_name, "vr-overlay-default-name") == 0) { - GetVR().pchOverlayName = optarg; - } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { - GetVR().pchOverlayIcon = optarg; - } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { - GetVR().bNudgeToVisible = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar") == 0) { - GetVR().bEnableControlBar = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-keyboard") == 0) { - GetVR().bEnableControlBarKeyboard = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { - GetVR().bEnableControlBarClose = true; - } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { - GetVR().bModal = true; - } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { - GetVR().flPhysicalWidth = atof( optarg ); - if ( GetVR().flPhysicalWidth <= 0.0f ) - GetVR().flPhysicalWidth = 2.0f; - } else if (strcmp(opt_name, "vr-overlay-physical-curvature") == 0) { - GetVR().flPhysicalCurvature = atof( optarg ); - } else if (strcmp(opt_name, "vr-overlay-physical-pre-curve-pitch") == 0) { - GetVR().flPhysicalPreCurvePitch = atof( optarg ); - } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { - GetVR().flScrollSpeed = atof( optarg ); - } - break; - case '?': - assert(false); // unreachable - } - } - - if (!GetVR().pchOverlayKey) - GetVR().pchOverlayKey = wlserver_get_wl_display_name(); - - if (!GetVR().pchOverlayName) - GetVR().pchOverlayName = "Gamescope"; - return true; -} // Not in public headers yet. namespace vr @@ -122,302 +42,6 @@ namespace vr const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); } -bool vrsession_init() -{ - // Setup the overlay. - - if ( !vr::VROverlay() ) - { - openvr_log.errorf("SteamVR runtime version mismatch!\n"); - return false; - } - - vr::VROverlay()->CreateDashboardOverlay( - GetVR().pchOverlayKey, - GetVR().pchOverlayName, - &GetVR().hOverlay, &GetVR().hOverlayThumbnail ); - - vr::VROverlay()->SetOverlayInputMethod( GetVR().hOverlay, vr::VROverlayInputMethod_Mouse ); - - vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; - vr::VROverlay()->SetOverlayMouseScale( GetVR().hOverlay, &vMouseScale ); - - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBar, GetVR().bEnableControlBar ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, GetVR().bEnableControlBarKeyboard ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarClose, GetVR().bEnableControlBarClose ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_WantsModalBehavior, GetVR().bModal ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); - vrsession_update_touch_mode(); - - vr::VROverlay()->SetOverlayWidthInMeters( GetVR().hOverlay, GetVR().flPhysicalWidth ); - vr::VROverlay()->SetOverlayCurvature ( GetVR().hOverlay, GetVR().flPhysicalCurvature ); - vr::VROverlay()->SetOverlayPreCurvePitch( GetVR().hOverlay, GetVR().flPhysicalPreCurvePitch ); - - if ( GetVR().pchOverlayIcon ) - { - vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); - if( err != vr::VROverlayError_None ) - { - openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", GetVR().pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); - } - } - - // Setup misc. stuff - - g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); - - std::thread input_thread_vrinput( vrsession_input_thread ); - input_thread_vrinput.detach(); - - return true; -} - -std::mutex g_OverlayVisibleMutex; -std::condition_variable g_OverlayVisibleCV; -std::atomic g_bOverlayVisible = { false }; - -bool vrsession_visible() -{ - return g_bOverlayVisible.load(); -} - -void vrsession_set_dashboard_visible( bool bVisible ) -{ - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); -} - -void vrsession_wait_until_visible() -{ - if (vrsession_visible()) - return; - - std::unique_lock lock(g_OverlayVisibleMutex); - g_OverlayVisibleCV.wait( lock, []{ return g_bOverlayVisible.load(); } ); -} - -void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ) -{ - vr::Texture_t texture = { pTextureData, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; - vr::VROverlay()->SetOverlayTexture( GetVR().hOverlay, &texture ); - if ( GetVR().bNudgeToVisible ) - { - vr::VROverlay()->ShowDashboard( GetVR().pchOverlayKey ); - GetVR().bNudgeToVisible = false; - } -} - -static void vector_append_unique_str( std::vector& exts, const char *str ) -{ - for ( auto &c_str : exts ) - { - if ( !strcmp( c_str, str ) ) - return; - } - - exts.push_back( str ); -} - -void vrsession_append_instance_exts( std::vector& exts ) -{ - static std::vector s_exts; - GetVulkanInstanceExtensionsRequired( s_exts ); - - for (const auto &str : s_exts) - vector_append_unique_str( exts, str.c_str() ); -} - -void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ) -{ - static std::vector s_exts; - GetVulkanDeviceExtensionsRequired( physDev, s_exts ); - - for (const auto &str : s_exts) - vector_append_unique_str( exts, str.c_str() ); -} - -bool vrsession_framesync( uint32_t timeoutMS ) -{ - return vr::VROverlay()->WaitFrameSync( timeoutMS ) != vr::VROverlayError_None; -} - -/* -static int VRButtonToWLButton( vr::EVRMouseButton mb ) -{ - switch( mb ) - { - default: - case vr::VRMouseButton_Left: - return BTN_LEFT; - case vr::VRMouseButton_Right: - return BTN_RIGHT; - case vr::VRMouseButton_Middle: - return BTN_MIDDLE; - } -} -*/ - -bool vrsession_ime_init() -{ - GetVR().pIME = create_local_ime(); - return true; -} - -static void vrsession_input_thread() -{ - pthread_setname_np( pthread_self(), "gamescope-vrinp" ); - - // Josh: PollNextOverlayEvent sucks. - // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. - while (true) - { - vr::VREvent_t vrEvent; - while( vr::VROverlay()->PollNextOverlayEvent( GetVR().hOverlay, &vrEvent, sizeof( vrEvent ) ) ) - { - uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; - - switch( vrEvent.eventType ) - { - case vr::VREvent_OverlayClosed: - case vr::VREvent_Quit: - raise( SIGTERM ); - break; - - case vr::VREvent_KeyboardCharInput: - { - if (GetVR().pIME) - { - type_text(GetVR().pIME, vrEvent.data.keyboard.cNewInput); - } - break; - } - - case vr::VREvent_MouseMove: - { - float x = vrEvent.data.mouse.x; - float y = g_nOutputHeight - vrEvent.data.mouse.y; - - x /= (float)g_nOutputWidth; - y /= (float)g_nOutputHeight; - - wlserver_lock(); - wlserver_touchmotion( x, y, 0, timestamp ); - wlserver_unlock(); - break; - } - case vr::VREvent_MouseButtonUp: - case vr::VREvent_MouseButtonDown: - { - float x = vrEvent.data.mouse.x; - float y = g_nOutputHeight - vrEvent.data.mouse.y; - - x /= (float)g_nOutputWidth; - y /= (float)g_nOutputHeight; - - wlserver_lock(); - if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) - wlserver_touchdown( x, y, 0, timestamp ); - else - wlserver_touchup( 0, timestamp ); - wlserver_unlock(); - break; - } - - case vr::VREvent_ScrollSmooth: - { - wlserver_lock(); - - GetVR().flScrollAccum[0] += -vrEvent.data.scroll.xdelta * GetVR().flScrollSpeed; - GetVR().flScrollAccum[1] += -vrEvent.data.scroll.ydelta * GetVR().flScrollSpeed; - - float dx, dy; - GetVR().flScrollAccum[0] = modf( GetVR().flScrollAccum[0], &dx ); - GetVR().flScrollAccum[1] = modf( GetVR().flScrollAccum[1], &dy ); - - wlserver_mousewheel( dx, dy, timestamp ); - wlserver_unlock(); - break; - } - - case vr::VREvent_ButtonPress: - { - 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) - openvr_log.infof("STEAM button pressed."); - else - openvr_log.infof("QAM button pressed."); - - wlserver_open_steam_menu( button == vr::k_EButton_QAM ); - break; - } - - case vr::VREvent_OverlayShown: - case vr::VREvent_OverlayHidden: - { - { - std::unique_lock lock(g_OverlayVisibleMutex); - g_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; - } - g_OverlayVisibleCV.notify_all(); - } - } - } - sleep_for_nanos(2'000'000); - } -} - -void vrsession_update_touch_mode() -{ - const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); -} - -struct rgba_t -{ - uint8_t r,g,b,a; -}; - -void vrsession_title( const char *title, std::shared_ptr> icon ) -{ - if ( !GetVR().bExplicitOverlayName ) - { - vr::VROverlay()->SetOverlayName( GetVR().hOverlay, (title && *title) ? title : GetVR().pchOverlayName ); - } - - if ( icon && icon->size() >= 3 ) - { - const uint32_t width = (*icon)[0]; - const uint32_t height = (*icon)[1]; - - for (uint32_t& val : *icon) - { - rgba_t rgb = *((rgba_t*)&val); - std::swap(rgb.r, rgb.b); - val = *((uint32_t*)&rgb); - } - - vr::VROverlay()->SetOverlayRaw( GetVR().hOverlayThumbnail, &(*icon)[2], width, height, sizeof(uint32_t) ); - } - else if ( GetVR().pchOverlayName ) - { - vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); - } - else - { - vr::VROverlay()->ClearOverlayTexture( GetVR().hOverlayThumbnail ); - } -} - -void vrsession_steam_mode( bool steamMode ) -{ - vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); -} - /////////////////////////////////////////////// // Josh: // GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( @@ -508,3 +132,630 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, return true; } + +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{}; + } + 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"; + } + + private: + BackendConnectorHDRInfo m_HDRInfo{}; + }; + + class COpenVRBackend final : public CBaseBackend, public INestedHints + { + public: + COpenVRBackend() + { + } + + virtual ~COpenVRBackend() + { + } + + ///////////// + // IBackend + ///////////// + + virtual bool Init() override + { + vr::EVRInitError error = vr::VRInitError_None; + VR_Init(&error, vr::VRApplication_Background); + + if ( error != vr::VRInitError_None ) + { + openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); + return false; + } + + // Reset getopt() state + optind = 1; + + int o; + int opt_index = -1; + while ((o = getopt_long(g_argc, g_argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) + { + const char *opt_name; + switch (o) { + case 0: // long options without a short option + opt_name = gamescope_options[opt_index].name; + if (strcmp(opt_name, "vr-overlay-key") == 0) { + m_pchOverlayKey = optarg; + } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { + m_pchOverlayName = optarg; + m_bExplicitOverlayName = true; + } else if (strcmp(opt_name, "vr-overlay-default-name") == 0) { + m_pchOverlayName = optarg; + } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { + m_pchOverlayIcon = optarg; + } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { + m_bNudgeToVisible = true; + } else if (strcmp(opt_name, "vr-overlay-enable-control-bar") == 0) { + m_bEnableControlBar = true; + } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-keyboard") == 0) { + 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-modal") == 0) { + m_bModal = true; + } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { + m_flPhysicalWidth = atof( optarg ); + if ( m_flPhysicalWidth <= 0.0f ) + m_flPhysicalWidth = 2.0f; + } else if (strcmp(opt_name, "vr-overlay-physical-curvature") == 0) { + m_flPhysicalCurvature = atof( optarg ); + } else if (strcmp(opt_name, "vr-overlay-physical-pre-curve-pitch") == 0) { + m_flPhysicalPreCurvePitch = atof( optarg ); + } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { + m_flScrollSpeed = atof( optarg ); + } + break; + case '?': + assert(false); // unreachable + } + } + + if ( m_pchOverlayKey ) + m_pchOverlayKey = wlserver_get_wl_display_name(); + + if ( m_pchOverlayName ) + m_pchOverlayName = "Gamescope"; + + if ( !vr::VROverlay() ) + { + openvr_log.errorf( "SteamVR runtime version mismatch!\n" ); + return false; + } + + vr::VROverlay()->CreateDashboardOverlay( + m_pchOverlayKey, + m_pchOverlayName, + &m_hOverlay, &m_hOverlayThumbnail ); + + vr::VROverlay()->SetOverlayInputMethod( m_hOverlay, vr::VROverlayInputMethod_Mouse ); + + vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; + vr::VROverlay()->SetOverlayMouseScale( m_hOverlay, &vMouseScale ); + + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBar, m_bEnableControlBar ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, m_bEnableControlBarKeyboard ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarClose, m_bEnableControlBarClose ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_bModal ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); + + vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_flPhysicalWidth ); + vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_flPhysicalCurvature ); + vr::VROverlay()->SetOverlayPreCurvePitch( m_hOverlay, m_flPhysicalPreCurvePitch ); + + if ( m_pchOverlayIcon ) + { + vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); + if( err != vr::VROverlayError_None ) + { + openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", m_pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); + } + } + + // Setup misc. stuff + g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); + + std::thread input_thread_vrinput( [this](){ this->VRInputThread(); } ); + input_thread_vrinput.detach(); + + return true; + } + + virtual bool PostInit() override + { + m_pIME = create_local_ime(); + if ( !m_pIME ) + return false; + + return true; + } + + virtual std::span GetInstanceExtensions() const override + { + static std::vector s_exts; + GetVulkanInstanceExtensionsRequired( s_exts ); + static std::vector s_extPtrs; + for ( const std::string &ext : s_exts ) + s_extPtrs.emplace_back( ext.c_str() ); + return std::span{ s_extPtrs.begin(), s_extPtrs.end() }; + } + virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { + static std::vector s_exts; + GetVulkanDeviceExtensionsRequired( pVkPhysicalDevice, s_exts ); + static std::vector s_extPtrs; + for ( const std::string &ext : s_exts ) + s_extPtrs.emplace_back( ext.c_str() ); + return std::span{ s_extPtrs.begin(), s_extPtrs.end() }; + } + virtual VkImageLayout GetPresentLayout() const override + { + return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + virtual void GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; + } + virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { + return true; + } + + virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + { + // TODO: Resolve const crap + std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + if ( !oCompositeResult ) + return -EINVAL; + + UpdateTouchMode(); + + auto outputImage = vulkan_get_last_output_image( false, false ); + + vr::VRVulkanTextureData_t data = + { + .m_nImage = (uint64_t)(uintptr_t)outputImage->vkImage(), + .m_pDevice = g_device.device(), + .m_pPhysicalDevice = g_device.physDev(), + .m_pInstance = g_device.instance(), + .m_pQueue = g_device.queue(), + .m_nQueueFamilyIndex = g_device.queueFamily(), + .m_nWidth = outputImage->width(), + .m_nHeight = outputImage->height(), + .m_nFormat = outputImage->format(), + .m_nSampleCount = 1, + }; + + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); + + // Wait for the composite result on our side *after* we + // commit the buffer to the compositor to avoid a bubble. + vulkan_wait( *oCompositeResult, true ); + + vr::Texture_t texture = { &data, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; + vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); + if ( m_bNudgeToVisible ) + { + vr::VROverlay()->ShowDashboard( m_pchOverlayKey ); + m_bNudgeToVisible = false; + } + + return 0; + } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override + { + } + + virtual bool PollState() override + { + return false; + } + + virtual std::shared_ptr CreateBackendBlob( std::span data ) override + { + return std::make_shared( data ); + } + + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override + { + return 0; + } + + virtual void LockBackendFb( uint32_t uFbId ) override + { + abort(); + } + virtual void UnlockBackendFb( uint32_t uFbId ) override + { + abort(); + } + virtual void DropBackendFb( uint32_t uFbId ) override + { + abort(); + } + + 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 IsVisible() const override + { + return m_bOverlayVisible.load(); + } + + virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { + return uvecSize; + } + + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { + return false; + } + + virtual void HackUpdatePatchedEdid() override + { + } + + virtual bool NeedsFrameSync() const override + { + 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 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 + { + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); + } + virtual void SetTitle( std::shared_ptr szTitle ) override + { + if ( !m_bExplicitOverlayName ) + vr::VROverlay()->SetOverlayName( m_hOverlay, szTitle ? szTitle->c_str() : m_pchOverlayName ); + + } + virtual void SetIcon( std::shared_ptr> uIconPixels ) override + { + if ( 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( m_hOverlayThumbnail, &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); + } + else if ( m_pchOverlayName ) + { + vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); + } + else + { + vr::VROverlay()->ClearOverlayTexture( m_hOverlayThumbnail ); + } + } + virtual std::optional GetHostCursor() override + { + return std::nullopt; + } + + protected: + + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override + { + } + + private: + + void UpdateTouchMode() + { + const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; + vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); + } + + void WaitUntilVisible() + { + m_bOverlayVisible.wait( false ); + } + + void VRInputThread() + { + pthread_setname_np( pthread_self(), "gamescope-vrinp" ); + + // Josh: PollNextOverlayEvent sucks. + // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. + while (true) + { + vr::VREvent_t vrEvent; + while( vr::VROverlay()->PollNextOverlayEvent( m_hOverlay, &vrEvent, sizeof( vrEvent ) ) ) + { + uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; + + switch( vrEvent.eventType ) + { + case vr::VREvent_OverlayClosed: + case vr::VREvent_Quit: + raise( SIGTERM ); + break; + + case vr::VREvent_KeyboardCharInput: + { + if (m_pIME) + { + type_text(m_pIME, vrEvent.data.keyboard.cNewInput); + } + break; + } + + case vr::VREvent_MouseMove: + { + float x = vrEvent.data.mouse.x; + float y = g_nOutputHeight - vrEvent.data.mouse.y; + + x /= (float)g_nOutputWidth; + y /= (float)g_nOutputHeight; + + wlserver_lock(); + wlserver_touchmotion( x, y, 0, timestamp ); + wlserver_unlock(); + break; + } + case vr::VREvent_MouseButtonUp: + case vr::VREvent_MouseButtonDown: + { + float x = vrEvent.data.mouse.x; + float y = g_nOutputHeight - vrEvent.data.mouse.y; + + x /= (float)g_nOutputWidth; + y /= (float)g_nOutputHeight; + + wlserver_lock(); + if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) + wlserver_touchdown( x, y, 0, timestamp ); + else + wlserver_touchup( 0, timestamp ); + wlserver_unlock(); + break; + } + + case vr::VREvent_ScrollSmooth: + { + wlserver_lock(); + + m_flScrollAccum[0] += -vrEvent.data.scroll.xdelta * m_flScrollSpeed; + m_flScrollAccum[1] += -vrEvent.data.scroll.ydelta * m_flScrollSpeed; + + float dx, dy; + m_flScrollAccum[0] = modf( m_flScrollAccum[0], &dx ); + m_flScrollAccum[1] = modf( m_flScrollAccum[1], &dy ); + + wlserver_mousewheel( dx, dy, timestamp ); + wlserver_unlock(); + break; + } + + case vr::VREvent_ButtonPress: + { + 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) + openvr_log.infof("STEAM button pressed."); + else + openvr_log.infof("QAM button pressed."); + + wlserver_open_steam_menu( button == vr::k_EButton_QAM ); + break; + } + + case vr::VREvent_OverlayShown: + case vr::VREvent_OverlayHidden: + { + m_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; + m_bOverlayVisible.notify_all(); + break; + } + + default: + break; + } + } + sleep_for_nanos( 2'000'000ul ); + } + } + + CVROverlayConnector m_Connector; + const char *m_pchOverlayKey = nullptr; + const char *m_pchOverlayName = nullptr; + const char *m_pchOverlayIcon = nullptr; + bool m_bExplicitOverlayName = false; + bool m_bNudgeToVisible = false; + bool m_bEnableControlBar = false; + bool m_bEnableControlBarKeyboard = false; + bool m_bEnableControlBarClose = false; + bool m_bModal = false; + float m_flPhysicalWidth = 2.0f; + float m_flPhysicalCurvature = 0.0f; + float m_flPhysicalPreCurvePitch = 0.0f; + float m_flScrollSpeed = 8.0f; + float m_flScrollAccum[2] = { 0.0f, 0.0f }; + vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; + vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; + wlserver_input_method *m_pIME = nullptr; + std::atomic m_bOverlayVisible = { false }; + }; + + ///////////////////////// + // Backend Instantiator + ///////////////////////// + + template <> + bool IBackend::Set() + { + return Set( new COpenVRBackend{} ); + } +} diff --git a/src/vr_session.hpp b/src/vr_session.hpp deleted file mode 100644 index f8007ae6d..000000000 --- a/src/vr_session.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#define VK_NO_PROTOTYPES -#include - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#include -#pragma GCC diagnostic pop - -bool vr_init(int argc, char **argv); - -bool vrsession_init(); -bool vrsession_visible(); -void vrsession_wait_until_visible(); -void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ); - -void vrsession_append_instance_exts( std::vector& exts ); -void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ); - -bool vrsession_framesync( uint32_t timeoutMS ); -void vrsession_update_touch_mode(); - -void vrsession_title( const char *title, std::shared_ptr> icon ); -bool vrsession_ime_init(); - -void vrsession_steam_mode( bool bSteamMode ); - -void vrsession_set_dashboard_visible( bool bVisible ); diff --git a/src/waitable.h b/src/waitable.h index cd806f698..fa7022fbe 100644 --- a/src/waitable.h +++ b/src/waitable.h @@ -7,6 +7,7 @@ #include #include +#include #include "log.hpp" diff --git a/src/wlserver.cpp b/src/wlserver.cpp index cc5e68be0..3a3a8a05e 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -40,13 +40,12 @@ #include "presentation-time-protocol.h" #include "wlserver.hpp" -#include "drm.hpp" +#include "drm_include.h" #include "main.hpp" #include "steamcompmgr.hpp" #include "log.hpp" #include "ime.hpp" #include "xwayland_ctx.hpp" -#include "sdlwindow.hpp" #if HAVE_PIPEWIRE #include "pipewire.hpp" @@ -766,8 +765,7 @@ static void gamescope_swapchain_set_hdr_metadata( struct wl_client *client, stru infoframe.max_cll = max_cll; infoframe.max_fall = max_fall; - wl_info->swapchain_feedback->hdr_metadata_blob = - drm_create_hdr_metadata_blob( &g_DRM, &metadata ); + wl_info->swapchain_feedback->hdr_metadata_blob = GetBackend()->CreateBackendBlob( metadata ); } } @@ -889,6 +887,46 @@ static const struct gamescope_control_interface gamescope_control_impl = { .set_app_target_refresh_cycle = gamescope_control_set_app_target_refresh_cycle, }; +static uint32_t get_conn_display_info_flags() +{ + gamescope::IBackendConnector *pConn = GetBackend()->GetCurrentConnector(); + + if ( !pConn ) + return 0; + + uint32_t flags = 0; + if ( pConn->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; + if ( pConn->SupportsVRR() ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; + if ( pConn->GetHDRInfo().bExposeHDRSupport ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; + + return flags; +} + +void wlserver_send_gamescope_control( wl_resource *control ) +{ + assert( wlserver_is_lock_held() ); + + gamescope::IBackendConnector *pConn = GetBackend()->GetCurrentConnector(); + if ( !pConn ) + return; + + uint32_t flags = get_conn_display_info_flags(); + + struct wl_array display_rates; + wl_array_init(&display_rates); + if ( pConn->GetValidDynamicRefreshRates().size() ) + { + size_t size = pConn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); + uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); + memcpy( ptr, pConn->GetValidDynamicRefreshRates().data(), size ); + } + gamescope_control_send_active_display_info( control, pConn->GetName(), pConn->GetMake(), pConn->GetModel(), flags, &display_rates ); + wl_array_release(&display_rates); +} + static void gamescope_control_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) { struct wl_resource *resource = wl_resource_create( client, &gamescope_control_interface, version, id ); @@ -904,10 +942,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_PIXEL_FILTER, 1, 0 ); gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); - if ( !BIsNested() ) - { - drm_send_gamescope_control( resource, &g_DRM ); - } + wlserver_send_gamescope_control( resource ); wlserver.gamescope_controls.push_back(resource); } @@ -1249,14 +1284,16 @@ void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle /////////////////////// +bool wlsession_active() +{ + return wlserver.wlr.session->active; +} + static void handle_session_active( struct wl_listener *listener, void *data ) { - if (wlserver.wlr.session->active) { - g_DRM.out_of_date = 1; - g_DRM.needs_modeset = 1; - } - g_DRM.paused = !wlserver.wlr.session->active; - wl_log.infof( "Session %s", g_DRM.paused ? "paused" : "resumed" ); + if (wlserver.wlr.session->active) + GetBackend()->DirtyState( true, true ); + wl_log.infof( "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" ); } static void handle_wlr_log(enum wlr_log_importance importance, const char *fmt, va_list args) @@ -1336,7 +1373,7 @@ bool wlsession_init( void ) { }; wlserver_set_output_info( &output_info ); - if ( BIsNested() ) + if ( !GetBackend()->IsSessionBased() ) return true; wlserver.wlr.session = wlr_session_create( wlserver.display ); @@ -1354,7 +1391,7 @@ bool wlsession_init( void ) { static void kms_device_handle_change( struct wl_listener *listener, void *data ) { - g_DRM.out_of_date = 1; + GetBackend()->DirtyState(); wl_log.infof( "Got change event for KMS device" ); nudge_steamcompmgr(); @@ -1570,8 +1607,6 @@ void xdg_surface_new(struct wl_listener *listener, void *data) bool wlserver_init( void ) { assert( wlserver.display != nullptr ); - bool bIsDRM = !BIsNested(); - wl_list_init(&pending_surfaces); wlserver.event_loop = wl_display_get_event_loop(wlserver.display); @@ -1583,7 +1618,7 @@ bool wlserver_init( void ) { wl_signal_add( &wlserver.wlr.multi_backend->events.new_input, &new_input_listener ); - if ( bIsDRM == True ) + if ( GetBackend()->IsSessionBased() ) { wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.display, wlserver.wlr.session ); if ( wlserver.wlr.libinput_backend == NULL) @@ -1945,22 +1980,23 @@ static void apply_touchscreen_orientation(double *x, double *y ) double ty = 0; // Use internal screen always for orientation purposes. - switch ( g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) + switch ( GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )->GetCurrentOrientation() ) { default: - case DRM_MODE_ROTATE_0: + case GAMESCOPE_PANEL_ORIENTATION_AUTO: + case GAMESCOPE_PANEL_ORIENTATION_0: tx = *x; ty = *y; break; - case DRM_MODE_ROTATE_90: + case GAMESCOPE_PANEL_ORIENTATION_90: tx = 1.0 - *y; ty = *x; break; - case DRM_MODE_ROTATE_180: + case GAMESCOPE_PANEL_ORIENTATION_180: tx = 1.0 - *x; ty = 1.0 - *y; break; - case DRM_MODE_ROTATE_270: + case GAMESCOPE_PANEL_ORIENTATION_270: tx = *y; ty = 1.0 - *x; break; @@ -1974,12 +2010,12 @@ bool g_bTrackpadTouchExternalDisplay = false; int get_effective_touch_mode() { - if (!BIsNested() && g_bTrackpadTouchExternalDisplay) - { - gamescope::GamescopeScreenType screenType = drm_get_screen_type(&g_DRM); - if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) - return WLSERVER_TOUCH_CLICK_TRACKPAD; - } + if ( !GetBackend() || !GetBackend()->GetCurrentConnector() ) + return g_nTouchClickMode; + + gamescope::GamescopeScreenType screenType = GetBackend()->GetCurrentConnector()->GetScreenType(); + if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) + return WLSERVER_TOUCH_CLICK_TRACKPAD; return g_nTouchClickMode; } diff --git a/src/wlserver.hpp b/src/wlserver.hpp index 852a5a15d..c884a3ab9 100644 --- a/src/wlserver.hpp +++ b/src/wlserver.hpp @@ -13,7 +13,6 @@ #include #include -#include "drm.hpp" #include "steamcompmgr_shared.hpp" #define WLSERVER_BUTTON_COUNT 7 @@ -31,7 +30,7 @@ struct wlserver_vk_swapchain_feedback VkPresentModeKHR vk_present_mode; VkBool32 vk_clipped; - std::shared_ptr hdr_metadata_blob; + std::shared_ptr hdr_metadata_blob; }; struct ResListEntry_t { @@ -270,3 +269,8 @@ void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle ); void wlserver_force_shutdown(); + +void wlserver_send_gamescope_control( wl_resource *control ); + +bool wlsession_active(); + diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index 229a0884a..d77a1844b 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -1,6 +1,6 @@ #pragma once -#include "drm.hpp" +#include "backend.h" #include "waitable.h" #include @@ -192,7 +192,6 @@ struct xwayland_ctx_t final : public gamescope::IWaitable Atom gamescopeDebugHDRHeatmap_MSWCG; Atom gamescopeHDROutputFeedback; Atom gamescopeSDROnHDRContentBrightness; - Atom gamescopeInternalDisplayBrightness; Atom gamescopeHDRInputGain; Atom gamescopeSDRInputGain; Atom gamescopeHDRItmEnable;