diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index e618003a7..83b60033b 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -365,13 +365,17 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat if (!m_surface) { Log_ErrorPrint("Emulation thread started without surface set."); - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + env->CallVoidMethod(emulation_activity, s_EmulationActivity_method_onEmulationStopped); return; } + { + std::unique_lock lock(m_mutex); + m_emulation_thread_running.store(true); + m_emulation_activity_object = emulation_activity; + m_emulation_thread_id = std::this_thread::get_id(); + } CreateImGuiContext(); - m_emulation_activity_object = emulation_activity; - m_emulation_thread_id = std::this_thread::get_id(); ApplySettings(true); // Boot system. @@ -398,24 +402,31 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat PowerOffSystem(); } - else + + // Drain any callbacks so we don't leave things in a screwed-up state for next boot. { - ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); + std::unique_lock lock(m_mutex); + while (!m_callback_queue.empty()) + { + auto callback = std::move(m_callback_queue.front()); + m_callback_queue.pop_front(); + lock.unlock(); + callback(); + lock.lock(); + } + m_emulation_thread_running.store(false); + m_emulation_thread_id = {}; + m_emulation_activity_object = {}; + m_callbacks_outstanding.store(false); } - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + env->CallVoidMethod(emulation_activity, s_EmulationActivity_method_onEmulationStopped); DestroyImGuiContext(); - m_emulation_activity_object = {}; } void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) { - { - std::unique_lock lock(m_mutex); - m_emulation_thread_running.store(true); - } - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); for (;;) @@ -440,7 +451,6 @@ void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) if (m_emulation_thread_stop_request.load()) { - m_emulation_thread_running.store(false); m_emulation_thread_stop_request.store(false); return; } @@ -1059,6 +1069,13 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j if (surface && !native_surface) Log_ErrorPrint("ANativeWindow_fromSurface() returned null"); + if (!surface && System::GetState() == System::State::Starting) + { + // User switched away from the app while it was compiling shaders. + Log_ErrorPrintf("Surface destroyed while starting, cancelling"); + System::CancelPendingStartup(); + } + // We should wait for the emu to finish if the surface is being destroyed or changed. AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); const bool block = (!native_surface || native_surface != hi->GetSurface()); @@ -1376,6 +1393,12 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveState, jobject obj, jboole DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveResumeState, jobject obj, jboolean wait_for_completion) { + if (!System::IsValid() || System::GetState() == System::State::Starting) + { + // This gets called when the surface is destroyed, which can happen while starting. + return; + } + AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread([hi]() { hi->SaveResumeSaveState(); }, wait_for_completion); } diff --git a/src/core/gpu_hw_d3d11.cpp b/src/core/gpu_hw_d3d11.cpp index 2565d2935..018a1dd5a 100644 --- a/src/core/gpu_hw_d3d11.cpp +++ b/src/core/gpu_hw_d3d11.cpp @@ -509,6 +509,10 @@ bool GPU_HW_D3D11::CompileShaders() do \ { \ progress_value++; \ + if (System::IsStartupCancelled()) \ + { \ + return false; \ + } \ if (compile_time.GetTimeSeconds() >= 1.0f) \ { \ compile_time.Reset(); \ diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index 7502f2cfe..71d6ddc2e 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -519,6 +519,10 @@ bool GPU_HW_OpenGL::CompilePrograms() do \ { \ progress_value++; \ + if (System::IsStartupCancelled()) \ + { \ + return false; \ + } \ if (compile_time.GetTimeSeconds() >= 1.0f) \ { \ compile_time.Reset(); \ diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index f21b1da57..93a0edbe5 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -829,6 +829,10 @@ bool GPU_HW_Vulkan::CompilePipelines() do \ { \ progress_value++; \ + if (System::IsStartupCancelled()) \ + { \ + return false; \ + } \ if (compile_time.GetTimeSeconds() >= 1.0f) \ { \ compile_time.Reset(); \ diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 787c165e7..d4e9ef0bc 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -115,8 +115,12 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters) if (!System::Boot(parameters)) { - ReportFormattedError( - g_host_interface->TranslateString("System", "System failed to boot. The log may contain more information.")); + if (!System::IsStartupCancelled()) + { + ReportFormattedError( + g_host_interface->TranslateString("System", "System failed to boot. The log may contain more information.")); + } + OnSystemDestroyed(); m_audio_stream.reset(); ReleaseHostDisplay(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 94835c047..cc314839a 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -85,6 +85,7 @@ static void UpdateRunningGame(const char* path, CDImage* image); static bool CheckForSBIFile(CDImage* image); static State s_state = State::Shutdown; +static std::atomic_bool s_startup_cancelled{false}; static ConsoleRegion s_region = ConsoleRegion::NTSC_U; TickCount g_ticks_per_second = MASTER_CLOCK; @@ -173,6 +174,17 @@ bool IsValid() return s_state != State::Shutdown && s_state != State::Starting; } +bool IsStartupCancelled() +{ + return s_startup_cancelled.load(); +} + +void CancelPendingStartup() +{ + if (s_state == State::Starting) + s_startup_cancelled.store(true); +} + ConsoleRegion GetRegion() { return s_region; @@ -584,7 +596,10 @@ bool RecreateGPU(GPURenderer renderer, bool update_display /* = true*/) g_gpu.reset(); if (!CreateGPU(renderer)) { - Panic("Failed to recreate GPU"); + if (!IsStartupCancelled()) + g_host_interface->ReportError("Failed to recreate GPU."); + + System::Shutdown(); return false; } @@ -628,6 +643,7 @@ bool Boot(const SystemBootParameters& params) Assert(s_state == State::Shutdown); Assert(s_media_playlist.empty()); s_state = State::Starting; + s_startup_cancelled.store(false); s_region = g_settings.region; if (params.state_stream) @@ -833,6 +849,13 @@ bool Initialize(bool force_software_renderer) if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer)) return false; + // Was startup cancelled? (e.g. shading compilers took too long and the user closed the application) + if (IsStartupCancelled()) + { + Shutdown(); + return false; + } + // CPU code cache must happen after GPU, because it might steal our address space. CPU::CodeCache::Initialize(); diff --git a/src/core/system.h b/src/core/system.h index daad15838..629219f94 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -92,6 +92,9 @@ bool IsPaused(); bool IsShutdown(); bool IsValid(); +bool IsStartupCancelled(); +void CancelPendingStartup(); + ConsoleRegion GetRegion(); bool IsPALRegion(); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 8443663e9..836fac668 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -825,6 +825,7 @@ void QtHostInterface::powerOffSystem() { if (!isOnWorkerThread()) { + System::CancelPendingStartup(); QMetaObject::invokeMethod(this, "powerOffSystem", Qt::QueuedConnection); return; } @@ -839,6 +840,7 @@ void QtHostInterface::powerOffSystemWithoutSaving() { if (!isOnWorkerThread()) { + System::CancelPendingStartup(); QMetaObject::invokeMethod(this, "powerOffSystemWithoutSaving", Qt::QueuedConnection); return; } @@ -849,9 +851,14 @@ void QtHostInterface::powerOffSystemWithoutSaving() void QtHostInterface::synchronousPowerOffSystem() { if (!isOnWorkerThread()) + { + System::CancelPendingStartup(); QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection); + } else + { powerOffSystem(); + } } void QtHostInterface::resetSystem()