Perf/frame pacing - #15
Merged
Merged
Conversation
kxxoling
force-pushed
the
perf/frame-pacing
branch
from
August 27, 2026 22:51
58ace82 to
a0e09de
Compare
Replace hardcoded 0.016f per-frame constants (sway clock, unlock timer, transition easing, inertia damping) with elapsed-time based updates so animation speed is identical on 60/90/120Hz screens and unaffected by dropped frames: exponential approach with a 0.2s time constant for the lock/unlock transition (equivalent to the old 8% per frame at 60fps) and framerate-normalized damping exp(-5t). Unit test assertions updated to the time-based easing (a 16ms step moves 1 - exp(-0.016/0.2) ~ 7.7% instead of exactly 8%).
Pacing slept until the exact frame boundary, so the next draw and buffer submission happened after it; the presentation then slipped a whole vblank period, juddering between paces (e.g. 33/50ms for a 30fps idle target on a 60Hz panel) - visibly choppy on the launcher. Subtract a 3ms present margin from the sleep target so submission lands before the intended vblank and presentation snaps onto it. sleepMillisForFrame takes the margin as a parameter (default 3ms, 0 restores the old behavior) with updated unit tests.
kxxoling
force-pushed
the
perf/frame-pacing
branch
3 times, most recently
from
August 27, 2026 22:56
a3bf795 to
d2ff2c3
Compare
The render loop always targeted a fixed 60fps: on high-refresh panels the wallpaper never used the extra smoothness, and sleeping until the exact frame boundary meant the next draw + buffer submission landed after the intended vblank, slipping presentation a whole refresh period (visible judder). - Full pace follows the display's current refresh rate, polled every ~0.5s. - Vote for the panel's highest supported refresh rate via Surface.setFrameRate (API 30+): without a vote the compositor keeps the panel at its default mode, so high-refresh devices never actually ran fast. - The sleep target is pulled back by a present margin scaled with the frame interval (1-3ms) so draw + submission land before the intended vblank instead of slipping a whole period.
Add a Frame rate setting so users can trade smoothness against battery on high-refresh panels. Behavior: - Auto (default): vote for the panel's highest supported refresh rate and pace at whatever the panel currently runs at. - Fixed tiers at 1/2, 1/3 and 1/4 of the panel's max refresh rate (tiers below 30fps are hidden). Fraction rates divide the max refresh evenly, so presentation stays vblank-aligned even while the panel still runs at max - fixed rates like 60 on a 144Hz panel would alternate 2/3 vblank periods and judder. Mainstream panels (60/90/120/144) all divide cleanly. - Pacing uses min(preference, current refresh) while the Surface.setFrameRate vote asks the panel to move toward it. Storage: mode string (auto|half|third|quarter) in DataStore with a SharedPreferences mirror, like the other wallpaper-visible settings; resolved against the display's supported modes at runtime so it adapts across devices. The engine's prefs listener pushes changes to the GL thread, which applies them on the very next frame (the poll counter is nudged); restarts restore the preference from prefs. UI: Material3 ExposedDropdownMenuBox (read-only outlined field with chevron) listing "Auto (max 144 Hz)" with the device's actual max, plus plain Hz values for the tiers.
kxxoling
force-pushed
the
perf/frame-pacing
branch
from
August 27, 2026 23:02
d2ff2c3 to
e39763a
Compare
While the screen is off the render loop kept waking at the render pace (~60x/s) just to run a no-op transition update, burning CPU all night. Nobody sees intermediate frames while off, so: - Advance the transition with real measured frame delta (previously it reused the stale delta from the last rendered frame) at a coarse 100ms cadence. - Once the transition settles, poll at ~1Hz. - setRendering(true) interrupts the sleep so screen-on still renders immediately; all sleeps swallow and clear the interrupt so a stale flag cannot busy-loop the pace loop.
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.
Frame pacing overhaul: refresh-rate adaptation + user-selectable rate
Problem
faster or slower depending on the display's refresh rate and on frame
drops (a dropped frame slowed the whole scene).
wallpaper never used the extra smoothness, and the pacing slept until
the exact frame boundary, so the next draw + buffer submission landed
after the intended vblank and presentation slipped a whole refresh
period (visible judder).
What this PR does
Real frame delta time — sway, lock/unlock transition easing and
drag inertia are driven by measured elapsed time (exponential
approach with a 0.2s time constant, framerate-normalized damping),
so animation speed is identical on any refresh rate and unaffected
by dropped frames.
Refresh-rate adaptation — the render pace follows the display's
current refresh rate and votes for the panel's highest supported
rate via
Surface.setFrameRate(API 30+); without the vote thecompositor keeps high-refresh panels at their default mode.
Frame rate setting — a Material3 dropdown lets users pick
Auto (max N Hz) (labeled with the device's actual maximum) or fixed
tiers at 1/2, 1/3 and 1/4 of the panel's max refresh (tiers below
30fps are hidden). Fraction tiers divide the panel's max evenly, so
presentation stays vblank-aligned even while the panel still runs at
max — fixed values like 60 on a 144Hz panel would alternate 2/3
vblank periods and judder. Changes apply on the very next frame via
the existing prefs listener.
Vblank-aligned pacing — the sleep target is pulled back by a
present margin (1–3ms, scaled with the frame interval) so draw +
submission land before the intended vblank instead of slipping a
whole period.