diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index c380d26fd2..2cdf998841 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 fb311fbd5b..506e813048 100644 --- a/src/main/generic/SDL_sysmain_callbacks.c +++ b/src/main/generic/SDL_sysmain_callbacks.c @@ -26,17 +26,31 @@ #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) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, 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; }