From 12a5cd1d65487124b7878fbffe43d4ad3755263e Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Wed, 10 Dec 2014 19:24:56 +0100 Subject: citra-qt: Add a vertex shader debugger. --- src/citra_qt/CMakeLists.txt | 2 + src/citra_qt/debugger/graphics_vertex_shader.cpp | 298 +++++++++++++++++++++++ src/citra_qt/debugger/graphics_vertex_shader.h | 51 ++++ src/citra_qt/main.cpp | 6 + 4 files changed, 357 insertions(+) create mode 100644 src/citra_qt/debugger/graphics_vertex_shader.cpp create mode 100644 src/citra_qt/debugger/graphics_vertex_shader.h (limited to 'src/citra_qt') diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index dbeb7c4c0..586bc84b0 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -12,6 +12,7 @@ set(SRCS 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 @@ -33,6 +34,7 @@ set(HEADERS 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_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 +#include + +#include +#include + +#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 + +#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 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 -- cgit v1.2.3