mirror of
https://github.com/encounter/aurora.git
synced 2026-07-09 18:19:33 -07:00
Hold SDL's Android activity lock around surface-critical areas
This commit is contained in:
+110
-98
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user