From 9811e1cc3b5bf025ec38551faa81e37035e39bea Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 1 Mar 2026 10:07:25 +0200 Subject: [PATCH] Debugger: Add HW PPU threads view --- rpcs3/Emu/CPU/CPUThread.cpp | 2 +- rpcs3/Emu/Cell/lv2/lv2.cpp | 22 +++++ rpcs3/Emu/Cell/lv2/sys_sync.h | 1 + rpcs3/rpcs3qt/debugger_frame.cpp | 155 +++++++++++++++++++++++++------ rpcs3/rpcs3qt/debugger_frame.h | 7 +- 5 files changed, 156 insertions(+), 31 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index e37e6ed0da..3ab011aa04 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -1373,7 +1373,7 @@ std::vector> cpu_thread::dump_callstack_list() const std::string cpu_thread::dump_misc() const { - return fmt::format("Type: %s; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", state.load()); + return fmt::format("%s[0x%x]; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", id, state.load()); } bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index 3b923e8c41..dbe11039e7 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -2219,6 +2219,28 @@ void lv2_obj::prepare_for_sleep(cpu_thread& cpu) cpu_counter::remove(&cpu); } +ppu_thread* lv2_obj::get_running_ppu(u32 index) +{ + usz thread_count = g_cfg.core.ppu_threads; + + if (index >= thread_count) + { + return nullptr; + } + + auto target = atomic_storage::load(g_ppu); + + for (usz cur = 0; target; target = atomic_storage::load(target->next_ppu), cur++) + { + if (cur == index) + { + return target; + } + } + + return nullptr; +} + void lv2_obj::notify_all() noexcept { for (auto cpu : g_to_notify) diff --git a/rpcs3/Emu/Cell/lv2/sys_sync.h b/rpcs3/Emu/Cell/lv2/sys_sync.h index 0aff5e1e7a..6d55f4b1f0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_sync.h +++ b/rpcs3/Emu/Cell/lv2/sys_sync.h @@ -453,6 +453,7 @@ public: // Can be called before the actual sleep call in order to move it out of mutex scope static void prepare_for_sleep(cpu_thread& cpu); + static ppu_thread* get_running_ppu(u32 index); struct notify_all_t { diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index 7ffe1b32a2..f1dd9cf892 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -16,6 +16,7 @@ #include "Emu/IdManager.h" #include "Emu/RSX/RSXThread.h" #include "Emu/RSX/RSXDisAsm.h" +#include "Emu/Cell/lv2/sys_sync.h" #include "Emu/Cell/PPUAnalyser.h" #include "Emu/Cell/PPUDisAsm.h" #include "Emu/Cell/PPUThread.h" @@ -804,12 +805,47 @@ cpu_thread* debugger_frame::get_cpu() return nullptr; } + if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx) + { + reader_lock lock(lv2_obj::g_mutex); + + const auto ppu = lv2_obj::get_running_ppu(cur - m_hw_ppu_idx); + + if (ppu == m_cpu.get()) + { + // Nothing to do + } + else if (ppu) + { + m_cpu = idm::get_unlocked>(ppu->id); + } + else + { + m_cpu.reset(); + } + } + + if (!!m_disasm != !!m_cpu) + { + // Fixup for HW PPU viewer + if (m_cpu) + { + m_disasm = make_disasm(m_cpu.get(), m_cpu); + } + else + { + m_disasm.reset(); + } + + m_debugger_list->UpdateCPUData(m_disasm); + m_breakpoint_list->UpdateCPUData(m_disasm); + } + // Wait flag is raised by the thread itself, acknowledging exit if (m_cpu) { if (m_cpu->state.all_of(cpu_flag::wait + cpu_flag::exit)) { - m_cpu.reset(); return nullptr; } @@ -823,12 +859,13 @@ cpu_thread* debugger_frame::get_cpu() { if (g_fxo->try_get() != m_rsx || !m_rsx->ctrl || m_rsx->state.all_of(cpu_flag::wait + cpu_flag::exit)) { - m_rsx = nullptr; return nullptr; } + + return m_rsx; } - return m_rsx; + return nullptr; } std::function debugger_frame::make_check_cpu(cpu_thread* cpu, bool unlocked) @@ -922,19 +959,20 @@ std::function debugger_frame::make_check_cpu(cpu_thread* cpu, boo void debugger_frame::UpdateUI() { - const auto cpu = get_cpu(); + auto cpu = get_cpu(); // Refresh at a high rate during initialization (looks weird otherwise) if (m_ui_update_ctr % (cpu || m_ui_update_ctr < 200 || m_debugger_list->m_dirty_flag ? 5 : 50) == 0) { // If no change to instruction position happened, update instruction list at 20hz - ShowPC(); + ShowPC(false, cpu); } if (m_ui_update_ctr % 20 == 0 && !m_thread_list_pending_update) { // Update threads list at 5hz (low priority) UpdateUnitList(); + cpu = get_cpu(); } if (!cpu) @@ -945,12 +983,13 @@ void debugger_frame::UpdateUI() { // Update threads list (thread exited) UpdateUnitList(); + cpu = get_cpu(); } - ShowPC(); + ShowPC(false, cpu); m_last_query_state.clear(); m_last_pc = -1; - DoUpdate(); + DoUpdate(cpu); } } else if (m_ui_update_ctr % 5 == 0 || m_ui_update_ctr < m_ui_fast_update_permission_deadline) @@ -966,7 +1005,7 @@ void debugger_frame::UpdateUI() std::memcpy(m_last_query_state.data(), static_cast(cpu), size_context); m_last_pc = cia; - DoUpdate(); + DoUpdate(cpu); const bool paused = !!(cpu->state & s_pause_flags); @@ -982,7 +1021,7 @@ void debugger_frame::UpdateUI() if (m_ui_update_ctr % 5) { // Call if it hasn't been called before - ShowPC(); + ShowPC(false, cpu); } if (is_using_interpreter(cpu->get_class())) @@ -1022,9 +1061,17 @@ void debugger_frame::UpdateUnitList() } std::vector>> cpu_list; - cpu_list.reserve(threads_created >= threads_deleted ? 0 : threads_created - threads_deleted); + cpu_list.reserve(threads_created >= threads_deleted ? threads_created - threads_deleted : 0); usz reselected_index = umax; + usz hw_ppu_idx = umax; + + if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx) + { + hw_ppu_idx = cur - m_hw_ppu_idx; + } + + m_hw_ppu_idx = umax; const auto on_select = [&](u32 id, cpu_thread& cpu) { @@ -1033,7 +1080,7 @@ void debugger_frame::UpdateUnitList() // Space at the end is to pad a gap on the right cpu_list.emplace_back(QString::fromStdString((id >> 24 == 0x55 ? "RSX[0x55555555]" : cpu.get_name()) + ' '), std::move(func_cpu)); - if (old_cpu_ptr == std::addressof(cpu)) + if (old_cpu_ptr == std::addressof(cpu) && hw_ppu_idx == umax) { reselected_index = cpu_list.size() - 1; } @@ -1046,6 +1093,40 @@ void debugger_frame::UpdateUnitList() idm::select>(on_select, idm::unlocked); } + m_hw_ppu_idx = cpu_list.size() + 1; // Account for NoThreadString thread + + for (u32 i = 0; i < g_cfg.core.ppu_threads + 0u; i++) + { + std::function get_ppu_at = [index = i, cpu_storage = shared_ptr>()]() mutable + { + reader_lock lock(lv2_obj::g_mutex); + + const auto ppu = lv2_obj::get_running_ppu(index); + + if (ppu == cpu_storage.get()) + { + // Nothing to do + } + else if (ppu) + { + cpu_storage = idm::get_unlocked>(ppu->id); + } + else + { + cpu_storage.reset(); + } + + return cpu_storage.get(); + }; + + if (hw_ppu_idx == i) + { + reselected_index = cpu_list.size(); + } + + cpu_list.emplace_back(tr("HwPPU[%0]: Hardware PPU Thread #%1").arg(i + 1).arg(i + 1), std::move(get_ppu_at)); + } + if (g_fxo->is_init>>()) { idm::select>(on_select, idm::unlocked); @@ -1114,6 +1195,7 @@ void debugger_frame::OnSelectUnit() } cpu_thread* selected = nullptr; + usz hw_ppu_idx = umax; if (m_emu_state != system_state::stopped) { @@ -1135,7 +1217,14 @@ void debugger_frame::OnSelectUnit() if (!selected && !m_rsx && !m_cpu) { - return; + if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx) + { + hw_ppu_idx = cur - m_hw_ppu_idx; + } + else + { + return; + } } } @@ -1144,6 +1233,15 @@ void debugger_frame::OnSelectUnit() m_rsx = nullptr; m_spu_disasm_memory.reset(); + if (hw_ppu_idx != umax) + { + if (hw_ppu_idx > 1) + { + // Sample PPU + selected = ::at32(m_threads_info, 1)(); + } + } + if (selected) { const u32 cpu_id = selected->id; @@ -1198,8 +1296,8 @@ void debugger_frame::OnSelectUnit() m_debugger_list->UpdateCPUData(m_disasm); m_breakpoint_list->UpdateCPUData(m_disasm); - ShowPC(true); - DoUpdate(); + ShowPC(true, selected); + DoUpdate(selected); UpdateUI(); } @@ -1339,30 +1437,28 @@ void debugger_frame::OnSelectSPUDisassembler() m_debugger_list->UpdateCPUData(m_disasm); m_breakpoint_list->UpdateCPUData(m_disasm); - ShowPC(true); - DoUpdate(); + ShowPC(true, nullptr); + DoUpdate(nullptr); UpdateUI(); }); m_spu_disasm_dialog->show(); } -void debugger_frame::DoUpdate() +void debugger_frame::DoUpdate(cpu_thread* cpu0) { // Check if we need to disable a step over bp - if (const auto cpu0 = get_cpu(); cpu0 && m_last_step_over_breakpoint != umax && cpu0->get_pc() == m_last_step_over_breakpoint) + if (cpu0 && m_last_step_over_breakpoint != umax && cpu0->get_pc() == m_last_step_over_breakpoint) { m_ppu_breakpoint_handler->RemoveBreakpoint(m_last_step_over_breakpoint); m_last_step_over_breakpoint = -1; } - WritePanels(); + WritePanels(cpu0); } -void debugger_frame::WritePanels() +void debugger_frame::WritePanels(cpu_thread* cpu) { - const auto cpu = get_cpu(); - if (!cpu) { m_misc_state->clear(); @@ -1412,7 +1508,9 @@ void debugger_frame::ShowGotoAddressDialog() QLineEdit* expression_input(new QLineEdit(m_goto_dialog)); expression_input->setFont(m_mono); - if (const auto thread = get_cpu(); !thread || thread->get_class() != thread_class::spu) + const auto thread = get_cpu(); + + if (!thread || thread->get_class() != thread_class::spu) { expression_input->setValidator(new HexValidator(expression_input)); } @@ -1436,8 +1534,8 @@ void debugger_frame::ShowGotoAddressDialog() m_goto_dialog->setLayout(vbox_panel); - const auto cpu_check = make_check_cpu(get_cpu()); - const auto cpu = cpu_check(); + const auto cpu_check = make_check_cpu(thread); + const auto cpu = thread; const QFont font = expression_input->font(); // -1 from get_pc() turns into 0 @@ -1544,9 +1642,12 @@ void debugger_frame::ClearCallStack() Q_EMIT CallStackUpdateRequested({}); } -void debugger_frame::ShowPC(bool user_requested) +void debugger_frame::ShowPC(bool user_requested, cpu_thread* cpu0) { - const auto cpu0 = get_cpu(); + if (!cpu0) + { + cpu0 = get_cpu(); + } const u32 pc = (cpu0 ? cpu0->get_pc() : (m_is_spu_disasm_mode ? m_spu_disasm_pc : 0)); diff --git a/rpcs3/rpcs3qt/debugger_frame.h b/rpcs3/rpcs3qt/debugger_frame.h index 6837347c3f..eb22d74680 100644 --- a/rpcs3/rpcs3qt/debugger_frame.h +++ b/rpcs3/rpcs3qt/debugger_frame.h @@ -79,6 +79,7 @@ class debugger_frame : public custom_dock_widget std::shared_ptr m_disasm; // Only shared to allow base/derived functionality shared_ptr m_cpu; rsx::thread* m_rsx = nullptr; + u32 m_hw_ppu_idx = umax; std::shared_ptr m_spu_disasm_memory; u32 m_spu_disasm_origin_eal = 0; u32 m_spu_disasm_pc = 0; @@ -107,8 +108,8 @@ public: void UpdateUI(); void UpdateUnitList(); - void DoUpdate(); - void WritePanels(); + void DoUpdate(cpu_thread* cpu0); + void WritePanels(cpu_thread* cpu); void EnableButtons(bool enable); void ShowGotoAddressDialog(); void PerformGoToRequest(const QString& text_argument); @@ -138,7 +139,7 @@ private Q_SLOTS: void OnSelectUnit(); void OnSelectSPUDisassembler(); void OnRegsContextMenu(const QPoint& pos); - void ShowPC(bool user_requested = false); + void ShowPC(bool user_requested = false, cpu_thread* cpu = nullptr); void EnableUpdateTimer(bool enable) const; void RunBtnPress(); void RegsShowMemoryViewerAction();