diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index a7c7381cc0..de051cfa7e 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -357,7 +357,9 @@ BreakAction BreakpointManager::ExecBreakPoint(u32 addr) { } if (tempBreakPoint_.valid && tempBreakPoint_.addr == addr) { - // The condition, when set, restricts the step to one thread - see SetTempBreakPointCond(). + // The condition, when set, narrows down which hit counts - to one thread for a step + // ("threadid == ..."), or to a later frame for run-to-cursor ("flipcount > ..."). A hit that + // fails it leaves the breakpoint armed, so the next one gets a chance. if (!tempBreakPoint_.hasCond || tempBreakPoint_.cond.Evaluate() != 0) { DEBUG_LOG(Log::Debugger, "Reached temporary breakpoint at %08x", addr); result |= BREAK_ACTION_PAUSE; diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 57a9189c36..b690a9781a 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -24,6 +24,7 @@ #include "Common/StringUtils.h" #include "Core/CoreTiming.h" +#include "Core/HW/Display.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/DebugInterface.h" @@ -49,6 +50,8 @@ enum ReferenceIndexType { REF_INDEX_MODULE = REF_INDEX_HLE | 1, REF_INDEX_USEC = REF_INDEX_HLE | 2, REF_INDEX_TICKS = REF_INDEX_HLE | 3, + REF_INDEX_VCOUNT = REF_INDEX_HLE | 4, + REF_INDEX_FLIPS = REF_INDEX_HLE | 5, }; @@ -133,6 +136,17 @@ public: referenceIndex = REF_INDEX_TICKS; return true; } + // The PSP's own vblank counter, as returned by sceDisplayGetVcount. + if (strcasecmp(str, "vcount") == 0) { + referenceIndex = REF_INDEX_VCOUNT; + return true; + } + // Frames actually presented. Unlike vcount this tracks what reached the screen, so + // "flipcount > N" is how a breakpoint condition says "only from the next frame onwards". + if (strcasecmp(str, "flipcount") == 0) { + referenceIndex = REF_INDEX_FLIPS; + return true; + } return false; } @@ -160,6 +174,10 @@ public: return (uint32_t)CoreTiming::GetGlobalTimeUs(); // Loses information if (referenceIndex == REF_INDEX_TICKS) return (uint32_t)CoreTiming::GetTicks(currentMIPS); + if (referenceIndex == REF_INDEX_VCOUNT) + return (uint32_t)__DisplayGetVCount(); + if (referenceIndex == REF_INDEX_FLIPS) + return (uint32_t)__DisplayGetFlipCount(); if ((referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)) < 32) return cpu->GetRegValue(1, referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)); if ((referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT)) < 128) diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 5cbeca8e2d..7b325298c5 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -14,6 +14,7 @@ #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSAsm.h" +#include "Core/HW/Display.h" #include "Core/Reporting.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" @@ -77,6 +78,37 @@ void ImDisasmView::assembleOpcode(u32 address, std::string_view defaultText) { assemblePopup_ = true; } +void ImDisasmView::RunToAddress(u32 address, bool nextFrame) { + g_breakpoints.SetTempBreakPoint(address); + + if (nextFrame) { + // The flip counter, so "greater than what it is now" means "not until something new has + // reached the screen". Handy when the address you're interested in is hit many times per + // frame and you want the next frame's first hit, not this frame's next one. + // + // Counting presented frames rather than vblanks matters for a game that doesn't render at + // the full refresh rate - at 30fps there are two vblanks per frame, so a vblank-based + // condition would let you through halfway into the frame you're trying to skip. + // + // The flip side is that it only advances when the framebuffer actually changed, so if the + // game has stopped drawing - or is wedged in the loop you're trying to debug - this never + // trips at all and the core just keeps running. + BreakPointCond cond; + cond.debug = currentDebugMIPS; + cond.expressionString = StringFromFormat("flipcount > %d", __DisplayGetFlipCount()); + if (initExpression(currentDebugMIPS, cond.expressionString.c_str(), cond.expression)) { + g_breakpoints.SetTempBreakPointCond(cond); + } else { + statusBarText_ = "Couldn't compile the frame condition - running without it."; + } + } + + g_breakpoints.SetSkipFirst(address); + if (Core_IsStepping()) { + Core_Resume(); + } +} + bool ImDisasmView::applyAssembly(u32 address, std::string_view op) { // Re-checked here, not just when the popup was requested: the popup is modeless, so the core // can have been resumed in between. @@ -776,11 +808,13 @@ void ImDisasmView::PopupMenu(MIPSState *mips, ImControl &control) { FollowBranch(); } if (ImGui::MenuItem("Run to here")) { - g_breakpoints.SetTempBreakPoint(curAddress_); - g_breakpoints.SetSkipFirst(curAddress_); - if (Core_IsStepping()) { - Core_Resume(); - } + RunToAddress(curAddress_, false); + } + if (ImGui::MenuItem("Run to here, next frame")) { + RunToAddress(curAddress_, true); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { + ImGui::SetTooltip("Ignores hits until a new frame has been presented, to skip the rest of this one."); } ImGui::Separator(); if (ImGui::MenuItem("Assemble")) { diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 3f07a99f4e..8e331f3e31 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -126,6 +126,9 @@ private: }; void ProcessKeyboardShortcuts(bool focused); + // Plants the one-shot "run to cursor" breakpoint and resumes. With nextFrame, hits are ignored + // until the next vblank, so the rest of the current frame is skipped over. + void RunToAddress(u32 address, bool nextFrame); // Requests the assemble popup - the actual input happens in PopupMenu(), since ImGui popups // can only be opened and drawn from inside the frame that owns them. void assembleOpcode(u32 address, std::string_view defaultText);