aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJavier <16884603+Xenon-Hacks@users.noreply.github.com>2026-10-31 20:56:43 -0400
committerJonas <baguettegamerofficial2020@gmail.com>2026-11-03 05:55:03 +0600
commit031e58e93046726446538a0efeebd56f7b5dc184 (patch)
treed3aa49dfddf2acd5b9f8beadfc65cb00725b07bc /src
parent3ffa971337144b32f88f12260362f5cf1cec8623 (diff)
nx2: video decoder: keep post-stream release fences pending
Bananza's movie player acquires one decoded frame every time a released output image's fence completes, and aborts (2111-3604) when the ring is empty. Completing those fences at once after the end of a stream (the Mario Kart World workaround) made it acquire from an empty ring the moment its cutscene movies ended. Released images now keep their normal pending fence after the end of a stream. Stop (207) and Finalize (115) complete every fence handed out so far, so a client that waits on its returned images after a movie is still released when it stops the decoder. EDEN_NX2_VDEC_EOS_RELEASE_NOW=1 restores the old immediate completion; EDEN_NX2_VDEC_TRACE=1 logs every decoder call. Tested on Bananza: the post-breakout cutscene movie ends and gameplay continues (mining tunnel). Mario Kart World not retested. Assisted-by: Claude (Anthropic) for investigation and code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit 0f8eaa54be5bff351de7c9a80cc06a4b3453bc76) (cherry picked from commit 8ed051843a33a5dec086d1ec6985798c8b336a6a)
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/nx2_gsv.h41
1 files changed, 39 insertions, 2 deletions
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 4731b35364..290d82746d 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -2347,7 +2347,7 @@ public:
{8, &INx2VideoSession::Flush, "Flush"},
{101, &INx2VideoSession::Configure, "Configure"},
{109, &INx2VideoSession::SubmitInput, "SubmitInputBuffer"},
- {115, &INx2VideoSession::Succeed, "Finalize"},
+ {115, &INx2VideoSession::Finalize, "Finalize"},
{200, &INx2VideoSession::Succeed, "Initialize"},
{201, &INx2VideoSession::Succeed, "InitializeWithFlag"},
{202, &INx2VideoSession::Succeed, "InitializeWithFlagAndValue"},
@@ -2419,6 +2419,10 @@ private:
if (Nx2FirstVideoCommand(GetServiceName(), ctx.GetCommand())) {
LOG_INFO(Service, "{} TIPC {} {} (first use): words {:08x}", GetServiceName(),
ctx.GetCommand(), what, fmt::join(std::span<const u32>{w, 12}, " "));
+ } else if (static const bool trace = std::getenv("EDEN_NX2_VDEC_TRACE") != nullptr; trace) {
+ // EDEN_NX2_VDEC_TRACE=1: every video decoder call at Info level.
+ LOG_INFO(Service, "{} TIPC {} {}: [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x}",
+ GetServiceName(), ctx.GetCommand(), what, w[2], w[3], w[4], w[5]);
} else {
LOG_DEBUG(Service, "{} TIPC {} {}: [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x}",
GetServiceName(), ctx.GetCommand(), what, w[2], w[3], w[4], w[5]);
@@ -2538,7 +2542,12 @@ private:
// 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()) {
+ static const bool release_now = [] {
+ const char* value = std::getenv("EDEN_NX2_VDEC_EOS_RELEASE_NOW");
+ return value != nullptr && value[0] != '0';
+ }();
+ if (release_now && release_fence && m_stream_ended && m_inputs.empty() &&
+ m_decoded.empty()) {
const u32 reached = static_cast<u32>(
system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(m_syncpoint));
m_sync_base = reached;
@@ -2610,11 +2619,39 @@ private:
m_flush = true;
m_stream_ended = false;
m_ready->room.store(true, std::memory_order_relaxed);
+ // Only Stop: Bananza flushes (8) after its movies end and treats every completed
+ // fence as a frame to acquire.
+ if (ctx.GetCommand() == 207) {
+ CompleteHandedFences();
+ }
}
m_cv.notify_one();
TipcReplyWords(ctx, {});
}
+ void Finalize(HLERequestContext& ctx) {
+ Log(ctx, "finalize");
+ {
+ std::scoped_lock lk{m_mutex};
+ CompleteHandedFences();
+ }
+ TipcReplyWords(ctx, {});
+ }
+
+ // Advances the decoder's syncpoint to the last value handed out (m_mutex held).
+ void CompleteHandedFences() {
+ if (m_syncpoint == NoSyncpoint) {
+ return;
+ }
+ auto& syncpoints = system.Host1x().GetSyncpointManager();
+ const u32 target = m_sync_base + m_handed;
+ u32 current = static_cast<u32>(syncpoints.GetGuestSyncpointValue(m_syncpoint));
+ while (static_cast<s32>(target - current) > 0) {
+ syncpoints.IncrementGuest(m_syncpoint);
+ ++current;
+ }
+ }
+
void GetOutputInfo(HLERequestContext& ctx) {
Log(ctx, "output info");
std::optional<Output> output;