From fa9c3331d5251db086b6edf32553f17aa2ab748e Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sun, 29 Dec 2024 00:42:42 -0500
Subject: [PATCH] main: Adjust how SDL_HINT_MAIN_CALLBACK_RATE works.
Now (only in the generic backend, where it is implemented), this hint is
always respected. Previously it would only be used if no windows were created,
to help reduce CPU load on things like loopwave.
Since it's always used now, the default has changed from 60 (Hz) to 0 (run as
fast as possible). Things like loopwave should still likely force this way
lower than the previous default (and already do: loopwave explicitly sets it
to 5).
The hint can now also be set to "waitevent" which will cause SDL_AppIterate
to only be called after new events have arrived, for apps that are entirely
driven by input and want to consume (almost) no power or CPU time until then.
Fixes #11093.
Fixes #11387.
---
include/SDL3/SDL_hints.h | 12 +++++--
src/main/generic/SDL_sysmain_callbacks.c | 43 +++++++++++++++---------
2 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index c380d26fd2933..2cdf998841497 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2391,13 +2391,21 @@ extern "C" {
/**
* Request SDL_AppIterate() be called at a specific rate.
*
- * This number is in Hz, so "60" means try to iterate 60 times per second.
+ * If this is set to a number, it represents Hz, so "60" means try to
+ * iterate 60 times per second. "0" means to iterate as fast as possible.
+ * Negative values are illegal, but reserved, in case they are useful in
+ * a future revision of SDL.
+ *
+ * There are other strings that have special meaning. If set to "waitevent",
+ * SDL_AppIterate will not be called until new event(s) have arrived (and been
+ * processed by SDL_AppEvent). This can be useful for apps that are completely
+ * idle except in response to input.
*
* On some platforms, or if you are using SDL_main instead of SDL_AppIterate,
* this hint is ignored. When the hint can be used, it is allowed to be
* changed at any time.
*
- * This defaults to 60, and specifying NULL for the hint's value will restore
+ * This defaults to 0, and specifying NULL for the hint's value will restore
* the default.
*
* This hint can be set anytime.
diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c
index fb311fbd5b14c..506e813048e93 100644
--- a/src/main/generic/SDL_sysmain_callbacks.c
+++ b/src/main/generic/SDL_sysmain_callbacks.c
@@ -26,15 +26,29 @@
#ifndef SDL_PLATFORM_IOS
static int callback_rate_increment = 0;
+static bool iterate_after_waitevent = false;
static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
- const int callback_rate = newValue ? SDL_atoi(newValue) : 60;
- if (callback_rate > 0) {
- callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate);
- } else {
+ iterate_after_waitevent = newValue && (SDL_strcmp(newValue, "waitevent") == 0);
+ if (iterate_after_waitevent) {
callback_rate_increment = 0;
+ } else {
+ const int callback_rate = newValue ? SDL_atoi(newValue) : 0;
+ if (callback_rate > 0) {
+ callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate);
+ } else {
+ callback_rate_increment = 0;
+ }
+ }
+}
+
+static SDL_AppResult GenericIterateMainCallbacks(void)
+{
+ if (iterate_after_waitevent) {
+ SDL_WaitEvent(NULL);
}
+ return SDL_IterateMainCallbacks(!iterate_after_waitevent);
}
int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
@@ -45,7 +59,7 @@ int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit,
Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0;
- while ((rc = SDL_IterateMainCallbacks(true)) == SDL_APP_CONTINUE) {
+ while ((rc = GenericIterateMainCallbacks()) == SDL_APP_CONTINUE) {
// !!! FIXME: this can be made more complicated if we decide to
// !!! FIXME: optionally hand off callback responsibility to the
// !!! FIXME: video subsystem (for example, if Wayland has a
@@ -53,21 +67,20 @@ int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit,
// !!! FIXME: off to them here if/when the video subsystem becomes
// !!! FIXME: initialized).
- // !!! FIXME: maybe respect this hint even if there _is_ a window.
- // if there's no window, try to run at about 60fps (or whatever rate
- // the hint requested). This makes this not eat all the CPU in
- // simple things like loopwave. If there's a window, we run as fast
- // as possible, which means we'll clamp to vsync in common cases,
- // and won't be restrained to vsync if the app is doing a benchmark
- // or doesn't want to be, based on how they've set up that window.
- if ((callback_rate_increment == 0) || SDL_HasWindows()) {
+ // Try to run at whatever rate the hint requested. This makes this
+ // not eat all the CPU in simple things like loopwave. By
+ // default, we run as fast as possible, which means we'll clamp to
+ // vsync in common cases, and won't be restrained to vsync if the
+ // app is doing a benchmark or doesn't want to be, based on how
+ // they've set up that window.
+ if (callback_rate_increment == 0) {
next_iteration = 0; // just clear the timer and run at the pace the video subsystem allows.
} else {
const Uint64 now = SDL_GetTicksNS();
- if (next_iteration > now) { // Running faster than the limit, sleep a little.
+ if (next_iteration > now) { // Running faster than the limit, sleep a little.
SDL_DelayPrecise(next_iteration - now);
} else {
- next_iteration = now; // running behind (or just lost the window)...reset the timer.
+ next_iteration = now; // if running behind, reset the timer. If right on time, `next_iteration` already equals `now`.
}
next_iteration += callback_rate_increment;
}