sceGe: sceGeBreak(1) takes pending interrupts with it

Resetting the GE also gets rid of an interrupt that was raised but not taken
yet, so a list that reached its FINISH just before never gets its finish
callback. We delivered one anyway, for a list that no longer existed. If the
break comes from inside a GE callback, the interrupt being handled is kept,
since its handler still has to return.

Found by gpu/ge/intrsuspend, which also confirms from a thread, with
interrupts suspended, that nothing moves along the queue until the FINISH
interrupt has been taken.

Savestates: bump GPUCommon to 7. We didn't use to mark a PAUSE signal as
delivered, which sceGeContinue now goes by, so a state saved with a list
paused that way would load into a game that could never continue it. Fixed
up on load.

gpu/signals/handlercalls goes in as known failing: with an old SDK version, a
stall address set from inside a SUSPEND callback doesn't reach the GE, which
we can't express with just the one stall address per list. See docs/sceGe.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Fable 5.1 committed 2026-09-21 12:48:03 -06:00
1 parent 41627cc0ce
commit 91c9c4d14e
5 files changed
+35 -3

No files matched your search

+12 -1
View File
@@ -15,6 +15,7 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <iterator>
#include <list>
#include <map>
#include <vector>
@@ -67,7 +68,8 @@ public:
bool run(PendingInterrupt& pend) override {
if (ge_pending_cb.empty()) {
ERROR_LOG_REPORT(Log::sceGe, "Unable to run GE interrupt: no pending interrupt");
// sceGeBreak(1) got there first. If interrupts were off, this one had already been raised.
DEBUG_LOG(Log::sceGe, "Ignoring GE interrupt, nothing pending anymore");
return false;
}
@@ -291,6 +293,15 @@ bool __GeTriggerSync(GPUSyncType type, int id, u64 atTicks) {
return true;
}
void __GeClearPendingInterrupts(bool interruptRunning) {
if (interruptRunning && !ge_pending_cb.empty()) {
ge_pending_cb.erase(std::next(ge_pending_cb.begin()), ge_pending_cb.end());
} else {
ge_pending_cb.clear();
}
CoreTiming::RemoveEvent(geInterruptEvent);
}
bool __GeTriggerInterrupt(int listid, u32 pc, u64 atTicks) {
GeInterruptData intrdata;
intrdata.listid = listid;
+3
View File
@@ -52,6 +52,9 @@ void __GeDoState(PointerWrap &p);
void __GeShutdown();
bool __GeTriggerSync(GPUSyncType waitType, int id, u64 atTicks);
bool __GeTriggerInterrupt(int listid, u32 pc, u64 atTicks);
// Forgets the interrupts that haven't been delivered yet, as resetting the GE does.
// The one being handled right now, if any, is still needed when its handler returns.
void __GeClearPendingInterrupts(bool interruptRunning);
void __GeWaitCurrentThread(GPUSyncType type, SceUID waitId, const char *reason);
bool __GeTriggerWait(GPUSyncType type, SceUID waitId);
+16 -1
View File
@@ -579,7 +579,11 @@ u32 GPUCommon::Break(int mode) {
{
dls[i].state = PSP_GE_DL_STATE_NONE;
dls[i].signal = PSP_GE_SIGNAL_NONE;
dls[i].pendingInterrupt = false;
}
// This resets the GE, and an interrupt that hasn't been taken yet goes with it: no callback
// for a list that reached its FINISH just before.
__GeClearPendingInterrupts(interruptRunning);
nextListID = 0;
currentList = NULL;
@@ -1487,7 +1491,7 @@ struct DisplayList_v2 {
};
void GPUCommon::DoState(PointerWrap &p) {
auto s = p.Section("GPUCommon", 1, 6);
auto s = p.Section("GPUCommon", 1, 7);
if (!s)
return;
@@ -1544,6 +1548,17 @@ void GPUCommon::DoState(PointerWrap &p) {
dls[i].stackAddr = 0;
}
}
if (s < 7 && p.mode == PointerWrap::MODE_READ) {
// We didn't use to mark a PAUSE signal as delivered, which sceGeContinue now goes by.
// Back then, a list was only PAUSED with that signal set once it had reached its FINISH,
// and if the interrupt for that is still to come, it'll take care of this.
for (DisplayList &dl : dls) {
if (dl.state == PSP_GE_DL_STATE_PAUSED && dl.signal == PSP_GE_SIGNAL_HANDLER_PAUSE && !dl.pendingInterrupt) {
dl.signal = PSP_GE_SIGNAL_HANDLER_SUSPEND;
}
}
}
int currentID = 0;
if (currentList != nullptr) {
currentID = (int)(currentList - &dls[0]);
+3
View File
@@ -204,6 +204,7 @@ tests_good = [
"gpu/ge/context",
"gpu/ge/edram",
"gpu/ge/enqueueparam",
"gpu/ge/intrsuspend",
"gpu/ge/queue",
"gpu/ge/queue2",
"gpu/primitives/indices",
@@ -478,6 +479,8 @@ tests_next = [
"gpu/reflection/reflection",
"gpu/rendertarget/rendertarget",
"gpu/signals/continue",
# Old SDK: a stall update from inside a SUSPEND handler is remembered, but not applied to the GE. See docs/sceGe.md.
"gpu/signals/handlercalls",
"gpu/signals/jumps",
"gpu/signals/simple",
"gpu/simple/simple",