From be922eca626c94484cc80ba4a376b7faef65d13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 4 Aug 2026 14:30:04 +0200 Subject: [PATCH] Make OpenGL render thread shutdown more robust. --- Common/GPU/GraphicsContext.h | 33 ++-------- Common/GPU/OpenGL/GLMemory.cpp | 14 ----- Common/GPU/OpenGL/GLRenderManager.cpp | 62 +++++++++++-------- Common/GPU/OpenGL/GLRenderManager.h | 10 ++- Common/GPU/OpenGL/OpenGLGraphicsContext.h | 14 +++-- Core/EmuThread.cpp | 43 +++++-------- Core/EmuThread.h | 1 + SDL/SDLGLGraphicsContext.h | 8 ++- SDL/SDLMain.cpp | 60 ++++++++++-------- UI/NativeApp.cpp | 8 ++- Windows/GPU/WindowsGLContext.cpp | 9 ++- Windows/GPU/WindowsGLContext.h | 6 +- android/jni/app-android.cpp | 9 ++- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 2 + ext/aemu_postoffice | 2 +- headless/SDLHeadlessHost.cpp | 6 +- headless/WindowsHeadlessHost.cpp | 6 +- ios/ViewController.mm | 8 ++- libretro/libretro.cpp | 44 ++++++------- pspautotests | 2 +- 20 files changed, 169 insertions(+), 178 deletions(-) diff --git a/Common/GPU/GraphicsContext.h b/Common/GPU/GraphicsContext.h index e67f85cd64..4b6b7ac12b 100644 --- a/Common/GPU/GraphicsContext.h +++ b/Common/GPU/GraphicsContext.h @@ -37,40 +37,17 @@ public: // Called from the render thread from threaded backends. virtual void ThreadStart() {} - virtual bool ThreadFrame(bool waitIfEmpty) { return true; } // waitIfEmpty should normally be true, except in exit scenarios. + virtual bool ThreadFrame() { return true; } // Returns false when it's time to exit the loop. virtual void ThreadEnd() {} + // When this is called, the emulation thread has stopped producing frames completely. + // Called from the emu thread, so be ready for that. + virtual void NotifyEmuThreadExit() {} + // Useful for checks that need to be performed every frame. // Should strive to get rid of these. virtual void Poll() {} // TODO: Store in a protected variable? virtual Draw::DrawContext *GetDrawContext() = 0; - - void ThreadFrameUntilCondition(std::function conditionStopped) { - _dbg_assert_(NeedsSeparateEmuThread()); - BeginShutdownSurface(); - bool exitOnEmpty = false; - - INFO_LOG(Log::System, "Executing graphicsContext->ThreadFrame to clear buffers"); - while (true) { - // When EmuThread is done, we know there are no more frames coming. When that happens, - // we'll bail. - if (!exitOnEmpty && conditionStopped()) { - INFO_LOG(Log::System, "Found out that the thread is done."); - exitOnEmpty = true; - } - - const bool retval = ThreadFrame(false); - if (!retval && exitOnEmpty) { - INFO_LOG(Log::System, "ThreadFrame returned false and emu thread is done, breaking."); - break; - } else { - sleep_ms(5, "exit poll"); - } - } - } - -protected: - virtual void BeginShutdownSurface() {} // This is currently only used on Android. }; diff --git a/Common/GPU/OpenGL/GLMemory.cpp b/Common/GPU/OpenGL/GLMemory.cpp index 86fb57ab76..7064c31f9c 100644 --- a/Common/GPU/OpenGL/GLMemory.cpp +++ b/Common/GPU/OpenGL/GLMemory.cpp @@ -4,13 +4,6 @@ #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/Data/Text/Parsers.h" -extern std::thread::id renderThreadId; -#if MAX_LOGLEVEL >= DEBUG_LEVEL -static bool OnRenderThread() { - return std::this_thread::get_id() == renderThreadId; -} -#endif - void *GLRBuffer::Map(GLBufferStrategy strategy) { _assert_(buffer_ != 0); @@ -102,7 +95,6 @@ void GLPushBuffer::Unmap() { void GLPushBuffer::Flush() { // Must be called from the render thread. - _dbg_assert_(OnRenderThread()); if (buf_ >= buffers_.size()) { _dbg_assert_msg_(false, "buf_ somehow got out of sync: %d vs %d", (int)buf_, (int)buffers_.size()); return; @@ -184,8 +176,6 @@ void GLPushBuffer::NextBuffer(size_t minSize) { } void GLPushBuffer::Defragment() { - _dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread"); - if (buffers_.size() <= 1) { // Let's take this opportunity to jettison any localMemory we don't need. for (auto &info : buffers_) { @@ -227,8 +217,6 @@ size_t GLPushBuffer::GetTotalSize() const { } void GLPushBuffer::MapDevice(GLBufferStrategy strategy) { - _dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread"); - strategy_ = strategy; if (strategy_ == GLBufferStrategy::SUBDATA) { return; @@ -261,8 +249,6 @@ void GLPushBuffer::MapDevice(GLBufferStrategy strategy) { } void GLPushBuffer::UnmapDevice() { - _dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread"); - for (auto &info : buffers_) { if (info.deviceMemory) { // TODO: Technically this can return false? diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index 426b344b21..e6385773a8 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -17,8 +17,6 @@ #define VLOG(...) #endif -std::thread::id renderThreadId; - GLRTexture::GLRTexture(const Draw::DeviceCaps &caps, int width, int height, int depth, int numMips) { w = width; h = height; @@ -38,8 +36,6 @@ GLRenderManager::GLRenderManager(HistoryBuffer lock(pushMutex_); + renderThreadQueue_.push(task); + } + pushCondVar_.notify_one(); +} + // Unlike in Vulkan, this isn't a full independent function, instead it gets called every frame. // // This means that we have to block and run the render queue until we've presented one frame, // at which point we can leave. -// -// NOTE: If run_ is true, we WILL run a task! -bool GLRenderManager::ThreadFrame(bool waitIfEmpty) { - _assert_(runCompileThread_); +bool GLRenderManager::ThreadFrame() { + if (hitExit_) { + return false; + } GLRRenderThreadTask *task = nullptr; @@ -134,22 +149,18 @@ bool GLRenderManager::ThreadFrame(bool waitIfEmpty) { // to keep things uniform. { std::unique_lock lock(pushMutex_); - - if (!waitIfEmpty && renderThreadQueue_.empty()) { - lock.unlock(); - // Oh, host wanted out. Let's leave, and also let's notify the host. - // This is unlike Vulkan too which can just block on the thread existing. - std::unique_lock lock(syncMutex_); - syncCondVar_.notify_one(); - syncDone_ = true; - return false; - } - pushCondVar_.wait(lock, [this] { return !renderThreadQueue_.empty(); }); task = renderThreadQueue_.front(); renderThreadQueue_.pop(); } + if (task->runType == GLRRunType::EXIT) { + VLOG(" PULL: Frame %d EXIT (%0.3f)", task->frame, time_now_d()); + delete task; + hitExit_ = true; + return false; + } + // Render the scene. VLOG(" PULL: Frame %d RUN (%0.3f)", task->frame, time_now_d()); if (Run(*task)) { @@ -159,7 +170,6 @@ bool GLRenderManager::ThreadFrame(bool waitIfEmpty) { } delete task; }; - return true; } @@ -325,13 +335,13 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i } void GLRenderManager::BeginFrame(bool enableProfiling) { + _dbg_assert_(!exitNotified_); + _dbg_assert_(!hitExit_); + #ifdef _DEBUG curProgram_ = nullptr; #endif - // Shouldn't call BeginFrame unless we're in a run state. - _dbg_assert_(runCompileThread_); - int curFrame = GetCurFrame(); FrameTimeData &frameTimeData = frameTimeHistory_.Add(frameIdGen_); diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 766bd1916f..805a55fca8 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -200,6 +200,7 @@ enum class GLRRunType { SUBMIT, PRESENT, SYNC, + EXIT, }; class GLRenderManager; @@ -237,7 +238,9 @@ public: void ThreadStart(Draw::DrawContext *draw); void ThreadEnd(); - bool ThreadFrame(bool waitIfEmpty); // Returns true if it did anything. False means the queue was empty. + bool ThreadFrame(); // False means it's time to exit. + + void NotifyEmuThreadExit(); void SetErrorCallback(ErrorCallbackFn callback, void *userdata) { queueRunner_.SetErrorCallback(callback, userdata); @@ -860,8 +863,6 @@ private: FastVec initSteps_; // Execution time state - // TODO: Rename this, as we don't actually use a compile thread on OpenGL. - bool runCompileThread_ = true; // Thread is managed elsewhere, and should call ThreadFrame. GLQueueRunner queueRunner_; @@ -896,6 +897,9 @@ private: int targetWidth_ = 0; int targetHeight_ = 0; + bool exitNotified_ = false; + bool hitExit_ = false; + #ifdef _DEBUG GLRProgram *curProgram_ = nullptr; #endif diff --git a/Common/GPU/OpenGL/OpenGLGraphicsContext.h b/Common/GPU/OpenGL/OpenGLGraphicsContext.h index 972e1a9c0c..88ba27f5f3 100644 --- a/Common/GPU/OpenGL/OpenGLGraphicsContext.h +++ b/Common/GPU/OpenGL/OpenGLGraphicsContext.h @@ -22,21 +22,23 @@ public: return draw_; } + // Called from render thread void ThreadStart() override { renderManager_->ThreadStart(draw_); } - - bool ThreadFrame(bool waitIfEmpty) override { - return renderManager_->ThreadFrame(waitIfEmpty); + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); } - void ThreadEnd() override { renderManager_->ThreadEnd(); } -protected: - void BeginShutdownSurface() override { + + // Call from emu thread + void NotifyEmuThreadExit() override { renderManager_->SetSkipGLCalls(); + renderManager_->NotifyEmuThreadExit(); } + private: Draw::DrawContext *draw_ = nullptr; GLRenderManager *renderManager_ = nullptr; diff --git a/Core/EmuThread.cpp b/Core/EmuThread.cpp index b4dae09cff..4d46198d83 100644 --- a/Core/EmuThread.cpp +++ b/Core/EmuThread.cpp @@ -40,6 +40,8 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext, Application *applica INFO_LOG(Log::G3D, "Entering separate emu thread"); SetCurrentThreadName("EmuThread"); + g_emuThreadState = EmuThreadState::RUNNING; + AndroidJNIThreadContext context; // This normally calls NativeInitGraphics() @@ -66,39 +68,34 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext, Application *applica INFO_LOG(Log::System, "emuThreadState was set to QUIT_REQUESTED, left EmuThreadFunc loop. Setting state to STOPPED."); - g_emuThreadState = EmuThreadState::STOPPED; - // This normally calls NativeShutdownGraphics() application->ShutdownGraphics(graphicsContext); delete application; INFO_LOG(Log::System, "Leaving separate emu thread"); + + g_emuThreadState = EmuThreadState::STOPPED; } std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function postFrame) { + INFO_LOG(Log::System, "EmuTread_Start"); _dbg_assert_(g_emuThreadState == EmuThreadState::STOPPED); - g_emuThreadState = EmuThreadState::RUNNING; std::thread emuThread = std::thread(&EmuThreadFunc, graphicsContext, application, postFrame); graphicsContext->ThreadStart(); return emuThread; } +// This is useful when the render thread is in control. +void EmuThread_RequestExit() { + INFO_LOG(Log::System, "EmuTread_RequestExit"); + g_emuThreadState = EmuThreadState::QUIT_REQUESTED; +} + void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread) { - const EmuThreadState state = g_emuThreadState; - if (state != EmuThreadState::QUIT_REQUESTED && - state != EmuThreadState::STOPPED) { - g_emuThreadState = EmuThreadState::QUIT_REQUESTED; - } - _dbg_assert_(emuThread.joinable()); - if (graphicsContext->NeedsSeparateEmuThread()) { - graphicsContext->ThreadFrameUntilCondition([] { - // Need to keep eating frames to allow the EmuThread to exit correctly. - return g_emuThreadState == EmuThreadState::STOPPED; - }); - } - graphicsContext->ThreadEnd(); + INFO_LOG(Log::System, "EmuTread_Join"); emuThread.join(); emuThread = std::thread(); + graphicsContext->ThreadEnd(); } bool RunMainLoop(GraphicsContext *graphicsContext, Application *application, std::function runCondition, std::function postFrame) { @@ -150,19 +147,9 @@ bool MainThreadFunc(GraphicsContext *graphicsContext, Application *application, g_inLoop = true; std::thread emuThread = EmuThread_Start(graphicsContext, application, postFrame); - graphicsContext->ThreadStart(); - // This thread becomes the render thread. - while (true) { - if (equals_any(g_emuThreadState, EmuThreadState::QUIT_REQUESTED, EmuThreadState::STOPPED)) { - break; - } - graphicsContext->ThreadFrame(true); - if (GetUIState() == UISTATE_EXIT) { - break; - } - } - + // This thread becomes the render thread. EmuThread will tell it when to quit by sending a message. + while (graphicsContext->ThreadFrame()) {} EmuThread_Join(graphicsContext, emuThread); g_inLoop = false; diff --git a/Core/EmuThread.h b/Core/EmuThread.h index d32cefce28..f6117c5534 100644 --- a/Core/EmuThread.h +++ b/Core/EmuThread.h @@ -38,6 +38,7 @@ bool MainThreadFunc(GraphicsContext * graphicsContext, Application *application, // the case where a separate EmuThread is not needed. // NOTE: Does take ownership over Application (which is just a wrapper for NativeInitGraphics/NativeShutdownGraphics/NativeFrame). std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function postFrame); +void EmuThread_RequestExit(); // Useful when the render thread is in control like on Android. void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread); // Call from the main thread. diff --git a/SDL/SDLGLGraphicsContext.h b/SDL/SDLGLGraphicsContext.h index efc7259d7d..061ffdc653 100644 --- a/SDL/SDLGLGraphicsContext.h +++ b/SDL/SDLGLGraphicsContext.h @@ -31,14 +31,18 @@ public: renderManager_->ThreadStart(draw_); } - bool ThreadFrame(bool waitIfEmpty) override { - return renderManager_->ThreadFrame(waitIfEmpty); + bool ThreadFrame() override { + return renderManager_->ThreadFrame(); } void ThreadEnd() override { renderManager_->ThreadEnd(); } + void NotifyEmuThreadExit() override { + renderManager_->NotifyEmuThreadExit(); + } + private: Draw::DrawContext *draw_ = nullptr; SDL_Window *window_ = nullptr; diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 63a35c1473..4da83c376c 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -2192,7 +2192,6 @@ int main(int argc, char *argv[]) { // We use the emuthread both for OpenGL and Vulkan, but in OpenGL mode we also render from the main thread. _dbg_assert_(graphicsContext); - std::thread emuThread = EmuThread_Start(graphicsContext, new NativeApplication(), nullptr); InputStateTracker inputTracker{}; @@ -2211,6 +2210,8 @@ int main(int argc, char *argv[]) { const bool mainThreadIsRender = graphicsContext->NeedsSeparateEmuThread(); if (!mainThreadIsRender) { + // TODO: This shouldn't be the EmuThread, but rather RunMainLoop? + std::thread emuThread = EmuThread_Start(graphicsContext, new NativeApplication(), nullptr); // Vulkan mode uses this. // We should only be a message pump. This allows for lower latency // input events, and so on. The spawned EmuThread runs emulation and rendering. @@ -2240,37 +2241,46 @@ int main(int argc, char *argv[]) { } } } - } else while (true) { - // OpenGL mode uses this. - { - SDL_Event event; - while (SDL_PollEvent(&event)) { - ProcessSDLEvent(window, event, &inputTracker); + EmuThread_Join(graphicsContext, emuThread); + } else { + std::thread emuThread = EmuThread_Start(graphicsContext, new NativeApplication(), nullptr); + while (true) { + // OpenGL mode uses this. + { + SDL_Event event; + while (SDL_PollEvent(&event)) { + ProcessSDLEvent(window, event, &inputTracker); + } } - } - if (g_QuitRequested || g_RestartRequested) - break; - - UpdateTextFocus(window); - UpdateSDLCursor(); - - inputTracker.MouseCaptureControl(window); - - bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized; - if (graphicsContext->NeedsSeparateEmuThread() && !renderThreadPaused) { - if (!graphicsContext->ThreadFrame(true)) + if (g_QuitRequested || g_RestartRequested) break; - } - { - std::lock_guard guard(g_mutexWindow); - if (g_windowState.update) { - UpdateWindowState(window); + UpdateTextFocus(window); + UpdateSDLCursor(); + + inputTracker.MouseCaptureControl(window); + + bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized; + if (graphicsContext->NeedsSeparateEmuThread() && !renderThreadPaused) { + if (!graphicsContext->ThreadFrame()) { + // The render thread was instructed to exit by the emu thread, + // and has now reached the end of the submitted frames. + // EmuThread will be in the process of exiting, so below + // we can just EmuThread_Join(). + break; + } + } + + { + std::lock_guard guard(g_mutexWindow); + if (g_windowState.update) { + UpdateWindowState(window); + } } } + EmuThread_Join(graphicsContext, emuThread); } - EmuThread_Join(graphicsContext, emuThread); delete joystick; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 34979f02d6..c1ae9917d1 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -944,17 +944,19 @@ bool CreateGlobalPipelines() { return true; } -void NativeShutdownGraphics(GraphicsContext *graphicContext) { +void NativeShutdownGraphics(GraphicsContext *graphicsContext) { INFO_LOG(Log::System, "NativeShutdownGraphics begin"); + graphicsContext->NotifyEmuThreadExit(); + if (g_screenManager) { g_screenManager->deviceLost(); } g_iconCache.ClearTextures(); - // TODO: This is not really necessary with Vulkan on Android - could keep shaders etc in memory - if (gpu) + if (gpu) { gpu->DeviceLost(); + } #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) if (winCamera) { diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 04bf61028d..357878b69a 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -485,10 +485,15 @@ void WindowsGLContext::ThreadStart() { renderManager_->ThreadStart(draw_); } -bool WindowsGLContext::ThreadFrame(bool waitIfEmpty) { - return renderManager_->ThreadFrame(waitIfEmpty); +bool WindowsGLContext::ThreadFrame() { + return renderManager_->ThreadFrame(); } void WindowsGLContext::ThreadEnd() { renderManager_->ThreadEnd(); } + +void WindowsGLContext::NotifyEmuThreadExit() { + // renderManager_->SetSkipGLCalls(); // not necessary on desktop, maybe unadvisable? + renderManager_->NotifyEmuThreadExit(); +} diff --git a/Windows/GPU/WindowsGLContext.h b/Windows/GPU/WindowsGLContext.h index 1b0070d148..47aaa3d8b9 100644 --- a/Windows/GPU/WindowsGLContext.h +++ b/Windows/GPU/WindowsGLContext.h @@ -33,7 +33,11 @@ public: // If these are used, they are called from the render thread. If NeedsRenderThread is false, they are not called. void ThreadStart() override; void ThreadEnd() override; - bool ThreadFrame(bool waitIfEmpty) override; + + // If this returns false, the render thread has been instructed to exit. + bool ThreadFrame() override; + + void NotifyEmuThreadExit() override; Draw::DrawContext *GetDrawContext() override { return draw_; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 2e014b7e36..74eeb6f35b 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -966,8 +966,10 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * e // Would be really nice if we could get something on the GL thread immediately when shutting down, // but the only mechanism for handling lost devices seems to be that onSurfaceCreated is called again, // which ends up calling displayInit. - - INFO_LOG(Log::G3D, "NativeApp.displayInit() restoring"); + INFO_LOG(Log::G3D, "NativeApp.displayInit(): Second time, joining the emuthread and starting it up again."); + EmuThread_RequestExit(); + // Eat up any remaining frames. + while (graphicsContext->ThreadFrame()) {} EmuThread_Join(graphicsContext, g_emuThread); graphicsContext->ShutdownSurface(); @@ -1157,8 +1159,9 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, } _assert_(graphicsContext->NeedsSeparateEmuThread()); - if (!graphicsContext->ThreadFrame(true)) { + if (!graphicsContext->ThreadFrame()) { INFO_LOG(Log::G3D, "ThreadFrame returned false"); + // TODO: We should stop calling ThreadFrame here. return; } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index ee4a6d47c2..35c01a2c4f 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -996,6 +996,8 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } } else if (mSurface != null) { // JavaGL path. + // TODO: This might not be the best place to do this. Seems to cause a surface recreation + // unnecessarily. Log.i(TAG, "notifySurface: Applying framerate."); applyFrameRate(mSurface, 60.0f); } diff --git a/ext/aemu_postoffice b/ext/aemu_postoffice index 9e533637b9..d4dd0e90f4 160000 --- a/ext/aemu_postoffice +++ b/ext/aemu_postoffice @@ -1 +1 @@ -Subproject commit 9e533637b9a13bb0194c75cf43b0bf6d25d8101a +Subproject commit d4dd0e90f411b7cd1a1a38610a3712f2b46a6bc7 diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessHost.cpp index ffefa2335e..1c46f028d2 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessHost.cpp @@ -185,7 +185,7 @@ bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext * gfx_->ThreadStart(); threadState_ = RenderThreadState::STARTED; - gfx_->ThreadFrameUntilCondition([this] { return threadState_ == RenderThreadState::STOP_REQUESTED; }); + while (gfx_->ThreadFrame()) {} threadState_ = RenderThreadState::STOPPING; gfx_->ThreadEnd(); @@ -202,9 +202,7 @@ bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext * } void SDLHeadlessHost::ShutdownGraphics() { - threadState_ = RenderThreadState::STOP_REQUESTED; - while (threadState_ != RenderThreadState::STOPPED && threadState_ != RenderThreadState::START_FAILED) - sleep_ms(1, "sdl-stop-poll"); + gfx_->NotifyEmuThreadExit(); gfx_->ShutdownAPI(); delete gfx_; diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index e20a2ae4e5..5e08b06f8b 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -121,7 +121,7 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte gfx_->ThreadStart(); threadState_ = RenderThreadState::STARTED; - gfx_->ThreadFrameUntilCondition([this] { return threadState_ == RenderThreadState::STOP_REQUESTED; }); + while (gfx_->ThreadFrame()) {}; threadState_ = RenderThreadState::STOPPING; gfx_->ThreadEnd(); @@ -153,9 +153,7 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte void WindowsHeadlessHost::ShutdownGraphics() { if (renderThread_.joinable()) { - threadState_ = RenderThreadState::STOP_REQUESTED; - while (threadState_ != RenderThreadState::STOPPED && threadState_ != RenderThreadState::IDLE) - sleep_ms(1, "render-thread-stop-poll"); + gfx_->NotifyEmuThreadExit(); renderThread_.join(); } else { gfx_->ShutdownSurface(); diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 488b446a52..d42f873af4 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -42,7 +42,6 @@ #error Must be built with ARC, please revise the flags for ViewController.mm to include -fobjc-arc. #endif -static std::atomic renderLoopRunning; static std::thread g_emuThread; PPSSPPBaseViewController *sharedViewController; @@ -90,6 +89,9 @@ PPSSPPBaseViewController *sharedViewController; - (void)requestExitGLRenderLoop { _assert_(g_emuThread.joinable()); + EmuThread_RequestExit(); + // Eat up any remaining frames. + while (graphicsContext->ThreadFrame()) {} EmuThread_Join(graphicsContext, g_emuThread); _assert_(!g_emuThread.joinable()); } @@ -226,7 +228,9 @@ PPSSPPBaseViewController *sharedViewController; return; } if (sharedViewController) { - graphicsContext->ThreadFrame(true); + if (!graphicsContext->ThreadFrame()) { + INFO_LOG(Log::G3D, "ThreadFrame returned false"); + } } } diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index b44c55cc7b..7b38fd175d 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1426,11 +1426,6 @@ namespace Libretro { emuThreadState = EmuThreadState::QUIT_REQUESTED; - // Need to keep eating frames to allow the EmuThread to exit correctly. - ctx->ThreadFrameUntilCondition([]() -> bool { - return emuThreadState == EmuThreadState::STOPPED; - }); - emuThread.join(); emuThread = std::thread(); ctx->ThreadEnd(); @@ -1443,7 +1438,7 @@ namespace Libretro { emuThreadState = EmuThreadState::PAUSE_REQUESTED; // Is this safe? - ctx->ThreadFrame(true); // Eat 1 frame + ctx->ThreadFrame(); // Eat 1 frame while (emuThreadState != EmuThreadState::PAUSED) sleep_ms(1, "libretro-pause-poll"); @@ -1656,6 +1651,7 @@ static void retro_input(void) { __CtrlSetAnalogXY(CTRL_STICK_RIGHT, x_right, y_right); } +// Called every frame by retroarch. void retro_run(void) { if (g_pendingBoot) { BootState state = PSP_InitUpdate(&g_bootErrorString); @@ -1701,30 +1697,31 @@ void retro_run(void) { retro_reset(); } + // Update setting if any have changed. bool updated; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) - && updated) + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(PSP_CoreParameter()); else check_dynamic_variables(PSP_CoreParameter()); + // Process input. retro_input(); - if (useEmuThread) - { - if ( emuThreadState == EmuThreadState::PAUSED || - emuThreadState == EmuThreadState::PAUSE_REQUESTED) - { + // Handle thread pumping. + if (useEmuThread) { + if (emuThreadState == EmuThreadState::PAUSED || + emuThreadState == EmuThreadState::PAUSE_REQUESTED) { VsyncSwapIntervalDetect(); ctx->SwapBuffers(); return; } - if (emuThreadState != EmuThreadState::RUNNING) + if (emuThreadState != EmuThreadState::RUNNING) { EmuThreadStart(); + } - if (!ctx->ThreadFrame(true)) - { + if (!ctx->ThreadFrame()) { + // We're done processing the last frame from the emu thread. VsyncSwapIntervalDetect(); return; } @@ -1763,22 +1760,21 @@ size_t retro_serialize_size(void) // We don't unpause intentionally } -bool retro_serialize(void *data, size_t size) -{ +bool retro_serialize(void *data, size_t size) { if (!gpu) // The HW renderer isn't ready on first pass. return false; // TODO: Libretro API extension to use the savestate queue - if (useEmuThread) + if (useEmuThread) { EmuThreadPause(); // Does nothing if already paused + } size_t measuredSize; SaveState::SaveStart state; auto err = CChunkFileReader::MeasureAndSavePtr(state, (u8 **)&data, &measuredSize); bool retVal = err == CChunkFileReader::ERROR_NONE; - if (useEmuThread) - { + if (useEmuThread) { EmuThreadStart(); sleep_ms(4, "libretro-serialize"); } @@ -1786,8 +1782,7 @@ bool retro_serialize(void *data, size_t size) return retVal; } -bool retro_unserialize(const void *data, size_t size) -{ +bool retro_unserialize(const void *data, size_t size) { // The HW renderer isn't ready on first pass. // So we save the data until we are ready to use it. if (!gpu) { @@ -1805,8 +1800,7 @@ bool retro_unserialize(const void *data, size_t size) bool retVal = CChunkFileReader::LoadPtr((u8 *)data, size, state, &errorString) == CChunkFileReader::ERROR_NONE; - if (useEmuThread) - { + if (useEmuThread) { EmuThreadStart(); sleep_ms(4, "libretro-unserialize"); } diff --git a/pspautotests b/pspautotests index 6ba9cf9e1e..4374348477 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 6ba9cf9e1eca2fd911dfa728f5509bb321d36b20 +Subproject commit 4374348477b425f48f99c8fd58a853c439f78acd