diff options
| author | 2015-09-07 16:54:45 -0300 | |
|---|---|---|
| committer | 2015-09-07 16:54:45 -0300 | |
| commit | 87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c (patch) | |
| tree | 7380029505ac8c21ab77aecb9ce3b9145b80705a /src | |
| parent | Merge pull request #1117 from yuriks/fix-glad-build (diff) | |
| parent | Shader Debugger: Allow editing of input vertex data (diff) | |
| download | yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.gz yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.tar.xz yuzu-87a2fd8b3fdc0931cce95300cd85c5ee3ea0382c.zip | |
Merge pull request #1052 from yuriks/vertex-disasm
Shader Debugger Improvements
Diffstat (limited to 'src')
| -rw-r--r-- | src/citra_qt/debugger/graphics_cmdlists.cpp | 2 | ||||
| -rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.cpp | 301 | ||||
| -rw-r--r-- | src/citra_qt/debugger/graphics_vertex_shader.h | 11 |
3 files changed, 187 insertions, 127 deletions
diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 025434687..fa32d24b5 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp | |||
| @@ -359,7 +359,7 @@ void GPUCommandListWidget::CopyAllToClipboard() { | |||
| 359 | QClipboard* clipboard = QApplication::clipboard(); | 359 | QClipboard* clipboard = QApplication::clipboard(); |
| 360 | QString text; | 360 | QString text; |
| 361 | 361 | ||
| 362 | QAbstractItemModel* model = static_cast<QAbstractListModel*>(list_widget->model()); | 362 | QAbstractItemModel* model = static_cast<QAbstractItemModel*>(list_widget->model()); |
| 363 | 363 | ||
| 364 | for (int row = 0; row < model->rowCount({}); ++row) { | 364 | for (int row = 0; row < model->rowCount({}); ++row) { |
| 365 | for (int col = 0; col < model->columnCount({}); ++col) { | 365 | for (int col = 0; col < model->columnCount({}); ++col) { |
diff --git a/src/citra_qt/debugger/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics_vertex_shader.cpp index 1d9a00e89..d48b3294b 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics_vertex_shader.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include <QBoxLayout> | 8 | #include <QBoxLayout> |
| 9 | #include <QFileDialog> | 9 | #include <QFileDialog> |
| 10 | #include <QFormLayout> | ||
| 10 | #include <QGroupBox> | 11 | #include <QGroupBox> |
| 11 | #include <QLabel> | 12 | #include <QLabel> |
| 12 | #include <QLineEdit> | 13 | #include <QLineEdit> |
| @@ -26,18 +27,10 @@ using nihstro::Instruction; | |||
| 26 | using nihstro::SourceRegister; | 27 | using nihstro::SourceRegister; |
| 27 | using nihstro::SwizzlePattern; | 28 | using nihstro::SwizzlePattern; |
| 28 | 29 | ||
| 29 | GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent): QAbstractItemModel(parent), par(parent) { | 30 | GraphicsVertexShaderModel::GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent): QAbstractTableModel(parent), par(parent) { |
| 30 | 31 | ||
| 31 | } | 32 | } |
| 32 | 33 | ||
| 33 | QModelIndex GraphicsVertexShaderModel::index(int row, int column, const QModelIndex& parent) const { | ||
| 34 | return createIndex(row, column); | ||
| 35 | } | ||
| 36 | |||
| 37 | QModelIndex GraphicsVertexShaderModel::parent(const QModelIndex& child) const { | ||
| 38 | return QModelIndex(); | ||
| 39 | } | ||
| 40 | |||
| 41 | int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const { | 34 | int GraphicsVertexShaderModel::columnCount(const QModelIndex& parent) const { |
| 42 | return 3; | 35 | return 3; |
| 43 | } | 36 | } |
| @@ -65,6 +58,28 @@ QVariant GraphicsVertexShaderModel::headerData(int section, Qt::Orientation orie | |||
| 65 | return QVariant(); | 58 | return QVariant(); |
| 66 | } | 59 | } |
| 67 | 60 | ||
| 61 | static std::string SelectorToString(u32 selector) { | ||
| 62 | std::string ret; | ||
| 63 | for (int i = 0; i < 4; ++i) { | ||
| 64 | int component = (selector >> ((3 - i) * 2)) & 3; | ||
| 65 | ret += "xyzw"[component]; | ||
| 66 | } | ||
| 67 | return ret; | ||
| 68 | } | ||
| 69 | |||
| 70 | // e.g. "-c92[a0.x].xyzw" | ||
| 71 | static void print_input(std::ostringstream& output, const SourceRegister& input, | ||
| 72 | bool negate, const std::string& swizzle_mask, bool align = true, | ||
| 73 | const std::string& address_register_name = std::string()) { | ||
| 74 | if (align) | ||
| 75 | output << std::setw(4) << std::right; | ||
| 76 | output << ((negate ? "-" : "") + input.GetName()); | ||
| 77 | |||
| 78 | if (!address_register_name.empty()) | ||
| 79 | output << '[' << address_register_name << ']'; | ||
| 80 | output << '.' << swizzle_mask; | ||
| 81 | }; | ||
| 82 | |||
| 68 | QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const { | 83 | QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) const { |
| 69 | switch (role) { | 84 | switch (role) { |
| 70 | case Qt::DisplayRole: | 85 | case Qt::DisplayRole: |
| @@ -81,102 +96,120 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con | |||
| 81 | 96 | ||
| 82 | case 2: | 97 | case 2: |
| 83 | { | 98 | { |
| 84 | std::stringstream output; | 99 | std::ostringstream output; |
| 85 | output.flags(std::ios::hex); | 100 | output.flags(std::ios::uppercase); |
| 86 | 101 | ||
| 87 | Instruction instr = par->info.code[index.row()]; | 102 | // To make the code aligning columns of assembly easier to keep track of, this function |
| 88 | const SwizzlePattern& swizzle = par->info.swizzle_info[instr.common.operand_desc_id].pattern; | 103 | // keeps track of the start of the start of the previous column, allowing alignment |
| 89 | 104 | // based on desired field widths. | |
| 90 | // longest known instruction name: "setemit " | 105 | int current_column = 0; |
| 91 | output << std::setw(8) << std::left << instr.opcode.Value().GetInfo().name; | 106 | auto AlignToColumn = [&](int col_width) { |
| 92 | 107 | // Prints spaces to the output to pad previous column to size and advances the | |
| 93 | // e.g. "-c92.xyzw" | 108 | // column marker. |
| 94 | static auto print_input = [](std::stringstream& output, const SourceRegister& input, | 109 | current_column += col_width; |
| 95 | bool negate, const std::string& swizzle_mask) { | 110 | int to_add = std::max(1, current_column - (int)output.tellp()); |
| 96 | output << std::setw(4) << std::right << (negate ? "-" : "") + input.GetName(); | 111 | for (int i = 0; i < to_add; ++i) { |
| 97 | output << "." << swizzle_mask; | 112 | output << ' '; |
| 113 | } | ||
| 98 | }; | 114 | }; |
| 99 | 115 | ||
| 100 | // e.g. "-c92[a0.x].xyzw" | 116 | const Instruction instr = par->info.code[index.row()]; |
| 101 | static auto print_input_indexed = [](std::stringstream& output, const SourceRegister& input, | 117 | const OpCode opcode = instr.opcode; |
| 102 | bool negate, const std::string& swizzle_mask, | 118 | const OpCode::Info opcode_info = opcode.GetInfo(); |
| 103 | const std::string& address_register_name) { | 119 | const u32 operand_desc_id = opcode_info.type == OpCode::Type::MultiplyAdd ? |
| 104 | std::string relative_address; | 120 | instr.mad.operand_desc_id.Value() : instr.common.operand_desc_id.Value(); |
| 105 | if (!address_register_name.empty()) | 121 | const SwizzlePattern swizzle = par->info.swizzle_info[operand_desc_id].pattern; |
| 106 | relative_address = "[" + address_register_name + "]"; | ||
| 107 | 122 | ||
| 108 | output << std::setw(10) << std::right << (negate ? "-" : "") + input.GetName() + relative_address; | 123 | // longest known instruction name: "setemit " |
| 109 | output << "." << swizzle_mask; | 124 | int kOpcodeColumnWidth = 8; |
| 110 | }; | 125 | // "rXX.xyzw " |
| 126 | int kOutputColumnWidth = 10; | ||
| 127 | // "-rXX.xyzw ", no attempt is made to align indexed inputs | ||
| 128 | int kInputOperandColumnWidth = 11; | ||
| 111 | 129 | ||
| 112 | // Use print_input or print_input_indexed depending on whether relative addressing is used or not. | 130 | output << opcode_info.name; |
| 113 | static auto print_input_indexed_compact = [](std::stringstream& output, const SourceRegister& input, | ||
| 114 | bool negate, const std::string& swizzle_mask, | ||
| 115 | const std::string& address_register_name) { | ||
| 116 | if (address_register_name.empty()) | ||
| 117 | print_input(output, input, negate, swizzle_mask); | ||
| 118 | else | ||
| 119 | print_input_indexed(output, input, negate, swizzle_mask, address_register_name); | ||
| 120 | }; | ||
| 121 | 131 | ||
| 122 | switch (instr.opcode.Value().GetInfo().type) { | 132 | switch (opcode_info.type) { |
| 123 | case OpCode::Type::Trivial: | 133 | case OpCode::Type::Trivial: |
| 124 | // Nothing to do here | 134 | // Nothing to do here |
| 125 | break; | 135 | break; |
| 126 | 136 | ||
| 127 | case OpCode::Type::Arithmetic: | 137 | case OpCode::Type::Arithmetic: |
| 138 | case OpCode::Type::MultiplyAdd: | ||
| 128 | { | 139 | { |
| 129 | // Use custom code for special instructions | 140 | // Use custom code for special instructions |
| 130 | switch (instr.opcode.Value().EffectiveOpCode()) { | 141 | switch (opcode.EffectiveOpCode()) { |
| 131 | case OpCode::Id::CMP: | 142 | case OpCode::Id::CMP: |
| 132 | { | 143 | { |
| 144 | AlignToColumn(kOpcodeColumnWidth); | ||
| 145 | |||
| 133 | // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. | 146 | // NOTE: CMP always writes both cc components, so we do not consider the dest mask here. |
| 134 | output << std::setw(4) << std::right << "cc."; | 147 | output << " cc.xy"; |
| 135 | output << "xy "; | 148 | AlignToColumn(kOutputColumnWidth); |
| 136 | 149 | ||
| 137 | SourceRegister src1 = instr.common.GetSrc1(false); | 150 | SourceRegister src1 = instr.common.GetSrc1(false); |
| 138 | SourceRegister src2 = instr.common.GetSrc2(false); | 151 | SourceRegister src2 = instr.common.GetSrc2(false); |
| 139 | 152 | ||
| 140 | print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), instr.common.AddressRegisterName()); | 153 | output << ' '; |
| 141 | output << " " << instr.common.compare_op.ToString(instr.common.compare_op.x) << " "; | 154 | print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(0,1), false, instr.common.AddressRegisterName()); |
| 142 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(0,1)); | 155 | output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.x) << ' '; |
| 156 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(0,1), false); | ||
| 143 | 157 | ||
| 144 | output << ", "; | 158 | output << ", "; |
| 145 | 159 | ||
| 146 | print_input_indexed_compact(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), instr.common.AddressRegisterName()); | 160 | print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false).substr(1,1), false, instr.common.AddressRegisterName()); |
| 147 | output << " " << instr.common.compare_op.ToString(instr.common.compare_op.y) << " "; | 161 | output << ' ' << instr.common.compare_op.ToString(instr.common.compare_op.y) << ' '; |
| 148 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(1,1)); | 162 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true).substr(1,1), false); |
| 149 | 163 | ||
| 150 | break; | 164 | break; |
| 151 | } | 165 | } |
| 152 | 166 | ||
| 167 | case OpCode::Id::MAD: | ||
| 168 | case OpCode::Id::MADI: | ||
| 169 | { | ||
| 170 | AlignToColumn(kOpcodeColumnWidth); | ||
| 171 | |||
| 172 | bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); | ||
| 173 | SourceRegister src1 = instr.mad.GetSrc1(src_is_inverted); | ||
| 174 | SourceRegister src2 = instr.mad.GetSrc2(src_is_inverted); | ||
| 175 | SourceRegister src3 = instr.mad.GetSrc3(src_is_inverted); | ||
| 176 | |||
| 177 | output << std::setw(3) << std::right << instr.mad.dest.Value().GetName() << '.' << swizzle.DestMaskToString(); | ||
| 178 | AlignToColumn(kOutputColumnWidth); | ||
| 179 | print_input(output, src1, swizzle.negate_src1, SelectorToString(swizzle.src1_selector)); | ||
| 180 | AlignToColumn(kInputOperandColumnWidth); | ||
| 181 | print_input(output, src2, swizzle.negate_src2, SelectorToString(swizzle.src2_selector)); | ||
| 182 | AlignToColumn(kInputOperandColumnWidth); | ||
| 183 | print_input(output, src3, swizzle.negate_src3, SelectorToString(swizzle.src3_selector)); | ||
| 184 | AlignToColumn(kInputOperandColumnWidth); | ||
| 185 | break; | ||
| 186 | } | ||
| 187 | |||
| 153 | default: | 188 | default: |
| 154 | { | 189 | { |
| 155 | bool src_is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed); | 190 | AlignToColumn(kOpcodeColumnWidth); |
| 191 | |||
| 192 | bool src_is_inverted = 0 != (opcode_info.subtype & OpCode::Info::SrcInversed); | ||
| 156 | 193 | ||
| 157 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Dest) { | 194 | if (opcode_info.subtype & OpCode::Info::Dest) { |
| 158 | // e.g. "r12.xy__" | 195 | // e.g. "r12.xy__" |
| 159 | output << std::setw(4) << std::right << instr.common.dest.Value().GetName() + "."; | 196 | output << std::setw(3) << std::right << instr.common.dest.Value().GetName() << '.' << swizzle.DestMaskToString(); |
| 160 | output << swizzle.DestMaskToString(); | 197 | } else if (opcode_info.subtype == OpCode::Info::MOVA) { |
| 161 | } else if (instr.opcode.Value().GetInfo().subtype == OpCode::Info::MOVA) { | 198 | output << " a0." << swizzle.DestMaskToString(); |
| 162 | output << std::setw(4) << std::right << "a0."; | ||
| 163 | output << swizzle.DestMaskToString(); | ||
| 164 | } else { | ||
| 165 | output << " "; | ||
| 166 | } | 199 | } |
| 167 | output << " "; | 200 | AlignToColumn(kOutputColumnWidth); |
| 168 | 201 | ||
| 169 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src1) { | 202 | if (opcode_info.subtype & OpCode::Info::Src1) { |
| 170 | SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); | 203 | SourceRegister src1 = instr.common.GetSrc1(src_is_inverted); |
| 171 | print_input_indexed(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), instr.common.AddressRegisterName()); | 204 | print_input(output, src1, swizzle.negate_src1, swizzle.SelectorToString(false), true, instr.common.AddressRegisterName()); |
| 172 | } else { | 205 | AlignToColumn(kInputOperandColumnWidth); |
| 173 | output << " "; | ||
| 174 | } | 206 | } |
| 175 | 207 | ||
| 176 | // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 | 208 | // TODO: In some cases, the Address Register is used as an index for SRC2 instead of SRC1 |
| 177 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::Src2) { | 209 | if (opcode_info.subtype & OpCode::Info::Src2) { |
| 178 | SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); | 210 | SourceRegister src2 = instr.common.GetSrc2(src_is_inverted); |
| 179 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true)); | 211 | print_input(output, src2, swizzle.negate_src2, swizzle.SelectorToString(true)); |
| 212 | AlignToColumn(kInputOperandColumnWidth); | ||
| 180 | } | 213 | } |
| 181 | break; | 214 | break; |
| 182 | } | 215 | } |
| @@ -186,46 +219,55 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con | |||
| 186 | } | 219 | } |
| 187 | 220 | ||
| 188 | case OpCode::Type::Conditional: | 221 | case OpCode::Type::Conditional: |
| 222 | case OpCode::Type::UniformFlowControl: | ||
| 189 | { | 223 | { |
| 190 | switch (instr.opcode.Value().EffectiveOpCode()) { | 224 | output << ' '; |
| 225 | |||
| 226 | switch (opcode.EffectiveOpCode()) { | ||
| 191 | case OpCode::Id::LOOP: | 227 | case OpCode::Id::LOOP: |
| 192 | output << "(unknown instruction format)"; | 228 | output << "(unknown instruction format)"; |
| 193 | break; | 229 | break; |
| 194 | 230 | ||
| 195 | default: | 231 | default: |
| 196 | output << "if "; | 232 | if (opcode_info.subtype & OpCode::Info::HasCondition) { |
| 197 | 233 | output << '('; | |
| 198 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasCondition) { | 234 | |
| 199 | const char* ops[] = { | 235 | if (instr.flow_control.op != instr.flow_control.JustY) { |
| 200 | " || ", " && ", "", "" | 236 | if (instr.flow_control.refx) output << '!'; |
| 201 | }; | 237 | output << "cc.x"; |
| 202 | if (instr.flow_control.op != instr.flow_control.JustY) | 238 | } |
| 203 | output << ((!instr.flow_control.refx) ? "!" : " ") << "cc.x"; | 239 | |
| 204 | 240 | if (instr.flow_control.op == instr.flow_control.Or) { | |
| 205 | output << ops[instr.flow_control.op]; | 241 | output << " || "; |
| 206 | 242 | } else if (instr.flow_control.op == instr.flow_control.And) { | |
| 207 | if (instr.flow_control.op != instr.flow_control.JustX) | 243 | output << " && "; |
| 208 | output << ((!instr.flow_control.refy) ? "!" : " ") << "cc.y"; | 244 | } |
| 209 | 245 | ||
| 210 | output << " "; | 246 | if (instr.flow_control.op != instr.flow_control.JustX) { |
| 211 | } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasUniformIndex) { | 247 | if (instr.flow_control.refy) output << '!'; |
| 212 | output << "b" << instr.flow_control.bool_uniform_id << " "; | 248 | output << "cc.y"; |
| 249 | } | ||
| 250 | |||
| 251 | output << ") "; | ||
| 252 | } else if (opcode_info.subtype & OpCode::Info::HasUniformIndex) { | ||
| 253 | output << 'b' << instr.flow_control.bool_uniform_id << ' '; | ||
| 213 | } | 254 | } |
| 214 | 255 | ||
| 215 | u32 target_addr = instr.flow_control.dest_offset; | 256 | u32 target_addr = instr.flow_control.dest_offset; |
| 216 | u32 target_addr_else = instr.flow_control.dest_offset; | 257 | u32 target_addr_else = instr.flow_control.dest_offset; |
| 217 | 258 | ||
| 218 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasAlternative) { | 259 | if (opcode_info.subtype & OpCode::Info::HasAlternative) { |
| 219 | output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; | 260 | output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset); |
| 220 | } else if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasExplicitDest) { | 261 | } else if (opcode_info.subtype & OpCode::Info::HasExplicitDest) { |
| 221 | output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << 4 * instr.flow_control.dest_offset << " "; | 262 | output << "jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex << (4 * instr.flow_control.dest_offset); |
| 222 | } else { | 263 | } else { |
| 223 | // TODO: Handle other cases | 264 | // TODO: Handle other cases |
| 265 | output << "(unknown destination)"; | ||
| 224 | } | 266 | } |
| 225 | 267 | ||
| 226 | if (instr.opcode.Value().GetInfo().subtype & OpCode::Info::HasFinishPoint) { | 268 | if (opcode_info.subtype & OpCode::Info::HasFinishPoint) { |
| 227 | output << "(return on " << std::setw(4) << std::right << std::setfill('0') | 269 | output << " (return on 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex |
| 228 | << 4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions << ")"; | 270 | << (4 * instr.flow_control.dest_offset + 4 * instr.flow_control.num_instructions) << ')'; |
| 229 | } | 271 | } |
| 230 | 272 | ||
| 231 | break; | 273 | break; |
| @@ -234,7 +276,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con | |||
| 234 | } | 276 | } |
| 235 | 277 | ||
| 236 | default: | 278 | default: |
| 237 | output << "(unknown instruction format)"; | 279 | output << " (unknown instruction format)"; |
| 238 | break; | 280 | break; |
| 239 | } | 281 | } |
| 240 | 282 | ||
| @@ -250,12 +292,23 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con | |||
| 250 | return GetMonospaceFont(); | 292 | return GetMonospaceFont(); |
| 251 | 293 | ||
| 252 | case Qt::BackgroundRole: | 294 | case Qt::BackgroundRole: |
| 253 | // Highlight instructions which have no debug data associated to them | 295 | { |
| 296 | // Highlight current instruction | ||
| 297 | int current_record_index = par->cycle_index->value(); | ||
| 298 | if (current_record_index < par->debug_data.records.size()) { | ||
| 299 | const auto& current_record = par->debug_data.records[current_record_index]; | ||
| 300 | if (index.row() == current_record.instruction_offset) { | ||
| 301 | return QColor(255, 255, 63); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | // Use a grey background for instructions which have no debug data associated to them | ||
| 254 | for (const auto& record : par->debug_data.records) | 306 | for (const auto& record : par->debug_data.records) |
| 255 | if (index.row() == record.instruction_offset) | 307 | if (index.row() == record.instruction_offset) |
| 256 | return QVariant(); | 308 | return QVariant(); |
| 257 | 309 | ||
| 258 | return QBrush(QColor(255, 255, 127)); | 310 | return QBrush(QColor(192, 192, 192)); |
| 311 | } | ||
| 259 | 312 | ||
| 260 | 313 | ||
| 261 | // TODO: Draw arrows for each "reachable" instruction to visualize control flow | 314 | // TODO: Draw arrows for each "reachable" instruction to visualize control flow |
| @@ -288,6 +341,13 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De | |||
| 288 | : BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) { | 341 | : BreakPointObserverDock(debug_context, "Pica Vertex Shader", parent) { |
| 289 | setObjectName("PicaVertexShader"); | 342 | setObjectName("PicaVertexShader"); |
| 290 | 343 | ||
| 344 | // Clear input vertex data so that it contains valid float values in case a debug shader | ||
| 345 | // execution happens before the first Vertex Loaded breakpoint. | ||
| 346 | // TODO: This makes a crash in the interpreter much less likely, but not impossible. The | ||
| 347 | // interpreter should guard against out-of-bounds accesses to ensure crashes in it aren't | ||
| 348 | // possible. | ||
| 349 | std::memset(&input_vertex, 0, sizeof(input_vertex)); | ||
| 350 | |||
| 291 | auto input_data_mapper = new QSignalMapper(this); | 351 | auto input_data_mapper = new QSignalMapper(this); |
| 292 | 352 | ||
| 293 | // TODO: Support inputting data in hexadecimal raw format | 353 | // TODO: Support inputting data in hexadecimal raw format |
| @@ -312,9 +372,6 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De | |||
| 312 | 372 | ||
| 313 | cycle_index = new QSpinBox; | 373 | cycle_index = new QSpinBox; |
| 314 | 374 | ||
| 315 | connect(this, SIGNAL(SelectCommand(const QModelIndex&, QItemSelectionModel::SelectionFlags)), | ||
| 316 | binary_list->selectionModel(), SLOT(select(const QModelIndex&, QItemSelectionModel::SelectionFlags))); | ||
| 317 | |||
| 318 | connect(dump_shader, SIGNAL(clicked()), this, SLOT(DumpShader())); | 375 | connect(dump_shader, SIGNAL(clicked()), this, SLOT(DumpShader())); |
| 319 | 376 | ||
| 320 | connect(cycle_index, SIGNAL(valueChanged(int)), this, SLOT(OnCycleIndexChanged(int))); | 377 | connect(cycle_index, SIGNAL(valueChanged(int)), this, SLOT(OnCycleIndexChanged(int))); |
| @@ -339,6 +396,9 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De | |||
| 339 | // Create an HBoxLayout to store the widgets used to specify a particular attribute | 396 | // Create an HBoxLayout to store the widgets used to specify a particular attribute |
| 340 | // and store it in a QWidget to allow for easy hiding and unhiding. | 397 | // and store it in a QWidget to allow for easy hiding and unhiding. |
| 341 | auto row_layout = new QHBoxLayout; | 398 | auto row_layout = new QHBoxLayout; |
| 399 | // Remove unecessary padding between rows | ||
| 400 | row_layout->setContentsMargins(0, 0, 0, 0); | ||
| 401 | |||
| 342 | row_layout->addWidget(new QLabel(tr("Attribute %1").arg(i, 2))); | 402 | row_layout->addWidget(new QLabel(tr("Attribute %1").arg(i, 2))); |
| 343 | for (unsigned comp = 0; comp < 4; ++comp) | 403 | for (unsigned comp = 0; comp < 4; ++comp) |
| 344 | row_layout->addWidget(input_data[4 * i + comp]); | 404 | row_layout->addWidget(input_data[4 * i + comp]); |
| @@ -358,20 +418,25 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(std::shared_ptr< Pica::De | |||
| 358 | input_data_group->setLayout(sub_layout); | 418 | input_data_group->setLayout(sub_layout); |
| 359 | main_layout->addWidget(input_data_group); | 419 | main_layout->addWidget(input_data_group); |
| 360 | } | 420 | } |
| 361 | { | 421 | |
| 362 | auto sub_layout = new QHBoxLayout; | 422 | // Make program listing expand to fill available space in the dialog |
| 363 | sub_layout->addWidget(binary_list); | 423 | binary_list->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); |
| 364 | main_layout->addLayout(sub_layout); | 424 | main_layout->addWidget(binary_list); |
| 365 | } | 425 | |
| 366 | main_layout->addWidget(dump_shader); | 426 | main_layout->addWidget(dump_shader); |
| 367 | { | 427 | { |
| 368 | auto sub_layout = new QHBoxLayout; | 428 | auto sub_layout = new QFormLayout; |
| 369 | sub_layout->addWidget(new QLabel(tr("Cycle Index:"))); | 429 | sub_layout->addRow(tr("Cycle Index:"), cycle_index); |
| 370 | sub_layout->addWidget(cycle_index); | 430 | |
| 371 | main_layout->addLayout(sub_layout); | 431 | main_layout->addLayout(sub_layout); |
| 372 | } | 432 | } |
| 433 | |||
| 434 | // Set a minimum height so that the size of this label doesn't cause the rest of the bottom | ||
| 435 | // part of the UI to keep jumping up and down when cycling through instructions. | ||
| 436 | instruction_description->setMinimumHeight(instruction_description->fontMetrics().lineSpacing() * 6); | ||
| 437 | instruction_description->setAlignment(Qt::AlignLeft | Qt::AlignTop); | ||
| 373 | main_layout->addWidget(instruction_description); | 438 | main_layout->addWidget(instruction_description); |
| 374 | main_layout->addStretch(); | 439 | |
| 375 | main_widget->setLayout(main_layout); | 440 | main_widget->setLayout(main_layout); |
| 376 | setWidget(main_widget); | 441 | setWidget(main_widget); |
| 377 | 442 | ||
| @@ -418,6 +483,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d | |||
| 418 | auto& shader_config = Pica::g_state.regs.vs; | 483 | auto& shader_config = Pica::g_state.regs.vs; |
| 419 | for (auto instr : shader_setup.program_code) | 484 | for (auto instr : shader_setup.program_code) |
| 420 | info.code.push_back({instr}); | 485 | info.code.push_back({instr}); |
| 486 | int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); | ||
| 421 | 487 | ||
| 422 | for (auto pattern : shader_setup.swizzle_data) | 488 | for (auto pattern : shader_setup.swizzle_data) |
| 423 | info.swizzle_info.push_back({pattern}); | 489 | info.swizzle_info.push_back({pattern}); |
| @@ -426,19 +492,18 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d | |||
| 426 | info.labels.insert({ entry_point, "main" }); | 492 | info.labels.insert({ entry_point, "main" }); |
| 427 | 493 | ||
| 428 | // Generate debug information | 494 | // Generate debug information |
| 429 | debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, 1, shader_config, shader_setup); | 495 | debug_data = Pica::Shader::ProduceDebugInfo(input_vertex, num_attributes, shader_config, shader_setup); |
| 430 | 496 | ||
| 431 | // Reload widget state | 497 | // Reload widget state |
| 432 | 498 | for (unsigned int attr = 0; attr < num_attributes; ++attr) { | |
| 433 | // Only show input attributes which are used as input to the shader | ||
| 434 | for (unsigned int attr = 0; attr < 16; ++attr) { | ||
| 435 | input_data_container[attr]->setVisible(false); | ||
| 436 | } | ||
| 437 | for (unsigned int attr = 0; attr < Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes(); ++attr) { | ||
| 438 | unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); | 499 | unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr); |
| 439 | input_data_mapping[source_attr]->setText(QString("-> v%1").arg(attr)); | 500 | input_data_mapping[source_attr]->setText(QString("-> v%1").arg(attr)); |
| 440 | input_data_container[source_attr]->setVisible(true); | 501 | input_data_container[source_attr]->setVisible(true); |
| 441 | } | 502 | } |
| 503 | // Only show input attributes which are used as input to the shader | ||
| 504 | for (unsigned int attr = num_attributes; attr < 16; ++attr) { | ||
| 505 | input_data_container[attr]->setVisible(false); | ||
| 506 | } | ||
| 442 | 507 | ||
| 443 | // Initialize debug info text for current cycle count | 508 | // Initialize debug info text for current cycle count |
| 444 | cycle_index->setMaximum(debug_data.records.size() - 1); | 509 | cycle_index->setMaximum(debug_data.records.size() - 1); |
| @@ -453,6 +518,8 @@ void GraphicsVertexShaderWidget::OnResumed() { | |||
| 453 | 518 | ||
| 454 | void GraphicsVertexShaderWidget::OnInputAttributeChanged(int index) { | 519 | void GraphicsVertexShaderWidget::OnInputAttributeChanged(int index) { |
| 455 | float value = input_data[index]->text().toFloat(); | 520 | float value = input_data[index]->text().toFloat(); |
| 521 | input_vertex.attr[index / 4][index % 4] = Pica::float24::FromFloat32(value); | ||
| 522 | // Re-execute shader with updated value | ||
| 456 | Reload(); | 523 | Reload(); |
| 457 | } | 524 | } |
| 458 | 525 | ||
| @@ -492,8 +559,8 @@ void GraphicsVertexShaderWidget::OnCycleIndexChanged(int index) { | |||
| 492 | 559 | ||
| 493 | instruction_description->setText(text); | 560 | instruction_description->setText(text); |
| 494 | 561 | ||
| 495 | // Scroll to current instruction | 562 | // Emit model update notification and scroll to current instruction |
| 496 | const QModelIndex& instr_index = model->index(record.instruction_offset, 0); | 563 | QModelIndex instr_index = model->index(record.instruction_offset, 0); |
| 497 | emit SelectCommand(instr_index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); | 564 | emit model->dataChanged(instr_index, model->index(record.instruction_offset, model->columnCount())); |
| 498 | binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible); | 565 | binary_list->scrollTo(instr_index, QAbstractItemView::EnsureVisible); |
| 499 | } | 566 | } |
diff --git a/src/citra_qt/debugger/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics_vertex_shader.h index 1b3f1f7ec..0bf1652fc 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics_vertex_shader.h | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <QAbstractListModel> | 7 | #include <QAbstractTableModel> |
| 8 | 8 | ||
| 9 | #include "graphics_breakpoint_observer.h" | 9 | #include "graphics_breakpoint_observer.h" |
| 10 | 10 | ||
| @@ -17,14 +17,12 @@ class QSpinBox; | |||
| 17 | 17 | ||
| 18 | class GraphicsVertexShaderWidget; | 18 | class GraphicsVertexShaderWidget; |
| 19 | 19 | ||
| 20 | class GraphicsVertexShaderModel : public QAbstractItemModel { | 20 | class GraphicsVertexShaderModel : public QAbstractTableModel { |
| 21 | Q_OBJECT | 21 | Q_OBJECT |
| 22 | 22 | ||
| 23 | public: | 23 | public: |
| 24 | GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent); | 24 | GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent); |
| 25 | 25 | ||
| 26 | QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; | ||
| 27 | QModelIndex parent(const QModelIndex& child) const override; | ||
| 28 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; | 26 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; |
| 29 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; | 27 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; |
| 30 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; | 28 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; |
| @@ -62,11 +60,6 @@ private slots: | |||
| 62 | */ | 60 | */ |
| 63 | void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr); | 61 | void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr); |
| 64 | 62 | ||
| 65 | |||
| 66 | signals: | ||
| 67 | // Call this to change the current command selection in the disassembly view | ||
| 68 | void SelectCommand(const QModelIndex&, QItemSelectionModel::SelectionFlags); | ||
| 69 | |||
| 70 | private: | 63 | private: |
| 71 | QLabel* instruction_description; | 64 | QLabel* instruction_description; |
| 72 | QTreeView* binary_list; | 65 | QTreeView* binary_list; |