diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index baa82f167..83bc0daef 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -4020,19 +4020,26 @@ namespace gamescope if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; - std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); + // Only present composites advance the output ring, so the last one is still on screen. + const bool bReuseLastComposite = pFrameInfo->bRepeatFrame && bNeedsFullComposite && + m_bWasCompositing && !m_bWasPartialCompositing && vulkan_get_last_output_image( false, false ) != nullptr; - m_bWasCompositing = true; + if ( !bReuseLastComposite ) + { + std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); - g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; + m_bWasCompositing = true; - if ( !oCompositeResult ) - { - xwm_log.errorf("vulkan_composite failed"); - return -EINVAL; - } + g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; - vulkan_wait( *oCompositeResult, true ); + if ( !oCompositeResult ) + { + xwm_log.errorf("vulkan_composite failed"); + return -EINVAL; + } + + vulkan_wait( *oCompositeResult, true ); + } FrameInfo_t presentCompFrameInfo = {}; presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR; diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index b601320b3..db731d565 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -4681,6 +4681,10 @@ bool vulkan_format_supports_features(VkFormat format, VkFormatFeatureFlags featu gamescope::Rc vulkan_get_last_output_image( bool partial, bool defer ) { + // A remake drops the deferred images until the next composite makes them. + if ( g_output.outputImages.empty() ) + return nullptr; + // Get previous image ( +2 ) // 1 2 3 // | diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 6d5e7dcc1..9bec6e5f0 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -301,6 +301,8 @@ struct FrameInfo_t gamescope::Rc lut3D[EOTF_Count]; bool allowVRR; + // Content identical to the previous present, eg. an adaptive sync idle hold. + bool bRepeatFrame; bool applyOutputColorMgmt; // drm only EOTF outputEncodingEOTF; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 18be19785..9d82f9ab0 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -301,6 +301,8 @@ gamescope::ConVar cv_adaptive_sync( "adaptive_sync", false, "Whether or no 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_adaptive_sync_cursor_min_fps( "adaptive_sync_cursor_min_fps", 30, "Content frame rate at or above which cursor repaints coalesce with content frames when adaptive sync is on. 0 = repaint the cursor immediately." ); +gamescope::ConVar cv_adaptive_sync_idle_hold( "adaptive_sync_idle_hold", true, "Hold the display at its refresh rate while the Steam UI is focused with adaptive sync." ); 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" ); @@ -1115,6 +1117,7 @@ uint32_t lastPublishedInputCounter; std::atomic hasRepaint = false; bool hasRepaintNonBasePlane = false; +std::atomic hasCursorRepaint = false; bool g_bUpdateForwardedVROverlays = false; @@ -2058,7 +2061,7 @@ void MouseCursor::checkSuspension() // We're hiding the cursor, force redraw if we were showing it if (window && !m_imageEmpty ) { - hasRepaintNonBasePlane = true; + hasCursorRepaint = true; nudge_steamcompmgr(); } } @@ -3096,7 +3099,7 @@ gamescope::ConVar cv_paint_cursor_plane{ "paint_cursor_plane", true }; gamescope::ConVar cv_paint_mura_plane{ "paint_mura_plane", true }; static void -paint_all( global_focus_t *pFocus, bool async ) +paint_all( global_focus_t *pFocus, bool async, bool bRepeatFrame ) { if ( !pFocus ) return; @@ -3110,8 +3113,13 @@ paint_all( global_focus_t *pFocus, bool async ) static long long int paintID = 0; + const uint32_t uColorMgmtSerial = g_ColorMgmt.serial; update_color_mgmt(); + // New LUTs need a fresh composite even when nothing was damaged. + if ( g_ColorMgmt.serial != uColorMgmtSerial ) + bRepeatFrame = false; + paintID++; gpuvis_trace_begin_ctx_printf( paintID, "paint_all" ); steamcompmgr_win_t *w; @@ -3156,6 +3164,7 @@ paint_all( global_focus_t *pFocus, bool async ) frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; frameInfo.allowVRR = cv_adaptive_sync; + frameInfo.bRepeatFrame = bRepeatFrame; frameInfo.bFadingOut = fadingOut; frameInfo.eUpscaleFilter = pFocus->eUpscaleFilter; frameInfo.eUpscaleScaler = pFocus->eUpscaleScaler; @@ -10735,6 +10744,7 @@ steamcompmgr_main(int argc, char **argv) bool bPainted = false; static int nIgnoredOverlayRepaints = 0; + static uint64_t s_ulLastContentPaintTime = 0; if ( !hasRepaintNonBasePlane ) nIgnoredOverlayRepaints = 0; @@ -10745,6 +10755,7 @@ steamcompmgr_main(int argc, char **argv) for ( auto &iter : g_VirtualConnectorFocuses ) { global_focus_t *pPaintFocus = &iter.second; + bool bRepeatFrame = false; const UpscaleSettings_t upscaleSettings = GetUpscaleSettings( window_is_steam( pPaintFocus->focusWindow ), @@ -10835,13 +10846,13 @@ steamcompmgr_main(int argc, char **argv) { case FlipType::Normal: { - bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip ); + bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || hasCursorRepaint || bForceSyncFlip ); break; } case FlipType::Async: { - bShouldPaint = hasRepaint; + bShouldPaint = hasRepaint || hasCursorRepaint; if ( vblank && !bShouldPaint && hasRepaintNonBasePlane ) nIgnoredOverlayRepaints++; @@ -10853,6 +10864,25 @@ steamcompmgr_main(int argc, char **argv) { bShouldPaint = hasRepaint && bVRRCanFlip; + // Content at or above adaptive_sync_cursor_min_fps carries the cursor with its own frames. + if ( !bShouldPaint && hasCursorRepaint && bVRRCanFlip ) + { + const int nCursorMinFPS = cv_adaptive_sync_cursor_min_fps; + if ( nCursorMinFPS <= 0 || + get_time_in_nanos() - s_ulLastContentPaintTime >= 1'000'000'000ul / uint64_t( nCursorMinFPS ) ) + { + bShouldPaint = true; + } + } + + // Hold the Steam UI at the refresh rate so an idle panel can't stretch toward its minimum and flicker. + if ( !bShouldPaint && bIsVBlankFromTimer && g_bSteamIsActiveWindow && + cv_adaptive_sync_idle_hold ) + { + bShouldPaint = true; + bRepeatFrame = !hasCursorRepaint && !hasRepaintNonBasePlane; + } + if ( bIsVBlankFromTimer ) { if ( hasRepaintNonBasePlane ) @@ -10890,7 +10920,7 @@ steamcompmgr_main(int argc, char **argv) if ( bShouldPaint ) { - paint_all( pPaintFocus, eFlipType == FlipType::Async ); + paint_all( pPaintFocus, eFlipType == FlipType::Async, bRepeatFrame ); bPainted = true; } @@ -10909,8 +10939,12 @@ steamcompmgr_main(int argc, char **argv) { GetBackend()->OnEndFrame(); + if ( hasRepaint ) + s_ulLastContentPaintTime = get_time_in_nanos(); + hasRepaint = false; hasRepaintNonBasePlane = false; + hasCursorRepaint = false; nIgnoredOverlayRepaints = 0; { diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 1d7684a2c..5852d7326 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -118,6 +118,7 @@ struct wlr_surface *wlserver_surface_to_main_surface( struct wlr_surface *pSurfa bool wlserver_process_hotkeys( wlr_keyboard *keyboard, uint32_t key, bool press ); extern std::atomic hasRepaint; +extern std::atomic hasCursorRepaint; std::vector& gamescope_xwayland_server_t::retrieve_commits() { @@ -2818,7 +2819,7 @@ void wlserver_oncursorevent() if ( !wlserver.bCursorHidden && wlserver.bCursorHasImage ) { - hasRepaint = true; + hasCursorRepaint = true; } } @@ -2972,7 +2973,7 @@ void wlserver_mousehide() if ( wlserver.bCursorHidden != true ) { wlserver.bCursorHidden = true; - hasRepaint = true; + hasCursorRepaint = true; } }