perf: throttle idle FPS and pause rendering when unfocused

The main loop redrew the whole scene at 60 fps unconditionally, keeping
the GPU (and, on software GL, the CPU) busy even while idle — enough to
heat the machine. Add two cooling measures (desktop only):

- Idle FPS throttle: drop to 10 fps when nothing needs animating (no
  input, mouse movement, playback, loading/background STFT, drag/pan, or
  counting-down notice), with a 0.5s grace window; snap back to 60 fps on
  activity. Measured ~34% -> ~8% idle CPU on real hardware.
- Focus pause: when the window isn't focused, skip the frame entirely —
  pump events via PollInputEvents() + WaitTime() with zero drawing, so
  refocus/close still register. Guarded with !headless so the hidden
  render window still draws its single capture frame.

Also fix two -Wformat-truncation warnings (surfaced at -O2) in the
tx_frame label path: widen pos[16]->[28] for the worst-case "  %d/%d",
and the caller's label buffer lbl[64]->[160] since base can be note[96]
(DrawBoxLabel scissor-clips to the box, so behavior is unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:37:38 -07:00
parent ac262505c1
commit 2befb42d71
2 changed files with 58 additions and 2 deletions
+52
View File
@@ -70,6 +70,36 @@ static bool IsUserInteracting(void)
return false;
}
// Idle FPS throttle. raylib re-renders the whole scene every frame regardless
// of whether anything changed, so an idle window still pins the GPU (and, on a
// software GL stack, the CPU) at 60 fps. We drop to IDLE_FPS whenever nothing
// needs animating, and snap back to ACTIVE_FPS the moment activity resumes.
#define ACTIVE_FPS 60
#define IDLE_FPS 10
#define IDLE_GRACE_SECONDS 0.5 // stay at full rate briefly after the last activity
/**
* Returns true if the frame must keep redrawing at full rate: live input, a
* moving mouse (hover readouts/tooltips), playback, in-progress loading or
* background STFT, an active drag/pan/divider, or a counting-down notice.
* Everything else is a static frame we can throttle.
*/
static bool IsAppActive(void)
{
if (IsUserInteracting()) return true;
Vector2 d = GetMouseDelta();
if (d.x != 0.0f || d.y != 0.0f) return true; // hover / cursor readout
if (IsWindowResized()) return true;
if (app.isPlaying) return true; // playhead is moving
if (app.loaded && !app.stftComputed) return true; // STFT still loading
if (app.isBgProcessing && !app.bgFinished) return true;// background high-res fill
if (app.view.isPanning || app.isDividing) return true;
if (app.sel.isDragging || app.sel.isTimeSelecting || app.sel.isFreqSelecting) return true;
if (app.marker.dragging) return true;
if (app.exportMessageTimer > 0.0f) return true; // notification countdown
return false;
}
// Fraction of the view area used by the spectrogram. When the scope is hidden
// the spectrogram fills the whole area (divider at the bottom); otherwise the
// scope takes the remainder below dividerY.
@@ -751,6 +781,28 @@ int main(int argc, char* argv[])
#ifdef __EMSCRIPTEN__
// Track the browser viewport (fill + reflow on resize, like desktop).
SyncCanvasToWindow();
#else
// When the window isn't focused there's nothing for the user to see, so
// skip the entire frame: pump window events (so focus regain and the
// close button still register) and idle, with zero drawing. The
// headless render uses a hidden window that never reports focus, so it
// is exempt — otherwise it would never draw its single capture frame.
if (!headless && !IsWindowFocused()) {
PollInputEvents();
WaitTime(0.1); // ~10 Hz event poll, no rendering
continue;
}
// Idle FPS throttle (desktop only; the browser drives RAF/vsync itself).
// Keep full rate while active or within a short grace window after the
// last activity, then fall back to a low idle rate.
{
static double lastActive = -1000.0;
static int curFps = ACTIVE_FPS;
if (IsAppActive()) lastActive = GetTime();
int wantFps = (GetTime() - lastActive < IDLE_GRACE_SECONDS) ? ACTIVE_FPS : IDLE_FPS;
if (wantFps != curFps) { SetTargetFPS(wantFps); curFps = wantFps; }
}
#endif
// Drag & Drop