4 changed files with 357 additions and 0 deletions
-
2src/citra_qt/CMakeLists.txt
-
298src/citra_qt/debugger/graphics_vertex_shader.cpp
-
51src/citra_qt/debugger/graphics_vertex_shader.h
-
6src/citra_qt/main.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); |
||||
|
} |
||||
@ -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: |
||||
|
|
||||
|
}; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue