While theme effects were on (the default) the main loop drew every frame it could. Where vsync is off (offscreen, some drivers, remote sessions) an idle wallet spun at ~3,000 fps, 88% of a core; with vsync it still drew every refresh. Frames are now paced by what needs them: - full rate while there is input, a held key, a window appearing or settling, a smooth scroll or the UI sweep; - ambient (30 fps focused, 10 fps unfocused) for theme effects, the acrylic backdrop, avatar animations and notifications; - progress (10 fps focused, 4 fps unfocused) for UI that changes without input. The updater and bootstrap downloads, console commands, lite-wallet operations, seed migration, the loading overlay and the lock-screen countdowns ask for it with material::RequestProgressFrame(); - otherwise idle, as before: wait for events, with a housekeeping wake every 2 s (5 s unfocused). A software frame cap (0.9x the display refresh) bounds the loop where vsync is off, and hidden, occluded or minimized windows get no ambient frames. Reduce Motion freezes the theme-effect clock (ThemeEffects::setMotionFrozen), so it needs no ambient frames either. The displayed-balance roll goes through one clamped StepDisplayLerp (balance_lerp.h). An idle-length frame pushed dt*speed past 1, so the roll overshot, and under Reduce Motion's speed of 999 it diverged to inf/NaN. The classic layout had its own copy of the old code. Idle CPU, Lite build, offscreen (no vsync): 88% -> 5% of a core with effects on, and 91% -> 3% with effects saved off (with the previous commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 lines
1.4 KiB
C++
38 lines
1.4 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cfloat>
|
|
#include <cmath>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
|
|
// One step of a displayed balance rolling toward its real value. Returns true while still moving.
|
|
//
|
|
// The per-step factor dt*speed is clamped to 1, so a long frame (the main loop idles between frames,
|
|
// and dt reaches the 0.25 s clamp) lands on the target instead of overshooting: the old unclamped
|
|
// `disp += diff*dt*speed` flipped between two wrong values at dt*speed == 2 and diverged to inf/NaN
|
|
// at Reduce Motion's speed of 999. `instant` (Reduce Motion) snaps to the target. A non-finite display
|
|
// value recovers on the next step, and the relative snap keeps the roll from stalling a few ULPs short
|
|
// of large targets (>= 2^23), where `disp + diff*a` stops changing.
|
|
inline bool StepDisplayLerp(double& disp, double target, float dt, float speed, bool instant)
|
|
{
|
|
if (instant || !std::isfinite(disp) || !std::isfinite(target)) {
|
|
disp = target;
|
|
return false;
|
|
}
|
|
const double diff = target - disp;
|
|
if (diff == 0.0) return false;
|
|
const double a = std::clamp(static_cast<double>(dt) * static_cast<double>(speed), 0.0, 1.0);
|
|
const double next = disp + diff * a;
|
|
if (next == disp || std::abs(target - next) <= std::max(5e-9, 4.0 * std::abs(target) * DBL_EPSILON)) {
|
|
disp = target;
|
|
return false;
|
|
}
|
|
disp = next;
|
|
return true;
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace dragonx
|