diff --git a/GPU/Common/GPUDebugInterface.cpp b/GPU/Common/GPUDebugInterface.cpp index d1e0098e44be..d5d9c564db8b 100644 --- a/GPU/Common/GPUDebugInterface.cpp +++ b/GPU/Common/GPUDebugInterface.cpp @@ -29,6 +29,8 @@ enum class GEReferenceIndex : uint32_t { PC, STALL, BFLAG, + OP, + DATA, BONE_MATRIX = 0x200, WORLD_MATRIX = 0x260, @@ -52,6 +54,8 @@ static constexpr ReferenceName referenceNames[] = { { GEReferenceIndex::STALL, "stall" }, { GEReferenceIndex::BFLAG, "bflag" }, { GEReferenceIndex::BFLAG, "boundflag" }, + { GEReferenceIndex::OP, "op" }, + { GEReferenceIndex::DATA, "data" }, }; class GEExpressionFunctions : public IExpressionFunctions { @@ -181,6 +185,17 @@ uint32_t GEExpressionFunctions::getReferenceValue(uint32_t referenceIndex) { return list.bboxResult ? 1 : 0; } return 0; + case GEReferenceIndex::OP: + // TODO: Support fields under this as per the cmd format? + if (gpu_->GetCurrentDisplayList(list)) { + return Memory::Read_U32(list.pc); + } + return 0; + case GEReferenceIndex::DATA: + if (gpu_->GetCurrentDisplayList(list)) { + return Memory::Read_U32(list.pc) & 0x00FFFFFF; + } + return 0; case GEReferenceIndex::BONE_MATRIX: case GEReferenceIndex::WORLD_MATRIX: diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp index d661c69043e6..8d76cbd5e7e4 100644 --- a/GPU/Debugger/Breakpoints.cpp +++ b/GPU/Debugger/Breakpoints.cpp @@ -16,9 +16,9 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include -#include #include #include +#include #include #include "Common/CommonFuncs.h" @@ -30,14 +30,15 @@ namespace GPUBreakpoints { struct BreakpointInfo { - bool isConditional; + bool isConditional = false; PostfixExpression expression; std::string expressionString; }; static std::mutex breaksLock; static bool breakCmds[256]; -static std::map breakPCs; +static BreakpointInfo breakCmdsInfo[256]; +static std::unordered_map breakPCs; static std::set breakTextures; static std::set breakRenderTargets; // Small optimization to avoid a lock/lookup for the common case. @@ -185,8 +186,24 @@ static bool HitAddressBreakpoint(u32 pc) { return true; } +static bool HitOpBreakpoint(u32 op) { + u8 cmd = op >> 24; + if (!IsCmdBreakpoint(cmd)) + return false; + + if (breakCmdsInfo[cmd].isConditional) { + std::lock_guard guard(breaksLock); + u32 result = 1; + if (!GPUDebugExecExpression(gpuDebug, breakCmdsInfo[cmd].expression, result)) + return false; + return result != 0; + } + + return true; +} + bool IsBreakpoint(u32 pc, u32 op) { - if (HitAddressBreakpoint(pc) || IsOpBreakpoint(op)) { + if (HitAddressBreakpoint(pc) || HitOpBreakpoint(op)) { return true; } @@ -308,7 +325,7 @@ void AddAddressBreakpoint(u32 addr, bool temp) { } else { // Remove the temporary marking. breakPCsTemp.erase(addr); - breakPCs[addr].isConditional = false; + breakPCs.insert(std::make_pair(addr, BreakpointInfo{})); } breakPCsCount = breakPCs.size(); @@ -320,12 +337,16 @@ void AddCmdBreakpoint(u8 cmd, bool temp) { if (!breakCmds[cmd]) { breakCmdsTemp[cmd] = true; breakCmds[cmd] = true; + breakCmdsInfo[cmd].isConditional = false; } // Ignore adding a temp breakpoint when a normal one exists. } else { // This is no longer temporary. breakCmdsTemp[cmd] = false; - breakCmds[cmd] = true; + if (!breakCmds[cmd]) { + breakCmds[cmd] = true; + breakCmdsInfo[cmd].isConditional = false; + } } notifyBreakpoints(true); } @@ -432,7 +453,7 @@ static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::st bp.isConditional = true; bp.expressionString = expression; } else { - bp.isConditional = false; + // Don't change if it failed. if (error) *error = getExpressionError(); success = false; @@ -463,6 +484,25 @@ bool GetAddressBreakpointCond(u32 addr, std::string *expression) { return false; } +bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error) { + // Must have one in the first place, make sure it's not temporary. + AddCmdBreakpoint(cmd); + + std::lock_guard guard(breaksLock); + return SetupCond(breakCmdsInfo[cmd], expression, error); +} + +bool GetCmdBreakpointCond(u8 cmd, std::string *expression) { + if (breakCmds[cmd] && breakCmdsInfo[cmd].isConditional) { + if (expression) { + std::lock_guard guard(breaksLock); + *expression = breakCmdsInfo[cmd].expressionString; + } + return true; + } + return false; +} + void UpdateLastTexture(u32 addr) { lastTexture = addr; } diff --git a/GPU/Debugger/Breakpoints.h b/GPU/Debugger/Breakpoints.h index fbe4bd88fcf3..0910a68e92f0 100644 --- a/GPU/Debugger/Breakpoints.h +++ b/GPU/Debugger/Breakpoints.h @@ -49,6 +49,8 @@ namespace GPUBreakpoints { bool SetAddressBreakpointCond(u32 addr, const std::string &expression, std::string *error); bool GetAddressBreakpointCond(u32 addr, std::string *expression); + bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error); + bool GetCmdBreakpointCond(u8 cmd, std::string *expression); void UpdateLastTexture(u32 addr); diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index 9d6735aabdd2..5ad864ed3f59 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -18,8 +18,9 @@ #include #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" -#include "Common/Log.h" +#include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Text/Parsers.h" +#include "Common/Log.h" #include "Common/StringUtils.h" #include "Windows/resource.h" #include "Windows/InputBox.h" @@ -900,7 +901,13 @@ void CtrlStateValues::OnDoubleClick(int row, int column) { const auto info = rows_[row]; if (column == STATEVALUES_COL_BREAKPOINT) { - SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); + bool proceed = true; + if (IsCmdBreakpoint(info.cmd)) { + int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); + proceed = ret == IDYES; + } + if (proceed) + SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); return; } @@ -960,6 +967,7 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_STATE); SetMenuDefaultItem(subMenu, ID_REGLIST_CHANGE, FALSE); + EnableMenuItem(subMenu, ID_GEDBG_SETCOND, GPUBreakpoints::IsCmdBreakpoint(info.cmd) ? MF_ENABLED : MF_GRAYED); // Ehh, kinda ugly. if (!watchList.empty() && rows_ == &watchList[0]) { @@ -975,8 +983,19 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { switch (TriggerContextMenu(ContextMenuID::GEDBG_STATE, GetHandle(), ContextPoint::FromClient(point))) { - case ID_DISASM_TOGGLEBREAKPOINT: - SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); + case ID_DISASM_TOGGLEBREAKPOINT: { + bool proceed = true; + if (IsCmdBreakpoint(info.cmd)) { + int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); + proceed = ret == IDYES; + } + if (proceed) + SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); + break; + } + + case ID_GEDBG_SETCOND: + PromptBreakpointCond(info); break; case ID_DISASM_COPYINSTRUCTIONHEX: { @@ -1043,6 +1062,24 @@ bool CtrlStateValues::RowValuesChanged(int row) { return false; } +void CtrlStateValues::PromptBreakpointCond(const TabStateRow &info) { + std::string expression; + GPUBreakpoints::GetCmdBreakpointCond(info.cmd, &expression); + if (!InputBox_GetString(GetModuleHandle(NULL), GetHandle(), L"Expression", expression, expression)) + return; + + std::string error; + if (!GPUBreakpoints::SetCmdBreakpointCond(info.cmd, expression, &error)) { + MessageBox(GetHandle(), ConvertUTF8ToWString(error).c_str(), L"Invalid expression", MB_OK | MB_ICONEXCLAMATION); + } else { + if (info.otherCmd) + GPUBreakpoints::SetCmdBreakpointCond(info.otherCmd, expression, &error); + if (info.otherCmd2) + GPUBreakpoints::SetCmdBreakpointCond(info.otherCmd2, expression, &error); + } + +} + TabStateValues::TabStateValues(const TabStateRow *rows, int rowCount, LPCSTR dialogID, HINSTANCE _hInstance, HWND _hParent) : Dialog(dialogID, _hInstance, _hParent) { values = new CtrlStateValues(rows, rowCount, GetDlgItem(m_hDlg, IDC_GEDBG_VALUES)); diff --git a/Windows/GEDebugger/TabState.h b/Windows/GEDebugger/TabState.h index dbc1558fa0cd..6992af14be54 100644 --- a/Windows/GEDebugger/TabState.h +++ b/Windows/GEDebugger/TabState.h @@ -47,6 +47,7 @@ class CtrlStateValues: public GenericListControl { private: bool RowValuesChanged(int row); void SetCmdValue(u32 op); + void PromptBreakpointCond(const TabStateRow &info); const TabStateRow *rows_; int rowCount_; diff --git a/Windows/GEDebugger/TabVertices.cpp b/Windows/GEDebugger/TabVertices.cpp index 0576b96032a1..9606672840e5 100644 --- a/Windows/GEDebugger/TabVertices.cpp +++ b/Windows/GEDebugger/TabVertices.cpp @@ -17,6 +17,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/Data/Encoding/Utf8.h" #include "Common/StringUtils.h" #include "Core/System.h" #include "Windows/resource.h" @@ -550,25 +551,52 @@ static constexpr MatrixCmdPair matrixCmds[] = { { MATRIXLIST_ROW_COUNT, GE_CMD_NOP }, }; -void CtrlMatrixList::ToggleBreakpoint(int row) { +static const MatrixCmdPair *FindCmdPair(int row) { for (int i = 0; i < ARRAY_SIZE(matrixCmds) - 1; ++i) { if (row < matrixCmds[i].row || row >= matrixCmds[i + 1].row) continue; - // Okay, this command is in range. Toggle the actual breakpoint. - auto &info = matrixCmds[i]; - bool state = !GPUBreakpoints::IsCmdBreakpoint(info.cmd); - if (state) - GPUBreakpoints::AddCmdBreakpoint(info.cmd); - else - GPUBreakpoints::RemoveCmdBreakpoint(info.cmd); + return &matrixCmds[i]; + } + return nullptr; +} - for (int r = matrixCmds[i].row; r < matrixCmds[i + 1].row; ++r) { - SetItemState(r, state ? 1 : 0); - } +void CtrlMatrixList::ToggleBreakpoint(int row) { + const MatrixCmdPair *info = FindCmdPair(row); + if (!info) + return; + + // Okay, this command is in range. Toggle the actual breakpoint. + bool state = !GPUBreakpoints::IsCmdBreakpoint(info->cmd); + if (state) { + GPUBreakpoints::AddCmdBreakpoint(info->cmd); + } else { + int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); + if (ret != IDYES) + return; + GPUBreakpoints::RemoveCmdBreakpoint(info->cmd); + } + + for (int r = info->row; r < (info + 1)->row; ++r) { + SetItemState(r, state ? 1 : 0); } } +void CtrlMatrixList::PromptBreakpointCond(int row) { + const MatrixCmdPair *info = FindCmdPair(row); + if (!info) + return; + + std::string expression; + GPUBreakpoints::GetCmdBreakpointCond(info->cmd, &expression); + if (!InputBox_GetString(GetModuleHandle(NULL), GetHandle(), L"Expression", expression, expression)) + return; + + std::string error; + if (!GPUBreakpoints::SetCmdBreakpointCond(info->cmd, expression, &error)) + MessageBox(GetHandle(), ConvertUTF8ToWString(error).c_str(), L"Invalid expression", MB_OK | MB_ICONEXCLAMATION); +} + void CtrlMatrixList::OnDoubleClick(int row, int column) { if (row >= GetRowCount()) return; @@ -624,18 +652,24 @@ void CtrlMatrixList::OnDoubleClick(int row, int column) { void CtrlMatrixList::OnRightClick(int row, int column, const POINT &point) { if (row >= GetRowCount()) return; + const MatrixCmdPair *info = FindCmdPair(row); POINT screenPt(point); ClientToScreen(GetHandle(), &screenPt); HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_MATRIX); SetMenuDefaultItem(subMenu, ID_REGLIST_CHANGE, FALSE); + EnableMenuItem(subMenu, ID_GEDBG_SETCOND, info && GPUBreakpoints::IsCmdBreakpoint(info->cmd) ? MF_ENABLED : MF_GRAYED); switch (TriggerContextMenu(ContextMenuID::GEDBG_MATRIX, GetHandle(), ContextPoint::FromClient(point))) { case ID_DISASM_TOGGLEBREAKPOINT: ToggleBreakpoint(row); break; + case ID_GEDBG_SETCOND: + PromptBreakpointCond(row); + break; + case ID_DISASM_COPYINSTRUCTIONDISASM: { float val; diff --git a/Windows/GEDebugger/TabVertices.h b/Windows/GEDebugger/TabVertices.h index 116f60f06ce2..0a1f48be0cb9 100644 --- a/Windows/GEDebugger/TabVertices.h +++ b/Windows/GEDebugger/TabVertices.h @@ -90,6 +90,7 @@ class CtrlMatrixList: public GenericListControl { bool GetValue(const GPUgstate &state, int row, int col, float &val); bool ColChanged(const GPUgstate &lastState, const GPUgstate &state, int row, int col); void ToggleBreakpoint(int row); + void PromptBreakpointCond(int row); }; class TabMatrices : public Dialog { diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index d44f5556a36b..9ffc1747f7ac 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -778,6 +778,7 @@ BEGIN MENUITEM "Copy Entire Tab (Formatted)",ID_GEDBG_COPYALL MENUITEM SEPARATOR MENUITEM "Toggle Breakpoint", ID_DISASM_TOGGLEBREAKPOINT + MENUITEM "Set Breakpoint Condition", ID_GEDBG_SETCOND MENUITEM "Add Watch", ID_GEDBG_WATCH END POPUP "gepreviewoptions" @@ -793,6 +794,7 @@ BEGIN MENUITEM "Copy Entire Tab (Formatted)",ID_GEDBG_COPYALL MENUITEM SEPARATOR MENUITEM "Toggle Breakpoint", ID_DISASM_TOGGLEBREAKPOINT + MENUITEM "Set Breakpoint Condition", ID_GEDBG_SETCOND END POPUP "getaboptions" BEGIN