Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/steamcompmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ update_runtime_info();
gamescope::ConVar<bool> cv_adaptive_sync( "adaptive_sync", false, "Whether or not adaptive sync is enabled if available." );
gamescope::ConVar<bool> 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<int> 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<bool> 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<bool> cv_upscale_preemptive( "upscale_preemptive", true, "Allow pre-emptive upscaling" );
gamescope::ConVar<bool> cv_upscale_preemptive_debug_force_sync( "upscale_preemptive_debug_force_sync", false, "Force synchronize pre-emptive upscaling" );
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -9053,7 +9061,7 @@ steamcompmgr_main(int argc, char **argv)

case FlipType::VRR:
{
bShouldPaint = hasRepaint;
bShouldPaint = hasRepaint && bVRRCanFlip;

if ( bIsVBlankFromTimer )
{
Expand Down
49 changes: 39 additions & 10 deletions src/vblankmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

LogScope g_VBlankLog("vblank");

extern gamescope::ConVar<bool> cv_adaptive_sync_uncapped;

namespace gamescope
{
ConVar<bool> vblank_debug( "vblank_debug", false, "Enable vblank debug spew to stderr." );
Expand Down Expand Up @@ -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<uint64_t>( 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();
Expand Down Expand Up @@ -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 );
Expand All @@ -189,7 +202,13 @@ namespace gamescope

std::optional<VBlankTime> 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<VBlankTime> 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 )
Expand All @@ -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<uint64_t>( 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()
Expand Down
6 changes: 6 additions & 0 deletions src/vblankmanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<uint64_t> m_ulLastVBlank = { 0 };
Expand Down Expand Up @@ -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<uint64_t> 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<uint64_t> m_ulVRRRollingMaxSubmitTime = { kStartingVBlankDrawTime };

//////////////////////////////////
// VBlank timing tuneables below!
Expand Down