GS: record a complete dump under the pipelined back-thread split

The dump's transfer and ReadFIFO hooks sit on the parse path, and its initial
state came from Freeze() on the renderer. Under GSBackThreadMode=Pipelined the
parse path belongs to the front object, so both were reading the wrong object:
the front's transfers never reached the dump at all. A Rogue Galaxy capture that
should be 39.4 MB of packets came out with 90 KB -- 0.2% of the stream, the
ReadFIFO and VSync packets alone -- and replayed as nothing. GSQueueSnapshot
warned about it rather than fixing it (GV7-2).

The dump stays owned by the renderer, which opens and closes it on the present
path; the parse side reaches it through GetDumpSink(), which routes via
m_mem_target, and the initial freeze goes through a new m_parse_target, the
inverse pointer. Both paths run on the MTGS thread -- the front's runahead is
over the back thread, not over the thread handling vsync -- so the front writes
straight into the back's dump with no synchronisation. m_parse_target->Freeze()
is the same call GSfreeze makes for a savestate, which already drains and
already takes registers from the front and local memory from the back.

Verified on a Rogue Galaxy savestate, frame-stepped over PINE so both arms start
from the identical state: the mode 3 dump is byte-identical to the mode 0 dump,
4.2 MB of initial state and 39.4 MB of packets, and it replays in gsrunner to
ten frames identical under both modes. Two mode 0 runs are likewise identical,
so the harness has no slack. Reverting just the transfer sink reproduces the
90 KB dump, so the comparison has teeth.

Two bytes of bookkeeping ride along: GSQueueSnapshot loses the warning, and the
MsgGSDump reply loses pipelined_incomplete, which now has nothing to report.
Whether the split engaged is a genuine question, so it moves to the stats reply
as gs_front_parser, next to gs_back_thread_pct where it belongs.
This commit is contained in:
Brian Degenhardt
2026-07-30 21:55:58 -07:00
parent 7f93a80dd7
commit 17b2be058a
6 changed files with 35 additions and 22 deletions
+8 -8
View File
@@ -369,7 +369,7 @@ namespace PINEServer
"\"tc_source_hit\":{:.1f},\"tc_source_miss\":{:.1f},"
"\"tc_target_hit\":{:.1f},\"tc_target_miss\":{:.1f},"
"\"hash_cache_hit\":{:.1f},\"hash_cache_miss\":{:.1f},"
"\"gs_memory\":\"{}\",\"frame_number\":{},"
"\"gs_memory\":\"{}\",\"frame_number\":{},\"gs_front_parser\":{},"
"\"renderer\":\"{}\",\"device_name\":\"{}\",\"driver_info\":\"{}\""
"}}",
PerformanceMetrics::GetFPS(), PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetSpeed(),
@@ -391,7 +391,10 @@ namespace PINEServer
counter(GSPerfMon::TCSourceHit), counter(GSPerfMon::TCSourceMiss),
counter(GSPerfMon::TCTargetHit), counter(GSPerfMon::TCTargetMiss),
counter(GSPerfMon::HashCacheHit), counter(GSPerfMon::HashCacheMiss),
gs_memory.view(), PerformanceMetrics::GetFrameNumber(),
// Whether the two-object split actually engaged, which the BackThreadMode setting
// alone does not tell you -- it downgrades to lockstep on an unsupported config.
// True is also what makes gs_back_thread_* worth reading next to gs_thread_*.
gs_memory.view(), PerformanceMetrics::GetFrameNumber(), GSHasFrontParser() ? "true" : "false",
Pcsx2Config::GSOptions::GetRendererName(EmuConfig.GS.Renderer), device_name, driver_info);
}
@@ -464,7 +467,7 @@ namespace PINEServer
}
}
bool queued = false, stopped = false, incomplete = false;
bool queued = false, stopped = false;
std::string base;
const char* dump_ext = "";
const char* reason = "";
@@ -509,18 +512,15 @@ namespace PINEServer
dump_ext = ".gs.zst";
break;
}
incomplete = GSHasFrontParser();
});
MTGS::WaitGS(false);
},
true);
*reply = fmt::format(
"{{\"queued\":{},\"stopped\":{},\"frames\":{},\"path\":\"{}\","
"\"pipelined_incomplete\":{},\"reason\":\"{}\"}}",
"{{\"queued\":{},\"stopped\":{},\"frames\":{},\"path\":\"{}\",\"reason\":\"{}\"}}",
queued ? "true" : "false", stopped ? "true" : "false", frames,
queued ? JsonEscape(base + dump_ext) : std::string(), incomplete ? "true" : "false", reason);
queued ? JsonEscape(base + dump_ext) : std::string(), reason);
return true;
}
} // namespace PINEServer