Add GUI widget for controlling pica breakpoints.
This commit is contained in:
		| @@ -8,6 +8,7 @@ set(SRCS | |||||||
|             debugger/callstack.cpp |             debugger/callstack.cpp | ||||||
|             debugger/disassembler.cpp |             debugger/disassembler.cpp | ||||||
|             debugger/graphics.cpp |             debugger/graphics.cpp | ||||||
|  |             debugger/graphics_breakpoints.cpp | ||||||
|             debugger/graphics_cmdlists.cpp |             debugger/graphics_cmdlists.cpp | ||||||
|             debugger/ramview.cpp |             debugger/ramview.cpp | ||||||
|             debugger/registers.cpp |             debugger/registers.cpp | ||||||
| @@ -24,6 +25,7 @@ set(HEADERS | |||||||
|             debugger/callstack.hxx |             debugger/callstack.hxx | ||||||
|             debugger/disassembler.hxx |             debugger/disassembler.hxx | ||||||
|             debugger/graphics.hxx |             debugger/graphics.hxx | ||||||
|  |             debugger/graphics_breakpoints.hxx | ||||||
|             debugger/graphics_cmdlists.hxx |             debugger/graphics_cmdlists.hxx | ||||||
|             debugger/ramview.hxx |             debugger/ramview.hxx | ||||||
|             debugger/registers.hxx |             debugger/registers.hxx | ||||||
|   | |||||||
							
								
								
									
										253
									
								
								src/citra_qt/debugger/graphics_breakpoints.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								src/citra_qt/debugger/graphics_breakpoints.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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")); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								src/citra_qt/debugger/graphics_breakpoints.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/citra_qt/debugger/graphics_breakpoints.hxx
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||||
|  | }; | ||||||
| @@ -20,6 +20,7 @@ | |||||||
| #include "debugger/callstack.hxx" | #include "debugger/callstack.hxx" | ||||||
| #include "debugger/ramview.hxx" | #include "debugger/ramview.hxx" | ||||||
| #include "debugger/graphics.hxx" | #include "debugger/graphics.hxx" | ||||||
|  | #include "debugger/graphics_breakpoints.hxx" | ||||||
| #include "debugger/graphics_cmdlists.hxx" | #include "debugger/graphics_cmdlists.hxx" | ||||||
|  |  | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| @@ -69,12 +70,17 @@ GMainWindow::GMainWindow() | |||||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); |     addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); | ||||||
|     graphicsCommandsWidget->hide(); |     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")); |     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); | ||||||
|     debug_menu->addAction(disasmWidget->toggleViewAction()); |     debug_menu->addAction(disasmWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(registersWidget->toggleViewAction()); |     debug_menu->addAction(registersWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(callstackWidget->toggleViewAction()); |     debug_menu->addAction(callstackWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsWidget->toggleViewAction()); |     debug_menu->addAction(graphicsWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); |     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); | ||||||
|  |     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||||||
|  |  | ||||||
|     // Set default UI state |     // Set default UI state | ||||||
|     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half |     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user