From e1deb5665c0fedd2faa3d846e75a340931eadeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 3 Nov 2026 23:24:34 +0100 Subject: [PATCH] Depth raster: Wait for it before GE callbacks instead of at every list end Since a37e1f3660, the CPU waits for the depth rasterizer every time a display list ends, because Syphon Filter reads its depth buffer for the light flares right after. It reads it in the list's GE interrupt callback though, and so do Wipeout Pure and Resistance: Retribution. On hardware the drawing is done when that runs, so that's where to wait now, letting the worker overlap with the CPU until then. sceGeDrawSync and sceGeListSync wait as before. With interpreter loads from the depth buffer counted and the worker slowed down by 10-50 ms a batch, no read in Syphon Filter: Logan's Shadow, Wipeout, Resistance or Midnight Club: L.A. Remix landed while it was busy. Without any wait, nearly all of Syphon Filter's, Wipeout's and Resistance's did. L.A. Rush reads its depth buffer only after sceGeDrawSync, and the new compat flag DepthRasterNoCallbackWait skips the callback wait for it. 10 emulated seconds on a Moto G54 (two Cortex-A78, six A55): Wipeout Pure 7.24 s -> 6.50 s Resistance 17.98 s -> 13.26 s Midnight Club 8.75 s -> 8.01 s L.A. Rush 5.95 s -> 4.76 s Co-Authored-By: Claude Opus 5.5 --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + Core/HLE/sceGe.cpp | 5 +++++ GPU/GPUCommon.cpp | 17 ++++------------- GPU/GPUCommon.h | 3 +-- assets/compat.ini | 9 +++++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index b93ca87d44..ce166eebe6 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -150,6 +150,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ForceEnableGPUReadback", &flags_.ForceEnableGPUReadback); CheckSetting(iniFile, gameID, "UseFFMPEGFindStreamInfo", &flags_.UseFFMPEGFindStreamInfo); CheckSetting(iniFile, gameID, "SoftwareRasterDepth", &flags_.SoftwareRasterDepth); + CheckSetting(iniFile, gameID, "DepthRasterNoCallbackWait", &flags_.DepthRasterNoCallbackWait); CheckSetting(iniFile, gameID, "DisableHLESceFont", &flags_.DisableHLESceFont); CheckSetting(iniFile, gameID, "ForceHLEPsmf", &flags_.ForceHLEPsmf); CheckSetting(iniFile, gameID, "SaveStatesNotRecommended", &flags_.SaveStatesNotRecommended); diff --git a/Core/Compatibility.h b/Core/Compatibility.h index ba428d5884..647c41b9cb 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -116,6 +116,7 @@ struct CompatFlags { bool ForceEnableGPUReadback; bool UseFFMPEGFindStreamInfo; bool SoftwareRasterDepth; + bool DepthRasterNoCallbackWait; bool DisableHLESceFont; bool ForceHLEPsmf; bool SaveStatesNotRecommended; diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 2c6cfb4cf3..f9abd35ed3 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -127,6 +127,11 @@ public: SubIntrHandler* handler = get(subintr); if (handler != NULL) { + // The callback may read the depth buffer on the CPU (light flares in Syphon Filter, Wipeout Pure + // and Resistance: Retribution), and on hardware the drawing is done by now. + if (!PSP_CoreParameter().compat.flags().DepthRasterNoCallbackWait) { + gpu->WaitForSubmittedDepth(); + } DEBUG_LOG(Log::CPU, "Entering GE interrupt handler %08x", handler->handlerAddress); currentMIPS->pc = handler->handlerAddress; u32 data = dl->subIntrToken; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 41cceebc34..a3d3d78ea3 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -942,18 +942,6 @@ inline void GPUCommon::UpdateState(GPURunState state) { // This is now called when coreState == CORE_RUNNING_GE, in addition to from the various sceGe commands. DLResult GPUCommon::ProcessDLQueue() { - const DLResult result = RunDLQueue(); - // Games read the depth buffer back on the CPU once a list has ended, without waiting for the GE - // (Syphon Filter's light flares). So the depth rasterizer has to be done by then. Not at a stall, - // though, which some games hit hundreds of times a frame. - if (listEndedSinceDepthWait_) { - listEndedSinceDepthWait_ = false; - drawEngineCommon_->WaitForSubmittedDepth(); - } - return result; -} - -DLResult GPUCommon::RunDLQueue() { if (!resumingFromDebugBreak_) { startingTicks = CoreTiming::GetTicks(currentMIPS); cyclesExecuted = 0; @@ -1259,7 +1247,6 @@ void GPUCommon::Execute_Ret(u32 op, u32 diff) { void GPUCommon::Execute_End(u32 op, u32 diff) { if (flushOnParams_) { drawEngineCommon_->SubmitQueuedDepth(); - listEndedSinceDepthWait_ = true; Flush(); } @@ -2110,6 +2097,10 @@ void GPUCommon::DoState(PointerWrap &p) { } } +void GPUCommon::WaitForSubmittedDepth() { + drawEngineCommon_->WaitForSubmittedDepth(); +} + void GPUCommon::InterruptStart(int listid) { interruptRunning = true; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index b8f71d7d51..4346bf1a0f 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -102,6 +102,7 @@ public: virtual void EndHostFrame(); void InterruptStart(int listid); + void WaitForSubmittedDepth(); void InterruptEnd(int listid); void SyncEnd(GPUSyncType waitType, int listid, bool wokeThreads); void EnableInterrupts(bool enable) { @@ -341,7 +342,6 @@ public: void NotifyFlush(); protected: - DLResult RunDLQueue(); // While debugging is active, these may block. void NotifyDisplay(u32 framebuf, u32 stride, int format); @@ -403,7 +403,6 @@ protected: TextureCacheCommon *textureCache_ = nullptr; bool flushOnParams_ = true; - bool listEndedSinceDepthWait_ = false; GraphicsContext *gfxCtx_; Draw::DrawContext *draw_ = nullptr; diff --git a/assets/compat.ini b/assets/compat.ini index b9b215d854..db1ae469c5 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1794,6 +1794,15 @@ NPEH00047 = true ULES00554 = true ULUS10174 = true +[DepthRasterNoCallbackWait] +# Without this, the CPU waits for the depth rasterizer before a GE interrupt callback runs, for games that +# read the depth buffer back there (the light flares in Syphon Filter, Wipeout Pure and Resistance: +# Retribution). These only read it after a sceGeDrawSync or sceGeListSync, which waits anyway. + +# L.A. Rush (sun lens flare) +ULES00554 = true +ULUS10174 = true + [BlockTransferDepth] # Iron Man - see issue #16530 # Note that this option also requires IntraVRAMBlockTransferAllowCreateFB.