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