perf(ui): render frames on demand instead of continuously
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>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "balance_components.h"
|
||||
#include "balance_lerp.h"
|
||||
#include "balance_tab_helpers.h"
|
||||
#include "balance_address_list.h"
|
||||
#include "balance_recent_tx.h"
|
||||
@@ -46,18 +47,12 @@ void UpdateBalanceLerp(App* app) {
|
||||
const auto& S = schema::UISchema::instance();
|
||||
const float kBalanceLerpSpeed = S.drawElement("tabs.balance", "balance-lerp-speed").sizeOr(8.0f);
|
||||
const auto& state = app->state();
|
||||
float dt = ImGui::GetIO().DeltaTime;
|
||||
float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
|
||||
auto lerp = [](double& disp, double target, float dt, float spd) {
|
||||
double diff = target - disp;
|
||||
if (std::abs(diff) < 1e-9) { disp = target; return; }
|
||||
disp += diff * (double)(dt * spd);
|
||||
if (std::abs(target - disp) < 1e-9) disp = target;
|
||||
};
|
||||
lerp(s_dispTotal, app->getTotalBalance(), dt, speed);
|
||||
lerp(s_dispShielded, app->getShieldedBalance(), dt, speed);
|
||||
lerp(s_dispTransparent, app->getTransparentBalance(), dt, speed);
|
||||
lerp(s_dispUnconfirmed, state.unconfirmed_balance, dt, speed);
|
||||
const float dt = ImGui::GetIO().DeltaTime;
|
||||
const bool instant = app->settings() && app->settings()->getReduceMotion();
|
||||
StepDisplayLerp(s_dispTotal, app->getTotalBalance(), dt, kBalanceLerpSpeed, instant);
|
||||
StepDisplayLerp(s_dispShielded, app->getShieldedBalance(), dt, kBalanceLerpSpeed, instant);
|
||||
StepDisplayLerp(s_dispTransparent, app->getTransparentBalance(), dt, kBalanceLerpSpeed, instant);
|
||||
StepDisplayLerp(s_dispUnconfirmed, state.unconfirmed_balance, dt, kBalanceLerpSpeed, instant);
|
||||
}
|
||||
|
||||
// Render compact hero line: logo + balance + USD + mining on one line
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#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
|
||||
@@ -226,7 +226,6 @@ static void RenderBalanceClassic(App* app)
|
||||
const auto syncBar = S.drawElement("tabs.balance", "sync-bar");
|
||||
|
||||
// Read layout properties from schema
|
||||
const float kBalanceLerpSpeed = S.drawElement("tabs.balance", "balance-lerp-speed").sizeOr(8.0f);
|
||||
const float kHeroPadTop = S.drawElement("tabs.balance", "hero-pad-top").sizeOr(12.0f);
|
||||
|
||||
const auto& state = app->state();
|
||||
@@ -240,22 +239,8 @@ static void RenderBalanceClassic(App* app)
|
||||
const float cGap = Layout::cardGap();
|
||||
const float dp = Layout::dpiScale();
|
||||
|
||||
// Lerp displayed balances toward actual values
|
||||
{
|
||||
float dt = ImGui::GetIO().DeltaTime;
|
||||
float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
|
||||
auto lerp = [](double& disp, double target, float dt, float spd) {
|
||||
double diff = target - disp;
|
||||
if (std::abs(diff) < 1e-9) { disp = target; return; }
|
||||
disp += diff * (double)(dt * spd);
|
||||
// Snap when very close
|
||||
if (std::abs(target - disp) < 1e-9) disp = target;
|
||||
};
|
||||
lerp(s_dispTotal, app->getTotalBalance(), dt, speed);
|
||||
lerp(s_dispShielded, app->getShieldedBalance(), dt, speed);
|
||||
lerp(s_dispTransparent, app->getTransparentBalance(), dt, speed);
|
||||
lerp(s_dispUnconfirmed, state.unconfirmed_balance, dt, speed);
|
||||
}
|
||||
// Lerp displayed balances toward actual values (shared with the other layouts)
|
||||
UpdateBalanceLerp(app);
|
||||
|
||||
// ================================================================
|
||||
// Card row — Total Balance | Shielded | Transparent | Market
|
||||
|
||||
@@ -62,6 +62,8 @@ public:
|
||||
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
// The download runs on its own thread: keep frames coming for the progress bar.
|
||||
if (s_state == State::Downloading) RequestProgressFrame();
|
||||
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("download_bootstrap"); ov.p_open = &s_open;
|
||||
|
||||
@@ -1427,6 +1427,7 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec)
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, inputLight ? IM_COL32(0, 0, 0, 16) : IM_COL32(255, 255, 255, 18));
|
||||
|
||||
const bool busy = exec.busy();
|
||||
if (busy) material::RequestProgressFrame(); // a command runs on a worker: show its result promptly
|
||||
if (busy) ImGui::BeginDisabled();
|
||||
if (ImGui::InputText("##ConsoleInput", input_buffer_, sizeof(input_buffer_), flags, callback, this)) {
|
||||
std::string cmd(input_buffer_);
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
// No window X during an active download/verify/extract — closing would orphan/block on the
|
||||
// worker; the in-dialog Cancel is the intended way to abort.
|
||||
const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting);
|
||||
// The updater works on its own thread: keep frames coming while it checks, lists, downloads or
|
||||
// installs, so progress and the next screen show promptly even when the app would otherwise idle.
|
||||
if (p.state != St::Unavailable && p.state != St::ReleaseList &&
|
||||
p.state != St::Done && p.state != St::Failed) {
|
||||
RequestProgressFrame();
|
||||
}
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("daemon_update_title");
|
||||
ov.p_open = active ? nullptr : &s_open;
|
||||
|
||||
@@ -86,6 +86,12 @@ public:
|
||||
const Progress p = s_sweep ? s_sweepProgress
|
||||
: (s_updater ? s_updater->getProgress() : Progress{});
|
||||
const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting);
|
||||
// The updater works on its own thread: keep frames coming while it checks, lists, downloads or
|
||||
// installs, so progress and the next screen show promptly even when the app would otherwise idle.
|
||||
if (p.state != St::Unavailable && p.state != St::ReleaseList &&
|
||||
p.state != St::Done && p.state != St::Failed) {
|
||||
RequestProgressFrame();
|
||||
}
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("xmrig_update_title");
|
||||
ov.p_open = active ? nullptr : &s_open;
|
||||
|
||||
Reference in New Issue
Block a user