diff options
-rw-r--r-- | src/citra_qt/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_breakpoints.cpp | 253 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_breakpoints.hxx | 78 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 6 |
4 files changed, 339 insertions, 0 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 7bf44197d..999724102 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -8,6 +8,7 @@ set(SRCS debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics.cpp + debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp debugger/ramview.cpp debugger/registers.cpp @@ -24,6 +25,7 @@ set(HEADERS debugger/callstack.hxx debugger/disassembler.hxx debugger/graphics.hxx + debugger/graphics_breakpoints.hxx debugger/graphics_cmdlists.hxx debugger/ramview.hxx debugger/registers.hxx diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp new file mode 100644 index 000000000..2f41d5f77 --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -0,0 +1,253 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <QMetaType> +#include <QPushButton> +#include <QTreeWidget> +#include <QVBoxLayout> +#include <QLabel> + +#include "graphics_breakpoints.hxx" + +BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) +{ + +} + +int BreakPointModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const +{ + return static_cast<int>(Pica::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const +{ + const auto event = static_cast<Pica::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::DisplayRole: + { + if (index.column() == 0) { + std::map<Pica::DebugContext::Event, QString> map; + map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); + map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); + map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incomming primitive batch")}); + map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); + + _dbg_assert_(GPU, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents)); + + return map[event]; + } else if (index.column() == 1) { + return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled"); + } + + break; + } + + case Qt::BackgroundRole: + { + if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: + { + auto context = context_weak.lock(); + return context && context->breakpoints[event].enabled; + } + + default: + break; + } + return QVariant(); +} + +QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Event"); + } else if (section == 1) { + return tr("Status"); + } + + break; + } + } + + return QVariant(); +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + const auto event = static_cast<Pica::DebugContext::Event>(index.row()); + + switch (role) { + case Role_IsEnabled: + { + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[event].enabled = value.toBool(); + QModelIndex changed_index = createIndex(index.row(), 1); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + + +void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) +{ + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(event), 0), + createIndex(static_cast<int>(event), 1)); +} + +void BreakPointModel::OnResumed() +{ + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), + createIndex(static_cast<int>(active_breakpoint), 1)); + active_breakpoint = context->active_breakpoint; +} + + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent) + : QDockWidget(tr("Pica Breakpoints"), parent), + Pica::DebugContext::BreakPointObserver(debug_context) +{ + setObjectName("PicaBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setModel(breakpoint_model); + + toggle_breakpoint_button = new QPushButton(tr("Enable")); + toggle_breakpoint_button->setEnabled(false); + + qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); + + connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(OnBreakpointSelectionChanged(QModelIndex))); + + connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled())); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_layout->addWidget(toggle_breakpoint_button); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) +{ + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnPicaResume() +{ + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() +{ + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() +{ + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index) +{ + if (!index.isValid()) { + toggle_breakpoint_button->setEnabled(false); + return; + } + + toggle_breakpoint_button->setEnabled(true); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled() +{ + QModelIndex index = breakpoint_list->selectionModel()->currentIndex(); + bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()); + + breakpoint_model->setData(index, new_state, + BreakPointModel::Role_IsEnabled); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index) +{ + if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) { + toggle_breakpoint_button->setText(tr("Disable")); + } else { + toggle_breakpoint_button->setText(tr("Enable")); + } +} diff --git a/src/citra_qt/debugger/graphics_breakpoints.hxx b/src/citra_qt/debugger/graphics_breakpoints.hxx new file mode 100644 index 000000000..edc41283e --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.hxx @@ -0,0 +1,78 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QAbstractListModel> +#include <QDockWidget> + +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr<Pica::DebugContext> context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event); + void OnResumed(); + +private: + bool at_breakpoint; + Pica::DebugContext::Event active_breakpoint; + std::weak_ptr<Pica::DebugContext> context_weak; +}; + +class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + +public: + GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data); + void OnResumeRequested(); + void OnResumed(); + void OnBreakpointSelectionChanged(const QModelIndex&); + void OnToggleBreakpointEnabled(); + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + void UpdateToggleBreakpointButton(const QModelIndex& index); + + QLabel* status_text; + QPushButton* resume_button; + QPushButton* toggle_breakpoint_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 869826e61..84afe59d8 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -20,6 +20,7 @@ #include "debugger/callstack.hxx" #include "debugger/ramview.hxx" #include "debugger/graphics.hxx" +#include "debugger/graphics_breakpoints.hxx" #include "debugger/graphics_cmdlists.hxx" #include "core/settings.h" @@ -69,12 +70,17 @@ GMainWindow::GMainWindow() addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + auto graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); debug_menu->addAction(callstackWidget->toggleViewAction()); debug_menu->addAction(graphicsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); // Set default UI state // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half |