Android/iOS backgrounding fixes; pause on focus lost

This commit is contained in:
Luke Street
2026-05-08 16:55:12 -06:00
parent 17be93f0ae
commit 1eeff98783
7 changed files with 210 additions and 133 deletions
+3
View File
@@ -83,6 +83,7 @@ typedef struct {
bool vsync;
bool startFullscreen;
bool allowJoystickBackgroundEvents;
bool pauseOnFocusLost;
bool allowTextureReplacements;
bool allowTextureDumps;
int32_t windowPosX;
@@ -124,6 +125,8 @@ bool aurora_begin_frame();
void aurora_end_frame();
void aurora_set_log_level(AuroraLogLevel level);
void aurora_set_pause_on_focus_lost(bool value);
void aurora_set_background_input(bool value);
AuroraBackend aurora_get_backend();
const AuroraBackend* aurora_get_available_backends(size_t* count);
+26 -8
View File
@@ -92,6 +92,7 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
g_sdlCustomEventsStart = SDL_RegisterEvents(2);
ASSERT(g_sdlCustomEventsStart, "Failed to allocate user events: {}", SDL_GetError());
ASSERT(window::initialize_event_watch(), "Error initializing SDL event watch");
#ifdef AURORA_ENABLE_GX
/* Attempt to create a window using the calling application's desired backend */
@@ -162,8 +163,6 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
};
}
void aurora_set_log_level(AuroraLogLevel level) noexcept { g_config.logLevel = level; }
#ifdef AURORA_ENABLE_GX
wgpu::TextureView g_currentView;
#endif
@@ -194,6 +193,13 @@ const AuroraEvent* update() noexcept {
bool begin_frame() noexcept {
ZoneScoped;
#ifdef AURORA_ENABLE_GX
if (!window::is_presentable()) {
webgpu::release_surface();
return false;
}
if (window::is_paused()) {
return false;
}
if (!g_surface) {
webgpu::refresh_surface(true);
if (!g_surface) {
@@ -216,8 +222,8 @@ bool begin_frame() noexcept {
return false;
case wgpu::SurfaceGetCurrentTextureStatus::Lost:
case wgpu::SurfaceGetCurrentTextureStatus::Error:
Log.warn("Surface texture is {}, recreating surface", magic_enum::enum_name(surfaceTexture.status));
webgpu::refresh_surface(true);
Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceTexture.status));
webgpu::release_surface();
return false;
default:
Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status));
@@ -298,9 +304,13 @@ void end_frame() noexcept {
const auto buffer = encoder.Finish(&cmdBufDescriptor);
g_queue.Submit(1, &buffer);
gfx::after_submit();
auto presentStatus = g_surface.Present();
if (presentStatus != wgpu::Status::Success) {
Log.warn("Surface present failed: {}", static_cast<int>(presentStatus));
if (window::is_presentable()) {
auto presentStatus = g_surface.Present();
if (presentStatus != wgpu::Status::Success) {
Log.warn("Surface present failed: {}", static_cast<int>(presentStatus));
}
} else {
Log.info("Skipping present; window not presentable");
}
g_currentView = {};
@@ -335,6 +345,14 @@ bool aurora_begin_frame() { return aurora::begin_frame(); }
void aurora_end_frame() { aurora::end_frame(); }
AuroraBackend aurora_get_backend() { return aurora::g_config.desiredBackend; }
const AuroraBackend* aurora_get_available_backends(size_t* count) {
*count = aurora::PreferredBackendOrder.size();
if (count != nullptr) {
*count = aurora::PreferredBackendOrder.size();
}
return aurora::PreferredBackendOrder.data();
}
void aurora_set_log_level(AuroraLogLevel level) { aurora::g_config.logLevel = level; }
void aurora_set_pause_on_focus_lost(bool value) { aurora::g_config.pauseOnFocusLost = value; }
void aurora_set_background_input(bool value) {
aurora::g_config.allowJoystickBackgroundEvents = value;
aurora::window::set_background_input(value);
}
+1 -10
View File
@@ -48,15 +48,6 @@ Rml::Vector2i presentation_dimensions_from_window_size(const AuroraWindowSize& s
return dimensions_from_viewport(viewport);
}
float density_ratio_for_window(const AuroraWindowSize& size) noexcept {
if (size.width == 0 || size.height == 0 || size.native_fb_width == 0 || size.native_fb_height == 0) {
return 1.f;
}
return std::min(static_cast<float>(size.native_fb_width) / static_cast<float>(size.width),
static_cast<float>(size.native_fb_height) / static_cast<float>(size.height));
}
void sync_context_metrics(Rml::Vector2i dimensions) noexcept {
if (g_context == nullptr || dimensions.x <= 0 || dimensions.y <= 0) {
return;
@@ -64,7 +55,7 @@ void sync_context_metrics(Rml::Vector2i dimensions) noexcept {
if (g_context->GetDimensions() != dimensions) {
g_context->SetDimensions(dimensions);
}
const float ratio = density_ratio_for_window(window::get_window_size());
const float ratio = window::get_window_size().scale;
if (g_context->GetDensityIndependentPixelRatio() != ratio) {
g_context->SetDensityIndependentPixelRatio(ratio);
}
+13 -2
View File
@@ -390,6 +390,7 @@ static bool create_surface() {
.nextInChain = chainedDescriptor.get(),
.label = "Surface",
};
release_surface();
g_surface = g_instance.CreateSurface(&surfaceDescriptor);
if (!g_surface) {
Log.error("Failed to create surface");
@@ -655,10 +656,21 @@ void shutdown() {
cache_shutdown();
}
void release_surface() noexcept {
if (g_surface) {
g_surface.Unconfigure();
}
g_surface = {};
}
bool refresh_surface(bool recreate) {
if (!g_instance || !g_device) {
return false;
}
if (!window::is_presentable()) {
release_surface();
return false;
}
if ((!g_surface || recreate) && !create_surface()) {
return false;
}
@@ -707,6 +719,5 @@ void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, ui
void aurora_enable_vsync(const bool enabled) {
aurora::webgpu::g_graphicsConfig.surfaceConfiguration.presentMode = aurora::webgpu::best_present_mode(enabled);
SDL_Event event{.type = aurora::g_sdlCustomEventsStart + 1};
SDL_PushEvent(&event);
aurora::window::push_custom_event(aurora::window::CustomEvent::RefreshSurface);
}
+1
View File
@@ -53,6 +53,7 @@ extern wgpu::Instance g_instance;
bool initialize(AuroraBackend backend);
void shutdown();
void release_surface() noexcept;
bool refresh_surface(bool recreate = true);
void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height,
bool force = false);
+153 -113
View File
@@ -21,7 +21,7 @@
#include <SDL3/SDL_pixels.h>
#include <algorithm>
#include <cmath>
#include <atomic>
#include <vector>
#include "rmlui.hpp"
@@ -37,6 +37,8 @@ float g_frameBufferScale = 0.f;
bool g_frameBufferAspectFit = false;
AuroraWindowSize g_windowSize;
std::vector<AuroraEvent> g_events;
std::atomic_bool g_backgrounded = false;
bool g_lastPaused = false;
bool operator==(const AuroraWindowSize& lhs, const AuroraWindowSize& rhs) {
return lhs.width == rhs.width && lhs.height == rhs.height && lhs.fb_width == rhs.fb_width &&
@@ -99,6 +101,118 @@ void set_window_icon() noexcept {
TRY_WARN(SDL_SetWindowIcon(g_window, iconSurface), "Failed to set window icon: {}", SDL_GetError());
SDL_DestroySurface(iconSurface);
}
bool SDLCALL lifecycle_event_watch(void*, SDL_Event* event) {
switch (event->type) {
case SDL_EVENT_WILL_ENTER_BACKGROUND:
g_backgrounded.store(true, std::memory_order_relaxed);
break;
case SDL_EVENT_WILL_ENTER_FOREGROUND:
g_backgrounded.store(false, std::memory_order_relaxed);
break;
default:
break;
}
return true;
}
void sync_paused() {
const bool paused = is_paused();
if (g_lastPaused == paused) {
return;
}
g_lastPaused = paused;
g_events.push_back(AuroraEvent{
.type = paused ? AURORA_PAUSED : AURORA_UNPAUSED,
});
}
void process_event(SDL_Event& event) {
#ifdef AURORA_ENABLE_GX
imgui::process_event(event);
#endif
#ifdef AURORA_ENABLE_RMLUI
rmlui::handle_event(event);
#endif
switch (event.type) {
case SDL_EVENT_RENDER_DEVICE_RESET: {
Log.info("Render device reset, recreating surface");
#ifdef AURORA_ENABLE_GX
webgpu::refresh_surface(true);
#endif
break;
}
case SDL_EVENT_WINDOW_MOVED: {
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_MOVED,
.windowPos = {.x = event.window.data1, .y = event.window.data2},
});
break;
}
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: {
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_DISPLAY_SCALE_CHANGED,
.windowSize = get_window_size(),
});
break;
}
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_RESIZED,
.windowSize = get_window_size(),
});
break;
}
case SDL_EVENT_GAMEPAD_ADDED: {
auto instance = input::add_controller(event.gdevice.which);
g_events.push_back(AuroraEvent{
.type = AURORA_CONTROLLER_ADDED,
.controller = instance,
});
break;
}
case SDL_EVENT_GAMEPAD_REMOVED: {
input::remove_controller(event.gdevice.which);
g_events.push_back(AuroraEvent{
.type = AURORA_CONTROLLER_REMOVED,
.controller = event.gdevice.which,
});
break;
}
case SDL_EVENT_MOUSE_WHEEL:
input::set_mouse_scroll(event.wheel.x, event.wheel.y);
break;
case SDL_EVENT_QUIT:
g_events.push_back(AuroraEvent{
.type = AURORA_EXIT,
});
break;
default:
if (event.type == g_sdlCustomEventsStart + CustomEvent::FutureResize) {
// Future resize event
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_RESIZED,
.windowSize = get_window_size(),
});
} else if (event.type == g_sdlCustomEventsStart + CustomEvent::RefreshSurface) {
// Refresh surface (vsync changed)
#ifdef AURORA_ENABLE_GX
webgpu::refresh_surface(false);
#endif
}
break;
}
sync_paused();
g_events.push_back(AuroraEvent{
.type = AURORA_SDL_EVENT,
.sdl = event,
});
}
} // namespace
const AuroraEvent* poll_events() {
@@ -107,117 +221,15 @@ const AuroraEvent* poll_events() {
SDL_Event event;
// Clear out the previous scroll values to prevent ghost input
input::set_mouse_scroll(0, 0);
if (is_paused()) {
if (SDL_WaitEvent(&event)) {
process_event(event);
} else {
Log.warn("SDL_WaitEvent failed: {}", SDL_GetError());
}
}
while (SDL_PollEvent(&event)) {
#ifdef AURORA_ENABLE_GX
imgui::process_event(event);
#endif
#ifdef AURORA_ENABLE_RMLUI
rmlui::handle_event(event);
#endif
switch (event.type) {
case SDL_EVENT_WINDOW_MINIMIZED: {
// Android/iOS: Application backgrounded
g_events.push_back(AuroraEvent{
.type = AURORA_PAUSED,
});
break;
}
case SDL_EVENT_WINDOW_RESTORED: {
// Android/iOS: Application focused
g_events.push_back(AuroraEvent{
.type = AURORA_UNPAUSED,
});
break;
}
case SDL_EVENT_RENDER_DEVICE_RESET: {
Log.info("Render device reset, recreating surface");
#ifdef AURORA_ENABLE_GX
webgpu::refresh_surface(true);
#endif
break;
}
#if defined(ANDROID)
case SDL_EVENT_WINDOW_FOCUS_LOST: {
g_events.push_back(AuroraEvent{
.type = AURORA_PAUSED,
});
break;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED: {
g_events.push_back(AuroraEvent{
.type = AURORA_UNPAUSED,
});
break;
}
#endif
case SDL_EVENT_WINDOW_MOVED: {
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_MOVED,
.windowPos = {.x = event.window.data1, .y = event.window.data2},
});
break;
}
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: {
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_DISPLAY_SCALE_CHANGED,
.windowSize = get_window_size(),
});
break;
}
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_RESIZED,
.windowSize = get_window_size(),
});
break;
}
case SDL_EVENT_GAMEPAD_ADDED: {
auto instance = input::add_controller(event.gdevice.which);
g_events.push_back(AuroraEvent{
.type = AURORA_CONTROLLER_ADDED,
.controller = instance,
});
break;
}
case SDL_EVENT_GAMEPAD_REMOVED: {
input::remove_controller(event.gdevice.which);
g_events.push_back(AuroraEvent{
.type = AURORA_CONTROLLER_REMOVED,
.controller = event.gdevice.which,
});
break;
}
case SDL_EVENT_MOUSE_WHEEL:
input::set_mouse_scroll(event.wheel.x, event.wheel.y);
break;
case SDL_EVENT_QUIT:
g_events.push_back(AuroraEvent{
.type = AURORA_EXIT,
});
default:
if (event.type == g_sdlCustomEventsStart) {
// Future resize event
resize_swapchain();
g_events.push_back(AuroraEvent{
.type = AURORA_WINDOW_RESIZED,
.windowSize = get_window_size(),
});
} else if (event.type == g_sdlCustomEventsStart + 1) {
// Refresh surface (vsync changed)
#ifdef AURORA_ENABLE_GX
webgpu::refresh_surface(false);
#endif
}
break;
}
g_events.push_back(AuroraEvent{
.type = AURORA_SDL_EVENT,
.sdl = event,
});
process_event(event);
}
g_events.push_back(AuroraEvent{
.type = AURORA_NONE,
@@ -336,6 +348,8 @@ void show_window() {
bool initialize() {
/* We don't want to initialize anything input related here, otherwise the add events will get lost to the void */
TRY(SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"), "Error setting {}: {}", SDL_HINT_ORIENTATIONS,
SDL_GetError());
TRY(SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO), "Error initializing SDL: {}", SDL_GetError());
#if !defined(_WIN32) && !defined(__APPLE__)
@@ -356,7 +370,13 @@ bool initialize() {
return true;
}
bool initialize_event_watch() {
TRY(SDL_AddEventWatch(lifecycle_event_watch, nullptr), "Error adding SDL event watch: {}", SDL_GetError());
return true;
}
void shutdown() {
SDL_RemoveEventWatch(lifecycle_event_watch, nullptr);
destroy_window();
TRY_WARN(SDL_EnableScreenSaver(), "Error enabling screensaver: {}", SDL_GetError());
SDL_Quit();
@@ -407,6 +427,24 @@ SDL_Window* get_sdl_window() { return g_window; }
SDL_Renderer* get_sdl_renderer() { return g_renderer; }
bool is_paused() noexcept {
if (!is_presentable()) {
return true;
}
const auto flags = SDL_GetWindowFlags(g_window);
if ((flags & SDL_WINDOW_HIDDEN) != 0u) {
return true;
}
return g_config.pauseOnFocusLost && ((flags & SDL_WINDOW_INPUT_FOCUS) == 0u || (flags & SDL_WINDOW_MINIMIZED) != 0u);
}
bool is_presentable() noexcept { return g_window != nullptr && !g_backgrounded.load(std::memory_order_relaxed); }
bool push_custom_event(CustomEvent eventType) {
SDL_Event event{.type = g_sdlCustomEventsStart + eventType};
return SDL_PushEvent(&event);
}
void set_title(const char* title) {
TRY_WARN(SDL_SetWindowTitle(g_window, title), "Failed to set window title: {}", SDL_GetError());
}
@@ -432,8 +470,8 @@ void center_window() {
}
static void push_future_resize_event() {
SDL_Event event{.type = g_sdlCustomEventsStart};
TRY_WARN(SDL_PushEvent(&event), "Failed to push SDL event for future resize: {}", SDL_GetError());
TRY_WARN(push_custom_event(CustomEvent::FutureResize), "Failed to push SDL event for future resize: {}",
SDL_GetError());
}
void request_frame_buffer_resize() {
@@ -463,4 +501,6 @@ void set_frame_buffer_aspect_fit(bool fit) {
request_frame_buffer_resize();
}
void set_background_input(bool value) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, value ? "1" : "0"); }
} // namespace aurora::window
+13
View File
@@ -7,7 +7,17 @@ struct SDL_Window;
struct SDL_Renderer;
namespace aurora::window {
enum class CustomEvent {
FutureResize,
RefreshSurface,
End,
};
static Uint32 operator+(Uint32 lhs, CustomEvent rhs) { return lhs + static_cast<Uint32>(rhs); }
bool initialize();
bool initialize_event_watch();
bool push_custom_event(CustomEvent eventType);
void shutdown();
bool create_window(AuroraBackend backend);
bool create_renderer();
@@ -17,6 +27,8 @@ AuroraWindowSize get_window_size();
const AuroraEvent* poll_events();
SDL_Window* get_sdl_window();
SDL_Renderer* get_sdl_renderer();
bool is_paused() noexcept;
bool is_presentable() noexcept;
void set_title(const char* title);
void set_fullscreen(bool fullscreen);
bool get_fullscreen();
@@ -26,4 +38,5 @@ void center_window();
void request_frame_buffer_resize();
void set_frame_buffer_scale(float scale);
void set_frame_buffer_aspect_fit(bool fit);
void set_background_input(bool value);
}; // namespace aurora::window