diff options
author | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
---|---|---|
committer | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
commit | 9427e27e24a7135880ee2881c3c44988e174b41a (patch) | |
tree | 83f0062a35be144f6b162eaa823c5b3c7620146e /src/citron/debugger | |
parent | b35ae725d20960411e8588b11c12a2d55f86c9d0 (diff) |
chore: update project branding to citron
Diffstat (limited to 'src/citron/debugger')
-rw-r--r-- | src/citron/debugger/console.cpp | 49 | ||||
-rw-r--r-- | src/citron/debugger/console.h | 13 | ||||
-rw-r--r-- | src/citron/debugger/controller.cpp | 116 | ||||
-rw-r--r-- | src/citron/debugger/controller.h | 56 | ||||
-rw-r--r-- | src/citron/debugger/profiler.cpp | 229 | ||||
-rw-r--r-- | src/citron/debugger/profiler.h | 27 | ||||
-rw-r--r-- | src/citron/debugger/wait_tree.cpp | 431 | ||||
-rw-r--r-- | src/citron/debugger/wait_tree.h | 188 |
8 files changed, 1109 insertions, 0 deletions
diff --git a/src/citron/debugger/console.cpp b/src/citron/debugger/console.cpp new file mode 100644 index 000000000..1c1342ff1 --- /dev/null +++ b/src/citron/debugger/console.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef _WIN32 +#include <windows.h> + +#include <wincon.h> +#endif + +#include "common/logging/backend.h" +#include "yuzu/debugger/console.h" +#include "yuzu/uisettings.h" + +namespace Debugger { +void ToggleConsole() { + static bool console_shown = false; + if (console_shown == UISettings::values.show_console.GetValue()) { + return; + } else { + console_shown = UISettings::values.show_console.GetValue(); + } + + using namespace Common::Log; +#if defined(_WIN32) && !defined(_DEBUG) + FILE* temp; + if (UISettings::values.show_console) { + if (AllocConsole()) { + // The first parameter for freopen_s is a out parameter, so we can just ignore it + freopen_s(&temp, "CONIN$", "r", stdin); + freopen_s(&temp, "CONOUT$", "w", stdout); + freopen_s(&temp, "CONOUT$", "w", stderr); + SetConsoleOutputCP(65001); + SetColorConsoleBackendEnabled(true); + } + } else { + if (FreeConsole()) { + // In order to close the console, we have to also detach the streams on it. + // Just redirect them to NUL if there is no console window + SetColorConsoleBackendEnabled(false); + freopen_s(&temp, "NUL", "r", stdin); + freopen_s(&temp, "NUL", "w", stdout); + freopen_s(&temp, "NUL", "w", stderr); + } + } +#else + SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue()); +#endif +} +} // namespace Debugger diff --git a/src/citron/debugger/console.h b/src/citron/debugger/console.h new file mode 100644 index 000000000..2491d1ec1 --- /dev/null +++ b/src/citron/debugger/console.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Debugger { + +/** + * Uses the WINAPI to hide or show the stderr console. This function is a placeholder until we can + * get a real qt logging window which would work for all platforms. + */ +void ToggleConsole(); +} // namespace Debugger diff --git a/src/citron/debugger/controller.cpp b/src/citron/debugger/controller.cpp new file mode 100644 index 000000000..216d2974d --- /dev/null +++ b/src/citron/debugger/controller.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QAction> +#include <QLayout> +#include <QString> +#include "common/settings.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "input_common/drivers/tas_input.h" +#include "input_common/main.h" +#include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/debugger/controller.h" + +ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_, + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_, + QWidget* parent) + : QWidget(parent, Qt::Dialog), hid_core{hid_core_}, input_subsystem{input_subsystem_} { + setObjectName(QStringLiteral("Controller")); + setWindowTitle(tr("Controller P1")); + resize(500, 350); + setMinimumSize(500, 350); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); + + widget = new PlayerControlPreview(this); + refreshConfiguration(); + QLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(widget); + setLayout(layout); + + // Configure focus so that widget is focusable and the dialog automatically forwards focus to + // it. + setFocusProxy(widget); + widget->setFocusPolicy(Qt::StrongFocus); + widget->setFocus(); +} + +void ControllerDialog::refreshConfiguration() { + UnloadController(); + auto* player_1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + // Display the correct controller + controller = handheld->IsConnected() ? handheld : player_1; + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, + .is_npad_service = true, + }; + callback_key = controller->SetCallback(engine_callback); + widget->SetController(controller); + is_controller_set = true; +} + +QAction* ControllerDialog::toggleViewAction() { + if (toggle_view_action == nullptr) { + toggle_view_action = new QAction(tr("&Controller P1"), this); + toggle_view_action->setCheckable(true); + toggle_view_action->setChecked(isVisible()); + connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible); + } + + return toggle_view_action; +} + +void ControllerDialog::UnloadController() { + widget->UnloadController(); + if (is_controller_set) { + controller->DeleteCallback(callback_key); + is_controller_set = false; + } +} + +void ControllerDialog::showEvent(QShowEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::showEvent(ev); +} + +void ControllerDialog::hideEvent(QHideEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::hideEvent(ev); +} + +void ControllerDialog::ControllerUpdate(Core::HID::ControllerTriggerType type) { + // TODO(german77): Remove TAS from here + switch (type) { + case Core::HID::ControllerTriggerType::Button: + case Core::HID::ControllerTriggerType::Stick: { + const auto buttons_values = controller->GetButtonsValues(); + const auto stick_values = controller->GetSticks(); + u64 buttons = 0; + std::size_t index = 0; + for (const auto& button : buttons_values) { + buttons |= button.value ? 1LLU << index : 0; + index++; + } + const InputCommon::TasInput::TasAnalog left_axis = { + .x = stick_values.left.x / 32767.f, + .y = stick_values.left.y / 32767.f, + }; + const InputCommon::TasInput::TasAnalog right_axis = { + .x = stick_values.right.x / 32767.f, + .y = stick_values.right.y / 32767.f, + }; + input_subsystem->GetTas()->RecordInput(buttons, left_axis, right_axis); + break; + } + default: + break; + } +} diff --git a/src/citron/debugger/controller.h b/src/citron/debugger/controller.h new file mode 100644 index 000000000..9651dfaa9 --- /dev/null +++ b/src/citron/debugger/controller.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <QWidget> + +class QAction; +class QHideEvent; +class QShowEvent; +class PlayerControlPreview; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Core::HID { +class HIDCore; +class EmulatedController; +enum class ControllerTriggerType; +} // namespace Core::HID + +class ControllerDialog : public QWidget { + Q_OBJECT + +public: + explicit ControllerDialog(Core::HID::HIDCore& hid_core_, + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_, + QWidget* parent = nullptr); + + /// Returns a QAction that can be used to toggle visibility of this dialog. + QAction* toggleViewAction(); + + /// Reloads the widget to apply any changes in the configuration + void refreshConfiguration(); + + /// Disables events from the emulated controller + void UnloadController(); + +protected: + void showEvent(QShowEvent* ev) override; + void hideEvent(QHideEvent* ev) override; + +private: + /// Redirects input from the widget to the TAS driver + void ControllerUpdate(Core::HID::ControllerTriggerType type); + + int callback_key; + bool is_controller_set{}; + Core::HID::EmulatedController* controller; + + QAction* toggle_view_action = nullptr; + PlayerControlPreview* widget; + Core::HID::HIDCore& hid_core; + std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; +}; diff --git a/src/citron/debugger/profiler.cpp b/src/citron/debugger/profiler.cpp new file mode 100644 index 000000000..493ee0b17 --- /dev/null +++ b/src/citron/debugger/profiler.cpp @@ -0,0 +1,229 @@ +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QAction> +#include <QLayout> +#include <QMouseEvent> +#include <QPainter> +#include <QString> +#include <QTimer> +#include "common/common_types.h" +#include "common/microprofile.h" +#include "yuzu/debugger/profiler.h" +#include "yuzu/util/util.h" + +// Include the implementation of the UI in this file. This isn't in microprofile.cpp because the +// non-Qt frontends don't need it (and don't implement the UI drawing hooks either). +#if MICROPROFILE_ENABLED +#define MICROPROFILEUI_IMPL 1 +#include "common/microprofileui.h" + +class MicroProfileWidget : public QWidget { +public: + MicroProfileWidget(QWidget* parent = nullptr); + +protected: + void paintEvent(QPaintEvent* ev) override; + void showEvent(QShowEvent* ev) override; + void hideEvent(QHideEvent* ev) override; + + void mouseMoveEvent(QMouseEvent* ev) override; + void mousePressEvent(QMouseEvent* ev) override; + void mouseReleaseEvent(QMouseEvent* ev) override; + void wheelEvent(QWheelEvent* ev) override; + + void keyPressEvent(QKeyEvent* ev) override; + void keyReleaseEvent(QKeyEvent* ev) override; + +private: + /// This timer is used to redraw the widget's contents continuously. To save resources, it only + /// runs while the widget is visible. + QTimer update_timer; + /// Scale the coordinate system appropriately when dpi != 96. + qreal x_scale = 1.0, y_scale = 1.0; +}; + +#endif + +MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { + setObjectName(QStringLiteral("MicroProfile")); + setWindowTitle(tr("&MicroProfile")); + resize(1000, 600); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); + +#if MICROPROFILE_ENABLED + + MicroProfileWidget* widget = new MicroProfileWidget(this); + + QLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(widget); + setLayout(layout); + + // Configure focus so that widget is focusable and the dialog automatically forwards focus to + // it. + setFocusProxy(widget); + widget->setFocusPolicy(Qt::StrongFocus); + widget->setFocus(); +#endif +} + +QAction* MicroProfileDialog::toggleViewAction() { + if (toggle_view_action == nullptr) { + toggle_view_action = new QAction(windowTitle(), this); + toggle_view_action->setCheckable(true); + toggle_view_action->setChecked(isVisible()); + connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible); + } + + return toggle_view_action; +} + +void MicroProfileDialog::showEvent(QShowEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::showEvent(ev); +} + +void MicroProfileDialog::hideEvent(QHideEvent* ev) { + if (toggle_view_action) { + toggle_view_action->setChecked(isVisible()); + } + QWidget::hideEvent(ev); +} + +#if MICROPROFILE_ENABLED + +/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the +/// QPainter available inside the drawing callbacks. +static QPainter* mp_painter = nullptr; + +MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { + // Send mouse motion events even when not dragging. + setMouseTracking(true); + + MicroProfileSetDisplayMode(1); // Timers screen + MicroProfileInitUI(); + + connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update)); +} + +void MicroProfileWidget::paintEvent(QPaintEvent* ev) { + QPainter painter(this); + + // The units used by Microprofile for drawing are based in pixels on a 96 dpi display. + x_scale = qreal(painter.device()->logicalDpiX()) / 96.0; + y_scale = qreal(painter.device()->logicalDpiY()) / 96.0; + painter.scale(x_scale, y_scale); + + painter.setBackground(Qt::black); + painter.eraseRect(rect()); + + QFont font = GetMonospaceFont(); + font.setPixelSize(MICROPROFILE_TEXT_HEIGHT); + painter.setFont(font); + + mp_painter = &painter; + MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale); + mp_painter = nullptr; +} + +void MicroProfileWidget::showEvent(QShowEvent* ev) { + update_timer.start(15); // ~60 Hz + QWidget::showEvent(ev); +} + +void MicroProfileWidget::hideEvent(QHideEvent* ev) { + update_timer.stop(); + QWidget::hideEvent(ev); +} + +void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { + const auto mouse_position = ev->pos(); + MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0); + ev->accept(); +} + +void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { + const auto mouse_position = ev->pos(); + MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0); + MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); + ev->accept(); +} + +void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { + const auto mouse_position = ev->pos(); + MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0); + MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); + ev->accept(); +} + +void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { + const auto wheel_position = ev->position().toPoint(); + MicroProfileMousePosition(wheel_position.x() / x_scale, wheel_position.y() / y_scale, + ev->angleDelta().y() / 120); + ev->accept(); +} + +void MicroProfileWidget::keyPressEvent(QKeyEvent* ev) { + if (ev->key() == Qt::Key_Control) { + // Inform MicroProfile that the user is holding Ctrl. + MicroProfileModKey(1); + } + QWidget::keyPressEvent(ev); +} + +void MicroProfileWidget::keyReleaseEvent(QKeyEvent* ev) { + if (ev->key() == Qt::Key_Control) { + MicroProfileModKey(0); + } + QWidget::keyReleaseEvent(ev); +} + +// These functions are called by MicroProfileDraw to draw the interface elements on the screen. + +void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 text_length) { + // hex_color does not include an alpha, so it must be assumed to be 255 + mp_painter->setPen(QColor::fromRgb(hex_color)); + + // It's impossible to draw a string using a monospaced font with a fixed width per cell in a + // way that's reliable across different platforms and fonts as far as I (yuriks) can tell, so + // draw each character individually in order to precisely control the text advance. + for (u32 i = 0; i < text_length; ++i) { + // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice + // vertical alignment of text for a wide range of tested fonts. + mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}}); + x += MICROPROFILE_TEXT_WIDTH + 1; + } +} + +void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color, + MicroProfileBoxType type) { + QColor color = QColor::fromRgba(hex_color); + QBrush brush = color; + if (type == MicroProfileBoxTypeBar) { + QLinearGradient gradient(left, top, left, bottom); + gradient.setColorAt(0.f, color.lighter(125)); + gradient.setColorAt(1.f, color.darker(125)); + brush = gradient; + } + mp_painter->fillRect(left, top, right - left, bottom - top, brush); +} + +void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) { + // Temporary vector used to convert between the float array and QPointF. Marked static to reuse + // the allocation across calls. + static std::vector<QPointF> point_buf; + + for (u32 i = 0; i < vertices_length; ++i) { + point_buf.emplace_back(vertices[i * 2 + 0], vertices[i * 2 + 1]); + } + + // hex_color does not include an alpha, so it must be assumed to be 255 + mp_painter->setPen(QColor::fromRgb(hex_color)); + mp_painter->drawPolyline(point_buf.data(), vertices_length); + point_buf.clear(); +} +#endif diff --git a/src/citron/debugger/profiler.h b/src/citron/debugger/profiler.h new file mode 100644 index 000000000..4c8ccd3c2 --- /dev/null +++ b/src/citron/debugger/profiler.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <QWidget> + +class QAction; +class QHideEvent; +class QShowEvent; + +class MicroProfileDialog : public QWidget { + Q_OBJECT + +public: + explicit MicroProfileDialog(QWidget* parent = nullptr); + + /// Returns a QAction that can be used to toggle visibility of this dialog. + QAction* toggleViewAction(); + +protected: + void showEvent(QShowEvent* ev) override; + void hideEvent(QHideEvent* ev) override; + +private: + QAction* toggle_view_action = nullptr; +}; diff --git a/src/citron/debugger/wait_tree.cpp b/src/citron/debugger/wait_tree.cpp new file mode 100644 index 000000000..c05a05057 --- /dev/null +++ b/src/citron/debugger/wait_tree.cpp @@ -0,0 +1,431 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> +#include <fmt/format.h> + +#include "yuzu/debugger/wait_tree.h" +#include "yuzu/uisettings.h" + +#include "core/arm/debug.h" +#include "core/core.h" +#include "core/hle/kernel/k_class_token.h" +#include "core/hle/kernel/k_handle_table.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc_common.h" +#include "core/hle/kernel/svc_types.h" +#include "core/memory.h" + +namespace { + +constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{ + {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green}, + {Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan}, + {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, + {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, + {Qt::GlobalColor::darkRed, Qt::GlobalColor::red}, + {Qt::GlobalColor::darkYellow, Qt::GlobalColor::yellow}, + {Qt::GlobalColor::red, Qt::GlobalColor::red}, + {Qt::GlobalColor::darkCyan, Qt::GlobalColor::cyan}, + {Qt::GlobalColor::gray, Qt::GlobalColor::gray}, +}}; + +bool IsDarkTheme() { + const auto& theme = UISettings::values.theme; + return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") || + theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue"); +} + +} // namespace + +WaitTreeItem::WaitTreeItem() = default; +WaitTreeItem::~WaitTreeItem() = default; + +QColor WaitTreeItem::GetColor() const { + if (IsDarkTheme()) { + return QColor(Qt::GlobalColor::white); + } else { + return QColor(Qt::GlobalColor::black); + } +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const { + return {}; +} + +void WaitTreeItem::Expand() { + if (IsExpandable() && !expanded) { + children = GetChildren(); + for (std::size_t i = 0; i < children.size(); ++i) { + children[i]->parent = this; + children[i]->row = i; + } + expanded = true; + } +} + +WaitTreeItem* WaitTreeItem::Parent() const { + return parent; +} + +const std::vector<std::unique_ptr<WaitTreeItem>>& WaitTreeItem::Children() const { + return children; +} + +bool WaitTreeItem::IsExpandable() const { + return false; +} + +std::size_t WaitTreeItem::Row() const { + return row; +} + +std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList( + Core::System& system) { + std::vector<std::unique_ptr<WaitTreeThread>> item_list; + std::size_t row = 0; + auto add_threads = [&](const std::vector<Kernel::KThread*>& threads) { + for (std::size_t i = 0; i < threads.size(); ++i) { + if (threads[i]->GetThreadType() == Kernel::ThreadType::User) { + item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i], system)); + item_list.back()->row = row; + } + ++row; + } + }; + + add_threads(system.GlobalSchedulerContext().GetThreadList()); + + return item_list; +} + +WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {} +WaitTreeText::~WaitTreeText() = default; + +QString WaitTreeText::GetText() const { + return text; +} + +WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_) + : thread{thread_}, system{system_} {} +WaitTreeCallstack::~WaitTreeCallstack() = default; + +QString WaitTreeCallstack::GetText() const { + return tr("Call stack"); +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list; + + if (thread.GetThreadType() != Kernel::ThreadType::User) { + return list; + } + + if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) { + return list; + } + + auto backtrace = Core::GetBacktraceFromContext(thread.GetOwnerProcess(), thread.GetContext()); + + for (auto& entry : backtrace) { + std::string s = fmt::format("{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, + entry.original_address, entry.offset, entry.name); + list.push_back(std::make_unique<WaitTreeText>(QString::fromStdString(s))); + } + + return list; +} + +WaitTreeSynchronizationObject::WaitTreeSynchronizationObject( + const Kernel::KSynchronizationObject& object_, Core::System& system_) + : object{object_}, system{system_} {} +WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default; + +WaitTreeExpandableItem::WaitTreeExpandableItem() = default; +WaitTreeExpandableItem::~WaitTreeExpandableItem() = default; + +bool WaitTreeExpandableItem::IsExpandable() const { + return true; +} + +QString WaitTreeSynchronizationObject::GetText() const { + return tr("[%1] %2") + .arg(object.GetId()) + .arg(QString::fromStdString(object.GetTypeObj().GetName())); +} + +std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make( + const Kernel::KSynchronizationObject& object, Core::System& system) { + const auto type = + static_cast<Kernel::KClassTokenGenerator::ObjectType>(object.GetTypeObj().GetClassToken()); + switch (type) { + case Kernel::KClassTokenGenerator::ObjectType::KReadableEvent: + return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::KReadableEvent&>(object), + system); + case Kernel::KClassTokenGenerator::ObjectType::KThread: + return std::make_unique<WaitTreeThread>(static_cast<const Kernel::KThread&>(object), + system); + default: + return std::make_unique<WaitTreeSynchronizationObject>(object, system); + } +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list; + + auto threads = object.GetWaitingThreadsForDebugging(); + if (threads.empty()) { + list.push_back(std::make_unique<WaitTreeText>(tr("waited by no thread"))); + } else { + list.push_back(std::make_unique<WaitTreeThreadList>(std::move(threads), system)); + } + + return list; +} + +WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread, Core::System& system_) + : WaitTreeSynchronizationObject(thread, system_), system{system_} {} +WaitTreeThread::~WaitTreeThread() = default; + +QString WaitTreeThread::GetText() const { + const auto& thread = static_cast<const Kernel::KThread&>(object); + QString status; + switch (thread.GetState()) { + case Kernel::ThreadState::Runnable: + if (!thread.IsSuspended()) { + status = tr("runnable"); + } else { + status = tr("paused"); + } + break; + case Kernel::ThreadState::Waiting: + switch (thread.GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::Sleep: + status = tr("sleeping"); + break; + case Kernel::ThreadWaitReasonForDebugging::IPC: + status = tr("waiting for IPC reply"); + break; + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + status = tr("waiting for objects"); + break; + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + status = tr("waiting for condition variable"); + break; + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + status = tr("waiting for address arbiter"); + break; + case Kernel::ThreadWaitReasonForDebugging::Suspended: + status = tr("waiting for suspend resume"); + break; + default: + status = tr("waiting"); + break; + } + break; + case Kernel::ThreadState::Initialized: + status = tr("initialized"); + break; + case Kernel::ThreadState::Terminated: + status = tr("terminated"); + break; + default: + status = tr("unknown"); + break; + } + + const auto& context = thread.GetContext(); + const QString pc_info = tr(" PC = 0x%1 LR = 0x%2") + .arg(context.pc, 8, 16, QLatin1Char{'0'}) + .arg(context.lr, 8, 16, QLatin1Char{'0'}); + return QStringLiteral("%1%2 (%3) ") + .arg(WaitTreeSynchronizationObject::GetText(), pc_info, status); +} + +QColor WaitTreeThread::GetColor() const { + const std::size_t color_index = IsDarkTheme() ? 1 : 0; + + const auto& thread = static_cast<const Kernel::KThread&>(object); + switch (thread.GetState()) { + case Kernel::ThreadState::Runnable: + if (!thread.IsSuspended()) { + return QColor(WaitTreeColors[0][color_index]); + } else { + return QColor(WaitTreeColors[2][color_index]); + } + case Kernel::ThreadState::Waiting: + switch (thread.GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::IPC: + return QColor(WaitTreeColors[4][color_index]); + case Kernel::ThreadWaitReasonForDebugging::Sleep: + return QColor(WaitTreeColors[5][color_index]); + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + case Kernel::ThreadWaitReasonForDebugging::Suspended: + return QColor(WaitTreeColors[6][color_index]); + break; + default: + return QColor(WaitTreeColors[3][color_index]); + } + case Kernel::ThreadState::Initialized: + return QColor(WaitTreeColors[7][color_index]); + case Kernel::ThreadState::Terminated: + return QColor(WaitTreeColors[8][color_index]); + default: + return WaitTreeItem::GetColor(); + } +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeSynchronizationObject::GetChildren()); + + const auto& thread = static_cast<const Kernel::KThread&>(object); + + QString processor; + switch (thread.GetActiveCore()) { + case Kernel::Svc::IdealCoreUseProcessValue: + processor = tr("ideal"); + break; + default: + processor = tr("core %1").arg(thread.GetActiveCore()); + break; + } + + list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); + list.push_back(std::make_unique<WaitTreeText>( + tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask()))); + list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); + list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") + .arg(thread.GetPriority()) + .arg(thread.GetBasePriority()))); + list.push_back(std::make_unique<WaitTreeText>( + tr("last running ticks = %1").arg(thread.GetLastScheduledTick()))); + + list.push_back(std::make_unique<WaitTreeCallstack>(thread, system)); + + return list; +} + +WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_) + : WaitTreeSynchronizationObject(object_, system_) {} +WaitTreeEvent::~WaitTreeEvent() = default; + +WaitTreeThreadList::WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_) + : thread_list(std::move(list)), system{system_} {} +WaitTreeThreadList::~WaitTreeThreadList() = default; + +QString WaitTreeThreadList::GetText() const { + return tr("waited by thread"); +} + +std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThreadList::GetChildren() const { + std::vector<std::unique_ptr<WaitTreeItem>> list(thread_list.size()); + std::transform(thread_list.begin(), thread_list.end(), list.begin(), + [this](const auto& t) { return std::make_unique<WaitTreeThread>(*t, system); }); + return list; +} + +WaitTreeModel::WaitTreeModel(Core::System& system_, QObject* parent) + : QAbstractItemModel(parent), system{system_} {} +WaitTreeModel::~WaitTreeModel() = default; + +QModelIndex WaitTreeModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent)) + return {}; + + if (parent.isValid()) { + WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer()); + parent_item->Expand(); + return createIndex(row, column, parent_item->Children()[row].get()); + } + + return createIndex(row, column, thread_items[row].get()); +} + +QModelIndex WaitTreeModel::parent(const QModelIndex& index) const { + if (!index.isValid()) + return {}; + + WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(index.internalPointer())->Parent(); + if (!parent_item) { + return QModelIndex(); + } + return createIndex(static_cast<int>(parent_item->Row()), 0, parent_item); +} + +int WaitTreeModel::rowCount(const QModelIndex& parent) const { + if (!parent.isValid()) + return static_cast<int>(thread_items.size()); + + WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer()); + parent_item->Expand(); + return static_cast<int>(parent_item->Children().size()); +} + +int WaitTreeModel::columnCount(const QModelIndex&) const { + return 1; +} + +QVariant WaitTreeModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return {}; + + switch (role) { + case Qt::DisplayRole: + return static_cast<WaitTreeItem*>(index.internalPointer())->GetText(); + case Qt::ForegroundRole: + return static_cast<WaitTreeItem*>(index.internalPointer())->GetColor(); + default: + return {}; + } +} + +void WaitTreeModel::ClearItems() { + thread_items.clear(); +} + +void WaitTreeModel::InitItems() { + thread_items = WaitTreeItem::MakeThreadItemList(system); +} + +WaitTreeWidget::WaitTreeWidget(Core::System& system_, QWidget* parent) + : QDockWidget(tr("&Wait Tree"), parent), system{system_} { + setObjectName(QStringLiteral("WaitTreeWidget")); + view = new QTreeView(this); + view->setHeaderHidden(true); + setWidget(view); + setEnabled(false); +} + +WaitTreeWidget::~WaitTreeWidget() = default; + +void WaitTreeWidget::OnDebugModeEntered() { + if (!system.IsPoweredOn()) + return; + model->InitItems(); + view->setModel(model); + setEnabled(true); +} + +void WaitTreeWidget::OnDebugModeLeft() { + setEnabled(false); + view->setModel(nullptr); + model->ClearItems(); +} + +void WaitTreeWidget::OnEmulationStarting(EmuThread* emu_thread) { + model = new WaitTreeModel(system, this); + view->setModel(model); + setEnabled(false); +} + +void WaitTreeWidget::OnEmulationStopping() { + view->setModel(nullptr); + delete model; + setEnabled(false); +} diff --git a/src/citron/debugger/wait_tree.h b/src/citron/debugger/wait_tree.h new file mode 100644 index 000000000..23c329fbe --- /dev/null +++ b/src/citron/debugger/wait_tree.h @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <cstddef> +#include <memory> +#include <vector> + +#include <QDockWidget> +#include <QTreeView> + +#include "common/common_types.h" +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/svc_common.h" + +class EmuThread; + +namespace Core { +class System; +} + +namespace Kernel { +class KHandleTable; +class KReadableEvent; +class KSynchronizationObject; +class KThread; +} // namespace Kernel + +class WaitTreeThread; + +class WaitTreeItem : public QObject { + Q_OBJECT +public: + WaitTreeItem(); + ~WaitTreeItem() override; + + virtual bool IsExpandable() const; + virtual std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const; + virtual QString GetText() const = 0; + virtual QColor GetColor() const; + + void Expand(); + WaitTreeItem* Parent() const; + const std::vector<std::unique_ptr<WaitTreeItem>>& Children() const; + std::size_t Row() const; + static std::vector<std::unique_ptr<WaitTreeThread>> MakeThreadItemList(Core::System& system); + +private: + std::size_t row; + bool expanded = false; + WaitTreeItem* parent = nullptr; + std::vector<std::unique_ptr<WaitTreeItem>> children; +}; + +class WaitTreeText : public WaitTreeItem { + Q_OBJECT +public: + explicit WaitTreeText(QString text); + ~WaitTreeText() override; + + QString GetText() const override; + +private: + QString text; +}; + +class WaitTreeExpandableItem : public WaitTreeItem { + Q_OBJECT +public: + WaitTreeExpandableItem(); + ~WaitTreeExpandableItem() override; + + bool IsExpandable() const override; +}; + +class WaitTreeCallstack : public WaitTreeExpandableItem { + Q_OBJECT +public: + explicit WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_); + ~WaitTreeCallstack() override; + + QString GetText() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +private: + const Kernel::KThread& thread; + + Core::System& system; +}; + +class WaitTreeSynchronizationObject : public WaitTreeExpandableItem { + Q_OBJECT +public: + explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object_, + Core::System& system_); + ~WaitTreeSynchronizationObject() override; + + static std::unique_ptr<WaitTreeSynchronizationObject> make( + const Kernel::KSynchronizationObject& object, Core::System& system); + QString GetText() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +protected: + const Kernel::KSynchronizationObject& object; + +private: + Core::System& system; +}; + +class WaitTreeThread : public WaitTreeSynchronizationObject { + Q_OBJECT +public: + explicit WaitTreeThread(const Kernel::KThread& thread, Core::System& system_); + ~WaitTreeThread() override; + + QString GetText() const override; + QColor GetColor() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +private: + Core::System& system; +}; + +class WaitTreeEvent : public WaitTreeSynchronizationObject { + Q_OBJECT +public: + explicit WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_); + ~WaitTreeEvent() override; +}; + +class WaitTreeThreadList : public WaitTreeExpandableItem { + Q_OBJECT +public: + explicit WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_); + ~WaitTreeThreadList() override; + + QString GetText() const override; + std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; + +private: + std::vector<Kernel::KThread*> thread_list; + + Core::System& system; +}; + +class WaitTreeModel : public QAbstractItemModel { + Q_OBJECT + +public: + explicit WaitTreeModel(Core::System& system_, QObject* parent = nullptr); + ~WaitTreeModel() override; + + QVariant data(const QModelIndex& index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + + void ClearItems(); + void InitItems(); + +private: + std::vector<std::unique_ptr<WaitTreeThread>> thread_items; + + Core::System& system; +}; + +class WaitTreeWidget : public QDockWidget { + Q_OBJECT + +public: + explicit WaitTreeWidget(Core::System& system_, QWidget* parent = nullptr); + ~WaitTreeWidget() override; + +public slots: + void OnDebugModeEntered(); + void OnDebugModeLeft(); + + void OnEmulationStarting(EmuThread* emu_thread); + void OnEmulationStopping(); + +private: + QTreeView* view; + WaitTreeModel* model; + + Core::System& system; +}; |