From f9c7731f4d8bdfbe45ddf66185bfa45d7cabd859 Mon Sep 17 00:00:00 2001 From: TryTwo Date: Wed, 11 Feb 2026 16:17:06 -0700 Subject: [PATCH] Debugger/ BreakpointsWidget: Add option to disable/enable all breaking without affecting individual breakpoint enabled states. Allows you to quickly stop breaking, play the game, then re-enable breaking. useful if you have many active breakpoints, but need to run the game. --- Source/Core/Core/PowerPC/BreakPoints.cpp | 14 +++++ Source/Core/Core/PowerPC/BreakPoints.h | 10 +++- Source/Core/Core/PowerPC/PowerPC.cpp | 3 +- .../DolphinQt/Debugger/BreakpointWidget.cpp | 55 ++++++++++++++++++- .../DolphinQt/Debugger/BreakpointWidget.h | 2 + .../DolphinQt/Debugger/CodeViewWidget.cpp | 3 +- 6 files changed, 82 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/PowerPC/BreakPoints.cpp b/Source/Core/Core/PowerPC/BreakPoints.cpp index 3cf564663e..e5dca73989 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.cpp +++ b/Source/Core/Core/PowerPC/BreakPoints.cpp @@ -34,6 +34,9 @@ bool BreakPoints::IsAddressBreakPoint(u32 address) const bool BreakPoints::IsBreakPointEnable(u32 address) const { + if (!m_breaking_enabled) + return false; + const TBreakPoint* bp = GetBreakpoint(address); return bp != nullptr && bp->is_enabled; } @@ -184,6 +187,11 @@ bool BreakPoints::ToggleEnable(u32 address) return true; } +void BreakPoints::EnableBreaking(bool enable) +{ + m_breaking_enabled = enable; +} + bool BreakPoints::Remove(u32 address) { const auto iter = std::ranges::find(m_breakpoints, address, &TBreakPoint::address); @@ -318,6 +326,12 @@ bool MemChecks::ToggleEnable(u32 address) return true; } +void MemChecks::EnableBreaking(bool enabled) +{ + m_breaking_enabled = enabled; + Update(); +} + DelayedMemCheckUpdate MemChecks::Remove(u32 address) { const auto iter = std::ranges::find(m_mem_checks, address, &TMemCheck::start_address); diff --git a/Source/Core/Core/PowerPC/BreakPoints.h b/Source/Core/Core/PowerPC/BreakPoints.h index de22192f7a..da5e755dd3 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.h +++ b/Source/Core/Core/PowerPC/BreakPoints.h @@ -87,6 +87,9 @@ public: bool ToggleBreakPoint(u32 address); bool ToggleEnable(u32 address); + void EnableBreaking(bool enable); + bool IsBreakingEnabled() const { return m_breaking_enabled; } + // Remove Breakpoint. Returns whether it was removed. bool Remove(u32 address); void Clear(); @@ -96,6 +99,7 @@ private: TBreakPoints m_breakpoints; std::optional m_temp_breakpoint; Core::System& m_system; + bool m_breaking_enabled = true; }; class DelayedMemCheckUpdate; @@ -126,9 +130,12 @@ public: bool OverlapsMemcheck(u32 address, u32 length) const; DelayedMemCheckUpdate Remove(u32 address); + void EnableBreaking(bool enable); + bool IsBreakingEnabled() const { return m_breaking_enabled; } + void Update(); void Clear(); - bool HasAny() const { return !m_mem_checks.empty(); } + bool HasAny() const { return !m_mem_checks.empty() && m_breaking_enabled; } BitSet32 GetGPRsUsedInConditions() { return m_gprs_used_in_conditions; } BitSet32 GetFPRsUsedInConditions() { return m_fprs_used_in_conditions; } @@ -142,6 +149,7 @@ private: BitSet32 m_gprs_used_in_conditions; BitSet32 m_fprs_used_in_conditions; bool m_mem_breakpoints_set = false; + bool m_breaking_enabled = true; }; class DelayedMemCheckUpdate final diff --git a/Source/Core/Core/PowerPC/PowerPC.cpp b/Source/Core/Core/PowerPC/PowerPC.cpp index e5ce8ffcdb..7939b6d4bc 100644 --- a/Source/Core/Core/PowerPC/PowerPC.cpp +++ b/Source/Core/Core/PowerPC/PowerPC.cpp @@ -639,7 +639,8 @@ bool PowerPCManager::CheckBreakPoints() { const TBreakPoint* bp = m_breakpoints.GetBreakpoint(m_ppc_state.pc); - if (!bp || !bp->is_enabled || !EvaluateCondition(m_system, bp->condition)) + if (!m_breakpoints.IsBreakingEnabled() || !bp || !bp->is_enabled || + !EvaluateCondition(m_system, bp->condition)) return false; if (bp->log_on_hit) diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp index 09b045688c..fbb5c38d9b 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp @@ -173,6 +173,7 @@ void BreakpointWidget::CreateWidgets() layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(0); + m_enabled = m_toolbar->addAction(tr("Disable"), this, &BreakpointWidget::OnToggleBreaking); m_new = m_toolbar->addAction(tr("New"), this, &BreakpointWidget::OnNewBreakpoint); m_clear = m_toolbar->addAction(tr("Clear"), this, &BreakpointWidget::OnClear); @@ -190,6 +191,10 @@ void BreakpointWidget::CreateWidgets() void BreakpointWidget::UpdateIcons() { + if (m_system.GetPowerPC().GetBreakPoints().IsBreakingEnabled()) + m_enabled->setIcon(Resources::GetThemeIcon("pause")); + else + m_enabled->setIcon(Resources::GetThemeIcon("play")); m_new->setIcon(Resources::GetThemeIcon("debugger_add_breakpoint")); m_clear->setIcon(Resources::GetThemeIcon("debugger_clear")); m_load->setIcon(Resources::GetThemeIcon("debugger_load")); @@ -289,6 +294,24 @@ void BreakpointWidget::Update() QPixmap enabled_icon = Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(downscale, downscale)); + auto& power_pc = m_system.GetPowerPC(); + auto& breakpoints = power_pc.GetBreakPoints(); + + if (!breakpoints.IsBreakingEnabled()) + { + // Use QPainter to draw a transparent hole in the center + QImage image = enabled_icon.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + QPainter painter(&image); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::transparent); + // Center and radius + painter.drawEllipse(QPoint(downscale / 2, downscale / 2), downscale / 4, downscale / 4); + painter.end(); + enabled_icon = QPixmap::fromImage(image); + } + const auto create_item = [](const QString& string = {}) { QTableWidgetItem* item = new QTableWidgetItem(string); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); @@ -306,8 +329,6 @@ void BreakpointWidget::Update() Settings::Instance().IsThemeDark() ? QColor(75, 75, 75) : QColor(225, 225, 225); disabled_item.setBackground(disabled_color); - auto& power_pc = m_system.GetPowerPC(); - auto& breakpoints = power_pc.GetBreakPoints(); auto& memchecks = power_pc.GetMemChecks(); auto& ppc_symbol_db = power_pc.GetSymbolDB(); @@ -466,6 +487,36 @@ void BreakpointWidget::OnEditBreakpoint(u32 address, bool is_instruction_bp) emit Host::GetInstance()->PPCBreakpointsChanged(); } +void BreakpointWidget::OnToggleBreaking() +{ + auto& breakpoints = m_system.GetPowerPC().GetBreakPoints(); + auto& memchecks = m_system.GetPowerPC().GetMemChecks(); + // Memcheck's HasAny() will report no memchecks while breaking is disabled, so only check when + // breaking is true. + bool has_memory_bp; + + // Currently toggles all code and memory breakpoints. Could be split if needed. + if (breakpoints.IsBreakingEnabled()) + { + has_memory_bp = memchecks.HasAny(); + breakpoints.EnableBreaking(false); + memchecks.EnableBreaking(false); + m_enabled->setText(tr("Enable")); + m_enabled->setIcon(Resources::GetThemeIcon("play")); + } + else + { + breakpoints.EnableBreaking(true); + memchecks.EnableBreaking(true); + has_memory_bp = memchecks.HasAny(); + m_enabled->setText(tr("Disable")); + m_enabled->setIcon(Resources::GetThemeIcon("pause")); + } + + if (has_memory_bp || !breakpoints.GetBreakPoints().empty()) + emit Host::GetInstance()->PPCBreakpointsChanged(); +} + void BreakpointWidget::OnLoad() { Common::IniFile ini; diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h index f48570f449..64dfcf1283 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h @@ -58,6 +58,7 @@ private: void OnClear(); void OnClicked(QTableWidgetItem* item); + void OnToggleBreaking(); void OnNewBreakpoint(); void OnEditBreakpoint(u32 address, bool is_instruction_bp); void OnLoad(); @@ -70,6 +71,7 @@ private: QToolBar* m_toolbar; QTableWidget* m_table; + QAction* m_enabled; QAction* m_new; QAction* m_clear; QAction* m_load; diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index ce02b6baa5..c7a74ee29d 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -319,6 +319,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) guard ? std::make_optional(power_pc.GetPPCState().pc) : std::nullopt; const bool dark_theme = Settings::Instance().IsThemeDark(); + const bool breaking_enabled = power_pc.GetBreakPoints().IsBreakingEnabled(); m_branches.clear(); @@ -400,7 +401,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) if (bp != nullptr) { auto icon = Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(rowh - 2, rowh - 2)); - if (!bp->is_enabled) + if (!breaking_enabled || !bp->is_enabled) { QPixmap disabled_icon(icon.size()); disabled_icon.fill(Qt::transparent);