diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index ac179692dc..2628d06785 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -295,6 +295,7 @@ update_runtime_info(); gamescope::ConVar cv_adaptive_sync( "adaptive_sync", false, "Whether or not adaptive sync is enabled if available." ); gamescope::ConVar cv_adaptive_sync_ignore_overlay( "adaptive_sync_ignore_overlay", false, "Whether or not to ignore overlay planes for pushing commits with adaptive sync." ); gamescope::ConVar cv_adaptive_sync_overlay_cycles( "adaptive_sync_overlay_cycles", 1, "Number of vblank cycles to ignore overlay repaints before forcing a commit with adaptive sync." ); +gamescope::ConVar cv_adaptive_sync_uncapped( "adaptive_sync_uncapped", true, "Whether or not to allow mailbox/immediate clients to run uncapped with adaptive sync by deferring paints until the display can take a new flip." ); gamescope::ConVar cv_upscale_preemptive( "upscale_preemptive", true, "Allow pre-emptive upscaling" ); gamescope::ConVar cv_upscale_preemptive_debug_force_sync( "upscale_preemptive_debug_force_sync", false, "Force synchronize pre-emptive upscaling" ); @@ -8509,8 +8510,15 @@ steamcompmgr_main(int argc, char **argv) // We can always vblank if VRR. const bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); + + // Timer vblanks are scheduled with the same lead as the ready check, + // so they always count as flip-ready. + bool bVRRCanFlip = bVRR; + if ( bVRRCanFlip && cv_adaptive_sync_uncapped && !bIsVBlankFromTimer ) + bVRRCanFlip = GetVBlankTimer().IsVRRFlipReady(); + if ( bVRR ) - vblank = true; + vblank = vblank || bVRRCanFlip; bool flush_root = false; @@ -9053,7 +9061,7 @@ steamcompmgr_main(int argc, char **argv) case FlipType::VRR: { - bShouldPaint = hasRepaint; + bShouldPaint = hasRepaint && bVRRCanFlip; if ( bIsVBlankFromTimer ) { diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp index f036d000a8..30da968b5b 100644 --- a/src/vblankmanager.cpp +++ b/src/vblankmanager.cpp @@ -21,6 +21,8 @@ LogScope g_VBlankLog("vblank"); +extern gamescope::ConVar cv_adaptive_sync_uncapped; + namespace gamescope { ConVar vblank_debug( "vblank_debug", false, "Enable vblank debug spew to stderr." ); @@ -93,6 +95,24 @@ namespace gamescope return ulTargetPoint; } + uint64_t CVBlankTimer::VRRWakeupOffset() const + { + uint64_t ulDrawTime = cv_adaptive_sync_uncapped ? m_ulVRRRollingMaxSubmitTime.load() : 0; + /// See comment of m_ulVBlankDrawTimeMinCompositing. + if ( m_bCurrentlyCompositing ) + ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); + + // Queueing a flip early is free, the kernel holds it to the panel cycle. + uint64_t ulRedZone = cv_adaptive_sync_uncapped ? m_ulVBlankDrawBufferRedZone : kVRRFlushingTime; + return ulDrawTime + ulRedZone; + } + + bool CVBlankTimer::IsVRRFlipReady() const + { + const uint64_t ulIntervalNSecs = mHzToRefreshCycle( GetRefresh() ); + return get_time_in_nanos() + VRRWakeupOffset() >= GetLastVBlank() + ulIntervalNSecs; + } + VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) { const GamescopeScreenType eScreenType = GetBackend()->GetScreenType(); @@ -163,17 +183,10 @@ namespace gamescope m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; } - uint64_t ulRedZone = kVRRFlushingTime; - - uint64_t ulDrawTime = 0; - /// See comment of m_ulVBlankDrawTimeMinCompositing. - if ( m_bCurrentlyCompositing ) - ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); - - ulOffset = ulDrawTime + ulRedZone; + ulOffset = VRRWakeupOffset(); if ( vblank_debug && !bPreemptive ) - VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); + VBlankDebugSpew( ulOffset, m_ulVRRRollingMaxSubmitTime, m_ulVBlankDrawBufferRedZone ); } const uint64_t ulScheduledWakeupPoint = GetNextVBlank( ulOffset ); @@ -189,7 +202,13 @@ namespace gamescope std::optional CVBlankTimer::ProcessVBlank() { - return std::exchange( m_PendingVBlank, std::nullopt ); + // Anchor vblank iterations at their scheduled wakeup point, so + // scheduler + dispatch latency counts against the VRR submit time. + std::optional oVBlank = std::exchange( m_PendingVBlank, std::nullopt ); + m_ulLastWakeupTime = oVBlank + ? oVBlank->schedule.ulScheduledWakeupPoint + : get_time_in_nanos(); + return oVBlank; } void CVBlankTimer::MarkVBlank( uint64_t ulNanos, bool bReArmTimer ) @@ -215,6 +234,16 @@ namespace gamescope void CVBlankTimer::UpdateLastDrawTime( uint64_t ulNanos ) { m_ulLastDrawTime = ulNanos; + + uint64_t ulSubmitTime = get_time_in_nanos() - m_ulLastWakeupTime; + ulSubmitTime = std::min( ulSubmitTime, mHzToRefreshCycle( GetRefresh() ) / 2 ); + + uint64_t ulRollingMax = m_ulVRRRollingMaxSubmitTime; + if ( ulSubmitTime > ulRollingMax ) + ulRollingMax = ulSubmitTime; + else + ulRollingMax = ( ( m_ulVBlankRateOfDecayPercentage * ulRollingMax ) + ( kVBlankRateOfDecayMax - m_ulVBlankRateOfDecayPercentage ) * ulSubmitTime ) / kVBlankRateOfDecayMax; + m_ulVRRRollingMaxSubmitTime = ulRollingMax; } void CVBlankTimer::WaitToBeArmed() diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp index 582f0e2a57..36725d1320 100644 --- a/src/vblankmanager.hpp +++ b/src/vblankmanager.hpp @@ -45,6 +45,7 @@ namespace gamescope int GetRefresh() const; uint64_t GetLastVBlank() const; uint64_t GetNextVBlank( uint64_t ulOffset ) const; + bool IsVRRFlipReady() const; VBlankScheduleTime CalcNextWakeupTime( bool bPreemptive ); void Reschedule(); @@ -64,6 +65,7 @@ namespace gamescope void OnPollIn() final; private: void VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ); + uint64_t VRRWakeupOffset() const; uint64_t m_ulTargetVBlank = 0; std::atomic m_ulLastVBlank = { 0 }; @@ -96,6 +98,10 @@ namespace gamescope // 3ms by default to get the ball rolling. // This is calculated by steamcompmgr/drm and fed-back to the vblank timer. std::atomic m_ulLastDrawTime = { kStartingVBlankDrawTime }; + // When the steamcompmgr loop last woke up. + uint64_t m_ulLastWakeupTime = 0; + // Rolling max of wakeup -> flip queued, feeding the VRR wakeup offset. + std::atomic m_ulVRRRollingMaxSubmitTime = { kStartingVBlankDrawTime }; ////////////////////////////////// // VBlank timing tuneables below!