From 7fe30d6f2d71f08fcf68faa037da0e305083dc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 31 Aug 2025 11:37:05 +0200 Subject: [PATCH] Fix OpenGL exit hang on Windows --- Common/Thread/ThreadUtil.h | 17 +++++++++++++++++ Windows/EmuThread.cpp | 26 +++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Common/Thread/ThreadUtil.h b/Common/Thread/ThreadUtil.h index 6bab7d13e2..51aefa2fe6 100644 --- a/Common/Thread/ThreadUtil.h +++ b/Common/Thread/ThreadUtil.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include // Note that the string pointed to must have a lifetime until the end of the thread, // for AssertCurrentThreadName to work. @@ -36,3 +38,18 @@ public: DetachThreadFromJNI(); } }; + +// Use on atomics to check if they're equal to one of multiple values. +template +bool equals_any(const T& first, const Ts... rest) { + // Make a single copy (or single load, if atomic) + auto value = [&]() { + if constexpr (std::is_same_v>) { + return first.load(); + } else { + return first; + } + }(); + + return ((value == rest) || ...); +} diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 802c1ce3d4..b2a82f3cc6 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -40,7 +40,7 @@ enum class EmuThreadState { }; static std::thread emuThread; -static std::atomic emuThreadState((int)EmuThreadState::DISABLED); +static std::atomic g_emuThreadState(EmuThreadState::DISABLED); static std::thread mainThread; static bool useEmuThread; @@ -78,11 +78,11 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext) { // There's no real requirement that NativeInit happen on this thread. // We just call the update/render loop here. - emuThreadState = (int)EmuThreadState::RUNNING; + g_emuThreadState = EmuThreadState::RUNNING; NativeInitGraphics(graphicsContext); - while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { + while (g_emuThreadState != EmuThreadState::QUIT_REQUESTED) { // We're here again, so the game quit. Restart Run() which controls the UI. // This way they can load a new game. if (!Core_IsActive()) { @@ -93,24 +93,25 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext) { NativeFrame(graphicsContext); if (GetUIState() == UISTATE_EXIT) { - emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + g_emuThreadState = EmuThreadState::QUIT_REQUESTED; } } - emuThreadState = (int)EmuThreadState::STOPPED; + g_emuThreadState = EmuThreadState::STOPPED; NativeShutdownGraphics(); } static void EmuThreadStart(GraphicsContext *graphicsContext) { - emuThreadState = (int)EmuThreadState::START_REQUESTED; + g_emuThreadState = EmuThreadState::START_REQUESTED; emuThread = std::thread(&EmuThreadFunc, graphicsContext); } static void EmuThreadStop() { - if (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED && - emuThreadState != (int)EmuThreadState::STOPPED) { - emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + const EmuThreadState state = g_emuThreadState; + if (state != EmuThreadState::QUIT_REQUESTED && + state != EmuThreadState::STOPPED) { + g_emuThreadState = EmuThreadState::QUIT_REQUESTED; } } @@ -273,7 +274,10 @@ void MainThreadFunc() { } if (useEmuThread) { - while (emuThreadState != (int)EmuThreadState::DISABLED) { + while (true) { + if (equals_any(g_emuThreadState, EmuThreadState::QUIT_REQUESTED, EmuThreadState::STOPPED)) { + break; + } graphicsContext->ThreadFrame(true); if (GetUIState() == UISTATE_EXIT) { break; @@ -302,7 +306,7 @@ void MainThreadFunc() { EmuThreadStop(); graphicsContext->ThreadFrameUntilCondition([] { // Need to keep eating frames to allow the EmuThread to exit correctly. - return emuThreadState == (int)EmuThreadState::STOPPED; + return g_emuThreadState == EmuThreadState::STOPPED; }); EmuThreadJoin(); }