diff --git a/lib/aurora.cpp b/lib/aurora.cpp index 4191dee..91e5455 100644 --- a/lib/aurora.cpp +++ b/lib/aurora.cpp @@ -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(webgpu::g_graphicsConfig.surfaceConfiguration.width), - static_cast(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(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(webgpu::g_graphicsConfig.surfaceConfiguration.width), + static_cast(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(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); diff --git a/lib/webgpu/gpu.cpp b/lib/webgpu/gpu.cpp index 1cae40f..0a9e3d3 100644 --- a/lib/webgpu/gpu.cpp +++ b/lib/webgpu/gpu.cpp @@ -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; } diff --git a/lib/window.cpp b/lib/window.cpp index 5d4473d..c7ea609 100644 --- a/lib/window.cpp +++ b/lib/window.cpp @@ -20,6 +20,12 @@ #include #include +#if defined(SDL_PLATFORM_ANDROID) +#include +extern "C" void Android_LockActivityMutex(void); +extern "C" void Android_UnlockActivityMutex(void); +#endif + #include #include #include @@ -38,6 +44,11 @@ bool g_frameBufferAspectFit = false; AuroraWindowSize g_windowSize; std::vector 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 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 diff --git a/lib/window.hpp b/lib/window.hpp index d5855ce..51257ea 100644 --- a/lib/window.hpp +++ b/lib/window.hpp @@ -15,6 +15,17 @@ enum class CustomEvent { }; static Uint32 operator+(Uint32 lhs, CustomEvent rhs) { return lhs + static_cast(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();