VRR flicker mitigations - #2326
Open
matte-schwartz wants to merge 3 commits into
Open
Conversation
Cursor motion sets hasRepaint, and the VRR flip path paints on every wakeup, so moving the cursor drives the display to its maximum refresh rate even while the focused game presents at a lower rate. The refresh rate then oscillates between the game rate and the maximum, which flickers visibly on some VRR panels. The cursor auto-hide has the same effect through the overlay path, spiking the refresh rate a fixed timeout after the last cursor motion. Track cursor damage in a separate hasCursorRepaint flag and only let it force a flip when no content frame was painted within the last 1/adaptive_sync_cursor_min_fps seconds, defaulting to 30fps to match the threshold KWin uses for its delayed VRR timer and activeWindowControlsVrrRefreshRate. A game presenting at or above that rate paces the display alone and the cursor rides along with its frames, while slow or static content keeps an immediately repainted cursor with the refresh rate pinned stably at the maximum. The auto-hide repaint is classified as cursor damage too.
The Steam UI only presents on changes, so with adaptive sync active the panel stretches toward its minimum refresh rate whenever the screen goes still and snaps back to maximum on the next update. Every swing shifts the panel's effective gamma, which shows as whole-screen brightness flicker on OLED and VA panels, and the panel starts stretching the moment a single frame is missed. Toggling VRR_ENABLED off for the UI instead would take the full modeset path, which blanks many sinks. While the Steam UI is focused and adaptive sync is active, present on every vblank timer wakeup, repeating the current frame when nothing changed. The display then behaves exactly like a fixed refresh rate display. The vblank timer keeps waking at the refresh interval while nothing else is flipping, so the hold rides its wakeups directly, and client frames still flip the moment they arrive on their own wakeups. Games pace the display themselves and are never touched. The adaptive_sync_idle_hold convar turns the hold off.
Adaptive sync idle holds re-present content that has not changed, so whenever the frame can't pass direct scanout each hold runs a full-screen composite whose output is identical to the image already on screen, measured as a clear rise in average SoC power versus near zero under scanout. The nested Wayland backend composites every present and does not take the reuse, the host compositor owns that case. Tag unchanged frames in FrameInfo_t and present the previously composited image again instead of dispatching an identical composite. The output image ring only advances when a present composite runs, so the last image is still the one on screen. Reuse only applies to consecutive full composites, frames with deferred cursor or overlay damage still composite so the update is really drawn, and the drm_reuse_last_composite convar turns it off for comparison.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
At present, when VRR is enabled but Steam is idle, displays drop to the bottom of their VRR-supported range on every idle. Then, when Steam exits an idle state, the refresh rate spikes back up to its maximum value, and on panels which are prone to VRR flicker, this process causes some pretty nasty flickering while navigating the Steam UI.
Mitigate this by holding the refresh rate while the Steam UI is focused. I checked power readings and there is minimal difference by doing this compared to keeping monitors at their minimum value, but the visible flicker still completely disappears. It's targeted only while the Steam UI is focused, as we want to make sure games still interact with VRR as cleanly as possible and keep LFC functioning correctly. The last commit skips recompositing for these repeated frames and presents the last composited image again, which is what keeps the power cost minimal.
Also, clamp the cursor to active content refresh rate when it's above a minimum threshold while VRR is enabled, mimicking KWin's implementation, as it offers a better user experience than spiking to max refresh rate every time the cursor moves while VRR is active in-game.