diff --git a/aurora-main b/aurora-main index 78850a8..ff0b617 160000 --- a/aurora-main +++ b/aurora-main @@ -1 +1 @@ -Subproject commit 78850a8d308c7d3f5aaddedea9875c250f25510f +Subproject commit ff0b617a925bf8ab6ca602bd46e25c3b85dabaf0 diff --git a/runtime/include/aurora_events.h b/runtime/include/aurora_events.h index 1809487..d76450b 100644 --- a/runtime/include/aurora_events.h +++ b/runtime/include/aurora_events.h @@ -134,6 +134,7 @@ inline void ApplyPendingMkwDynamicAspectSurface() { void GxWorkerBeginFrame(); void GxWorkerEndFrame(AuroraImguiFrame* imgui); +void SetAuroraFrameGxBegun(bool begun); inline bool BeginAuroraFrame() { if (GxWorker::ShouldDefer()) { @@ -149,6 +150,7 @@ inline bool BeginAuroraFrame() { if (!aurora_begin_frame()) { return false; } + SetAuroraFrameGxBegun(true); ApplyPendingMkwDynamicAspectSurface(); return true; } @@ -163,6 +165,7 @@ inline void EndAuroraFrame() { return; } aurora_end_frame(); + SetAuroraFrameGxBegun(false); } // Poll Aurora events and update cached window/framebuffer dimensions. diff --git a/runtime/include/gx_worker.h b/runtime/include/gx_worker.h index 8016131..25179e2 100644 --- a/runtime/include/gx_worker.h +++ b/runtime/include/gx_worker.h @@ -28,6 +28,8 @@ void FlushFifo(); // Starts the worker unless [video] gx_worker disables it. Call once Aurora is up. void Start(); void Stop(); +// Stops without running queued work; bounded, for fatal exits. +void Abandon(); inline bool Active() noexcept { return detail::g_active.load(std::memory_order_relaxed); } inline bool OnWorker() noexcept { return detail::t_onWorker; } diff --git a/runtime/src/hle/gx/gx_frame.cpp b/runtime/src/hle/gx/gx_frame.cpp index 960cfe7..78fd1cc 100644 --- a/runtime/src/hle/gx/gx_frame.cpp +++ b/runtime/src/hle/gx/gx_frame.cpp @@ -112,26 +112,29 @@ void EnsureAuroraFrameActive() { } namespace { -// GX worker only. -bool g_workerFrameBegun = false; +// Whether the GX half of the current frame has begun, on either path. Inline frame calls only run +// after a sync, so the ring orders every access. +bool g_frameGxBegun = false; } +void SetAuroraFrameGxBegun(bool begun) { g_frameGxBegun = begun; } + void GxWorkerBeginFrame() { // gfx::begin_frame can briefly fail to map a staging buffer; retry like the inline path does. const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); - while (!(g_workerFrameBegun = aurora_begin_frame_gx()) && std::chrono::steady_clock::now() < deadline) { + while (!(g_frameGxBegun = aurora_begin_frame_gx()) && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - if (!g_workerFrameBegun) { + if (!g_frameGxBegun) { RT_LOGF(RT_TAG_GX, "GX worker could not begin an aurora frame\n"); } } void GxWorkerEndFrame(AuroraImguiFrame* imgui) { - if (g_workerFrameBegun) { + if (g_frameGxBegun) { aurora_end_frame_gx(imgui); } else { aurora_release_imgui_frame(imgui); } - g_workerFrameBegun = false; + g_frameGxBegun = false; } diff --git a/runtime/src/hle/gx/gx_worker.cpp b/runtime/src/hle/gx/gx_worker.cpp index b0b5d58..717ebc7 100644 --- a/runtime/src/hle/gx/gx_worker.cpp +++ b/runtime/src/hle/gx/gx_worker.cpp @@ -60,6 +60,8 @@ std::mutex g_tailMutex; std::condition_variable g_tailCv; void (*g_waitCallback)() = nullptr; std::atomic g_stop{false}; +std::atomic g_abandon{false}; +std::atomic g_exited{false}; std::thread* g_thread = nullptr; std::vector g_fifoBytes; @@ -139,6 +141,13 @@ void ReplayFifo(void* payload) { void WorkerMain() { detail::t_onWorker = true; + struct SignalExit { + ~SignalExit() { + std::lock_guard lock{g_tailMutex}; + g_exited.store(true, std::memory_order_release); + g_tailCv.notify_all(); + } + } signalExit; AuroraSetGxProducerThread(); #if defined(_WIN32) SetThreadDescription(GetCurrentThread(), L"GX worker"); @@ -173,6 +182,9 @@ void WorkerMain() { continue; } while (tail != head) { + if (g_abandon.load(std::memory_order_acquire)) { + return; + } uint8_t* record = g_ring + tail % kCapacity; const Header* header = reinterpret_cast(record); if (header->run != nullptr) { @@ -205,15 +217,16 @@ void* Reserve(size_t payloadSize, void (*run)(void*)) { size_t offset = static_cast(g_write % kCapacity); const size_t remaining = kCapacity - offset; if (total > remaining) { - WaitForSpace(remaining + total); + WaitForSpace(remaining); auto* wrap = reinterpret_cast(g_ring + offset); wrap->run = nullptr; wrap->size = static_cast(remaining); g_write += remaining; + // Publish the skip so the worker can drain past it while we wait for the front. + Commit(); offset = 0; - } else { - WaitForSpace(total); } + WaitForSpace(total); auto* header = reinterpret_cast(g_ring + offset); header->run = run; header->size = static_cast(total); @@ -303,6 +316,8 @@ void Start() { } g_fifoBytes.reserve(kFifoFlushBytes + 4096); g_stop.store(false, std::memory_order_relaxed); + g_abandon.store(false, std::memory_order_relaxed); + g_exited.store(false, std::memory_order_relaxed); g_thread = new std::thread(WorkerMain); detail::g_active.store(true, std::memory_order_release); RT_LOGF(RT_TAG_GX, "GX worker started\n"); @@ -325,4 +340,32 @@ void Stop() { } } +void Abandon() { + if (!Active() || detail::t_onWorker) { + return; + } + detail::g_active.store(false, std::memory_order_release); + g_abandon.store(true, std::memory_order_release); + g_stop.store(true, std::memory_order_release); + { + std::lock_guard lock{g_headMutex}; + g_headCv.notify_one(); + } + bool exited = false; + { + std::unique_lock lock{g_tailMutex}; + exited = g_tailCv.wait_for(lock, std::chrono::seconds{2}, + [] { return g_exited.load(std::memory_order_acquire); }); + } + if (g_thread != nullptr) { + if (exited) { + g_thread->join(); + delete g_thread; + } else { + g_thread->detach(); + } + g_thread = nullptr; + } +} + } // namespace GxWorker diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 4f4adde..edb9abe 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -765,6 +765,8 @@ extern "C" void DumpHostStackTraceForRuntimeHelper() { void MarkFatalErrorReported() { g_fatalErrorReported.store(true, std::memory_order_release); + // Every caller exits next; keep the worker out of static teardown. + GxWorker::Abandon(); } void ShowRuntimeFatalPopup(std::string_view category, std::string_view details) noexcept {