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