GE: Complete lists dropped on error, flush before immediate draws

- A list dropped for a bad pc or a GE error stayed RUNNING: its ID was never
  freed and sceGeListSync on it never returned. Complete it like a finished
  one.
- FlushImm switches to through mode and another vertex decoder, so flush the
  queued draws first even when the immediate flags match.
- Clear leftover temporary GE breakpoints when setting or clearing the next
  break, so a step that never got there doesn't trip later.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-29 10:33:32 -06:00
1 parent 16ea2cbf88
commit 507bb0801b
2 files changed
+25 -2

No files matched your search

+24 -2
View File
@@ -957,6 +957,7 @@ DLResult GPUCommon::ProcessDLQueue() {
// Nothing to execute here, and leaving it at the head of the queue would block everything // Nothing to execute here, and leaving it at the head of the queue would block everything
// behind it for good. Treat it like a list that ran into an error. // behind it for good. Treat it like a list that ran into an error.
ERROR_LOG(Log::G3D, "Display list %d has a bad pc %08x (state %d), dropping it", listIndex, list.pc, (int)list.state); ERROR_LOG(Log::G3D, "Display list %d has a bad pc %08x (state %d), dropping it", listIndex, list.pc, (int)list.state);
CompleteFailedList(list);
dlQueue.erase(std::remove(dlQueue.begin(), dlQueue.end(), listIndex), dlQueue.end()); dlQueue.erase(std::remove(dlQueue.begin(), dlQueue.end(), listIndex), dlQueue.end());
continue; continue;
} }
@@ -1045,7 +1046,8 @@ DLResult GPUCommon::ProcessDLQueue() {
} }
break; break;
case GPUSTATE_ERROR: case GPUSTATE_ERROR:
// don't do anything - though dunno about error... // The list can't continue. It's removed from the queue below.
CompleteFailedList(list);
break; break;
case GPUSTATE_STALL: case GPUSTATE_STALL:
// Resume work on this same display list later. The GE is still busy with what it has // Resume work on this same display list later. The GE is still busy with what it has
@@ -1390,6 +1392,20 @@ void GPUCommon::Execute_End(u32 op, u32 diff) {
} }
} }
// A list dropped on an error still has to complete like a finished one, or its ID is never freed and
// sceGeListSync on it never returns.
void GPUCommon::CompleteFailedList(DisplayList &list) {
if (list.started && list.context.IsValid()) {
gstate.Restore(list.context);
ReapplyGfxState();
list.started = false;
}
list.state = PSP_GE_DL_STATE_COMPLETED;
list.waitUntilTicks = startingTicks + cyclesExecuted;
busyTicks = std::max(busyTicks, list.waitUntilTicks);
__GeTriggerSync(GPU_SYNC_LIST, list.id, list.waitUntilTicks);
}
void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) {
// Just resetting, nothing to check bounds for. // Just resetting, nothing to check bounds for.
const u32 count = op & 0xFFFF; const u32 count = op & 0xFFFF;
@@ -1552,8 +1568,10 @@ void GPUCommon::FlushImm() {
bool changed = texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither; bool changed = texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither;
changed = changed || prevShading != shading || prevFog != fog; changed = changed || prevShading != shading || prevFog != fog;
// Always flush, even if the flags match: DispatchSubmitImm switches to through mode and a different
// vertex decoder, which would otherwise apply to the draws already queued.
Flush();
if (changed) { if (changed) {
Flush();
gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)antialias; gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)antialias;
gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)shading; gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)shading;
gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)cullEnable; gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)cullEnable;
@@ -2290,12 +2308,16 @@ bool GPUCommon::NeedsSlowInterpreter() const {
void GPUCommon::ClearBreakNext() { void GPUCommon::ClearBreakNext() {
breakNext_ = GPUDebug::BreakNext::NONE; breakNext_ = GPUDebug::BreakNext::NONE;
breakAtCount_ = -1; breakAtCount_ = -1;
// A step that never reached its target leaves these behind, and they'd trip unexpectedly later.
breakpoints_.ClearTempBreakpoints();
GPUStepping::ResumeFromStepping(); GPUStepping::ResumeFromStepping();
} }
void GPUCommon::SetBreakNext(GPUDebug::BreakNext next) { void GPUCommon::SetBreakNext(GPUDebug::BreakNext next) {
breakNext_ = next; breakNext_ = next;
breakAtCount_ = -1; breakAtCount_ = -1;
// Drop the ones from a previous step that didn't get there, before adding this one's.
breakpoints_.ClearTempBreakpoints();
switch (next) { switch (next) {
case GPUDebug::BreakNext::TEX: case GPUDebug::BreakNext::TEX:
breakpoints_.AddTextureChangeTempBreakpoint(); breakpoints_.AddTextureChangeTempBreakpoint();
+1
View File
@@ -452,4 +452,5 @@ protected:
private: private:
void DoExecuteCall(u32 target); void DoExecuteCall(u32 target);
void PopDLQueue(); void PopDLQueue();
void CompleteFailedList(DisplayList &list);
}; };