From 6ea003c7b5ec97d0a754197654cdf6e7fccdba24 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 17 Aug 2014 14:06:58 +0200 Subject: Pica: Add debug utility functions for dumping geometry data. --- src/video_core/debug_utils/debug_utils.cpp | 60 ++++++++++++++++++++++++++++++ src/video_core/debug_utils/debug_utils.h | 40 ++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/video_core/debug_utils/debug_utils.cpp create mode 100644 src/video_core/debug_utils/debug_utils.h (limited to 'src/video_core/debug_utils') diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp new file mode 100644 index 000000000..ac895ec3a --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -0,0 +1,60 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include + +#include "video_core/pica.h" + +#include "debug_utils.h" + +namespace Pica { + +namespace DebugUtils { + +void GeometryDumper::AddVertex(std::array pos, TriangleTopology topology) { + vertices.push_back({pos[0], pos[1], pos[2]}); + + int num_vertices = vertices.size(); + + switch (topology) { + case TriangleTopology::List: + case TriangleTopology::ListIndexed: + if (0 == (num_vertices % 3)) + faces.push_back({ num_vertices-3, num_vertices-2, num_vertices-1 }); + break; + + default: + ERROR_LOG(GPU, "Unknown triangle topology %x", (int)topology); + exit(0); + break; + } +} + +void GeometryDumper::Dump() { + // NOTE: Permanently enabling this just trashes hard disks for no reason. + // Hence, this is currently disabled. + return; + + static int index = 0; + std::string filename = std::string("geometry_dump") + std::to_string(++index) + ".obj"; + + std::ofstream file(filename); + + for (const auto& vertex : vertices) { + file << "v " << vertex.pos[0] + << " " << vertex.pos[1] + << " " << vertex.pos[2] << std::endl; + } + + for (const Face& face : faces) { + file << "f " << 1+face.index[0] + << " " << 1+face.index[1] + << " " << 1+face.index[2] << std::endl; + } +} + +} // namespace + +} // namespace diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h new file mode 100644 index 000000000..9b4dce539 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h @@ -0,0 +1,40 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "video_core/pica.h" + +namespace Pica { + +namespace DebugUtils { + +using TriangleTopology = Regs::TriangleTopology; + +// Simple utility class for dumping geometry data to an OBJ file +class GeometryDumper { +public: + void AddVertex(std::array pos, TriangleTopology topology); + + void Dump(); + +private: + struct Vertex { + std::array pos; + }; + + struct Face { + int index[3]; + }; + + std::vector vertices; + std::vector faces; +}; + +} // namespace + +} // namespace -- cgit v1.2.3 From f37e39deb9abe88b4874ebc2889ed52e02ed9c13 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 14 Aug 2014 14:30:38 +0200 Subject: Pica: Add debug utilities for dumping shaders. --- src/video_core/debug_utils/debug_utils.cpp | 205 +++++++++++++++++++++++++++++ src/video_core/debug_utils/debug_utils.h | 3 + src/video_core/pica.h | 2 +- src/video_core/vertex_shader.cpp | 18 +++ 4 files changed, 227 insertions(+), 1 deletion(-) (limited to 'src/video_core/debug_utils') diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index ac895ec3a..f41249eac 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 // Refer to the license.txt file included. +#include #include #include @@ -55,6 +56,210 @@ void GeometryDumper::Dump() { } } +#pragma pack(1) +struct DVLBHeader { + enum : u32 { + MAGIC_WORD = 0x424C5644, // "DVLB" + }; + + u32 magic_word; + u32 num_programs; +// u32 dvle_offset_table[]; +}; +static_assert(sizeof(DVLBHeader) == 0x8, "Incorrect structure size"); + +struct DVLPHeader { + enum : u32 { + MAGIC_WORD = 0x504C5644, // "DVLP" + }; + + u32 magic_word; + u32 version; + u32 binary_offset; // relative to DVLP start + u32 binary_size_words; + u32 swizzle_patterns_offset; + u32 swizzle_patterns_num_entries; + u32 unk2; +}; +static_assert(sizeof(DVLPHeader) == 0x1C, "Incorrect structure size"); + +struct DVLEHeader { + enum : u32 { + MAGIC_WORD = 0x454c5644, // "DVLE" + }; + + enum class ShaderType : u8 { + VERTEX = 0, + GEOMETRY = 1, + }; + + u32 magic_word; + u16 pad1; + ShaderType type; + u8 pad2; + u32 main_offset_words; // offset within binary blob + u32 endmain_offset_words; + u32 pad3; + u32 pad4; + u32 constant_table_offset; + u32 constant_table_size; // number of entries + u32 label_table_offset; + u32 label_table_size; + u32 output_register_table_offset; + u32 output_register_table_size; + u32 uniform_table_offset; + u32 uniform_table_size; + u32 symbol_table_offset; + u32 symbol_table_size; + +}; +static_assert(sizeof(DVLEHeader) == 0x40, "Incorrect structure size"); +#pragma pack() + +void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size, + u32 main_offset, const Regs::VSOutputAttributes* output_attributes) +{ + // NOTE: Permanently enabling this just trashes hard disks for no reason. + // Hence, this is currently disabled. + return; + + struct StuffToWrite { + u8* pointer; + u32 size; + }; + std::vector writing_queue; + u32 write_offset = 0; + + auto QueueForWriting = [&writing_queue,&write_offset](u8* pointer, u32 size) { + writing_queue.push_back({pointer, size}); + u32 old_write_offset = write_offset; + write_offset += size; + return old_write_offset; + }; + + // First off, try to translate Pica state (one enum for output attribute type and component) + // into shbin format (separate type and component mask). + union OutputRegisterInfo { + enum Type : u64 { + POSITION = 0, + COLOR = 2, + TEXCOORD0 = 3, + TEXCOORD1 = 5, + TEXCOORD2 = 6, + }; + + BitField< 0, 64, u64> hex; + + BitField< 0, 16, Type> type; + BitField<16, 16, u64> id; + BitField<32, 4, u64> component_mask; + }; + + // This is put into a try-catch block to make sure we notice unknown configurations. + std::vector output_info_table; + for (int i = 0; i < 7; ++i) { + using OutputAttributes = Pica::Regs::VSOutputAttributes; + + // TODO: It's still unclear how the attribute components map to the register! + // Once we know that, this code probably will not make much sense anymore. + std::map > map = { + { OutputAttributes::POSITION_X, { OutputRegisterInfo::POSITION, 1} }, + { OutputAttributes::POSITION_Y, { OutputRegisterInfo::POSITION, 2} }, + { OutputAttributes::POSITION_Z, { OutputRegisterInfo::POSITION, 4} }, + { OutputAttributes::POSITION_W, { OutputRegisterInfo::POSITION, 8} }, + { OutputAttributes::COLOR_R, { OutputRegisterInfo::COLOR, 1} }, + { OutputAttributes::COLOR_G, { OutputRegisterInfo::COLOR, 2} }, + { OutputAttributes::COLOR_B, { OutputRegisterInfo::COLOR, 4} }, + { OutputAttributes::COLOR_A, { OutputRegisterInfo::COLOR, 8} }, + { OutputAttributes::TEXCOORD0_U, { OutputRegisterInfo::TEXCOORD0, 1} }, + { OutputAttributes::TEXCOORD0_V, { OutputRegisterInfo::TEXCOORD0, 2} }, + { OutputAttributes::TEXCOORD1_U, { OutputRegisterInfo::TEXCOORD1, 1} }, + { OutputAttributes::TEXCOORD1_V, { OutputRegisterInfo::TEXCOORD1, 2} }, + { OutputAttributes::TEXCOORD2_U, { OutputRegisterInfo::TEXCOORD2, 1} }, + { OutputAttributes::TEXCOORD2_V, { OutputRegisterInfo::TEXCOORD2, 2} } + }; + + for (const auto& semantic : std::vector{ + output_attributes[i].map_x, + output_attributes[i].map_y, + output_attributes[i].map_z, + output_attributes[i].map_w }) { + if (semantic == OutputAttributes::INVALID) + continue; + + try { + OutputRegisterInfo::Type type = map.at(semantic).first; + u32 component_mask = map.at(semantic).second; + + auto it = std::find_if(output_info_table.begin(), output_info_table.end(), + [&i, &type](const OutputRegisterInfo& info) { + return info.id == i && info.type == type; + } + ); + + if (it == output_info_table.end()) { + output_info_table.push_back({}); + output_info_table.back().type = type; + output_info_table.back().component_mask = component_mask; + output_info_table.back().id = i; + } else { + it->component_mask = it->component_mask | component_mask; + } + } catch (const std::out_of_range& oor) { + _dbg_assert_msg_(GPU, 0, "Unknown output attribute mapping"); + ERROR_LOG(GPU, "Unknown output attribute mapping: %03x, %03x, %03x, %03x", + (int)output_attributes[i].map_x.Value(), + (int)output_attributes[i].map_y.Value(), + (int)output_attributes[i].map_z.Value(), + (int)output_attributes[i].map_w.Value()); + } + } + } + + + struct { + DVLBHeader header; + u32 dvle_offset; + } dvlb{ {DVLBHeader::MAGIC_WORD, 1 } }; // 1 DVLE + + DVLPHeader dvlp{ DVLPHeader::MAGIC_WORD }; + DVLEHeader dvle{ DVLEHeader::MAGIC_WORD }; + + QueueForWriting((u8*)&dvlb, sizeof(dvlb)); + u32 dvlp_offset = QueueForWriting((u8*)&dvlp, sizeof(dvlp)); + dvlb.dvle_offset = QueueForWriting((u8*)&dvle, sizeof(dvle)); + + // TODO: Reduce the amount of binary code written to relevant portions + dvlp.binary_offset = write_offset - dvlp_offset; + dvlp.binary_size_words = binary_size; + QueueForWriting((u8*)binary_data, binary_size * sizeof(u32)); + + dvlp.swizzle_patterns_offset = write_offset - dvlp_offset; + dvlp.swizzle_patterns_num_entries = swizzle_size; + u32 dummy = 0; + for (int i = 0; i < swizzle_size; ++i) { + QueueForWriting((u8*)&swizzle_data[i], sizeof(swizzle_data[i])); + QueueForWriting((u8*)&dummy, sizeof(dummy)); + } + + dvle.main_offset_words = main_offset; + dvle.output_register_table_offset = write_offset - dvlb.dvle_offset; + dvle.output_register_table_size = output_info_table.size(); + QueueForWriting((u8*)output_info_table.data(), output_info_table.size() * sizeof(OutputRegisterInfo)); + + // TODO: Create a label table for "main" + + + // Write data to file + static int dump_index = 0; + std::string filename = std::string("shader_dump") + std::to_string(++dump_index) + std::string(".shbin"); + std::ofstream file(filename, std::ios_base::out | std::ios_base::binary); + + for (auto& chunk : writing_queue) { + file.write((char*)chunk.pointer, chunk.size); + } +} + } // namespace } // namespace diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 9b4dce539..bd7a0a89b 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -35,6 +35,9 @@ private: std::vector faces; }; +void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size, + u32 main_offset, const Regs::VSOutputAttributes* output_attributes); + } // namespace } // namespace diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 640830144..fe886c16f 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -57,7 +57,7 @@ struct Regs { INSERT_PADDING_WORDS(0x1); - union { + union VSOutputAttributes { // Maps components of output vertex attributes to semantics enum Semantic : u32 { diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index 93830a96a..8df14b51f 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -4,6 +4,7 @@ #include "pica.h" #include "vertex_shader.h" +#include "debug_utils/debug_utils.h" #include #include @@ -50,6 +51,11 @@ struct VertexShaderState { }; u32 call_stack[8]; // TODO: What is the maximal call stack depth? u32* call_stack_pointer; + + struct { + u32 max_offset; // maximum program counter ever reached + u32 max_opdesc_id; // maximum swizzle pattern index ever used + } debug; }; static void ProcessShaderCode(VertexShaderState& state) { @@ -57,6 +63,7 @@ static void ProcessShaderCode(VertexShaderState& state) { bool increment_pc = true; bool exit_loop = false; const Instruction& instr = *(const Instruction*)state.program_counter; + state.debug.max_offset = std::max(state.debug.max_offset, 1 + (state.program_counter - shader_memory)); const float24* src1_ = (instr.common.src1 < 0x10) ? state.input_register_table[instr.common.src1] : (instr.common.src1 < 0x20) ? &state.temporary_registers[instr.common.src1-0x10].x @@ -88,6 +95,7 @@ static void ProcessShaderCode(VertexShaderState& state) { switch (instr.opcode) { case Instruction::OpCode::ADD: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -100,6 +108,7 @@ static void ProcessShaderCode(VertexShaderState& state) { case Instruction::OpCode::MUL: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -113,6 +122,7 @@ static void ProcessShaderCode(VertexShaderState& state) { case Instruction::OpCode::DP3: case Instruction::OpCode::DP4: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); float24 dot = float24::FromFloat32(0.f); int num_components = (instr.opcode == Instruction::OpCode::DP3) ? 3 : 4; for (int i = 0; i < num_components; ++i) @@ -130,6 +140,7 @@ static void ProcessShaderCode(VertexShaderState& state) { // Reciprocal case Instruction::OpCode::RCP: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -145,6 +156,7 @@ static void ProcessShaderCode(VertexShaderState& state) { // Reciprocal Square Root case Instruction::OpCode::RSQ: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -159,6 +171,7 @@ static void ProcessShaderCode(VertexShaderState& state) { case Instruction::OpCode::MOV: { + state.debug.max_opdesc_id = std::max(state.debug.max_opdesc_id, 1+instr.common.operand_desc_id); for (int i = 0; i < 4; ++i) { if (!swizzle.DestComponentEnabled(i)) continue; @@ -212,6 +225,8 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes) const u32* main = &shader_memory[registers.vs_main_offset]; state.program_counter = (u32*)main; + state.debug.max_offset = 0; + state.debug.max_opdesc_id = 0; // Setup input register table const auto& attribute_register_map = registers.vs_input_register_map; @@ -255,6 +270,9 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes) state.call_stack_pointer = &state.call_stack[0]; ProcessShaderCode(state); + DebugUtils::DumpShader(shader_memory, state.debug.max_offset, swizzle_data, + state.debug.max_opdesc_id, registers.vs_main_offset, + registers.vs_output_attributes); DEBUG_LOG(GPU, "Output vertex: pos (%.2f, %.2f, %.2f, %.2f), col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f)", ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(), -- cgit v1.2.3 From 26ade98411c1d76540695f15378ff7f6b5388b1a Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 14 Aug 2014 19:21:55 +0200 Subject: Pica/citra-qt: Replace command list view and command list debugging code with something more sophisticated. --- src/citra_qt/debugger/graphics_cmdlists.cpp | 144 ++++++++++------------------ src/citra_qt/debugger/graphics_cmdlists.hxx | 42 +++----- src/citra_qt/main.cpp | 4 +- src/core/hle/service/gsp.cpp | 5 - src/video_core/command_processor.cpp | 2 + src/video_core/debug_utils/debug_utils.cpp | 55 +++++++++++ src/video_core/debug_utils/debug_utils.h | 21 ++++ src/video_core/gpu_debugger.h | 63 ------------ 8 files changed, 142 insertions(+), 194 deletions(-) (limited to 'src/video_core/debug_utils') diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index e98560a19..71dd166cd 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -2,53 +2,21 @@ // Licensed under GPLv2 // Refer to the license.txt file included. -#include "graphics_cmdlists.hxx" +#include +#include +#include #include -extern GraphicsDebugger g_debugger; - -GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractItemModel(parent) -{ - root_item = new TreeItem(TreeItem::ROOT, 0, NULL, this); - - connect(this, SIGNAL(CommandListCalled()), this, SLOT(OnCommandListCalledInternal()), Qt::UniqueConnection); -} - -QModelIndex GPUCommandListModel::index(int row, int column, const QModelIndex& parent) const -{ - TreeItem* item; - - if (!parent.isValid()) { - item = root_item; - } else { - item = (TreeItem*)parent.internalPointer(); - } - - return createIndex(row, column, item->children[row]); -} +#include "graphics_cmdlists.hxx" -QModelIndex GPUCommandListModel::parent(const QModelIndex& child) const +GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { - if (!child.isValid()) - return QModelIndex(); - - TreeItem* item = (TreeItem*)child.internalPointer(); - - if (item->parent == NULL) - return QModelIndex(); - return createIndex(item->parent->index, 0, item->parent); } int GPUCommandListModel::rowCount(const QModelIndex& parent) const { - TreeItem* item; - if (!parent.isValid()) { - item = root_item; - } else { - item = (TreeItem*)parent.internalPointer(); - } - return item->children.size(); + return pica_trace.writes.size(); } int GPUCommandListModel::columnCount(const QModelIndex& parent) const @@ -61,79 +29,67 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const if (!index.isValid()) return QVariant(); - const TreeItem* item = (const TreeItem*)index.internalPointer(); - - if (item->type == TreeItem::COMMAND_LIST) - { - const GraphicsDebugger::PicaCommandList& cmdlist = command_lists[item->index].second; - u32 address = command_lists[item->index].first; - - if (role == Qt::DisplayRole && index.column() == 0) - { - return QVariant(QString("0x%1 bytes at 0x%2").arg(cmdlist.size(), 0, 16).arg(address, 8, 16, QLatin1Char('0'))); - } - } - else - { - // index refers to a specific command - const GraphicsDebugger::PicaCommandList& cmdlist = command_lists[item->parent->index].second; - const GraphicsDebugger::PicaCommand& cmd = cmdlist[item->index]; - const Pica::CommandProcessor::CommandHeader& header = cmd.GetHeader(); - - if (role == Qt::DisplayRole) { - QString content; - if (index.column() == 0) { - content = QString::fromLatin1(Pica::Regs::GetCommandName(header.cmd_id).c_str()); - content.append(" "); - } else if (index.column() == 1) { - for (int j = 0; j < cmd.size(); ++j) - content.append(QString("%1 ").arg(cmd[j], 8, 16, QLatin1Char('0'))); - } - - return QVariant(content); + const auto& writes = pica_trace.writes; + const Pica::CommandProcessor::CommandHeader cmd{writes[index.row()].Id()}; + const u32 val{writes[index.row()].Value()}; + + if (role == Qt::DisplayRole) { + QString content; + if (index.column() == 0) { + content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str()); + content.append(" "); + } else if (index.column() == 1) { + content.append(QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0'))); + content.append(QString("%1 ").arg(val, 8, 16, QLatin1Char('0'))); } + + return QVariant(content); } return QVariant(); } -void GPUCommandListModel::OnCommandListCalled(const GraphicsDebugger::PicaCommandList& lst, bool is_new) +void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) { - emit CommandListCalled(); + beginResetModel(); + + pica_trace = trace; + + endResetModel(); } -void GPUCommandListModel::OnCommandListCalledInternal() +GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) { - beginResetModel(); + GPUCommandListModel* model = new GPUCommandListModel(this); - command_lists = GetDebugger()->GetCommandLists(); + QWidget* main_widget = new QWidget; - // delete root item and rebuild tree - delete root_item; - root_item = new TreeItem(TreeItem::ROOT, 0, NULL, this); + QTreeView* list_widget = new QTreeView; + list_widget->setModel(model); + list_widget->setFont(QFont("monospace")); + list_widget->setRootIsDecorated(false); - for (int command_list_idx = 0; command_list_idx < command_lists.size(); ++command_list_idx) { - TreeItem* command_list_item = new TreeItem(TreeItem::COMMAND_LIST, command_list_idx, root_item, root_item); - root_item->children.push_back(command_list_item); + QPushButton* toggle_tracing = new QPushButton(tr("Start Tracing")); - const GraphicsDebugger::PicaCommandList& command_list = command_lists[command_list_idx].second; - for (int command_idx = 0; command_idx < command_list.size(); ++command_idx) { - TreeItem* command_item = new TreeItem(TreeItem::COMMAND, command_idx, command_list_item, command_list_item); - command_list_item->children.push_back(command_item); - } - } + connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing())); + connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), + model, SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&))); - endResetModel(); + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addWidget(list_widget); + main_layout->addWidget(toggle_tracing); + main_widget->setLayout(main_layout); + + setWidget(main_widget); } -GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) +void GPUCommandListWidget::OnToggleTracing() { - GPUCommandListModel* model = new GPUCommandListModel(this); - g_debugger.RegisterObserver(model); - - QTreeView* tree_widget = new QTreeView; - tree_widget->setModel(model); - tree_widget->setFont(QFont("monospace")); - setWidget(tree_widget); + if (!Pica::DebugUtils::IsPicaTracing()) { + Pica::DebugUtils::StartPicaTracing(); + } else { + pica_trace = Pica::DebugUtils::FinishPicaTracing(); + emit TracingFinished(*pica_trace); + } } diff --git a/src/citra_qt/debugger/graphics_cmdlists.hxx b/src/citra_qt/debugger/graphics_cmdlists.hxx index b4e6e3c8a..479ef0326 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.hxx +++ b/src/citra_qt/debugger/graphics_cmdlists.hxx @@ -4,53 +4,28 @@ #pragma once -#include +#include #include #include "video_core/gpu_debugger.h" +#include "video_core/debug_utils/debug_utils.h" -// TODO: Rename class, since it's not actually a list model anymore... -class GPUCommandListModel : public QAbstractItemModel, public GraphicsDebugger::DebuggerObserver +class GPUCommandListModel : public QAbstractListModel { Q_OBJECT public: GPUCommandListModel(QObject* parent); - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex& child) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; -public: - void OnCommandListCalled(const GraphicsDebugger::PicaCommandList& lst, bool is_new) override; - public slots: - void OnCommandListCalledInternal(); - -signals: - void CommandListCalled(); + void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace); private: - struct TreeItem : public QObject - { - enum Type { - ROOT, - COMMAND_LIST, - COMMAND - }; - - TreeItem(Type type, int index, TreeItem* item_parent, QObject* parent) : QObject(parent), type(type), index(index), parent(item_parent) {} - - Type type; - int index; - std::vector children; - TreeItem* parent; - }; - - std::vector> command_lists; - TreeItem* root_item; + Pica::DebugUtils::PicaTrace pica_trace; }; class GPUCommandListWidget : public QDockWidget @@ -60,5 +35,12 @@ class GPUCommandListWidget : public QDockWidget public: GPUCommandListWidget(QWidget* parent = 0); +public slots: + void OnToggleTracing(); + +signals: + void TracingFinished(const Pica::DebugUtils::PicaTrace&); + private: + std::unique_ptr pica_trace; }; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 9a16cf92d..a6b87f781 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -52,11 +52,11 @@ GMainWindow::GMainWindow() graphicsWidget = new GPUCommandStreamWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsWidget); - callstackWidget->hide(); + graphicsWidget ->hide(); graphicsCommandsWidget = new GPUCommandListWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); - callstackWidget->hide(); + graphicsCommandsWidget->hide(); QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); diff --git a/src/core/hle/service/gsp.cpp b/src/core/hle/service/gsp.cpp index 027ba5a37..46c5a8ddd 100644 --- a/src/core/hle/service/gsp.cpp +++ b/src/core/hle/service/gsp.cpp @@ -230,11 +230,6 @@ void ExecuteCommand(const Command& command, u32 thread_id) { // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though WriteGPURegister(GPU_REG_INDEX(command_processor_config.trigger), 1); - // TODO: Move this to GPU - // TODO: Not sure what units the size is measured in - g_debugger.CommandListCalled(params.address, - (u32*)Memory::GetPointer(params.address), - params.size); SignalInterrupt(InterruptId::P3D); break; } diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index f7a412bc1..76fdb4e4a 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -33,6 +33,8 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { u32 old_value = registers[id]; registers[id] = (old_value & ~mask) | (value & mask); + DebugUtils::OnPicaRegWrite(id, registers[id]); + switch(id) { // It seems like these trigger vertex rendering case PICA_REG_INDEX(trigger_draw): diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index f41249eac..1bbc0330c 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "video_core/pica.h" @@ -260,6 +261,60 @@ void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data } } +static std::unique_ptr pica_trace; +static std::mutex pica_trace_mutex; +static int is_pica_tracing = false; + +void StartPicaTracing() +{ + if (is_pica_tracing) { + ERROR_LOG(GPU, "StartPicaTracing called even though tracing already running!"); + return; + } + + pica_trace_mutex.lock(); + pica_trace = std::unique_ptr(new PicaTrace); + + is_pica_tracing = true; + pica_trace_mutex.unlock(); +} + +bool IsPicaTracing() +{ + return is_pica_tracing; +} + +void OnPicaRegWrite(u32 id, u32 value) +{ + // Double check for is_pica_tracing to avoid pointless locking overhead + if (!is_pica_tracing) + return; + + std::unique_lock lock(pica_trace_mutex); + + if (!is_pica_tracing) + return; + + pica_trace->writes.push_back({id, value}); +} + +std::unique_ptr FinishPicaTracing() +{ + if (!is_pica_tracing) { + ERROR_LOG(GPU, "FinishPicaTracing called even though tracing already running!"); + return {}; + } + + // signalize that no further tracing should be performed + is_pica_tracing = false; + + // Wait until running tracing is finished + pica_trace_mutex.lock(); + std::unique_ptr ret(std::move(pica_trace)); + pica_trace_mutex.unlock(); + return std::move(ret); +} + } // namespace } // namespace diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index bd7a0a89b..023500066 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include "video_core/pica.h" @@ -38,6 +39,26 @@ private: void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size, u32 main_offset, const Regs::VSOutputAttributes* output_attributes); + +// Utility class to log Pica commands. +struct PicaTrace { + struct Write : public std::pair { + Write(u32 id, u32 value) : std::pair(id, value) {} + + u32& Id() { return first; } + const u32& Id() const { return first; } + + u32& Value() { return second; } + const u32& Value() const { return second; } + }; + std::vector writes; +}; + +void StartPicaTracing(); +bool IsPicaTracing(); +void OnPicaRegWrite(u32 id, u32 value); +std::unique_ptr FinishPicaTracing(); + } // namespace } // namespace diff --git a/src/video_core/gpu_debugger.h b/src/video_core/gpu_debugger.h index 2ba873457..5a81fcfcb 100644 --- a/src/video_core/gpu_debugger.h +++ b/src/video_core/gpu_debugger.h @@ -18,19 +18,6 @@ class GraphicsDebugger { public: - // A few utility structs used to expose data - // A vector of commands represented by their raw byte sequence - struct PicaCommand : public std::vector - { - const Pica::CommandProcessor::CommandHeader& GetHeader() const - { - const u32& val = at(1); - return *(Pica::CommandProcessor::CommandHeader*)&val; - } - }; - - typedef std::vector PicaCommandList; - // Base class for all objects which need to be notified about GPU events class DebuggerObserver { @@ -55,16 +42,6 @@ public: ERROR_LOG(GSP, "Received command: id=%x", (int)cmd.id.Value()); } - /** - * @param lst command list which triggered this call - * @param is_new true if the command list was called for the first time - * @todo figure out how to make sure called functions don't keep references around beyond their life time - */ - virtual void OnCommandListCalled(const PicaCommandList& lst, bool is_new) - { - ERROR_LOG(GSP, "Command list called: %d", (int)is_new); - } - protected: const GraphicsDebugger* GetDebugger() const { @@ -93,49 +70,12 @@ public: } ); } - void CommandListCalled(u32 address, u32* command_list, u32 size_in_words) - { - if (observers.empty()) - return; - - PicaCommandList cmdlist; - for (u32* parse_pointer = command_list; parse_pointer < command_list + size_in_words;) - { - const Pica::CommandProcessor::CommandHeader& header = *(Pica::CommandProcessor::CommandHeader*)(&parse_pointer[1]); - - cmdlist.push_back(PicaCommand()); - auto& cmd = cmdlist.back(); - - size_t size = 2 + header.extra_data_length; - size = (size + 1) / 2 * 2; // align to 8 bytes - cmd.reserve(size); - std::copy(parse_pointer, parse_pointer + size, std::back_inserter(cmd)); - - parse_pointer += size; - } - - auto obj = std::pair(address, cmdlist); - auto it = std::find(command_lists.begin(), command_lists.end(), obj); - bool is_new = (it == command_lists.end()); - if (is_new) - command_lists.push_back(obj); - - ForEachObserver([&](DebuggerObserver* observer) { - observer->OnCommandListCalled(obj.second, is_new); - } ); - } - const GSP_GPU::Command& ReadGXCommandHistory(int index) const { // TODO: Is this thread-safe? return gx_command_history[index]; } - const std::vector>& GetCommandLists() const - { - return command_lists; - } - void RegisterObserver(DebuggerObserver* observer) { // TODO: Check for duplicates @@ -158,7 +98,4 @@ private: std::vector observers; std::vector gx_command_history; - - // vector of pairs of command lists and their storage address - std::vector> command_lists; }; -- cgit v1.2.3 From c4691b784bd7746c5845df00a82ea0909b37ec0f Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sat, 16 Aug 2014 14:06:40 +0200 Subject: Pica: Add support for dumping textures. --- CMakeLists.txt | 5 ++ src/citra/CMakeLists.txt | 2 +- src/citra_qt/CMakeLists.txt | 2 +- src/video_core/debug_utils/debug_utils.cpp | 131 +++++++++++++++++++++++++++++ src/video_core/debug_utils/debug_utils.h | 2 + src/video_core/pica.h | 45 +++++++++- 6 files changed, 184 insertions(+), 3 deletions(-) (limited to 'src/video_core/debug_utils') diff --git a/CMakeLists.txt b/CMakeLists.txt index 56f5d02b0..4c7b3dd73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,11 @@ add_definitions(-Wno-attributes) add_definitions(-DSINGLETHREADED) add_definitions(${CXX_COMPILE_FLAGS}) +find_package(PNG) +if (PNG_FOUND) + add_definitions(-DHAVE_PNG) +endif () + # dependency checking list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMakeTests) diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index b1ab67598..1cbe22cc0 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -12,7 +12,7 @@ add_executable(citra ${SRCS} ${HEADERS}) if (APPLE) target_link_libraries(citra core common video_core iconv pthread ${COREFOUNDATION_LIBRARY} ${OPENGL_LIBRARIES} ${GLEW_LIBRARY} ${GLFW_LIBRARIES}) else() - target_link_libraries(citra core common video_core GLEW pthread X11 Xxf86vm Xi Xcursor ${OPENGL_LIBRARIES} ${GLFW_LIBRARIES} rt ${X11_Xrandr_LIB} ${X11_xv86vmode_LIB}) + target_link_libraries(citra core common video_core GLEW pthread X11 Xxf86vm Xi Xcursor ${OPENGL_LIBRARIES} ${GLFW_LIBRARIES} rt ${X11_Xrandr_LIB} ${X11_xv86vmode_LIB} ${PNG_LIBRARIES}) endif() #install(TARGETS citra RUNTIME DESTINATION ${bindir}) diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index ff1fbc460..8ad6759b7 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -47,7 +47,7 @@ else() set(RT_LIBRARY rt) endif() -target_link_libraries(citra-qt core common video_core qhexedit ${ICONV_LIBRARY} ${COREFOUNDATION_LIBRARY} ${QT_LIBRARIES} ${OPENGL_LIBRARIES} ${RT_LIBRARY} ${GLEW_LIBRARY}) +target_link_libraries(citra-qt core common video_core qhexedit ${ICONV_LIBRARY} ${COREFOUNDATION_LIBRARY} ${QT_LIBRARIES} ${OPENGL_LIBRARIES} ${RT_LIBRARY} ${GLEW_LIBRARY} ${PNG_LIBRARIES}) if(USE_QT5) target_link_libraries(citra-qt Qt5::Gui Qt5::Widgets Qt5::OpenGL) endif() diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 1bbc0330c..753de41e8 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -3,10 +3,17 @@ // Refer to the license.txt file included. #include +#include #include #include #include +#ifdef HAVE_PNG +#include +#endif + +#include "common/file_util.h" + #include "video_core/pica.h" #include "debug_utils.h" @@ -315,6 +322,130 @@ std::unique_ptr FinishPicaTracing() return std::move(ret); } +void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { + // NOTE: Permanently enabling this just trashes hard disks for no reason. + // Hence, this is currently disabled. + return; + +#ifndef HAVE_PNG + return; +#else + if (!data) + return; + + // Write data to file + static int dump_index = 0; + std::string filename = std::string("texture_dump") + std::to_string(++dump_index) + std::string(".png"); + u32 row_stride = texture_config.width * 3; + + u8* buf; + + char title[] = "Citra texture dump"; + char title_key[] = "Title"; + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + + // Open file for writing (binary mode) + File::IOFile fp(filename, "wb"); + + // Initialize write structure + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png_ptr == nullptr) { + ERROR_LOG(GPU, "Could not allocate write struct\n"); + goto finalise; + + } + + // Initialize info structure + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + ERROR_LOG(GPU, "Could not allocate info struct\n"); + goto finalise; + } + + // Setup Exception handling + if (setjmp(png_jmpbuf(png_ptr))) { + ERROR_LOG(GPU, "Error during png creation\n"); + goto finalise; + } + + png_init_io(png_ptr, fp.GetHandle()); + + // Write header (8 bit colour depth) + png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height, + 8, PNG_COLOR_TYPE_RGB /*_ALPHA*/, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_text title_text; + title_text.compression = PNG_TEXT_COMPRESSION_NONE; + title_text.key = title_key; + title_text.text = title; + png_set_text(png_ptr, info_ptr, &title_text, 1); + + png_write_info(png_ptr, info_ptr); + + buf = new u8[row_stride * texture_config.height]; + for (int y = 0; y < texture_config.height; ++y) { + for (int x = 0; x < texture_config.width; ++x) { + // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each + // of which is composed of four 2x2 subtiles each of which is composed of four texels. + // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. + // texels are laid out in a 2x2 subtile like this: + // 2 3 + // 0 1 + // + // The full 8x8 tile has the texels arranged like this: + // + // 42 43 46 47 58 59 62 63 + // 40 41 44 45 56 57 60 61 + // 34 35 38 39 50 51 54 55 + // 32 33 36 37 48 49 52 53 + // 10 11 14 15 26 27 30 31 + // 08 09 12 13 24 25 28 29 + // 02 03 06 07 18 19 22 23 + // 00 01 04 05 16 17 20 21 + int texel_index_within_tile = 0; + for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { + int sub_tile_width = 1 << block_size_index; + int sub_tile_height = 1 << block_size_index; + + int sub_tile_index = (x & sub_tile_width) << block_size_index; + sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); + texel_index_within_tile += sub_tile_index; + } + + const int block_width = 8; + const int block_height = 8; + + int coarse_x = (x / block_width) * block_width; + int coarse_y = (y / block_height) * block_height; + + u8* source_ptr = (u8*)data + coarse_x * block_height * 3 + coarse_y * row_stride + texel_index_within_tile * 3; + buf[3 * x + y * row_stride ] = source_ptr[2]; + buf[3 * x + y * row_stride + 1] = source_ptr[1]; + buf[3 * x + y * row_stride + 2] = source_ptr[0]; + } + } + + // Write image data + for (auto y = 0; y < texture_config.height; ++y) + { + u8* row_ptr = (u8*)buf + y * row_stride; + u8* ptr = row_ptr; + png_write_row(png_ptr, row_ptr); + } + + delete[] buf; + + // End write + png_write_end(png_ptr, nullptr); + +finalise: + if (info_ptr != nullptr) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + if (png_ptr != nullptr) png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); +#endif +} + } // namespace } // namespace diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 023500066..49b19b28a 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -59,6 +59,8 @@ bool IsPicaTracing(); void OnPicaRegWrite(u32 id, u32 value); std::unique_ptr FinishPicaTracing(); +void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); + } // namespace } // namespace diff --git a/src/video_core/pica.h b/src/video_core/pica.h index fe886c16f..f288615b8 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -94,7 +94,46 @@ struct Regs { BitField<16, 16, u32> y; } viewport_corner; - INSERT_PADDING_WORDS(0xa7); + INSERT_PADDING_WORDS(0x18); + + struct TextureConfig { + INSERT_PADDING_WORDS(0x1); + + union { + BitField< 0, 16, u32> height; + BitField<16, 16, u32> width; + }; + + INSERT_PADDING_WORDS(0x2); + + u32 address; + + u32 GetPhysicalAddress() { + return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_GSP_VADDR; + } + + // texture1 and texture2 store the texture format directly after the address + // whereas texture0 inserts some additional flags inbetween. + // Hence, we store the format separately so that all other parameters can be described + // in a single structure. + }; + + enum class TextureFormat : u32 { + RGBA8 = 0, + RGB8 = 1, + RGBA5551 = 2, + RGB565 = 3, + RGBA4 = 4, + + // TODO: Support for the other formats is not implemented, yet. + // Seems like they are luminance formats and compressed textures. + }; + + TextureConfig texture0; + INSERT_PADDING_WORDS(0x8); + BitField<0, 4, TextureFormat> texture0_format; + + INSERT_PADDING_WORDS(0x81); struct { enum ColorFormat : u32 { @@ -403,6 +442,8 @@ struct Regs { ADD_FIELD(viewport_depth_range); ADD_FIELD(viewport_depth_far_plane); ADD_FIELD(viewport_corner); + ADD_FIELD(texture0); + ADD_FIELD(texture0_format); ADD_FIELD(framebuffer); ADD_FIELD(vertex_attributes); ADD_FIELD(index_array); @@ -460,6 +501,8 @@ ASSERT_REG_POSITION(viewport_depth_far_plane, 0x4e); ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); ASSERT_REG_POSITION(viewport_corner, 0x68); +ASSERT_REG_POSITION(texture0, 0x81); +ASSERT_REG_POSITION(texture0_format, 0x8e); ASSERT_REG_POSITION(framebuffer, 0x110); ASSERT_REG_POSITION(vertex_attributes, 0x200); ASSERT_REG_POSITION(index_array, 0x227); -- cgit v1.2.3 From 34fa0b6d9cd9e4027198ce11562da6c03375cd70 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 17 Aug 2014 12:17:32 +0200 Subject: Pica/DebugUtils: Add convenient tev setup printer. --- src/video_core/command_processor.cpp | 2 + src/video_core/debug_utils/debug_utils.cpp | 97 ++++++++++++++++++++++++++++++ src/video_core/debug_utils/debug_utils.h | 2 + 3 files changed, 101 insertions(+) (limited to 'src/video_core/debug_utils') diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 76fdb4e4a..8da030601 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -40,6 +40,8 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX(trigger_draw): case PICA_REG_INDEX(trigger_draw_indexed): { + DebugUtils::DumpTevStageConfig(registers.GetTevStages()); + const auto& attribute_config = registers.vertex_attributes; const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 753de41e8..619c0fbe5 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -446,6 +446,103 @@ finalise: #endif } +void DumpTevStageConfig(const std::array& stages) +{ + using Source = Pica::Regs::TevStageConfig::Source; + using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier; + using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier; + using Operation = Pica::Regs::TevStageConfig::Operation; + + std::string stage_info = "Tev setup:\n"; + for (int index = 0; index < stages.size(); ++index) { + const auto& tev_stage = stages[index]; + + const std::map source_map = { + { Source::PrimaryColor, "PrimaryColor" }, + { Source::Texture0, "Texture0" }, + { Source::Constant, "Constant" }, + { Source::Previous, "Previous" }, + }; + + const std::map color_modifier_map = { + { ColorModifier::SourceColor, { "%source.rgb" } } + }; + const std::map alpha_modifier_map = { + { AlphaModifier::SourceAlpha, "%source.a" } + }; + + std::map combiner_map = { + { Operation::Replace, "%source1" }, + { Operation::Modulate, "(%source1 * %source2) / 255" }, + }; + + auto ReplacePattern = + [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string { + size_t start = input.find(pattern); + if (start == std::string::npos) + return input; + + std::string ret = input; + ret.replace(start, pattern.length(), replacement); + return ret; + }; + auto GetColorSourceStr = + [&source_map,&color_modifier_map,&ReplacePattern](const Source& src, const ColorModifier& modifier) { + auto src_it = source_map.find(src); + std::string src_str = "Unknown"; + if (src_it != source_map.end()) + src_str = src_it->second; + + auto modifier_it = color_modifier_map.find(modifier); + std::string modifier_str = "%source.????"; + if (modifier_it != color_modifier_map.end()) + modifier_str = modifier_it->second; + + return ReplacePattern(modifier_str, "%source", src_str); + }; + auto GetColorCombinerStr = + [&](const Regs::TevStageConfig& tev_stage) { + auto op_it = combiner_map.find(tev_stage.color_op); + std::string op_str = "Unknown op (%source1, %source2, %source3)"; + if (op_it != combiner_map.end()) + op_str = op_it->second; + + op_str = ReplacePattern(op_str, "%source1", GetColorSourceStr(tev_stage.color_source1, tev_stage.color_modifier1)); + op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2)); + return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3)); + }; + auto GetAlphaSourceStr = + [&source_map,&alpha_modifier_map,&ReplacePattern](const Source& src, const AlphaModifier& modifier) { + auto src_it = source_map.find(src); + std::string src_str = "Unknown"; + if (src_it != source_map.end()) + src_str = src_it->second; + + auto modifier_it = alpha_modifier_map.find(modifier); + std::string modifier_str = "%source.????"; + if (modifier_it != alpha_modifier_map.end()) + modifier_str = modifier_it->second; + + return ReplacePattern(modifier_str, "%source", src_str); + }; + auto GetAlphaCombinerStr = + [&](const Regs::TevStageConfig& tev_stage) { + auto op_it = combiner_map.find(tev_stage.alpha_op); + std::string op_str = "Unknown op (%source1, %source2, %source3)"; + if (op_it != combiner_map.end()) + op_str = op_it->second; + + op_str = ReplacePattern(op_str, "%source1", GetAlphaSourceStr(tev_stage.alpha_source1, tev_stage.alpha_modifier1)); + op_str = ReplacePattern(op_str, "%source2", GetAlphaSourceStr(tev_stage.alpha_source2, tev_stage.alpha_modifier2)); + return ReplacePattern(op_str, "%source3", GetAlphaSourceStr(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); + }; + + stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n"; + } + + DEBUG_LOG(GPU, "%s", stage_info.c_str()); +} + } // namespace } // namespace diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 49b19b28a..53c33c96e 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -61,6 +61,8 @@ std::unique_ptr FinishPicaTracing(); void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); +void DumpTevStageConfig(const std::array& stages); + } // namespace } // namespace -- cgit v1.2.3 From 9679d231df0bc8fac9e0e596ab78750bb38ef248 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 17 Aug 2014 12:31:19 +0200 Subject: Pica/Rasterizer: Add texturing support. --- src/video_core/debug_utils/debug_utils.cpp | 18 +-------- src/video_core/pica.h | 5 ++- src/video_core/rasterizer.cpp | 64 ++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 18 deletions(-) (limited to 'src/video_core/debug_utils') diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 619c0fbe5..f7d9455be 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -387,23 +387,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { buf = new u8[row_stride * texture_config.height]; for (int y = 0; y < texture_config.height; ++y) { for (int x = 0; x < texture_config.width; ++x) { - // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each - // of which is composed of four 2x2 subtiles each of which is composed of four texels. - // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. - // texels are laid out in a 2x2 subtile like this: - // 2 3 - // 0 1 - // - // The full 8x8 tile has the texels arranged like this: - // - // 42 43 46 47 58 59 62 63 - // 40 41 44 45 56 57 60 61 - // 34 35 38 39 50 51 54 55 - // 32 33 36 37 48 49 52 53 - // 10 11 14 15 26 27 30 31 - // 08 09 12 13 24 25 28 29 - // 02 03 06 07 18 19 22 23 - // 00 01 04 05 16 17 20 21 + // Cf. rasterizer code for an explanation of this algorithm. int texel_index_within_tile = 0; for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { int sub_tile_width = 1 << block_size_index; diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 7bd4388b5..cfdc9b934 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -95,7 +95,7 @@ struct Regs { BitField<16, 16, u32> y; } viewport_corner; - INSERT_PADDING_WORDS(0x18); + INSERT_PADDING_WORDS(0x17); struct TextureConfig { INSERT_PADDING_WORDS(0x1); @@ -130,6 +130,7 @@ struct Regs { // Seems like they are luminance formats and compressed textures. }; + BitField<0, 1, u32> texturing_enable; TextureConfig texture0; INSERT_PADDING_WORDS(0x8); BitField<0, 4, TextureFormat> texture0_format; @@ -533,6 +534,7 @@ struct Regs { ADD_FIELD(viewport_depth_range); ADD_FIELD(viewport_depth_far_plane); ADD_FIELD(viewport_corner); + ADD_FIELD(texturing_enable); ADD_FIELD(texture0); ADD_FIELD(texture0_format); ADD_FIELD(tev_stage0); @@ -598,6 +600,7 @@ ASSERT_REG_POSITION(viewport_depth_far_plane, 0x4e); ASSERT_REG_POSITION(vs_output_attributes[0], 0x50); ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); ASSERT_REG_POSITION(viewport_corner, 0x68); +ASSERT_REG_POSITION(texturing_enable, 0x80); ASSERT_REG_POSITION(texture0, 0x81); ASSERT_REG_POSITION(texture0_format, 0x8e); ASSERT_REG_POSITION(tev_stage0, 0xc0); diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index 5a4155c84..1bd32e8d0 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -11,6 +11,8 @@ #include "rasterizer.h" #include "vertex_shader.h" +#include "debug_utils/debug_utils.h" + namespace Pica { namespace Rasterizer { @@ -165,6 +167,62 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, (u8)(GetInterpolatedAttribute(v0.color.a(), v1.color.a(), v2.color.a()).ToFloat32() * 255) }; + Math::Vec4 texture_color{}; + float24 u = GetInterpolatedAttribute(v0.tc0.u(), v1.tc0.u(), v2.tc0.u()); + float24 v = GetInterpolatedAttribute(v0.tc0.v(), v1.tc0.v(), v2.tc0.v()); + if (registers.texturing_enable) { + // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each + // of which is composed of four 2x2 subtiles each of which is composed of four texels. + // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. + // texels are laid out in a 2x2 subtile like this: + // 2 3 + // 0 1 + // + // The full 8x8 tile has the texels arranged like this: + // + // 42 43 46 47 58 59 62 63 + // 40 41 44 45 56 57 60 61 + // 34 35 38 39 50 51 54 55 + // 32 33 36 37 48 49 52 53 + // 10 11 14 15 26 27 30 31 + // 08 09 12 13 24 25 28 29 + // 02 03 06 07 18 19 22 23 + // 00 01 04 05 16 17 20 21 + + // TODO: This is currently hardcoded for RGB8 + u32* texture_data = (u32*)Memory::GetPointer(registers.texture0.GetPhysicalAddress()); + + // TODO(neobrain): Not sure if this swizzling pattern is used for all textures. + // To be flexible in case different but similar patterns are used, we keep this + // somewhat inefficient code around for now. + int s = (int)(u * float24::FromFloat32(registers.texture0.width)).ToFloat32(); + int t = (int)(v * float24::FromFloat32(registers.texture0.height)).ToFloat32(); + int texel_index_within_tile = 0; + for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { + int sub_tile_width = 1 << block_size_index; + int sub_tile_height = 1 << block_size_index; + + int sub_tile_index = (s & sub_tile_width) << block_size_index; + sub_tile_index += 2 * ((t & sub_tile_height) << block_size_index); + texel_index_within_tile += sub_tile_index; + } + + const int block_width = 8; + const int block_height = 8; + + int coarse_s = (s / block_width) * block_width; + int coarse_t = (t / block_height) * block_height; + + const int row_stride = registers.texture0.width * 3; + u8* source_ptr = (u8*)texture_data + coarse_s * block_height * 3 + coarse_t * row_stride + texel_index_within_tile * 3; + texture_color.r() = source_ptr[2]; + texture_color.g() = source_ptr[1]; + texture_color.b() = source_ptr[0]; + texture_color.a() = 0xFF; + + DebugUtils::DumpTexture(registers.texture0, (u8*)texture_data); + } + // Texture environment - consists of 6 stages of color and alpha combining. // // Color combiners take three input color values from some source (e.g. interpolated @@ -184,6 +242,9 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, case Source::PrimaryColor: return primary_color.rgb(); + case Source::Texture0: + return texture_color.rgb(); + case Source::Constant: return {tev_stage.const_r, tev_stage.const_g, tev_stage.const_b}; @@ -201,6 +262,9 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0, case Source::PrimaryColor: return primary_color.a(); + case Source::Texture0: + return texture_color.a(); + case Source::Constant: return tev_stage.const_a; -- cgit v1.2.3 From 2f1c129f6407fe2d5c8c3e57c6717d5668570de5 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Sun, 17 Aug 2014 17:44:55 +0200 Subject: Pica: Consolidate the primitive assembly code in PrimitiveAssembly and GeometryDumper. --- src/video_core/command_processor.cpp | 20 ++++++++++++---- src/video_core/debug_utils/debug_utils.cpp | 22 +++++------------ src/video_core/debug_utils/debug_utils.h | 12 ++++------ src/video_core/primitive_assembly.cpp | 28 +++++++++++++--------- src/video_core/primitive_assembly.h | 38 +++++++++++++++++++++++------- 5 files changed, 74 insertions(+), 46 deletions(-) (limited to 'src/video_core/debug_utils') diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 8da030601..9567a9849 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 // Refer to the license.txt file included. +#include "clipper.h" #include "command_processor.h" #include "math.h" #include "pica.h" @@ -79,6 +80,8 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { bool index_u16 = (bool)index_info.format; DebugUtils::GeometryDumper geometry_dumper; + PrimitiveAssembler clipper_primitive_assembler(registers.triangle_topology.Value()); + PrimitiveAssembler dumping_primitive_assembler(registers.triangle_topology.Value()); for (int index = 0; index < registers.num_vertices; ++index) { @@ -108,16 +111,25 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { } } - // NOTE: For now, we simply assume that the first input attribute corresponds to the position. - geometry_dumper.AddVertex({input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32()}, registers.triangle_topology); - + // NOTE: When dumping geometry, we simply assume that the first input attribute + // corresponds to the position for now. + DebugUtils::GeometryDumper::Vertex dumped_vertex = { + input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() + }; + using namespace std::placeholders; + dumping_primitive_assembler.SubmitVertex(dumped_vertex, + std::bind(&DebugUtils::GeometryDumper::AddTriangle, + &geometry_dumper, _1, _2, _3)); + + // Send to vertex shader VertexShader::OutputVertex output = VertexShader::RunShader(input, attribute_config.GetNumTotalAttributes()); if (is_indexed) { // TODO: Add processed vertex to vertex cache! } - PrimitiveAssembly::SubmitVertex(output); + // Send to triangle clipper + clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); } geometry_dumper.Dump(); break; diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index f7d9455be..48e6dd182 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -22,27 +22,17 @@ namespace Pica { namespace DebugUtils { -void GeometryDumper::AddVertex(std::array pos, TriangleTopology topology) { - vertices.push_back({pos[0], pos[1], pos[2]}); +void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { + vertices.push_back(v0); + vertices.push_back(v1); + vertices.push_back(v2); int num_vertices = vertices.size(); - - switch (topology) { - case TriangleTopology::List: - case TriangleTopology::ListIndexed: - if (0 == (num_vertices % 3)) - faces.push_back({ num_vertices-3, num_vertices-2, num_vertices-1 }); - break; - - default: - ERROR_LOG(GPU, "Unknown triangle topology %x", (int)topology); - exit(0); - break; - } + faces.push_back({ num_vertices-3, num_vertices-2, num_vertices-1 }); } void GeometryDumper::Dump() { - // NOTE: Permanently enabling this just trashes hard disks for no reason. + // NOTE: Permanently enabling this just trashes the hard disk for no reason. // Hence, this is currently disabled. return; diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 53c33c96e..8b1499bf2 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -14,20 +14,18 @@ namespace Pica { namespace DebugUtils { -using TriangleTopology = Regs::TriangleTopology; - // Simple utility class for dumping geometry data to an OBJ file class GeometryDumper { public: - void AddVertex(std::array pos, TriangleTopology topology); - - void Dump(); - -private: struct Vertex { std::array pos; }; + void AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2); + + void Dump(); + +private: struct Face { int index[3]; }; diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index 2354ffb99..dabf2d1a3 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -2,21 +2,23 @@ // Licensed under GPLv2 // Refer to the license.txt file included. -#include "clipper.h" #include "pica.h" #include "primitive_assembly.h" #include "vertex_shader.h" -namespace Pica { +#include "video_core/debug_utils/debug_utils.h" -namespace PrimitiveAssembly { +namespace Pica { -static OutputVertex buffer[2]; -static int buffer_index = 0; // TODO: reset this on emulation restart +template +PrimitiveAssembler::PrimitiveAssembler(Regs::TriangleTopology topology) + : topology(topology), buffer_index(0) { +} -void SubmitVertex(OutputVertex& vtx) +template +void PrimitiveAssembler::SubmitVertex(VertexType& vtx, TriangleHandler triangle_handler) { - switch (registers.triangle_topology) { + switch (topology) { case Regs::TriangleTopology::List: case Regs::TriangleTopology::ListIndexed: if (buffer_index < 2) { @@ -24,7 +26,7 @@ void SubmitVertex(OutputVertex& vtx) } else { buffer_index = 0; - Clipper::ProcessTriangle(buffer[0], buffer[1], vtx); + triangle_handler(buffer[0], buffer[1], vtx); } break; @@ -32,7 +34,7 @@ void SubmitVertex(OutputVertex& vtx) if (buffer_index == 2) { buffer_index = 0; - Clipper::ProcessTriangle(buffer[0], buffer[1], vtx); + triangle_handler(buffer[0], buffer[1], vtx); buffer[1] = vtx; } else { @@ -41,11 +43,15 @@ void SubmitVertex(OutputVertex& vtx) break; default: - ERROR_LOG(GPU, "Unknown triangle mode %x:", (int)registers.triangle_topology.Value()); + ERROR_LOG(GPU, "Unknown triangle topology %x:", (int)topology); break; } } -} // namespace +// explicitly instantiate use cases +template +struct PrimitiveAssembler; +template +struct PrimitiveAssembler; } // namespace diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index 2a2b0c170..ea2e2f61e 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -4,18 +4,40 @@ #pragma once -namespace Pica { +#include -namespace VertexShader { - struct OutputVertex; -} +#include "video_core/pica.h" -namespace PrimitiveAssembly { +#include "video_core/vertex_shader.h" -using VertexShader::OutputVertex; +namespace Pica { -void SubmitVertex(OutputVertex& vtx); +/* + * Utility class to build triangles from a series of vertices, + * according to a given triangle topology. + */ +template +struct PrimitiveAssembler { + using TriangleHandler = std::function; + + PrimitiveAssembler(Regs::TriangleTopology topology); + + /* + * Queues a vertex, builds primitives from the vertex queue according to the given + * triangle topology, and calls triangle_handler for each generated primitive. + * NOTE: We could specify the triangle handler in the constructor, but this way we can + * keep event and handler code next to each other. + */ + void SubmitVertex(VertexType& vtx, TriangleHandler triangle_handler); + +private: + Regs::TriangleTopology topology; + + int buffer_index; + VertexType buffer[2]; +}; -} // namespace } // namespace -- cgit v1.2.3