Hold SDL's Android activity lock around surface-critical areas

This commit is contained in:
Luke Street
2026-05-09 15:02:38 -06:00
parent 022a271f71
commit ac165cf1a6
4 changed files with 171 additions and 102 deletions
+110 -98
View File
@@ -193,47 +193,51 @@ 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);
{
window::SurfaceLock surfaceLock;
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) {
return false;
}
}
wgpu::SurfaceTexture surfaceTexture;
g_surface.GetCurrentTexture(&surfaceTexture);
switch (surfaceTexture.status) {
case wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal:
g_currentView = surfaceTexture.texture.CreateView();
break;
case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
Log.warn("Surface texture acquisition timed out");
return false;
case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceTexture.status));
webgpu::refresh_surface(false);
return false;
case wgpu::SurfaceGetCurrentTextureStatus::Lost:
Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceTexture.status));
webgpu::release_surface();
case wgpu::SurfaceGetCurrentTextureStatus::Error:
Log.warn("Surface texture is {}, dropping surface", magic_enum::enum_name(surfaceTexture.status));
g_surface = {};
return false;
default:
Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status));
return false;
}
}
wgpu::SurfaceTexture surfaceTexture;
g_surface.GetCurrentTexture(&surfaceTexture);
switch (surfaceTexture.status) {
case wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal:
g_currentView = surfaceTexture.texture.CreateView();
break;
case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
Log.warn("Surface texture acquisition timed out");
return false;
case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceTexture.status));
webgpu::refresh_surface(false);
return false;
case wgpu::SurfaceGetCurrentTextureStatus::Lost:
Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceTexture.status));
webgpu::release_surface();
case wgpu::SurfaceGetCurrentTextureStatus::Error:
Log.warn("Surface texture is {}, dropping surface", magic_enum::enum_name(surfaceTexture.status));
g_surface = {};
return false;
default:
Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status));
return false;
}
imgui::new_frame(window::get_window_size());
if (!gfx::begin_frame()) {
g_currentView = {};
return false;
}
#endif
@@ -250,74 +254,82 @@ void end_frame() noexcept {
auto encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
gfx::end_frame(encoder);
gfx::render(encoder);
const auto& presentSource = webgpu::present_source();
auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
presentSource.size.width, presentSource.size.height);
wgpu::BindGroup presentBindGroup = webgpu::g_CopyBindGroup;
#if AURORA_ENABLE_RMLUI
if (rmlui::is_initialized()) {
const auto rmlOutput = rmlui::render(encoder, viewport);
if (rmlOutput.texture != nullptr) {
presentBindGroup = rmlOutput.copyBindGroup;
}
}
#endif
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "EFB copy render pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
// Copy EFB -> XFB (swapchain)
pass.SetPipeline(webgpu::g_CopyPipeline);
pass.SetBindGroup(0, presentBindGroup, 0, nullptr);
pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar);
window::SurfaceLock surfaceLock;
if (window::is_presentable() && g_surface && g_currentView) {
const auto& presentSource = webgpu::present_source();
auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
presentSource.size.width, presentSource.size.height);
wgpu::BindGroup presentBindGroup = webgpu::g_CopyBindGroup;
#if AURORA_ENABLE_RMLUI
if (rmlui::is_initialized()) {
const auto rmlOutput = rmlui::render(encoder, viewport);
if (rmlOutput.texture != nullptr) {
presentBindGroup = rmlOutput.copyBindGroup;
}
}
#endif
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "EFB copy render pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
// Copy EFB -> XFB (swapchain)
pass.SetPipeline(webgpu::g_CopyPipeline);
pass.SetBindGroup(0, presentBindGroup, 0, nullptr);
pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar);
pass.Draw(3);
pass.End();
}
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.loadOp = wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "ImGui render pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.SetViewport(0.f, 0.f, static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.width),
static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.height), 0.f, 1.f);
imgui::render(pass);
pass.End();
}
const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"};
const auto buffer = encoder.Finish(&cmdBufDescriptor);
g_queue.Submit(1, &buffer);
gfx::after_submit();
if (window::is_presentable()) {
auto presentStatus = g_surface.Present();
if (presentStatus != wgpu::Status::Success) {
Log.warn("Surface present failed: {}", static_cast<int>(presentStatus));
pass.Draw(3);
pass.End();
}
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.loadOp = wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "ImGui render pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.SetViewport(0.f, 0.f, static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.width),
static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.height), 0.f, 1.f);
imgui::render(pass);
pass.End();
}
} else {
Log.info("Skipping present; window not presentable");
webgpu::release_surface();
}
} else {
Log.info("Skipping present; window not presentable");
webgpu::release_surface();
const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"};
const auto buffer = encoder.Finish(&cmdBufDescriptor);
g_queue.Submit(1, &buffer);
gfx::after_submit();
if (window::is_presentable() && g_surface) {
auto presentStatus = g_surface.Present();
if (presentStatus != wgpu::Status::Success) {
Log.warn("Surface present failed: {}", static_cast<int>(presentStatus));
webgpu::release_surface();
}
} else if (g_surface) {
webgpu::release_surface();
}
g_currentView = {};
}
g_currentView = {};
TracyPlotConfig("aurora: lastVertSize", tracy::PlotFormatType::Memory, false, true, 0);
TracyPlotConfig("aurora: lastUniformSize", tracy::PlotFormatType::Memory, false, true, 0);
+9 -3
View File
@@ -427,8 +427,11 @@ bool initialize(AuroraBackend auroraBackend) {
g_dawnInstance->EnableBackendValidation(backend != WGPUBackendType::D3D12);
#endif
if (!create_surface()) {
return false;
{
window::SurfaceLock surfaceLock;
if (!create_surface()) {
return false;
}
}
{
const wgpu::RequestAdapterOptions options{
@@ -636,7 +639,10 @@ bool initialize(AuroraBackend auroraBackend) {
.textureAnisotropy = g_config.maxTextureAnisotropy,
};
create_copy_pipeline();
resize_swapchain(size.fb_width, size.fb_height, size.native_fb_width, size.native_fb_height, true);
{
window::SurfaceLock surfaceLock;
resize_swapchain(size.fb_width, size.fb_height, size.native_fb_width, size.native_fb_height, true);
}
return true;
}
+40 -1
View File
@@ -20,6 +20,12 @@
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_pixels.h>
#if defined(SDL_PLATFORM_ANDROID)
#include <jni.h>
extern "C" void Android_LockActivityMutex(void);
extern "C" void Android_UnlockActivityMutex(void);
#endif
#include <algorithm>
#include <atomic>
#include <vector>
@@ -38,6 +44,11 @@ bool g_frameBufferAspectFit = false;
AuroraWindowSize g_windowSize;
std::vector<AuroraEvent> g_events;
std::atomic_bool g_backgrounded = false;
#if defined(SDL_PLATFORM_ANDROID)
std::atomic_bool g_surfaceReady = false;
#else
std::atomic_bool g_surfaceReady = true;
#endif
bool g_lastPaused = false;
bool operator==(const AuroraWindowSize& lhs, const AuroraWindowSize& rhs) {
@@ -69,6 +80,9 @@ Vec2<int> fit_frame_buffer_to_aspect(int width, int height, float aspect) {
}
void resize_swapchain() noexcept {
#if defined(SDL_PLATFORM_ANDROID)
SurfaceLock surfaceLock;
#endif
const auto size = get_window_size();
if (size == g_windowSize) {
return;
@@ -194,6 +208,7 @@ void process_event(SDL_Event& event) {
} else if (event.type == g_sdlCustomEventsStart + CustomEvent::RefreshSurface) {
// Refresh surface (vsync changed)
#ifdef AURORA_ENABLE_GX
SurfaceLock surfaceLock;
webgpu::refresh_surface(false);
#endif
}
@@ -431,7 +446,24 @@ bool is_paused() noexcept {
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 is_presentable() noexcept {
return g_window != nullptr && !g_backgrounded.load(std::memory_order_acquire) &&
g_surfaceReady.load(std::memory_order_acquire);
}
void set_surface_ready(bool ready) noexcept { g_surfaceReady.store(ready, std::memory_order_release); }
SurfaceLock::SurfaceLock() noexcept {
#if defined(SDL_PLATFORM_ANDROID)
Android_LockActivityMutex();
#endif
}
SurfaceLock::~SurfaceLock() {
#if defined(SDL_PLATFORM_ANDROID)
Android_UnlockActivityMutex();
#endif
}
bool push_custom_event(CustomEvent eventType) {
SDL_Event event{.type = g_sdlCustomEventsStart + eventType};
@@ -497,3 +529,10 @@ void set_frame_buffer_aspect_fit(bool fit) {
void set_background_input(bool value) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, value ? "1" : "0"); }
} // namespace aurora::window
#if defined(SDL_PLATFORM_ANDROID)
extern "C" JNIEXPORT void JNICALL Java_org_libsdl_app_SDLSurface_auroraNativeSetSurfaceReady(JNIEnv*, jclass,
jboolean ready) {
aurora::window::set_surface_ready(ready == JNI_TRUE);
}
#endif
+12
View File
@@ -15,6 +15,17 @@ enum class CustomEvent {
};
static Uint32 operator+(Uint32 lhs, CustomEvent rhs) { return lhs + static_cast<Uint32>(rhs); }
// On Android in particular, we need to hold a mutex around critical areas like surface creation
// and presentation, so that the SDLActivity doesn't destroy the surface out from underneath us.
class SurfaceLock {
public:
SurfaceLock() noexcept;
~SurfaceLock();
SurfaceLock(const SurfaceLock&) = delete;
SurfaceLock& operator=(const SurfaceLock&) = delete;
};
bool initialize();
bool initialize_event_watch();
bool push_custom_event(CustomEvent eventType);
@@ -29,6 +40,7 @@ SDL_Window* get_sdl_window();
SDL_Renderer* get_sdl_renderer();
bool is_paused() noexcept;
bool is_presentable() noexcept;
void set_surface_ready(bool ready) noexcept;
void set_title(const char* title);
void set_fullscreen(bool fullscreen);
bool get_fullscreen();