diff options
| author | 2018-01-11 19:21:20 -0700 | |
|---|---|---|
| committer | 2018-01-12 19:11:03 -0700 | |
| commit | ebf9a784a9f7f4148a669dbb39e7cd50df779a14 (patch) | |
| tree | d585685a1c0a34b903af1d086d62560bf56bb29f /src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp | |
| parent | config: Default CPU core to Unicorn. (diff) | |
| download | yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.gz yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.xz yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.zip | |
Massive removal of unused modules
Diffstat (limited to 'src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp')
| -rw-r--r-- | src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp | 622 |
1 files changed, 0 insertions, 622 deletions
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp deleted file mode 100644 index 7f4ec0c52..000000000 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ /dev/null | |||
| @@ -1,622 +0,0 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <iomanip> | ||
| 6 | #include <sstream> | ||
| 7 | #include <QBoxLayout> | ||
| 8 | #include <QFileDialog> | ||
| 9 | #include <QFormLayout> | ||
| 10 | #include <QGroupBox> | ||
| 11 | #include <QLabel> | ||
| 12 | #include <QLineEdit> | ||
| 13 | #include <QPushButton> | ||
| 14 | #include <QSignalMapper> | ||
| 15 | #include <QSpinBox> | ||
| 16 | #include <QTreeView> | ||
| 17 | #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" | ||
| 18 | #include "citra_qt/util/util.h" | ||
| 19 | #include "video_core/pica_state.h" | ||
| 20 | #include "video_core/shader/debug_data.h" | ||
| 21 | #include "video_core/shader/shader.h" | ||
| 22 | #include "video_core/shader/shader_interpreter.h" | ||
| 23 | |||
| 24 | using nihstro::OpCode; | ||
| 25 | using nihstro::Instruction; | ||
| 26 | using nihstro::SourceRegister; | ||
| 27 | using nihstro::SwizzlePattern; | ||
| 28 | |||
| 29 | GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent) | ||
| 30 | : QAbstractTableModel(parent), par(parent) {} | ||
| 31 | |||
| 32 | int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const { | ||
| 33 | return 3; | ||
| 34 | } | ||
| 35 | |||
| 36 | int GraphicsVertexShaderModel::rowCount(const QModelIndex& parent) const { | ||
| 37 | return static_cast<int>(par->info.code.size()); | ||
| 38 | } | ||
| 39 | |||
| 40 | QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orientation, | ||
| 41 | int role) const { | ||
| 42 | switch (role) { | ||
| 43 | case Qt::DisplayRole: { | ||
| 44 | if (section == 0) { | ||
| 45 | return tr("Offset"); | ||
| 46 | } else if (section == 1) { | ||
| 47 | return tr("Raw"); | ||
| 48 | } else if (section == 2) { | ||
| 49 | return tr("Disassembly"); | ||
| 50 | } | ||
| 51 | |||
| 52 | break; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | return QVariant(); | ||
| 57 | } | ||
| 58 | |||
| 59 | static std::string SelectorToString(u32 selector) { | ||
| 60 | std::string ret; | ||
| 61 | for (int i = 0; i < 4; ++i) { | ||
| 62 | int component = (selector >> ((3 - i) * 2)) & 3; | ||
| 63 | ret += "xyzw"[component]; | ||
| 64 | } | ||
| 65 | return ret; | ||
| 66 | } | ||
| 67 | |||
| 68 | // e.g. "-c92[a0.x].xyzw" | ||
| 69 | static void print_input(std::ostringstream& output, const SourceRegister& input, bool negate, | ||
| 70 | const std::string& swizzle_mask, bool align = true, | ||
| 71 | const std::string& address_register_name = std::string()) { | ||
| 72 | if (align) | ||
| 73 | output << std::setw(4) << std::right; | ||
| 74 | output << ((negate ? "-" : "") + input.GetName()); | ||
| 75 | |||
| 76 | if (!address_register_name.empty()) | ||
| 77 | output << '[' << address_register_name << ']'; | ||
| 78 | output << '.' << swizzle_mask; | ||
| 79 | }; | ||
| 80 | |||
| 81 | QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const { | ||
| 82 | switch (role) { | ||
| 83 | case Qt::DisplayRole: { | ||
| 84 | switch (index.column()) { | ||
| 85 | case 0: | ||
| 86 | if (par->info.HasLabel(index.row())) | ||
| 87 | return QString::fromStdString(par->info.GetLabel(index.row())); | ||
| 88 | |||
| 89 | return QString("%1").arg(4 * index.row(), 4, 16, QLatin1Char('0')); | ||
| 90 | |||
| 91 | case 1: | ||
| 92 | return QString("%1").arg(par->info.code[index.row()].hex, 8, 16, QLatin1Char('0')); | ||
| 93 | |||
| 94 | case 2: { | ||
| 95 | std::ostringstream output; | ||
| 96 | output.flags(std::ios::uppercase); | ||
| 97 | |||
| 98 | // To make the code aligning columns of assembly easier to keep track of, this function | ||
| 99 | // keeps track of the start of the start of the previous column, allowing alignment | ||
| 100 | // based on desired field widths. | ||
| 101 | int current_column = 0; | ||
| 102 | auto AlignToColumn = [&](int col_width) { | ||
| 103 | // Prints spaces to the output to pad previous column to size and advances the | ||
| 104 | // column marker. | ||
| 105 | current_column += col_width; | ||
| 106 | int to_add = std::max(1, current_column - (int)output.tellp()); | ||
| 107 | for (int i = 0; i < to_add; ++i) { | ||
| 108 | output << ' '; | ||
| 109 | } | ||
| 110 | }; | ||
| 111 | |||
| 112 | const Instruction instr = par->info.code[index.row()]; | ||
| 113 | const OpCode opcode = instr.opcode; | ||
| 114 | const OpCode::Info opcode_info = opcode.GetInfo(); | ||
| 115 | const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd | ||
| 116 | ? instr.mad.operand_desc_id.Value() | ||
| 117 | : instr.common.operand_desc_id.Value(); | ||
| 118 | const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern; | ||
| 119 | |||
| 120 | // longest known instruction name: "setemit " | ||
| 121 | int kOpcodeColumnWidth = 8; | ||
| 122 | // "rXX.xyzw " | ||
| 123 | int kOutputColumnWidth = 10; | ||
| 124 | // "-rXX.xyzw ", no attempt is made to align indexed inputs | ||
| 125 | int kInputOperandColumnWidth = 11; | ||
| 126 | |||
| 127 | output << opcode_info.name; | ||
| 128 | |||
| 129 | switch (opcode_info.type) { | ||
| 130 | case OpCode::Type::Trivial: | ||
| 131 | // Nothing to do here | ||
| 132 | break; | ||
| 133 | |||
| 134 | case OpCode::Type::Arithmetic: | ||
| 135 | case OpCode::Type::MultiplyAdd: { | ||
| 136 | // Use custom code for special instructions | ||
| 137 | switch (opcode.EffectiveOpCode()) { | ||
| 138 | case OpCode::Id::CMP: { | ||
| 139 | AlignToColumn(kOpcodeColumnWidth); | ||
| 140 | |||
| 141 | // NOTE: CMP always writes both cc components, so we do not consider the dest | ||
| 142 | // mask here. | ||
| 143 | output << " cc.xy"; | ||
| 144 | AlignToColumn(kOutputColumnWidth); | ||
| 145 | |||
| 146 | SourceRegister src1 = instr.common.GetSrc1(false); | ||
| 147 | SourceRegister src2 = instr.common.GetSrc2(false); | ||
| 148 | |||
| 149 | output << ' '; | ||
| 150 | print_input(output, src1, swizzle.negate_src1, | ||
| 151 | swizzle.SelectorToString(false).substr(0, 1), false, | ||
| 152 | instr.common.AddressRegisterName()); | ||
| 153 | output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.x) | ||
| 154 | << ' '; | ||
| 155 | print_input(output, src2, swizzle.negate_src2, | ||
| 156 | swizzle.SelectorToString(true).substr(0, 1), false); | ||
| 157 | |||
| 158 | output << ", "; | ||
| 159 | |||
| 160 | print_input(output, src1, swizzle.negate_src1, | ||
| 161 | swizzle.SelectorToString(false).substr(1, 1), false, | ||
| 162 | instr.common.AddressRegisterName()); | ||
| 163 | output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.y) | ||
| 164 | << ' '; | ||
| 165 | print_input(output, src2, swizzle.negate_src2, | ||
| 166 | swizzle.SelectorToString(true).substr(1, 1), false); | ||
| 167 | |||
| 168 | break; | ||
| 169 | } | ||
| 170 | |||
| 171 | case OpCode::Id::MAD: | ||
| 172 | case OpCode::Id::MADI: { | ||
| 173 | AlignToColumn(kOpcodeColumnWidth); | ||
| 174 | |||
| 175 | bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); | ||
| 176 | SourceRegister src1 = instr.mad.GetSrc1(src_is_inverted); | ||
| 177 | SourceRegister src2 = instr.mad.GetSrc2(src_is_inverted); | ||
| 178 | SourceRegister src3 = instr.mad.GetSrc3(src_is_inverted); | ||
| 179 | |||
| 180 | output << std::setw(3) << std::right << instr.mad.dest.Value().GetName() << '.' | ||
| 181 | << swizzle.DestMaskToString(); | ||
| 182 | AlignToColumn(kOutputColumnWidth); | ||
| 183 | print_input(output, src1, swizzle.negate_src1, | ||
| 184 | SelectorToString(swizzle.src1_selector)); | ||
| 185 | AlignToColumn(kInputOperandColumnWidth); | ||
| 186 | print_input(output, src2, swizzle.negate_src2, | ||
| 187 | SelectorToString(swizzle.src2_selector), true, | ||
| 188 | src_is_inverted ? "" : instr.mad.AddressRegisterName()); | ||
| 189 | AlignToColumn(kInputOperandColumnWidth); | ||
| 190 | print_input(output, src3, swizzle.negate_src3, | ||
| 191 | SelectorToString(swizzle.src3_selector), true, | ||
| 192 | src_is_inverted ? instr.mad.AddressRegisterName() : ""); | ||
| 193 | AlignToColumn(kInputOperandColumnWidth); | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | |||
| 197 | default: { | ||
| 198 | AlignToColumn(kOpcodeColumnWidth); | ||
| 199 | |||
| 200 | bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); | ||
| 201 | |||
| 202 | if (opcode_info.subtype & OpCode::Info::Dest) { | ||
| 203 | // e.g. "r12.xy__" | ||
| 204 | output << std::setw(3) << std::right << instr.common.dest.Value().GetName() | ||
| 205 | << '.' << swizzle.DestMaskToString(); | ||
| 206 | } else if (opcode_info.subtype == OpCode::Info::MOVA) { | ||
| 207 | output << " a0." << swizzle.DestMaskToString(); | ||
| 208 | } | ||
| 209 | AlignToColumn(kOutputColumnWidth); | ||
| 210 | |||
| 211 | if (opcode_info.subtype & OpCode::Info::Src1) { | ||
| 212 | SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); | ||
| 213 | print_input(output, src1, swizzle.negate_src1, | ||
| 214 | swizzle.SelectorToString(false), true, | ||
| 215 | src_is_inverted ? "" : instr.common.AddressRegisterName()); | ||
| 216 | AlignToColumn(kInputOperandColumnWidth); | ||
| 217 | } | ||
| 218 | |||
| 219 | if (opcode_info.subtype & OpCode::Info::Src2) { | ||
| 220 | SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); | ||
| 221 | print_input(output, src2, swizzle.negate_src2, | ||
| 222 | swizzle.SelectorToString(true), true, | ||
| 223 | src_is_inverted ? instr.common.AddressRegisterName() : ""); | ||
| 224 | AlignToColumn(kInputOperandColumnWidth); | ||
| 225 | } | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | break; | ||
| 231 | } | ||
| 232 | |||
| 233 | case OpCode::Type::Conditional: | ||
| 234 | case OpCode::Type::UniformFlowControl: { | ||
| 235 | output << ' '; | ||
| 236 | |||
| 237 | switch (opcode.EffectiveOpCode()) { | ||
| 238 | case OpCode::Id::LOOP: | ||
| 239 | output << 'i' << instr.flow_control.int_uniform_id << " (end on 0x" | ||
| 240 | << std::setw(4) << std::right << std::setfill('0') << std::hex | ||
| 241 | << (4 * instr.flow_control.dest_offset) << ")"; | ||
| 242 | break; | ||
| 243 | |||
| 244 | default: | ||
| 245 | if (opcode_info.subtype & OpCode::Info::HasCondition) { | ||
| 246 | output << '('; | ||
| 247 | |||
| 248 | if (instr.flow_control.op != instr.flow_control.JustY) { | ||
| 249 | if (!instr.flow_control.refx) | ||
| 250 | output << '!'; | ||
| 251 | output << "cc.x"; | ||
| 252 | } | ||
| 253 | |||
| 254 | if (instr.flow_control.op == instr.flow_control.Or) { | ||
| 255 | output << " || "; | ||
| 256 | } else if (instr.flow_control.op == instr.flow_control.And) { | ||
| 257 | output << " && "; | ||
| 258 | } | ||
| 259 | |||
| 260 | if (instr.flow_control.op != instr.flow_control.JustX) { | ||
| 261 | if (!instr.flow_control.refy) | ||
| 262 | output << '!'; | ||
| 263 | output << "cc.y"; | ||
| 264 | } | ||
| 265 | |||
| 266 | output << ") "; | ||
| 267 | } else if (opcode_info.subtype & OpCode::Info::HasUniformIndex) { | ||
| 268 | if (opcode.EffectiveOpCode() == OpCode::Id::JMPU && | ||
| 269 | (instr.flow_control.num_instructions & 1) == 1) { | ||
| 270 | output << '!'; | ||
| 271 | } | ||
| 272 | output << 'b' << instr.flow_control.bool_uniform_id << ' '; | ||
| 273 | } | ||
| 274 | |||
| 275 | if (opcode_info.subtype & OpCode::Info::HasAlternative) { | ||
| 276 | output << "else jump to 0x" << std::setw(4) << std::right | ||
| 277 | << std::setfill('0') << std::hex | ||
| 278 | << (4 * instr.flow_control.dest_offset); | ||
| 279 | } else if (opcode_info.subtype & OpCode::Info::HasExplicitDest) { | ||
| 280 | output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') | ||
| 281 | << std::hex << (4 * instr.flow_control.dest_offset); | ||
| 282 | } else { | ||
| 283 | // TODO: Handle other cases | ||
| 284 | output << "(unknown destination)"; | ||
| 285 | } | ||
| 286 | |||
| 287 | if (opcode_info.subtype & OpCode::Info::HasFinishPoint) { | ||
| 288 | output << " (return on 0x" << std::setw(4) << std::right | ||
| 289 | << std::setfill('0') << std::hex | ||
| 290 | << (4 * instr.flow_control.dest_offset + | ||
| 291 | 4 * instr.flow_control.num_instructions) | ||
| 292 | << ')'; | ||
| 293 | } | ||
| 294 | |||
| 295 | break; | ||
| 296 | } | ||
| 297 | break; | ||
| 298 | } | ||
| 299 | |||
| 300 | default: | ||
| 301 | output << " (unknown instruction format)"; | ||
| 302 | break; | ||
| 303 | } | ||
| 304 | |||
| 305 | return QString::fromLatin1(output.str().c_str()); | ||
| 306 | } | ||
| 307 | |||
| 308 | default: | ||
| 309 | break; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | case Qt::FontRole: | ||
| 314 | return GetMonospaceFont(); | ||
| 315 | |||
| 316 | case Qt::BackgroundRole: { | ||
| 317 | // Highlight current instruction | ||
| 318 | int current_record_index = par->cycle_index->value(); | ||
| 319 | if (current_record_index < static_cast<int>(par->debug_data.records.size())) { | ||
| 320 | const auto& current_record = par->debug_data.records[current_record_index]; | ||
| 321 | if (index.row() == static_cast<int>(current_record.instruction_offset)) { | ||
| 322 | return QColor(255, 255, 63); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | // Use a grey background for instructions which have no debug data associated to them | ||
| 327 | for (const auto& record : par->debug_data.records) | ||
| 328 | if (index.row() == static_cast<int>(record.instruction_offset)) | ||
| 329 | return QVariant(); | ||
| 330 | |||
| 331 | return QBrush(QColor(192, 192, 192)); | ||
| 332 | } | ||
| 333 | |||
| 334 | // TODO: Draw arrows for each "reachable" instruction to visualize control flow | ||
| 335 | |||
| 336 | default: | ||
| 337 | break; | ||
| 338 | } | ||
| 339 | |||
| 340 | return QVariant(); | ||
| 341 | } | ||
| 342 | |||
| 343 | void GraphicsVertexShaderWidget::DumpShader() { | ||
| 344 | QString filename = QFileDialog::getSaveFileName( | ||
| 345 | this, tr("Save Shader Dump"), "shader_dump.shbin", tr("Shader Binary (*.shbin)")); | ||
| 346 | |||
| 347 | if (filename.isEmpty()) { | ||
| 348 | // If the user canceled the dialog, don't dump anything. | ||
| 349 | return; | ||
| 350 | } | ||
| 351 | |||
| 352 | auto& setup = Pica::g_state.vs; | ||
| 353 | auto& config = Pica::g_state.regs.vs; | ||
| 354 | |||
| 355 | Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup, | ||
| 356 | Pica::g_state.regs.rasterizer.vs_output_attributes); | ||
| 357 | } | ||
| 358 | |||
| 359 | GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( | ||
| 360 | std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent) | ||
| 361 | : BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) { | ||
| 362 | setObjectName("PicaVertexShader"); | ||
| 363 | |||
| 364 | // Clear input vertex data so that it contains valid float values in case a debug shader | ||
| 365 | // execution happens before the first Vertex Loaded breakpoint. | ||
| 366 | // TODO: This makes a crash in the interpreter much less likely, but not impossible. The | ||
| 367 | // interpreter should guard against out-of-bounds accesses to ensure crashes in it aren't | ||
| 368 | // possible. | ||
| 369 | std::memset(&input_vertex, 0, sizeof(input_vertex)); | ||
| 370 | |||
| 371 | auto input_data_mapper = new QSignalMapper(this); | ||
| 372 | |||
| 373 | // TODO: Support inputting data in hexadecimal raw format | ||
| 374 | for (unsigned i = 0; i < ARRAY_SIZE(input_data); ++i) { | ||
| 375 | input_data[i] = new QLineEdit; | ||
| 376 | input_data[i]->setValidator(new QDoubleValidator(input_data[i])); | ||
| 377 | } | ||
| 378 | |||
| 379 | breakpoint_warning = | ||
| 380 | new QLabel(tr("(data only available at vertex shader invocation breakpoints)")); | ||
| 381 | |||
| 382 | // TODO: Add some button for jumping to the shader entry point | ||
| 383 | |||
| 384 | model = new GraphicsVertexShaderModel(this); | ||
| 385 | binary_list = new QTreeView; | ||
| 386 | binary_list->setModel(model); | ||
| 387 | binary_list->setRootIsDecorated(false); | ||
| 388 | binary_list->setAlternatingRowColors(true); | ||
| 389 | |||
| 390 | auto dump_shader = new QPushButton(QIcon::fromTheme("document-save"), tr("Dump")); | ||
| 391 | |||
| 392 | instruction_description = new QLabel; | ||
| 393 | |||
| 394 | cycle_index = new QSpinBox; | ||
| 395 | |||
| 396 | connect(dump_shader, SIGNAL(clicked()), this, SLOT(DumpShader())); | ||
| 397 | |||
| 398 | connect(cycle_index, SIGNAL(valueChanged(int)), this, SLOT(OnCycleIndexChanged(int))); | ||
| 399 | |||
| 400 | for (unsigned i = 0; i < ARRAY_SIZE(input_data); ++i) { | ||
| 401 | connect(input_data[i], SIGNAL(textEdited(const QString&)), input_data_mapper, SLOT(map())); | ||
| 402 | input_data_mapper->setMapping(input_data[i], i); | ||
| 403 | } | ||
| 404 | connect(input_data_mapper, SIGNAL(mapped(int)), this, SLOT(OnInputAttributeChanged(int))); | ||
| 405 | |||
| 406 | auto main_widget = new QWidget; | ||
| 407 | auto main_layout = new QVBoxLayout; | ||
| 408 | { | ||
| 409 | auto input_data_group = new QGroupBox(tr("Input Data")); | ||
| 410 | |||
| 411 | // For each vertex attribute, add a QHBoxLayout consisting of: | ||
| 412 | // - A QLabel denoting the source attribute index | ||
| 413 | // - Four QLineEdits for showing and manipulating attribute data | ||
| 414 | // - A QLabel denoting the shader input attribute index | ||
| 415 | auto sub_layout = new QVBoxLayout; | ||
| 416 | for (unsigned i = 0; i < 16; ++i) { | ||
| 417 | // Create an HBoxLayout to store the widgets used to specify a particular attribute | ||
| 418 | // and store it in a QWidget to allow for easy hiding and unhiding. | ||
| 419 | auto row_layout = new QHBoxLayout; | ||
| 420 | // Remove unnecessary padding between rows | ||
| 421 | row_layout->setContentsMargins(0, 0, 0, 0); | ||
| 422 | |||
| 423 | row_layout->addWidget(new QLabel(tr("Attribute %1").arg(i, 2))); | ||
| 424 | for (unsigned comp = 0; comp < 4; ++comp) | ||
| 425 | row_layout->addWidget(input_data[4 * i + comp]); | ||
| 426 | |||
| 427 | row_layout->addWidget(input_data_mapping[i] = new QLabel); | ||
| 428 | |||
| 429 | input_data_container[i] = new QWidget; | ||
| 430 | input_data_container[i]->setLayout(row_layout); | ||
| 431 | input_data_container[i]->hide(); | ||
| 432 | |||
| 433 | sub_layout->addWidget(input_data_container[i]); | ||
| 434 | } | ||
| 435 | |||
| 436 | sub_layout->addWidget(breakpoint_warning); | ||
| 437 | breakpoint_warning->hide(); | ||
| 438 | |||
| 439 | input_data_group->setLayout(sub_layout); | ||
| 440 | main_layout->addWidget(input_data_group); | ||
| 441 | } | ||
| 442 | |||
| 443 | // Make program listing expand to fill available space in the dialog | ||
| 444 | binary_list->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); | ||
| 445 | main_layout->addWidget(binary_list); | ||
| 446 | |||
| 447 | main_layout->addWidget(dump_shader); | ||
| 448 | { | ||
| 449 | auto sub_layout = new QFormLayout; | ||
| 450 | sub_layout->addRow(tr("Cycle Index:"), cycle_index); | ||
| 451 | |||
| 452 | main_layout->addLayout(sub_layout); | ||
| 453 | } | ||
| 454 | |||
| 455 | // Set a minimum height so that the size of this label doesn't cause the rest of the bottom | ||
| 456 | // part of the UI to keep jumping up and down when cycling through instructions. | ||
| 457 | instruction_description->setMinimumHeight(instruction_description->fontMetrics().lineSpacing() * | ||
| 458 | 6); | ||
| 459 | instruction_description->setAlignment(Qt::AlignLeft | Qt::AlignTop); | ||
| 460 | main_layout->addWidget(instruction_description); | ||
| 461 | |||
| 462 | main_widget->setLayout(main_layout); | ||
| 463 | setWidget(main_widget); | ||
| 464 | |||
| 465 | widget()->setEnabled(false); | ||
| 466 | } | ||
| 467 | |||
| 468 | void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { | ||
| 469 | if (event == Pica::DebugContext::Event::VertexShaderInvocation) { | ||
| 470 | Reload(true, data); | ||
| 471 | } else { | ||
| 472 | // No vertex data is retrievable => invalidate currently stored vertex data | ||
| 473 | Reload(true, nullptr); | ||
| 474 | } | ||
| 475 | widget()->setEnabled(true); | ||
| 476 | } | ||
| 477 | |||
| 478 | void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_data) { | ||
| 479 | model->beginResetModel(); | ||
| 480 | |||
| 481 | if (replace_vertex_data) { | ||
| 482 | if (vertex_data) { | ||
| 483 | memcpy(&input_vertex, vertex_data, sizeof(input_vertex)); | ||
| 484 | for (unsigned attr = 0; attr < 16; ++attr) { | ||
| 485 | for (unsigned comp = 0; comp < 4; ++comp) { | ||
| 486 | input_data[4 * attr + comp]->setText( | ||
| 487 | QString("%1").arg(input_vertex.attr[attr][comp].ToFloat32())); | ||
| 488 | } | ||
| 489 | } | ||
| 490 | breakpoint_warning->hide(); | ||
| 491 | } else { | ||
| 492 | for (unsigned attr = 0; attr < 16; ++attr) { | ||
| 493 | for (unsigned comp = 0; comp < 4; ++comp) { | ||
| 494 | input_data[4 * attr + comp]->setText(QString("???")); | ||
| 495 | } | ||
| 496 | } | ||
| 497 | breakpoint_warning->show(); | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | // Reload shader code | ||
| 502 | info.Clear(); | ||
| 503 | |||
| 504 | auto& shader_setup = Pica::g_state.vs; | ||
| 505 | auto& shader_config = Pica::g_state.regs.vs; | ||
| 506 | for (auto instr : shader_setup.program_code) | ||
| 507 | info.code.push_back({instr}); | ||
| 508 | int num_attributes = shader_config.max_input_attribute_index + 1; | ||
| 509 | |||
| 510 | for (auto pattern : shader_setup.swizzle_data) | ||
| 511 | info.swizzle_info.push_back({pattern}); | ||
| 512 | |||
| 513 | u32 entry_point = Pica::g_state.regs.vs.main_offset; | ||
| 514 | info.labels.insert({entry_point, "main"}); | ||
| 515 | |||
| 516 | // Generate debug information | ||
| 517 | Pica::Shader::InterpreterEngine shader_engine; | ||
| 518 | shader_engine.SetupBatch(shader_setup, entry_point); | ||
| 519 | debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, shader_config); | ||
| 520 | |||
| 521 | // Reload widget state | ||
| 522 | for (int attr = 0; attr < num_attributes; ++attr) { | ||
| 523 | unsigned source_attr = shader_config.GetRegisterForAttribute(attr); | ||
| 524 | input_data_mapping[attr]->setText(QString("-> v%1").arg(source_attr)); | ||
| 525 | input_data_container[attr]->setVisible(true); | ||
| 526 | } | ||
| 527 | // Only show input attributes which are used as input to the shader | ||
| 528 | for (unsigned int attr = num_attributes; attr < 16; ++attr) { | ||
| 529 | input_data_container[attr]->setVisible(false); | ||
| 530 | } | ||
| 531 | |||
| 532 | // Initialize debug info text for current cycle count | ||
| 533 | cycle_index->setMaximum(static_cast<int>(debug_data.records.size() - 1)); | ||
| 534 | OnCycleIndexChanged(cycle_index->value()); | ||
| 535 | |||
| 536 | model->endResetModel(); | ||
| 537 | } | ||
| 538 | |||
| 539 | void GraphicsVertexShaderWidget::OnResumed() { | ||
| 540 | widget()->setEnabled(false); | ||
| 541 | } | ||
| 542 | |||
| 543 | void GraphicsVertexShaderWidget::OnInputAttributeChanged(int index) { | ||
| 544 | float value = input_data[index]->text().toFloat(); | ||
| 545 | input_vertex.attr[index / 4][index % 4] = Pica::float24::FromFloat32(value); | ||
| 546 | // Re-execute shader with updated value | ||
| 547 | Reload(); | ||
| 548 | } | ||
| 549 | |||
| 550 | void GraphicsVertexShaderWidget::OnCycleIndexChanged(int index) { | ||
| 551 | QString text; | ||
| 552 | |||
| 553 | auto& record = debug_data.records[index]; | ||
| 554 | if (record.mask & Pica::Shader::DebugDataRecord::SRC1) | ||
| 555 | text += tr("SRC1: %1, %2, %3, %4\n") | ||
| 556 | .arg(record.src1.x.ToFloat32()) | ||
| 557 | .arg(record.src1.y.ToFloat32()) | ||
| 558 | .arg(record.src1.z.ToFloat32()) | ||
| 559 | .arg(record.src1.w.ToFloat32()); | ||
| 560 | if (record.mask & Pica::Shader::DebugDataRecord::SRC2) | ||
| 561 | text += tr("SRC2: %1, %2, %3, %4\n") | ||
| 562 | .arg(record.src2.x.ToFloat32()) | ||
| 563 | .arg(record.src2.y.ToFloat32()) | ||
| 564 | .arg(record.src2.z.ToFloat32()) | ||
| 565 | .arg(record.src2.w.ToFloat32()); | ||
| 566 | if (record.mask & Pica::Shader::DebugDataRecord::SRC3) | ||
| 567 | text += tr("SRC3: %1, %2, %3, %4\n") | ||
| 568 | .arg(record.src3.x.ToFloat32()) | ||
| 569 | .arg(record.src3.y.ToFloat32()) | ||
| 570 | .arg(record.src3.z.ToFloat32()) | ||
| 571 | .arg(record.src3.w.ToFloat32()); | ||
| 572 | if (record.mask & Pica::Shader::DebugDataRecord::DEST_IN) | ||
| 573 | text += tr("DEST_IN: %1, %2, %3, %4\n") | ||
| 574 | .arg(record.dest_in.x.ToFloat32()) | ||
| 575 | .arg(record.dest_in.y.ToFloat32()) | ||
| 576 | .arg(record.dest_in.z.ToFloat32()) | ||
| 577 | .arg(record.dest_in.w.ToFloat32()); | ||
| 578 | if (record.mask & Pica::Shader::DebugDataRecord::DEST_OUT) | ||
| 579 | text += tr("DEST_OUT: %1, %2, %3, %4\n") | ||
| 580 | .arg(record.dest_out.x.ToFloat32()) | ||
| 581 | .arg(record.dest_out.y.ToFloat32()) | ||
| 582 | .arg(record.dest_out.z.ToFloat32()) | ||
| 583 | .arg(record.dest_out.w.ToFloat32()); | ||
| 584 | |||
| 585 | if (record.mask & Pica::Shader::DebugDataRecord::ADDR_REG_OUT) | ||
| 586 | text += tr("Address Registers: %1, %2\n") | ||
| 587 | .arg(record.address_registers[0]) | ||
| 588 | .arg(record.address_registers[1]); | ||
| 589 | if (record.mask & Pica::Shader::DebugDataRecord::CMP_RESULT) | ||
| 590 | text += tr("Compare Result: %1, %2\n") | ||
| 591 | .arg(record.conditional_code[0] ? "true" : "false") | ||
| 592 | .arg(record.conditional_code[1] ? "true" : "false"); | ||
| 593 | |||
| 594 | if (record.mask & Pica::Shader::DebugDataRecord::COND_BOOL_IN) | ||
| 595 | text += tr("Static Condition: %1\n").arg(record.cond_bool ? "true" : "false"); | ||
| 596 | if (record.mask & Pica::Shader::DebugDataRecord::COND_CMP_IN) | ||
| 597 | text += tr("Dynamic Conditions: %1, %2\n") | ||
| 598 | .arg(record.cond_cmp[0] ? "true" : "false") | ||
| 599 | .arg(record.cond_cmp[1] ? "true" : "false"); | ||
| 600 | if (record.mask & Pica::Shader::DebugDataRecord::LOOP_INT_IN) | ||
| 601 | text += tr("Loop Parameters: %1 (repeats), %2 (initializer), %3 (increment), %4\n") | ||
| 602 | .arg(record.loop_int.x) | ||
| 603 | .arg(record.loop_int.y) | ||
| 604 | .arg(record.loop_int.z) | ||
| 605 | .arg(record.loop_int.w); | ||
| 606 | |||
| 607 | text += | ||
| 608 | tr("Instruction offset: 0x%1").arg(4 * record.instruction_offset, 4, 16, QLatin1Char('0')); | ||
| 609 | if (record.mask & Pica::Shader::DebugDataRecord::NEXT_INSTR) { | ||
| 610 | text += tr(" -> 0x%2").arg(4 * record.next_instruction, 4, 16, QLatin1Char('0')); | ||
| 611 | } else { | ||
| 612 | text += tr(" (last instruction)"); | ||
| 613 | } | ||
| 614 | |||
| 615 | instruction_description->setText(text); | ||
| 616 | |||
| 617 | // Emit model update notification and scroll to current instruction | ||
| 618 | QModelIndex instr_index = model->index(record.instruction_offset, 0); | ||
| 619 | emit model->dataChanged(instr_index, | ||
| 620 | model->index(record.instruction_offset, model->columnCount())); | ||
| 621 | binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible); | ||
| 622 | } | ||