From bfd6430918ff4eda8c8c0e5fd0a927b3f64ff258 Mon Sep 17 00:00:00 2001 From: Yuri Olive Date: Sat, 31 Oct 2026 15:14:38 -0300 Subject: service: nx2:vdecsvc: handle end-of-stream without aborting - Complete pending fences immediately on release once the stream ends and no frames remain to be processed, preventing infinite waits in movie teardown. - Stop re-arming m_ready->room when an end-of-stream packet arrives or the final frame is output, avoiding spurious wakeups and subsequent empty queue queries. - Reset m_stream_ended when new input is submitted or on Flush/Stop. Fixes an abort (2111-3604 / 0x1c286f) in Donkey Kong Bananza during the tutorial transition scene when the video decoder stream ends. Assisted-by: Gemini (vdecsvc end-of-stream fence completion and timer de-arming) (cherry picked from commit b180df1ed77568da7c3e78dd97c0c46d84e54494) --- src/core/hle/service/nx2_gsv.h | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 259681a2a2..e488be098c 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -2513,7 +2513,19 @@ private: if (release_fence) { m_release_fences[image] = *release_fence; } - value = m_sync_base + ++m_handed; + // After the end of a stream nothing fills an image again, so a release completes at + // once: its fence is the syncpoint's current value. Mario Kart World releases its + // images after a movie ends and waits for the returned fences; a fence for a frame + // that never comes hung the game on a black screen after its title movie. + if (release_fence && m_stream_ended && m_inputs.empty() && m_decoded.empty()) { + const u32 reached = static_cast( + system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(m_syncpoint)); + m_sync_base = reached; + m_handed = 0; + value = reached; + } else { + value = m_sync_base + ++m_handed; + } } m_cv.notify_one(); return {~m_syncpoint, 0, value, 0}; @@ -2559,7 +2571,8 @@ private: { std::scoped_lock lk{m_mutex}; m_inputs.push_back(std::move(input)); - m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed); + m_stream_ended = false; + m_ready->room.store(!end && m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed); } m_cv.notify_one(); const u64 id = ++m_submitted; @@ -2574,6 +2587,7 @@ private: std::scoped_lock lk{m_mutex}; m_inputs.clear(); m_flush = true; + m_stream_ended = false; m_ready->room.store(true, std::memory_order_relaxed); } m_cv.notify_one(); @@ -2647,6 +2661,10 @@ private: lk.lock(); // Even an image written during a flush counts: the client waits for its value. m_outputs.push_back({image, width, height, frame.pts, frame.end}); + if (frame.end) { + m_stream_ended = true; + m_ready->room.store(false, std::memory_order_relaxed); + } system.Host1x().GetSyncpointManager().IncrementGuest(m_syncpoint); ++m_frames_out; if (m_frames_out == 1 || (m_frames_out % 600) == 0 || frame.end) { @@ -2658,7 +2676,9 @@ private: } Input input = std::move(m_inputs.front()); m_inputs.pop_front(); - m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed); + if (!m_stream_ended) { + m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed); + } const std::string codec = m_codec; lk.unlock(); if (!decoder || decoder->Name() != codec) { @@ -2809,6 +2829,7 @@ private: std::deque m_free; ///< Output images, in the order the client acquires them std::map m_release_fences; std::deque m_outputs; ///< Finished, for 213 + bool m_stream_ended{}; ///< End-of-stream frame handed out, nothing submitted since bool m_flush{}; bool m_quit{}; std::thread m_worker; -- cgit v1.2.3