diff options
Diffstat (limited to '')
-rw-r--r-- | src/citra_qt/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_breakpoint_observer.cpp | 32 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_breakpoint_observer.h | 33 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.cpp | 27 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.h | 24 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.cpp | 298 | ||||
-rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.h | 51 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 6 |
8 files changed, 425 insertions, 50 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index bbc521f8a..586bc84b0 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -8,9 +8,11 @@ set(SRCS debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics.cpp + debugger/graphics_breakpoint_observer.cpp debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp debugger/graphics_framebuffer.cpp + debugger/graphics_vertex_shader.cpp debugger/ramview.cpp debugger/registers.cpp util/spinbox.cpp @@ -27,10 +29,12 @@ set(HEADERS debugger/callstack.h debugger/disassembler.h debugger/graphics.h + debugger/graphics_breakpoint_observer.h debugger/graphics_breakpoints.h debugger/graphics_breakpoints_p.h debugger/graphics_cmdlists.h debugger/graphics_framebuffer.h + debugger/graphics_vertex_shader.h debugger/ramview.h debugger/registers.h util/spinbox.h diff --git a/src/citra_qt/debugger/graphics_breakpoint_observer.cpp b/src/citra_qt/debugger/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..10ac1ebad --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoint_observer.cpp @@ -0,0 +1,32 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMetaType> + +#include "graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) +{ + qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnPicaResume() +{ + emit Resumed(); +} diff --git a/src/citra_qt/debugger/graphics_breakpoint_observer.h b/src/citra_qt/debugger/graphics_breakpoint_observer.h new file mode 100644 index 000000000..f0d3361f8 --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoint_observer.h @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> + +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. + * This is because the Pica breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, private Pica::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +private slots: + virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); +}; diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index 43c59738f..1ba60021f 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -6,7 +6,6 @@ #include <QComboBox> #include <QDebug> #include <QLabel> -#include <QMetaType> #include <QPushButton> #include <QSpinBox> @@ -17,32 +16,6 @@ #include "util/spinbox.h" -BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, - const QString& title, QWidget* parent) - : QDockWidget(title, parent), BreakPointObserver(debug_context) -{ - qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); - - connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); - - // NOTE: This signal is emitted from a non-GUI thread, but connect() takes - // care of delaying its handling to the GUI thread. - connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), - this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), - Qt::BlockingQueuedConnection); -} - -void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) -{ - emit BreakPointHit(event, data); -} - -void BreakPointObserverDock::OnPicaResume() -{ - emit Resumed(); -} - - GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent) : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h index 56215761e..c6e293bc9 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.h +++ b/src/citra_qt/debugger/graphics_framebuffer.h @@ -6,7 +6,7 @@ #include <QDockWidget> -#include "video_core/debug_utils/debug_utils.h" +#include "graphics_breakpoint_observer.h" class QComboBox; class QLabel; @@ -14,28 +14,6 @@ class QSpinBox; class CSpinBox; -// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. -// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while -// the widget usually wants to perform reactions in the GUI thread. -class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { - Q_OBJECT - -public: - BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, - QWidget* parent = nullptr); - - void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; - void OnPicaResume() override; - -private slots: - virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; - virtual void OnResumed() = 0; - -signals: - void Resumed(); - void BreakPointHit(Pica::DebugContext::Event event, void* data); -}; - class GraphicsFramebufferWidget : public BreakPointObserverDock { Q_OBJECT diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp new file mode 100644 index 000000000..06eaf0bf0 --- /dev/null +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp @@ -0,0 +1,298 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <iomanip> +#include <sstream> + +#include <QBoxLayout> +#include <QTreeView> + +#include "video_core/vertex_shader.h" + +#include "graphics_vertex_shader.h" + +using nihstro::Instruction; +using nihstro::SourceRegister; +using nihstro::SwizzlePattern; + +GraphicsVertexShaderModel::GraphicsVertexShaderModel(QObject* parent): QAbstractItemModel(parent) { + +} + +QModelIndex GraphicsVertexShaderModel::index(int row, int column, const QModelIndex& parent) const { + return createIndex(row, column); +} + +QModelIndex GraphicsVertexShaderModel::parent(const QModelIndex& child) const { + return QModelIndex(); +} + +int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const { + return 3; +} + +int GraphicsVertexShaderModel::rowCount(const QModelIndex& parent) const { + return info.code.size(); +} + +QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orientation, int role) const { + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Offset"); + } else if (section == 1) { + return tr("Raw"); + } else if (section == 2) { + return tr("Disassembly"); + } + + break; + } + } + + return QVariant(); +} + +QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const { + switch (role) { + case Qt::DisplayRole: + { + switch (index.column()) { + case 0: + if (info.HasLabel(index.row())) + return QString::fromStdString(info.GetLabel(index.row())); + + return QString("%1").arg(4*index.row(), 4, 16, QLatin1Char('0')); + + case 1: + return QString("%1").arg(info.code[index.row()].hex, 8, 16, QLatin1Char('0')); + + case 2: + { + std::stringstream output; + output.flags(std::ios::hex); + + Instruction instr = info.code[index.row()]; + const SwizzlePattern& swizzle = info.swizzle_info[instr.common.operand_desc_id].pattern; + + // longest known instruction name: "setemit " + output << std::setw(8) << std::left << instr.opcode.GetInfo().name; + + // e.g. "-c92.xyzw" + static auto print_input = [](std::stringstream& output, const SourceRegister& input, + bool negate, const std::string& swizzle_mask) { + output << std::setw(4) << std::right << (negate ? "-" : "") + input.GetName(); + output << "." << swizzle_mask; + }; + + // e.g. "-c92[a0.x].xyzw" + static auto print_input_indexed = [](std::stringstream& output, const SourceRegister& input, + bool negate, const std::string& swizzle_mask, + const std::string& address_register_name) { + std::string relative_address; + if (!address_register_name.empty()) + relative_address = "[" + address_register_name + "]"; + + output << std::setw(10) << std::right << (negate ? "-" : "") + input.GetName() + relative_address; + output << "." << swizzle_mask; + }; + + // Use print_input or print_input_indexed depending on whether relative addressing is used or not. + static auto print_input_indexed_compact = [](std::stringstream& output, const SourceRegister& input, + bool negate, const std::string& swizzle_mask, + const std::string& address_register_name) { + if (address_register_name.empty()) + print_input(output, input, negate, swizzle_mask); + else + print_input_indexed(output, input, negate, swizzle_mask, address_register_name); + }; + + switch (instr.opcode.GetInfo().type) { + case Instruction::OpCodeType::Trivial: + // Nothing to do here + break; + + case Instruction::OpCodeType::Arithmetic: + { + // Use custom code for special instructions + switch (instr.opcode.EffectiveOpCode()) { + case Instruction::OpCode::CMP: + { + // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. + output << std::setw(4) << std::right << "cc."; + output << "xy "; + + SourceRegister src1 = instr.common.GetSrc1(false); + SourceRegister src2 = instr.common.GetSrc2(false); + + print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), instr.common.AddressRegisterName()); + output << " " << instr.common.compare_op.ToString(instr.common.compare_op.x) << " "; + print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(false).substr(0,1)); + + output << ", "; + + print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), instr.common.AddressRegisterName()); + output << " " << instr.common.compare_op.ToString(instr.common.compare_op.y) << " "; + print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(false).substr(1,1)); + + break; + } + + default: + { + bool src_is_inverted = 0 != (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::SrcInversed); + + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Dest) { + // e.g. "r12.xy__" + output << std::setw(4) << std::right << instr.common.dest.GetName() + "."; + output << swizzle.DestMaskToString(); + } else if (instr.opcode.GetInfo().subtype == Instruction::OpCodeInfo::MOVA) { + output << std::setw(4) << std::right << "a0."; + output << swizzle.DestMaskToString(); + } else { + output << " "; + } + output << " "; + + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src1) { + SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); + print_input_indexed(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), instr.common.AddressRegisterName()); + } else { + output << " "; + } + + // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::Src2) { + SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); + print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(false)); + } + break; + } + } + + break; + } + + case Instruction::OpCodeType::Conditional: + { + switch (instr.opcode.EffectiveOpCode()) { + case Instruction::OpCode::LOOP: + output << "(unknown instruction format)"; + break; + + default: + output << "if "; + + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasCondition) { + const char* ops[] = { + " || ", " && ", "", "" + }; + if (instr.flow_control.op != instr.flow_control.JustY) + output << ((!instr.flow_control.refx) ? "!" : " ") << "cc.x"; + + output << ops[instr.flow_control.op]; + + if (instr.flow_control.op != instr.flow_control.JustX) + output << ((!instr.flow_control.refy) ? "!" : " ") << "cc.y"; + + output << " "; + } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasUniformIndex) { + output << "b" << instr.flow_control.bool_uniform_id << " "; + } + + u32 target_addr = instr.flow_control.dest_offset; + u32 target_addr_else = instr.flow_control.dest_offset; + + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasAlternative) { + output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; + } else if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasExplicitDest) { + output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; + } else { + // TODO: Handle other cases + } + + if (instr.opcode.GetInfo().subtype & Instruction::OpCodeInfo::HasFinishPoint) { + output << "(return on " << std::setw(4) << std::right << std::setfill('0') + << 4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions << ")"; + } + + break; + } + break; + } + + default: + output << "(unknown instruction format)"; + break; + } + + return QString::fromLatin1(output.str().c_str()); + } + + default: + break; + } + } + + case Qt::FontRole: + return QFont("monospace"); + + default: + break; + } + + return QVariant(); +} + +void GraphicsVertexShaderModel::OnUpdate() +{ + beginResetModel(); + + info.Clear(); + + for (auto instr : Pica::VertexShader::GetShaderBinary()) + info.code.push_back({instr}); + + for (auto pattern : Pica::VertexShader::GetSwizzlePatterns()) + info.swizzle_info.push_back({pattern}); + + info.labels.insert({Pica::registers.vs_main_offset, "main"}); + + endResetModel(); +} + + +GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::DebugContext > debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) { + setObjectName("PicaVertexShader"); + + auto binary_model = new GraphicsVertexShaderModel(this); + auto binary_list = new QTreeView; + binary_list->setModel(binary_model); + binary_list->setRootIsDecorated(false); + binary_list->setAlternatingRowColors(true); + + connect(this, SIGNAL(Update()), binary_model, SLOT(OnUpdate())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(binary_list); + main_layout->addLayout(sub_layout); + } + main_widget->setLayout(main_layout); + setWidget(main_widget); +} + +void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsVertexShaderWidget::OnResumed() { + widget()->setEnabled(false); +} diff --git a/src/citra_qt/debugger/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics_vertex_shader.h new file mode 100644 index 000000000..38339dc05 --- /dev/null +++ b/src/citra_qt/debugger/graphics_vertex_shader.h @@ -0,0 +1,51 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QAbstractListModel> + +#include "graphics_breakpoint_observer.h" + +#include "nihstro/parser_shbin.h" + +class GraphicsVertexShaderModel : public QAbstractItemModel { + Q_OBJECT + +public: + GraphicsVertexShaderModel(QObject* parent); + + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + 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; + +public slots: + void OnUpdate(); + +private: + nihstro::ShaderInfo info; +}; + +class GraphicsVertexShaderWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + +public: + GraphicsVertexShaderWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); + +private slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnResumed() override; + +signals: + void Update(); + +private: + +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 653ffec75..881c7d337 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -34,6 +34,7 @@ #include "debugger/graphics_breakpoints.h" #include "debugger/graphics_cmdlists.h" #include "debugger/graphics_framebuffer.h" +#include "debugger/graphics_vertex_shader.h" #include "core/settings.h" #include "core/system.h" @@ -84,6 +85,10 @@ GMainWindow::GMainWindow() addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); graphicsFramebufferWidget->hide(); + auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); + graphicsVertexShaderWidget->hide(); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); @@ -92,6 +97,7 @@ GMainWindow::GMainWindow() debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); + debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); // Set default UI state // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half |