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 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-03 23:32:11 +01:00
1 parent b00f806d3e
commit e1deb5665c
6 files changed
+21 -15

No files matched your search

+1
View File
@@ -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);
+1
View File
@@ -116,6 +116,7 @@ struct CompatFlags {
bool ForceEnableGPUReadback;
bool UseFFMPEGFindStreamInfo;
bool SoftwareRasterDepth;
bool DepthRasterNoCallbackWait;
bool DisableHLESceFont;
bool ForceHLEPsmf;
bool SaveStatesNotRecommended;
+5
View File
@@ -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;
+4 -13
View File
@@ -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;
}
+1 -2
View File
@@ -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;
+9
View File
@@ -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.