summaryrefslogtreecommitdiff
path: root/src/citra_qt/debugger/graphics
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
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')
-rw-r--r--src/citra_qt/debugger/graphics/graphics.cpp77
-rw-r--r--src/citra_qt/debugger/graphics/graphics.h41
-rw-r--r--src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp27
-rw-r--r--src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h33
-rw-r--r--src/citra_qt/debugger/graphics/graphics_breakpoints.cpp213
-rw-r--r--src/citra_qt/debugger/graphics/graphics_breakpoints.h46
-rw-r--r--src/citra_qt/debugger/graphics/graphics_breakpoints_p.h36
-rw-r--r--src/citra_qt/debugger/graphics/graphics_cmdlists.cpp259
-rw-r--r--src/citra_qt/debugger/graphics/graphics_cmdlists.h61
-rw-r--r--src/citra_qt/debugger/graphics/graphics_surface.cpp710
-rw-r--r--src/citra_qt/debugger/graphics/graphics_surface.h118
-rw-r--r--src/citra_qt/debugger/graphics/graphics_tracing.cpp178
-rw-r--r--src/citra_qt/debugger/graphics/graphics_tracing.h33
-rw-r--r--src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp629
-rw-r--r--src/citra_qt/debugger/graphics/graphics_vertex_shader.h87
15 files changed, 2548 insertions, 0 deletions
diff --git a/src/citra_qt/debugger/graphics/graphics.cpp b/src/citra_qt/debugger/graphics/graphics.cpp
new file mode 100644
index 000000000..6a76adeae
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics.cpp
@@ -0,0 +1,77 @@
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 <QListView>
6#include "citra_qt/debugger/graphics/graphics.h"
7#include "citra_qt/util/util.h"
8
9extern GraphicsDebugger g_debugger;
10
11GPUCommandStreamItemModel::GPUCommandStreamItemModel(QObject* parent)
12 : QAbstractListModel(parent), command_count(0) {
13 connect(this, SIGNAL(GXCommandFinished(int)), this, SLOT(OnGXCommandFinishedInternal(int)));
14}
15
16int GPUCommandStreamItemModel::rowCount(const QModelIndex& parent) const {
17 return command_count;
18}
19
20QVariant GPUCommandStreamItemModel::data(const QModelIndex& index, int role) const {
21 if (!index.isValid())
22 return QVariant();
23
24 int command_index = index.row();
25 const Service::GSP::Command& command = GetDebugger()->ReadGXCommandHistory(command_index);
26 if (role == Qt::DisplayRole) {
27 std::map<Service::GSP::CommandId, const char*> command_names = {
28 {Service::GSP::CommandId::REQUEST_DMA, "REQUEST_DMA"},
29 {Service::GSP::CommandId::SUBMIT_GPU_CMDLIST, "SUBMIT_GPU_CMDLIST"},
30 {Service::GSP::CommandId::SET_MEMORY_FILL, "SET_MEMORY_FILL"},
31 {Service::GSP::CommandId::SET_DISPLAY_TRANSFER, "SET_DISPLAY_TRANSFER"},
32 {Service::GSP::CommandId::SET_TEXTURE_COPY, "SET_TEXTURE_COPY"},
33 {Service::GSP::CommandId::CACHE_FLUSH, "CACHE_FLUSH"},
34 };
35 const u32* command_data = reinterpret_cast<const u32*>(&command);
36 QString str = QString("%1 %2 %3 %4 %5 %6 %7 %8 %9")
37 .arg(command_names[command.id])
38 .arg(command_data[0], 8, 16, QLatin1Char('0'))
39 .arg(command_data[1], 8, 16, QLatin1Char('0'))
40 .arg(command_data[2], 8, 16, QLatin1Char('0'))
41 .arg(command_data[3], 8, 16, QLatin1Char('0'))
42 .arg(command_data[4], 8, 16, QLatin1Char('0'))
43 .arg(command_data[5], 8, 16, QLatin1Char('0'))
44 .arg(command_data[6], 8, 16, QLatin1Char('0'))
45 .arg(command_data[7], 8, 16, QLatin1Char('0'));
46 return QVariant(str);
47 } else {
48 return QVariant();
49 }
50}
51
52void GPUCommandStreamItemModel::GXCommandProcessed(int total_command_count) {
53 emit GXCommandFinished(total_command_count);
54}
55
56void GPUCommandStreamItemModel::OnGXCommandFinishedInternal(int total_command_count) {
57 if (total_command_count == 0)
58 return;
59
60 int prev_command_count = command_count;
61 command_count = total_command_count;
62 emit dataChanged(index(prev_command_count, 0), index(total_command_count - 1, 0));
63}
64
65GPUCommandStreamWidget::GPUCommandStreamWidget(QWidget* parent)
66 : QDockWidget(tr("Graphics Debugger"), parent) {
67 setObjectName("GraphicsDebugger");
68
69 GPUCommandStreamItemModel* command_model = new GPUCommandStreamItemModel(this);
70 g_debugger.RegisterObserver(command_model);
71
72 QListView* command_list = new QListView;
73 command_list->setModel(command_model);
74 command_list->setFont(GetMonospaceFont());
75
76 setWidget(command_list);
77}
diff --git a/src/citra_qt/debugger/graphics/graphics.h b/src/citra_qt/debugger/graphics/graphics.h
new file mode 100644
index 000000000..8837fb792
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics.h
@@ -0,0 +1,41 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QAbstractListModel>
8#include <QDockWidget>
9#include "video_core/gpu_debugger.h"
10
11class GPUCommandStreamItemModel : public QAbstractListModel,
12 public GraphicsDebugger::DebuggerObserver {
13 Q_OBJECT
14
15public:
16 explicit GPUCommandStreamItemModel(QObject* parent);
17
18 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
19 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
20
21public:
22 void GXCommandProcessed(int total_command_count) override;
23
24public slots:
25 void OnGXCommandFinishedInternal(int total_command_count);
26
27signals:
28 void GXCommandFinished(int total_command_count);
29
30private:
31 int command_count;
32};
33
34class GPUCommandStreamWidget : public QDockWidget {
35 Q_OBJECT
36
37public:
38 GPUCommandStreamWidget(QWidget* parent = nullptr);
39
40private:
41};
diff --git a/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp
new file mode 100644
index 000000000..dc6070dea
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.cpp
@@ -0,0 +1,27 @@
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 <QMetaType>
6#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h"
7
8BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context,
9 const QString& title, QWidget* parent)
10 : QDockWidget(title, parent), BreakPointObserver(debug_context) {
11 qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
12
13 connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
14
15 // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
16 // care of delaying its handling to the GUI thread.
17 connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), this,
18 SLOT(OnBreakPointHit(Pica::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
19}
20
21void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) {
22 emit BreakPointHit(event, data);
23}
24
25void BreakPointObserverDock::OnPicaResume() {
26 emit Resumed();
27}
diff --git a/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h
new file mode 100644
index 000000000..e77df4f5b
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_breakpoint_observer.h
@@ -0,0 +1,33 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QDockWidget>
8#include "video_core/debug_utils/debug_utils.h"
9
10/**
11 * Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots.
12 * This is because the Pica breakpoint callbacks are called from a non-GUI thread, while
13 * the widget usually wants to perform reactions in the GUI thread.
14 */
15class BreakPointObserverDock : public QDockWidget,
16 protected Pica::DebugContext::BreakPointObserver {
17 Q_OBJECT
18
19public:
20 BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title,
21 QWidget* parent = nullptr);
22
23 void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override;
24 void OnPicaResume() override;
25
26private slots:
27 virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0;
28 virtual void OnResumed() = 0;
29
30signals:
31 void Resumed();
32 void BreakPointHit(Pica::DebugContext::Event event, void* data);
33};
diff --git a/src/citra_qt/debugger/graphics/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics/graphics_breakpoints.cpp
new file mode 100644
index 000000000..030828ba8
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_breakpoints.cpp
@@ -0,0 +1,213 @@
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 <QLabel>
6#include <QMetaType>
7#include <QPushButton>
8#include <QTreeView>
9#include <QVBoxLayout>
10#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
11#include "citra_qt/debugger/graphics/graphics_breakpoints_p.h"
12#include "common/assert.h"
13
14BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent)
15 : QAbstractListModel(parent), context_weak(debug_context),
16 at_breakpoint(debug_context->at_breakpoint),
17 active_breakpoint(debug_context->active_breakpoint) {}
18
19int BreakPointModel::columnCount(const QModelIndex& parent) const {
20 return 1;
21}
22
23int BreakPointModel::rowCount(const QModelIndex& parent) const {
24 return static_cast<int>(Pica::DebugContext::Event::NumEvents);
25}
26
27QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
28 const auto event = static_cast<Pica::DebugContext::Event>(index.row());
29
30 switch (role) {
31 case Qt::DisplayRole: {
32 if (index.column() == 0) {
33 static const std::map<Pica::DebugContext::Event, QString> map = {
34 {Pica::DebugContext::Event::PicaCommandLoaded, tr("Pica command loaded")},
35 {Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed")},
36 {Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")},
37 {Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")},
38 {Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation")},
39 {Pica::DebugContext::Event::IncomingDisplayTransfer,
40 tr("Incoming display transfer")},
41 {Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed")},
42 {Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped")},
43 };
44
45 DEBUG_ASSERT(map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents));
46 return (map.find(event) != map.end()) ? map.at(event) : QString();
47 }
48
49 break;
50 }
51
52 case Qt::CheckStateRole: {
53 if (index.column() == 0)
54 return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
55 break;
56 }
57
58 case Qt::BackgroundRole: {
59 if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
60 return QBrush(QColor(0xE0, 0xE0, 0x10));
61 }
62 break;
63 }
64
65 case Role_IsEnabled: {
66 auto context = context_weak.lock();
67 return context && context->breakpoints[(int)event].enabled;
68 }
69
70 default:
71 break;
72 }
73 return QVariant();
74}
75
76Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
77 if (!index.isValid())
78 return 0;
79
80 Qt::ItemFlags flags = Qt::ItemIsEnabled;
81 if (index.column() == 0)
82 flags |= Qt::ItemIsUserCheckable;
83 return flags;
84}
85
86bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
87 const auto event = static_cast<Pica::DebugContext::Event>(index.row());
88
89 switch (role) {
90 case Qt::CheckStateRole: {
91 if (index.column() != 0)
92 return false;
93
94 auto context = context_weak.lock();
95 if (!context)
96 return false;
97
98 context->breakpoints[(int)event].enabled = value == Qt::Checked;
99 QModelIndex changed_index = createIndex(index.row(), 0);
100 emit dataChanged(changed_index, changed_index);
101 return true;
102 }
103 }
104
105 return false;
106}
107
108void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) {
109 auto context = context_weak.lock();
110 if (!context)
111 return;
112
113 active_breakpoint = context->active_breakpoint;
114 at_breakpoint = context->at_breakpoint;
115 emit dataChanged(createIndex(static_cast<int>(event), 0),
116 createIndex(static_cast<int>(event), 0));
117}
118
119void BreakPointModel::OnResumed() {
120 auto context = context_weak.lock();
121 if (!context)
122 return;
123
124 at_breakpoint = context->at_breakpoint;
125 emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
126 createIndex(static_cast<int>(active_breakpoint), 0));
127 active_breakpoint = context->active_breakpoint;
128}
129
130GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
131 std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent)
132 : QDockWidget(tr("Pica Breakpoints"), parent),
133 Pica::DebugContext::BreakPointObserver(debug_context) {
134 setObjectName("PicaBreakPointsWidget");
135
136 status_text = new QLabel(tr("Emulation running"));
137 resume_button = new QPushButton(tr("Resume"));
138 resume_button->setEnabled(false);
139
140 breakpoint_model = new BreakPointModel(debug_context, this);
141 breakpoint_list = new QTreeView;
142 breakpoint_list->setRootIsDecorated(false);
143 breakpoint_list->setHeaderHidden(true);
144 breakpoint_list->setModel(breakpoint_model);
145
146 qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event");
147
148 connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
149 SLOT(OnItemDoubleClicked(const QModelIndex&)));
150
151 connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
152
153 connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), this,
154 SLOT(OnBreakPointHit(Pica::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
155 connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
156
157 connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event, void*)), breakpoint_model,
158 SLOT(OnBreakPointHit(Pica::DebugContext::Event)), Qt::BlockingQueuedConnection);
159 connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
160
161 connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
162 breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
163
164 QWidget* main_widget = new QWidget;
165 auto main_layout = new QVBoxLayout;
166 {
167 auto sub_layout = new QHBoxLayout;
168 sub_layout->addWidget(status_text);
169 sub_layout->addWidget(resume_button);
170 main_layout->addLayout(sub_layout);
171 }
172 main_layout->addWidget(breakpoint_list);
173 main_widget->setLayout(main_layout);
174
175 setWidget(main_widget);
176}
177
178void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) {
179 // Process in GUI thread
180 emit BreakPointHit(event, data);
181}
182
183void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
184 status_text->setText(tr("Emulation halted at breakpoint"));
185 resume_button->setEnabled(true);
186}
187
188void GraphicsBreakPointsWidget::OnPicaResume() {
189 // Process in GUI thread
190 emit Resumed();
191}
192
193void GraphicsBreakPointsWidget::OnResumed() {
194 status_text->setText(tr("Emulation running"));
195 resume_button->setEnabled(false);
196}
197
198void GraphicsBreakPointsWidget::OnResumeRequested() {
199 if (auto context = context_weak.lock())
200 context->Resume();
201}
202
203void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
204 if (!index.isValid())
205 return;
206
207 QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
208 QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
209 QVariant new_state = Qt::Unchecked;
210 if (enabled == Qt::Unchecked)
211 new_state = Qt::Checked;
212 breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
213}
diff --git a/src/citra_qt/debugger/graphics/graphics_breakpoints.h b/src/citra_qt/debugger/graphics/graphics_breakpoints.h
new file mode 100644
index 000000000..bec72a2db
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_breakpoints.h
@@ -0,0 +1,46 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QDockWidget>
9#include "video_core/debug_utils/debug_utils.h"
10
11class QLabel;
12class QPushButton;
13class QTreeView;
14
15class BreakPointModel;
16
17class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver {
18 Q_OBJECT
19
20 using Event = Pica::DebugContext::Event;
21
22public:
23 explicit GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context,
24 QWidget* parent = nullptr);
25
26 void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override;
27 void OnPicaResume() override;
28
29public slots:
30 void OnBreakPointHit(Pica::DebugContext::Event event, void* data);
31 void OnItemDoubleClicked(const QModelIndex&);
32 void OnResumeRequested();
33 void OnResumed();
34
35signals:
36 void Resumed();
37 void BreakPointHit(Pica::DebugContext::Event event, void* data);
38 void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
39
40private:
41 QLabel* status_text;
42 QPushButton* resume_button;
43
44 BreakPointModel* breakpoint_model;
45 QTreeView* breakpoint_list;
46};
diff --git a/src/citra_qt/debugger/graphics/graphics_breakpoints_p.h b/src/citra_qt/debugger/graphics/graphics_breakpoints_p.h
new file mode 100644
index 000000000..dc64706bd
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_breakpoints_p.h
@@ -0,0 +1,36 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QAbstractListModel>
9#include "video_core/debug_utils/debug_utils.h"
10
11class BreakPointModel : public QAbstractListModel {
12 Q_OBJECT
13
14public:
15 enum {
16 Role_IsEnabled = Qt::UserRole,
17 };
18
19 BreakPointModel(std::shared_ptr<Pica::DebugContext> context, QObject* parent);
20
21 int columnCount(const QModelIndex& parent = QModelIndex()) const override;
22 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
23 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
24 Qt::ItemFlags flags(const QModelIndex& index) const override;
25
26 bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
27
28public slots:
29 void OnBreakPointHit(Pica::DebugContext::Event event);
30 void OnResumed();
31
32private:
33 std::weak_ptr<Pica::DebugContext> context_weak;
34 bool at_breakpoint;
35 Pica::DebugContext::Event active_breakpoint;
36};
diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
new file mode 100644
index 000000000..dab529e3a
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
@@ -0,0 +1,259 @@
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 <QApplication>
6#include <QClipboard>
7#include <QComboBox>
8#include <QHeaderView>
9#include <QLabel>
10#include <QListView>
11#include <QMainWindow>
12#include <QPushButton>
13#include <QSpinBox>
14#include <QTreeView>
15#include <QVBoxLayout>
16#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
17#include "citra_qt/util/spinbox.h"
18#include "citra_qt/util/util.h"
19#include "common/vector_math.h"
20#include "video_core/debug_utils/debug_utils.h"
21#include "video_core/pica.h"
22#include "video_core/pica_state.h"
23
24namespace {
25QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) {
26 QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
27 for (int y = 0; y < info.height; ++y) {
28 for (int x = 0; x < info.width; ++x) {
29 Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true);
30 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
31 }
32 }
33
34 return decoded_image;
35}
36
37class TextureInfoWidget : public QWidget {
38public:
39 TextureInfoWidget(const u8* src, const Pica::DebugUtils::TextureInfo& info,
40 QWidget* parent = nullptr)
41 : QWidget(parent) {
42 QLabel* image_widget = new QLabel;
43 QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info));
44 image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
45 image_widget->setPixmap(image_pixmap);
46
47 QVBoxLayout* layout = new QVBoxLayout;
48 layout->addWidget(image_widget);
49 setLayout(layout);
50 }
51};
52} // Anonymous namespace
53
54GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {}
55
56int GPUCommandListModel::rowCount(const QModelIndex& parent) const {
57 return static_cast<int>(pica_trace.writes.size());
58}
59
60int GPUCommandListModel::columnCount(const QModelIndex& parent) const {
61 return 4;
62}
63
64QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const {
65 if (!index.isValid())
66 return QVariant();
67
68 const auto& write = pica_trace.writes[index.row()];
69
70 if (role == Qt::DisplayRole) {
71 switch (index.column()) {
72 case 0:
73 return QString::fromLatin1(Pica::Regs::GetCommandName(write.cmd_id).c_str());
74 case 1:
75 return QString("%1").arg(write.cmd_id, 3, 16, QLatin1Char('0'));
76 case 2:
77 return QString("%1").arg(write.mask, 4, 2, QLatin1Char('0'));
78 case 3:
79 return QString("%1").arg(write.value, 8, 16, QLatin1Char('0'));
80 }
81 } else if (role == CommandIdRole) {
82 return QVariant::fromValue<int>(write.cmd_id);
83 }
84
85 return QVariant();
86}
87
88QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const {
89 switch (role) {
90 case Qt::DisplayRole: {
91 switch (section) {
92 case 0:
93 return tr("Command Name");
94 case 1:
95 return tr("Register");
96 case 2:
97 return tr("Mask");
98 case 3:
99 return tr("New Value");
100 }
101
102 break;
103 }
104 }
105
106 return QVariant();
107}
108
109void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) {
110 beginResetModel();
111
112 pica_trace = trace;
113
114 endResetModel();
115}
116
117#define COMMAND_IN_RANGE(cmd_id, reg_name) \
118 (cmd_id >= PICA_REG_INDEX(reg_name) && \
119 cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::g_state.regs.reg_name)) / 4)
120
121void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
122 const unsigned int command_id =
123 list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
124 if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
125 COMMAND_IN_RANGE(command_id, texture2)) {
126
127 unsigned texture_index;
128 if (COMMAND_IN_RANGE(command_id, texture0)) {
129 texture_index = 0;
130 } else if (COMMAND_IN_RANGE(command_id, texture1)) {
131 texture_index = 1;
132 } else if (COMMAND_IN_RANGE(command_id, texture2)) {
133 texture_index = 2;
134 } else {
135 UNREACHABLE_MSG("Unknown texture command");
136 }
137
138 const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
139 const auto config = texture.config;
140 const auto format = texture.format;
141 const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
142
143 // TODO: Open a surface debugger
144 }
145}
146
147void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
148 QWidget* new_info_widget = nullptr;
149
150 const unsigned int command_id =
151 list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
152 if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
153 COMMAND_IN_RANGE(command_id, texture2)) {
154
155 unsigned texture_index;
156 if (COMMAND_IN_RANGE(command_id, texture0)) {
157 texture_index = 0;
158 } else if (COMMAND_IN_RANGE(command_id, texture1)) {
159 texture_index = 1;
160 } else {
161 texture_index = 2;
162 }
163
164 const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
165 const auto config = texture.config;
166 const auto format = texture.format;
167
168 const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
169 const u8* src = Memory::GetPhysicalPointer(config.GetPhysicalAddress());
170 new_info_widget = new TextureInfoWidget(src, info);
171 }
172 if (command_info_widget) {
173 delete command_info_widget;
174 command_info_widget = nullptr;
175 }
176 if (new_info_widget) {
177 widget()->layout()->addWidget(new_info_widget);
178 command_info_widget = new_info_widget;
179 }
180}
181#undef COMMAND_IN_RANGE
182
183GPUCommandListWidget::GPUCommandListWidget(QWidget* parent)
184 : QDockWidget(tr("Pica Command List"), parent) {
185 setObjectName("Pica Command List");
186 GPUCommandListModel* model = new GPUCommandListModel(this);
187
188 QWidget* main_widget = new QWidget;
189
190 list_widget = new QTreeView;
191 list_widget->setModel(model);
192 list_widget->setFont(GetMonospaceFont());
193 list_widget->setRootIsDecorated(false);
194 list_widget->setUniformRowHeights(true);
195
196#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
197 list_widget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
198#else
199 list_widget->header()->setResizeMode(QHeaderView::ResizeToContents);
200#endif
201
202 connect(list_widget->selectionModel(),
203 SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this,
204 SLOT(SetCommandInfo(const QModelIndex&)));
205 connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)), this,
206 SLOT(OnCommandDoubleClicked(const QModelIndex&)));
207
208 toggle_tracing = new QPushButton(tr("Start Tracing"));
209 QPushButton* copy_all = new QPushButton(tr("Copy All"));
210
211 connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing()));
212 connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), model,
213 SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&)));
214
215 connect(copy_all, SIGNAL(clicked()), this, SLOT(CopyAllToClipboard()));
216
217 command_info_widget = nullptr;
218
219 QVBoxLayout* main_layout = new QVBoxLayout;
220 main_layout->addWidget(list_widget);
221 {
222 QHBoxLayout* sub_layout = new QHBoxLayout;
223 sub_layout->addWidget(toggle_tracing);
224 sub_layout->addWidget(copy_all);
225 main_layout->addLayout(sub_layout);
226 }
227 main_widget->setLayout(main_layout);
228
229 setWidget(main_widget);
230}
231
232void GPUCommandListWidget::OnToggleTracing() {
233 if (!Pica::DebugUtils::IsPicaTracing()) {
234 Pica::DebugUtils::StartPicaTracing();
235 toggle_tracing->setText(tr("Finish Tracing"));
236 } else {
237 pica_trace = Pica::DebugUtils::FinishPicaTracing();
238 emit TracingFinished(*pica_trace);
239 toggle_tracing->setText(tr("Start Tracing"));
240 }
241}
242
243void GPUCommandListWidget::CopyAllToClipboard() {
244 QClipboard* clipboard = QApplication::clipboard();
245 QString text;
246
247 QAbstractItemModel* model = static_cast<QAbstractItemModel*>(list_widget->model());
248
249 for (int row = 0; row < model->rowCount({}); ++row) {
250 for (int col = 0; col < model->columnCount({}); ++col) {
251 QModelIndex index = model->index(row, col);
252 text += model->data(index).value<QString>();
253 text += '\t';
254 }
255 text += '\n';
256 }
257
258 clipboard->setText(text);
259}
diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.h b/src/citra_qt/debugger/graphics/graphics_cmdlists.h
new file mode 100644
index 000000000..8f40b94c5
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.h
@@ -0,0 +1,61 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QAbstractListModel>
8#include <QDockWidget>
9#include "video_core/debug_utils/debug_utils.h"
10#include "video_core/gpu_debugger.h"
11
12class QPushButton;
13class QTreeView;
14
15class GPUCommandListModel : public QAbstractListModel {
16 Q_OBJECT
17
18public:
19 enum {
20 CommandIdRole = Qt::UserRole,
21 };
22
23 explicit GPUCommandListModel(QObject* parent);
24
25 int columnCount(const QModelIndex& parent = QModelIndex()) const override;
26 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
27 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
28 QVariant headerData(int section, Qt::Orientation orientation,
29 int role = Qt::DisplayRole) const override;
30
31public slots:
32 void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace);
33
34private:
35 Pica::DebugUtils::PicaTrace pica_trace;
36};
37
38class GPUCommandListWidget : public QDockWidget {
39 Q_OBJECT
40
41public:
42 explicit GPUCommandListWidget(QWidget* parent = nullptr);
43
44public slots:
45 void OnToggleTracing();
46 void OnCommandDoubleClicked(const QModelIndex&);
47
48 void SetCommandInfo(const QModelIndex&);
49
50 void CopyAllToClipboard();
51
52signals:
53 void TracingFinished(const Pica::DebugUtils::PicaTrace&);
54
55private:
56 std::unique_ptr<Pica::DebugUtils::PicaTrace> pica_trace;
57
58 QTreeView* list_widget;
59 QWidget* command_info_widget;
60 QPushButton* toggle_tracing;
61};
diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp
new file mode 100644
index 000000000..4efd95d3c
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp
@@ -0,0 +1,710 @@
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 <QBoxLayout>
6#include <QComboBox>
7#include <QDebug>
8#include <QFileDialog>
9#include <QLabel>
10#include <QMouseEvent>
11#include <QPushButton>
12#include <QScrollArea>
13#include <QSpinBox>
14#include "citra_qt/debugger/graphics/graphics_surface.h"
15#include "citra_qt/util/spinbox.h"
16#include "common/color.h"
17#include "core/hw/gpu.h"
18#include "core/memory.h"
19#include "video_core/pica.h"
20#include "video_core/pica_state.h"
21#include "video_core/utils.h"
22
23SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
24 : QLabel(parent), surface_widget(surface_widget_) {}
25SurfacePicture::~SurfacePicture() {}
26
27void SurfacePicture::mousePressEvent(QMouseEvent* event) {
28 // Only do something while the left mouse button is held down
29 if (!(event->buttons() & Qt::LeftButton))
30 return;
31
32 if (pixmap() == nullptr)
33 return;
34
35 if (surface_widget)
36 surface_widget->Pick(event->x() * pixmap()->width() / width(),
37 event->y() * pixmap()->height() / height());
38}
39
40void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
41 // We also want to handle the event if the user moves the mouse while holding down the LMB
42 mousePressEvent(event);
43}
44
45GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
46 QWidget* parent)
47 : BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
48 surface_source(Source::ColorBuffer) {
49 setObjectName("PicaSurface");
50
51 surface_source_list = new QComboBox;
52 surface_source_list->addItem(tr("Color Buffer"));
53 surface_source_list->addItem(tr("Depth Buffer"));
54 surface_source_list->addItem(tr("Stencil Buffer"));
55 surface_source_list->addItem(tr("Texture 0"));
56 surface_source_list->addItem(tr("Texture 1"));
57 surface_source_list->addItem(tr("Texture 2"));
58 surface_source_list->addItem(tr("Custom"));
59 surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
60
61 surface_address_control = new CSpinBox;
62 surface_address_control->SetBase(16);
63 surface_address_control->SetRange(0, 0xFFFFFFFF);
64 surface_address_control->SetPrefix("0x");
65
66 unsigned max_dimension = 16384; // TODO: Find actual maximum
67
68 surface_width_control = new QSpinBox;
69 surface_width_control->setRange(0, max_dimension);
70
71 surface_height_control = new QSpinBox;
72 surface_height_control->setRange(0, max_dimension);
73
74 surface_picker_x_control = new QSpinBox;
75 surface_picker_x_control->setRange(0, max_dimension - 1);
76
77 surface_picker_y_control = new QSpinBox;
78 surface_picker_y_control->setRange(0, max_dimension - 1);
79
80 surface_format_control = new QComboBox;
81
82 // Color formats sorted by Pica texture format index
83 surface_format_control->addItem(tr("RGBA8"));
84 surface_format_control->addItem(tr("RGB8"));
85 surface_format_control->addItem(tr("RGB5A1"));
86 surface_format_control->addItem(tr("RGB565"));
87 surface_format_control->addItem(tr("RGBA4"));
88 surface_format_control->addItem(tr("IA8"));
89 surface_format_control->addItem(tr("RG8"));
90 surface_format_control->addItem(tr("I8"));
91 surface_format_control->addItem(tr("A8"));
92 surface_format_control->addItem(tr("IA4"));
93 surface_format_control->addItem(tr("I4"));
94 surface_format_control->addItem(tr("A4"));
95 surface_format_control->addItem(tr("ETC1"));
96 surface_format_control->addItem(tr("ETC1A4"));
97 surface_format_control->addItem(tr("D16"));
98 surface_format_control->addItem(tr("D24"));
99 surface_format_control->addItem(tr("D24X8"));
100 surface_format_control->addItem(tr("X24S8"));
101 surface_format_control->addItem(tr("Unknown"));
102
103 surface_info_label = new QLabel();
104 surface_info_label->setWordWrap(true);
105
106 surface_picture_label = new SurfacePicture(0, this);
107 surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
108 surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
109 surface_picture_label->setScaledContents(false);
110
111 auto scroll_area = new QScrollArea();
112 scroll_area->setBackgroundRole(QPalette::Dark);
113 scroll_area->setWidgetResizable(false);
114 scroll_area->setWidget(surface_picture_label);
115
116 save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
117
118 // Connections
119 connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
120 connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
121 SLOT(OnSurfaceSourceChanged(int)));
122 connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
123 SLOT(OnSurfaceAddressChanged(qint64)));
124 connect(surface_width_control, SIGNAL(valueChanged(int)), this,
125 SLOT(OnSurfaceWidthChanged(int)));
126 connect(surface_height_control, SIGNAL(valueChanged(int)), this,
127 SLOT(OnSurfaceHeightChanged(int)));
128 connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
129 SLOT(OnSurfaceFormatChanged(int)));
130 connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
131 SLOT(OnSurfacePickerXChanged(int)));
132 connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
133 SLOT(OnSurfacePickerYChanged(int)));
134 connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
135
136 auto main_widget = new QWidget;
137 auto main_layout = new QVBoxLayout;
138 {
139 auto sub_layout = new QHBoxLayout;
140 sub_layout->addWidget(new QLabel(tr("Source:")));
141 sub_layout->addWidget(surface_source_list);
142 main_layout->addLayout(sub_layout);
143 }
144 {
145 auto sub_layout = new QHBoxLayout;
146 sub_layout->addWidget(new QLabel(tr("Physical Address:")));
147 sub_layout->addWidget(surface_address_control);
148 main_layout->addLayout(sub_layout);
149 }
150 {
151 auto sub_layout = new QHBoxLayout;
152 sub_layout->addWidget(new QLabel(tr("Width:")));
153 sub_layout->addWidget(surface_width_control);
154 main_layout->addLayout(sub_layout);
155 }
156 {
157 auto sub_layout = new QHBoxLayout;
158 sub_layout->addWidget(new QLabel(tr("Height:")));
159 sub_layout->addWidget(surface_height_control);
160 main_layout->addLayout(sub_layout);
161 }
162 {
163 auto sub_layout = new QHBoxLayout;
164 sub_layout->addWidget(new QLabel(tr("Format:")));
165 sub_layout->addWidget(surface_format_control);
166 main_layout->addLayout(sub_layout);
167 }
168 main_layout->addWidget(scroll_area);
169
170 auto info_layout = new QHBoxLayout;
171 {
172 auto xy_layout = new QVBoxLayout;
173 {
174 {
175 auto sub_layout = new QHBoxLayout;
176 sub_layout->addWidget(new QLabel(tr("X:")));
177 sub_layout->addWidget(surface_picker_x_control);
178 xy_layout->addLayout(sub_layout);
179 }
180 {
181 auto sub_layout = new QHBoxLayout;
182 sub_layout->addWidget(new QLabel(tr("Y:")));
183 sub_layout->addWidget(surface_picker_y_control);
184 xy_layout->addLayout(sub_layout);
185 }
186 }
187 info_layout->addLayout(xy_layout);
188 surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
189 info_layout->addWidget(surface_info_label);
190 }
191 main_layout->addLayout(info_layout);
192
193 main_layout->addWidget(save_surface);
194 main_widget->setLayout(main_layout);
195 setWidget(main_widget);
196
197 // Load current data - TODO: Make sure this works when emulation is not running
198 if (debug_context && debug_context->at_breakpoint) {
199 emit Update();
200 widget()->setEnabled(debug_context->at_breakpoint);
201 } else {
202 widget()->setEnabled(false);
203 }
204}
205
206void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
207 emit Update();
208 widget()->setEnabled(true);
209}
210
211void GraphicsSurfaceWidget::OnResumed() {
212 widget()->setEnabled(false);
213}
214
215void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
216 surface_source = static_cast<Source>(new_value);
217 emit Update();
218}
219
220void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
221 if (surface_address != new_value) {
222 surface_address = static_cast<unsigned>(new_value);
223
224 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
225 emit Update();
226 }
227}
228
229void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
230 if (surface_width != static_cast<unsigned>(new_value)) {
231 surface_width = static_cast<unsigned>(new_value);
232
233 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
234 emit Update();
235 }
236}
237
238void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
239 if (surface_height != static_cast<unsigned>(new_value)) {
240 surface_height = static_cast<unsigned>(new_value);
241
242 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
243 emit Update();
244 }
245}
246
247void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
248 if (surface_format != static_cast<Format>(new_value)) {
249 surface_format = static_cast<Format>(new_value);
250
251 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
252 emit Update();
253 }
254}
255
256void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
257 if (surface_picker_x != new_value) {
258 surface_picker_x = new_value;
259 Pick(surface_picker_x, surface_picker_y);
260 }
261}
262
263void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
264 if (surface_picker_y != new_value) {
265 surface_picker_y = new_value;
266 Pick(surface_picker_x, surface_picker_y);
267 }
268}
269
270void GraphicsSurfaceWidget::Pick(int x, int y) {
271 surface_picker_x_control->setValue(x);
272 surface_picker_y_control->setValue(y);
273
274 if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) {
275 surface_info_label->setText(tr("Pixel out of bounds"));
276 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
277 return;
278 }
279
280 u8* buffer = Memory::GetPhysicalPointer(surface_address);
281 if (buffer == nullptr) {
282 surface_info_label->setText(tr("(unable to access pixel data)"));
283 surface_info_label->setAlignment(Qt::AlignCenter);
284 return;
285 }
286
287 unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
288 unsigned stride = nibbles_per_pixel * surface_width / 2;
289
290 unsigned bytes_per_pixel;
291 bool nibble_mode = (nibbles_per_pixel == 1);
292 if (nibble_mode) {
293 // As nibbles are contained in a byte we still need to access one byte per nibble
294 bytes_per_pixel = 1;
295 } else {
296 bytes_per_pixel = nibbles_per_pixel / 2;
297 }
298
299 const u32 coarse_y = y & ~7;
300 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
301 const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset);
302
303 auto GetText = [offset](Format format, const u8* pixel) {
304 switch (format) {
305 case Format::RGBA8: {
306 auto value = Color::DecodeRGBA8(pixel) / 255.0f;
307 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
308 .arg(QString::number(value.r(), 'f', 2))
309 .arg(QString::number(value.g(), 'f', 2))
310 .arg(QString::number(value.b(), 'f', 2))
311 .arg(QString::number(value.a(), 'f', 2));
312 }
313 case Format::RGB8: {
314 auto value = Color::DecodeRGB8(pixel) / 255.0f;
315 return QString("Red: %1, Green: %2, Blue: %3")
316 .arg(QString::number(value.r(), 'f', 2))
317 .arg(QString::number(value.g(), 'f', 2))
318 .arg(QString::number(value.b(), 'f', 2));
319 }
320 case Format::RGB5A1: {
321 auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
322 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
323 .arg(QString::number(value.r(), 'f', 2))
324 .arg(QString::number(value.g(), 'f', 2))
325 .arg(QString::number(value.b(), 'f', 2))
326 .arg(QString::number(value.a(), 'f', 2));
327 }
328 case Format::RGB565: {
329 auto value = Color::DecodeRGB565(pixel) / 255.0f;
330 return QString("Red: %1, Green: %2, Blue: %3")
331 .arg(QString::number(value.r(), 'f', 2))
332 .arg(QString::number(value.g(), 'f', 2))
333 .arg(QString::number(value.b(), 'f', 2));
334 }
335 case Format::RGBA4: {
336 auto value = Color::DecodeRGBA4(pixel) / 255.0f;
337 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
338 .arg(QString::number(value.r(), 'f', 2))
339 .arg(QString::number(value.g(), 'f', 2))
340 .arg(QString::number(value.b(), 'f', 2))
341 .arg(QString::number(value.a(), 'f', 2));
342 }
343 case Format::IA8:
344 return QString("Index: %1, Alpha: %2").arg(pixel[0]).arg(pixel[1]);
345 case Format::RG8: {
346 auto value = Color::DecodeRG8(pixel) / 255.0f;
347 return QString("Red: %1, Green: %2")
348 .arg(QString::number(value.r(), 'f', 2))
349 .arg(QString::number(value.g(), 'f', 2));
350 }
351 case Format::I8:
352 return QString("Index: %1").arg(*pixel);
353 case Format::A8:
354 return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
355 case Format::IA4:
356 return QString("Index: %1, Alpha: %2").arg(*pixel & 0xF).arg((*pixel & 0xF0) >> 4);
357 case Format::I4: {
358 u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
359 return QString("Index: %1").arg(i);
360 }
361 case Format::A4: {
362 u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
363 return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
364 }
365 case Format::ETC1:
366 case Format::ETC1A4:
367 // TODO: Display block information or channel values?
368 return QString("Compressed data");
369 case Format::D16: {
370 auto value = Color::DecodeD16(pixel);
371 return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
372 }
373 case Format::D24: {
374 auto value = Color::DecodeD24(pixel);
375 return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
376 }
377 case Format::D24X8:
378 case Format::X24S8: {
379 auto values = Color::DecodeD24S8(pixel);
380 return QString("Depth: %1, Stencil: %2")
381 .arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4))
382 .arg(values[1]);
383 }
384 case Format::Unknown:
385 return QString("Unknown format");
386 default:
387 return QString("Unhandled format");
388 }
389 return QString("");
390 };
391
392 QString nibbles = "";
393 for (unsigned i = 0; i < nibbles_per_pixel; i++) {
394 unsigned nibble_index = i;
395 if (nibble_mode) {
396 nibble_index += (offset % 2) ? 0 : 1;
397 }
398 u8 byte = pixel[nibble_index / 2];
399 u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF;
400 nibbles.append(QString::number(nibble, 16).toUpper());
401 }
402
403 surface_info_label->setText(
404 QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
405 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
406}
407
408void GraphicsSurfaceWidget::OnUpdate() {
409 QPixmap pixmap;
410
411 switch (surface_source) {
412 case Source::ColorBuffer: {
413 // TODO: Store a reference to the registers in the debug context instead of accessing them
414 // directly...
415
416 const auto& framebuffer = Pica::g_state.regs.framebuffer;
417
418 surface_address = framebuffer.GetColorBufferPhysicalAddress();
419 surface_width = framebuffer.GetWidth();
420 surface_height = framebuffer.GetHeight();
421
422 switch (framebuffer.color_format) {
423 case Pica::Regs::ColorFormat::RGBA8:
424 surface_format = Format::RGBA8;
425 break;
426
427 case Pica::Regs::ColorFormat::RGB8:
428 surface_format = Format::RGB8;
429 break;
430
431 case Pica::Regs::ColorFormat::RGB5A1:
432 surface_format = Format::RGB5A1;
433 break;
434
435 case Pica::Regs::ColorFormat::RGB565:
436 surface_format = Format::RGB565;
437 break;
438
439 case Pica::Regs::ColorFormat::RGBA4:
440 surface_format = Format::RGBA4;
441 break;
442
443 default:
444 surface_format = Format::Unknown;
445 break;
446 }
447
448 break;
449 }
450
451 case Source::DepthBuffer: {
452 const auto& framebuffer = Pica::g_state.regs.framebuffer;
453
454 surface_address = framebuffer.GetDepthBufferPhysicalAddress();
455 surface_width = framebuffer.GetWidth();
456 surface_height = framebuffer.GetHeight();
457
458 switch (framebuffer.depth_format) {
459 case Pica::Regs::DepthFormat::D16:
460 surface_format = Format::D16;
461 break;
462
463 case Pica::Regs::DepthFormat::D24:
464 surface_format = Format::D24;
465 break;
466
467 case Pica::Regs::DepthFormat::D24S8:
468 surface_format = Format::D24X8;
469 break;
470
471 default:
472 surface_format = Format::Unknown;
473 break;
474 }
475
476 break;
477 }
478
479 case Source::StencilBuffer: {
480 const auto& framebuffer = Pica::g_state.regs.framebuffer;
481
482 surface_address = framebuffer.GetDepthBufferPhysicalAddress();
483 surface_width = framebuffer.GetWidth();
484 surface_height = framebuffer.GetHeight();
485
486 switch (framebuffer.depth_format) {
487 case Pica::Regs::DepthFormat::D24S8:
488 surface_format = Format::X24S8;
489 break;
490
491 default:
492 surface_format = Format::Unknown;
493 break;
494 }
495
496 break;
497 }
498
499 case Source::Texture0:
500 case Source::Texture1:
501 case Source::Texture2: {
502 unsigned texture_index;
503 if (surface_source == Source::Texture0)
504 texture_index = 0;
505 else if (surface_source == Source::Texture1)
506 texture_index = 1;
507 else if (surface_source == Source::Texture2)
508 texture_index = 2;
509 else {
510 qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
511 break;
512 }
513
514 const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
515 auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
516
517 surface_address = info.physical_address;
518 surface_width = info.width;
519 surface_height = info.height;
520 surface_format = static_cast<Format>(info.format);
521
522 if (surface_format > Format::MaxTextureFormat) {
523 qDebug() << "Unknown texture format " << static_cast<int>(info.format);
524 }
525 break;
526 }
527
528 case Source::Custom: {
529 // Keep user-specified values
530 break;
531 }
532
533 default:
534 qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
535 break;
536 }
537
538 surface_address_control->SetValue(surface_address);
539 surface_width_control->setValue(surface_width);
540 surface_height_control->setValue(surface_height);
541 surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
542
543 // TODO: Implement a good way to visualize alpha components!
544
545 QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
546 u8* buffer = Memory::GetPhysicalPointer(surface_address);
547
548 if (buffer == nullptr) {
549 surface_picture_label->hide();
550 surface_info_label->setText(tr("(invalid surface address)"));
551 surface_info_label->setAlignment(Qt::AlignCenter);
552 surface_picker_x_control->setEnabled(false);
553 surface_picker_y_control->setEnabled(false);
554 save_surface->setEnabled(false);
555 return;
556 }
557
558 if (surface_format == Format::Unknown) {
559 surface_picture_label->hide();
560 surface_info_label->setText(tr("(unknown surface format)"));
561 surface_info_label->setAlignment(Qt::AlignCenter);
562 surface_picker_x_control->setEnabled(false);
563 surface_picker_y_control->setEnabled(false);
564 save_surface->setEnabled(false);
565 return;
566 }
567
568 surface_picture_label->show();
569
570 unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
571 unsigned stride = nibbles_per_pixel * surface_width / 2;
572
573 // We handle depth formats here because DebugUtils only supports TextureFormats
574 if (surface_format <= Format::MaxTextureFormat) {
575
576 // Generate a virtual texture
577 Pica::DebugUtils::TextureInfo info;
578 info.physical_address = surface_address;
579 info.width = surface_width;
580 info.height = surface_height;
581 info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
582 info.stride = stride;
583
584 for (unsigned int y = 0; y < surface_height; ++y) {
585 for (unsigned int x = 0; x < surface_width; ++x) {
586 Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true);
587 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
588 }
589 }
590
591 } else {
592
593 ASSERT_MSG(nibbles_per_pixel >= 2,
594 "Depth decoder only supports formats with at least one byte per pixel");
595 unsigned bytes_per_pixel = nibbles_per_pixel / 2;
596
597 for (unsigned int y = 0; y < surface_height; ++y) {
598 for (unsigned int x = 0; x < surface_width; ++x) {
599 const u32 coarse_y = y & ~7;
600 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
601 const u8* pixel = buffer + offset;
602 Math::Vec4<u8> color = {0, 0, 0, 0};
603
604 switch (surface_format) {
605 case Format::D16: {
606 u32 data = Color::DecodeD16(pixel);
607 color.r() = data & 0xFF;
608 color.g() = (data >> 8) & 0xFF;
609 break;
610 }
611 case Format::D24: {
612 u32 data = Color::DecodeD24(pixel);
613 color.r() = data & 0xFF;
614 color.g() = (data >> 8) & 0xFF;
615 color.b() = (data >> 16) & 0xFF;
616 break;
617 }
618 case Format::D24X8: {
619 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
620 color.r() = data.x & 0xFF;
621 color.g() = (data.x >> 8) & 0xFF;
622 color.b() = (data.x >> 16) & 0xFF;
623 break;
624 }
625 case Format::X24S8: {
626 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
627 color.r() = color.g() = color.b() = data.y;
628 break;
629 }
630 default:
631 qDebug() << "Unknown surface format " << static_cast<int>(surface_format);
632 break;
633 }
634
635 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
636 }
637 }
638 }
639
640 pixmap = QPixmap::fromImage(decoded_image);
641 surface_picture_label->setPixmap(pixmap);
642 surface_picture_label->resize(pixmap.size());
643
644 // Update the info with pixel data
645 surface_picker_x_control->setEnabled(true);
646 surface_picker_y_control->setEnabled(true);
647 Pick(surface_picker_x, surface_picker_y);
648
649 // Enable saving the converted pixmap to file
650 save_surface->setEnabled(true);
651}
652
653void GraphicsSurfaceWidget::SaveSurface() {
654 QString png_filter = tr("Portable Network Graphic (*.png)");
655 QString bin_filter = tr("Binary data (*.bin)");
656
657 QString selectedFilter;
658 QString filename = QFileDialog::getSaveFileName(
659 this, tr("Save Surface"),
660 QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
661 QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
662
663 if (filename.isEmpty()) {
664 // If the user canceled the dialog, don't save anything.
665 return;
666 }
667
668 if (selectedFilter == png_filter) {
669 const QPixmap* pixmap = surface_picture_label->pixmap();
670 ASSERT_MSG(pixmap != nullptr, "No pixmap set");
671
672 QFile file(filename);
673 file.open(QIODevice::WriteOnly);
674 if (pixmap)
675 pixmap->save(&file, "PNG");
676 } else if (selectedFilter == bin_filter) {
677 const u8* buffer = Memory::GetPhysicalPointer(surface_address);
678 ASSERT_MSG(buffer != nullptr, "Memory not accessible");
679
680 QFile file(filename);
681 file.open(QIODevice::WriteOnly);
682 int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2;
683 QByteArray data(reinterpret_cast<const char*>(buffer), size);
684 file.write(data);
685 } else {
686 UNREACHABLE_MSG("Unhandled filter selected");
687 }
688}
689
690unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) {
691 if (format <= Format::MaxTextureFormat) {
692 return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format));
693 }
694
695 switch (format) {
696 case Format::D24X8:
697 case Format::X24S8:
698 return 4 * 2;
699 case Format::D24:
700 return 3 * 2;
701 case Format::D16:
702 return 2 * 2;
703 default:
704 UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this should not be reached as this "
705 "function should be given a format which is in "
706 "GraphicsSurfaceWidget::Format. Instead got %i",
707 static_cast<int>(format));
708 return 0;
709 }
710}
diff --git a/src/citra_qt/debugger/graphics/graphics_surface.h b/src/citra_qt/debugger/graphics/graphics_surface.h
new file mode 100644
index 000000000..28f5650a7
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_surface.h
@@ -0,0 +1,118 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QLabel>
8#include <QPushButton>
9#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h"
10
11class QComboBox;
12class QSpinBox;
13class CSpinBox;
14
15class GraphicsSurfaceWidget;
16
17class SurfacePicture : public QLabel {
18 Q_OBJECT
19
20public:
21 explicit SurfacePicture(QWidget* parent = nullptr,
22 GraphicsSurfaceWidget* surface_widget = nullptr);
23 ~SurfacePicture();
24
25protected slots:
26 virtual void mouseMoveEvent(QMouseEvent* event);
27 virtual void mousePressEvent(QMouseEvent* event);
28
29private:
30 GraphicsSurfaceWidget* surface_widget;
31};
32
33class GraphicsSurfaceWidget : public BreakPointObserverDock {
34 Q_OBJECT
35
36 using Event = Pica::DebugContext::Event;
37
38 enum class Source {
39 ColorBuffer = 0,
40 DepthBuffer = 1,
41 StencilBuffer = 2,
42 Texture0 = 3,
43 Texture1 = 4,
44 Texture2 = 5,
45 Custom = 6,
46 };
47
48 enum class Format {
49 // These must match the TextureFormat type!
50 RGBA8 = 0,
51 RGB8 = 1,
52 RGB5A1 = 2,
53 RGB565 = 3,
54 RGBA4 = 4,
55 IA8 = 5,
56 RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
57 I8 = 7,
58 A8 = 8,
59 IA4 = 9,
60 I4 = 10,
61 A4 = 11,
62 ETC1 = 12, // compressed
63 ETC1A4 = 13,
64 MaxTextureFormat = 13,
65 D16 = 14,
66 D24 = 15,
67 D24X8 = 16,
68 X24S8 = 17,
69 Unknown = 18,
70 };
71
72 static unsigned int NibblesPerPixel(Format format);
73
74public:
75 explicit GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
76 QWidget* parent = nullptr);
77 void Pick(int x, int y);
78
79public slots:
80 void OnSurfaceSourceChanged(int new_value);
81 void OnSurfaceAddressChanged(qint64 new_value);
82 void OnSurfaceWidthChanged(int new_value);
83 void OnSurfaceHeightChanged(int new_value);
84 void OnSurfaceFormatChanged(int new_value);
85 void OnSurfacePickerXChanged(int new_value);
86 void OnSurfacePickerYChanged(int new_value);
87 void OnUpdate();
88
89private slots:
90 void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
91 void OnResumed() override;
92
93 void SaveSurface();
94
95signals:
96 void Update();
97
98private:
99 QComboBox* surface_source_list;
100 CSpinBox* surface_address_control;
101 QSpinBox* surface_width_control;
102 QSpinBox* surface_height_control;
103 QComboBox* surface_format_control;
104
105 SurfacePicture* surface_picture_label;
106 QSpinBox* surface_picker_x_control;
107 QSpinBox* surface_picker_y_control;
108 QLabel* surface_info_label;
109 QPushButton* save_surface;
110
111 Source surface_source;
112 unsigned surface_address;
113 unsigned surface_width;
114 unsigned surface_height;
115 Format surface_format;
116 int surface_picker_x = 0;
117 int surface_picker_y = 0;
118};
diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
new file mode 100644
index 000000000..716ed50b8
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
@@ -0,0 +1,178 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <iterator>
8#include <memory>
9#include <QBoxLayout>
10#include <QComboBox>
11#include <QFileDialog>
12#include <QMessageBox>
13#include <QPushButton>
14#include <boost/range/algorithm/copy.hpp>
15#include "citra_qt/debugger/graphics/graphics_tracing.h"
16#include "common/common_types.h"
17#include "core/hw/gpu.h"
18#include "core/hw/lcd.h"
19#include "core/tracer/recorder.h"
20#include "nihstro/float24.h"
21#include "video_core/pica.h"
22#include "video_core/pica_state.h"
23
24GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context,
25 QWidget* parent)
26 : BreakPointObserverDock(debug_context, tr("CiTrace Recorder"), parent) {
27
28 setObjectName("CiTracing");
29
30 QPushButton* start_recording = new QPushButton(tr("Start Recording"));
31 QPushButton* stop_recording =
32 new QPushButton(QIcon::fromTheme("document-save"), tr("Stop and Save"));
33 QPushButton* abort_recording = new QPushButton(tr("Abort Recording"));
34
35 connect(this, SIGNAL(SetStartTracingButtonEnabled(bool)), start_recording,
36 SLOT(setVisible(bool)));
37 connect(this, SIGNAL(SetStopTracingButtonEnabled(bool)), stop_recording,
38 SLOT(setVisible(bool)));
39 connect(this, SIGNAL(SetAbortTracingButtonEnabled(bool)), abort_recording,
40 SLOT(setVisible(bool)));
41 connect(start_recording, SIGNAL(clicked()), this, SLOT(StartRecording()));
42 connect(stop_recording, SIGNAL(clicked()), this, SLOT(StopRecording()));
43 connect(abort_recording, SIGNAL(clicked()), this, SLOT(AbortRecording()));
44
45 stop_recording->setVisible(false);
46 abort_recording->setVisible(false);
47
48 auto main_widget = new QWidget;
49 auto main_layout = new QVBoxLayout;
50 {
51 auto sub_layout = new QHBoxLayout;
52 sub_layout->addWidget(start_recording);
53 sub_layout->addWidget(stop_recording);
54 sub_layout->addWidget(abort_recording);
55 main_layout->addLayout(sub_layout);
56 }
57 main_widget->setLayout(main_layout);
58 setWidget(main_widget);
59}
60
61void GraphicsTracingWidget::StartRecording() {
62 auto context = context_weak.lock();
63 if (!context)
64 return;
65
66 auto shader_binary = Pica::g_state.vs.program_code;
67 auto swizzle_data = Pica::g_state.vs.swizzle_data;
68
69 // Encode floating point numbers to 24-bit values
70 // TODO: Drop this explicit conversion once we store float24 values bit-correctly internally.
71 std::array<u32, 4 * 16> default_attributes;
72 for (unsigned i = 0; i < 16; ++i) {
73 for (unsigned comp = 0; comp < 3; ++comp) {
74 default_attributes[4 * i + comp] =
75 nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32());
76 }
77 }
78
79 std::array<u32, 4 * 96> vs_float_uniforms;
80 for (unsigned i = 0; i < 96; ++i)
81 for (unsigned comp = 0; comp < 3; ++comp)
82 vs_float_uniforms[4 * i + comp] =
83 nihstro::to_float24(Pica::g_state.vs.uniforms.f[i][comp].ToFloat32());
84
85 CiTrace::Recorder::InitialState state;
86 std::copy_n((u32*)&GPU::g_regs, sizeof(GPU::g_regs) / sizeof(u32),
87 std::back_inserter(state.gpu_registers));
88 std::copy_n((u32*)&LCD::g_regs, sizeof(LCD::g_regs) / sizeof(u32),
89 std::back_inserter(state.lcd_registers));
90 std::copy_n((u32*)&Pica::g_state.regs, sizeof(Pica::g_state.regs) / sizeof(u32),
91 std::back_inserter(state.pica_registers));
92 boost::copy(default_attributes, std::back_inserter(state.default_attributes));
93 boost::copy(shader_binary, std::back_inserter(state.vs_program_binary));
94 boost::copy(swizzle_data, std::back_inserter(state.vs_swizzle_data));
95 boost::copy(vs_float_uniforms, std::back_inserter(state.vs_float_uniforms));
96 // boost::copy(TODO: Not implemented, std::back_inserter(state.gs_program_binary));
97 // boost::copy(TODO: Not implemented, std::back_inserter(state.gs_swizzle_data));
98 // boost::copy(TODO: Not implemented, std::back_inserter(state.gs_float_uniforms));
99
100 auto recorder = new CiTrace::Recorder(state);
101 context->recorder = std::shared_ptr<CiTrace::Recorder>(recorder);
102
103 emit SetStartTracingButtonEnabled(false);
104 emit SetStopTracingButtonEnabled(true);
105 emit SetAbortTracingButtonEnabled(true);
106}
107
108void GraphicsTracingWidget::StopRecording() {
109 auto context = context_weak.lock();
110 if (!context)
111 return;
112
113 QString filename = QFileDialog::getSaveFileName(this, tr("Save CiTrace"), "citrace.ctf",
114 tr("CiTrace File (*.ctf)"));
115
116 if (filename.isEmpty()) {
117 // If the user canceled the dialog, keep recording
118 return;
119 }
120
121 context->recorder->Finish(filename.toStdString());
122 context->recorder = nullptr;
123
124 emit SetStopTracingButtonEnabled(false);
125 emit SetAbortTracingButtonEnabled(false);
126 emit SetStartTracingButtonEnabled(true);
127}
128
129void GraphicsTracingWidget::AbortRecording() {
130 auto context = context_weak.lock();
131 if (!context)
132 return;
133
134 context->recorder = nullptr;
135
136 emit SetStopTracingButtonEnabled(false);
137 emit SetAbortTracingButtonEnabled(false);
138 emit SetStartTracingButtonEnabled(true);
139}
140
141void GraphicsTracingWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
142 widget()->setEnabled(true);
143}
144
145void GraphicsTracingWidget::OnResumed() {
146 widget()->setEnabled(false);
147}
148
149void GraphicsTracingWidget::OnEmulationStarting(EmuThread* emu_thread) {
150 // Disable tracing starting/stopping until a GPU breakpoint is reached
151 widget()->setEnabled(false);
152}
153
154void GraphicsTracingWidget::OnEmulationStopping() {
155 // TODO: Is it safe to access the context here?
156
157 auto context = context_weak.lock();
158 if (!context)
159 return;
160
161 if (context->recorder) {
162 auto reply =
163 QMessageBox::question(this, tr("CiTracing still active"),
164 tr("A CiTrace is still being recorded. Do you want to save it? "
165 "If not, all recorded data will be discarded."),
166 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
167
168 if (reply == QMessageBox::Yes) {
169 StopRecording();
170 } else {
171 AbortRecording();
172 }
173 }
174
175 // If the widget was disabled before, enable it now to allow starting
176 // tracing before starting the next emulation session
177 widget()->setEnabled(true);
178}
diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.h b/src/citra_qt/debugger/graphics/graphics_tracing.h
new file mode 100644
index 000000000..3f73bcd2e
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_tracing.h
@@ -0,0 +1,33 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h"
8
9class EmuThread;
10
11class GraphicsTracingWidget : public BreakPointObserverDock {
12 Q_OBJECT
13
14public:
15 explicit GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context,
16 QWidget* parent = nullptr);
17
18private slots:
19 void StartRecording();
20 void StopRecording();
21 void AbortRecording();
22
23 void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
24 void OnResumed() override;
25
26 void OnEmulationStarting(EmuThread* emu_thread);
27 void OnEmulationStopping();
28
29signals:
30 void SetStartTracingButtonEnabled(bool enable);
31 void SetStopTracingButtonEnabled(bool enable);
32 void SetAbortTracingButtonEnabled(bool enable);
33};
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
new file mode 100644
index 000000000..b75b94ef8
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
@@ -0,0 +1,629 @@
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.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}
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h
new file mode 100644
index 000000000..bedea0bed
--- /dev/null
+++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h
@@ -0,0 +1,87 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QAbstractTableModel>
8#include <QTreeView>
9#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h"
10#include "nihstro/parser_shbin.h"
11#include "video_core/shader/shader.h"
12
13class QLabel;
14class QSpinBox;
15
16class GraphicsVertexShaderWidget;
17
18class GraphicsVertexShaderModel : public QAbstractTableModel {
19 Q_OBJECT
20
21public:
22 explicit GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent);
23
24 int columnCount(const QModelIndex& parent = QModelIndex()) const override;
25 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
26 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
27 QVariant headerData(int section, Qt::Orientation orientation,
28 int role = Qt::DisplayRole) const override;
29
30private:
31 GraphicsVertexShaderWidget* par;
32
33 friend class GraphicsVertexShaderWidget;
34};
35
36class GraphicsVertexShaderWidget : public BreakPointObserverDock {
37 Q_OBJECT
38
39 using Event = Pica::DebugContext::Event;
40
41public:
42 GraphicsVertexShaderWidget(std::shared_ptr<Pica::DebugContext> debug_context,
43 QWidget* parent = nullptr);
44
45private slots:
46 void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
47 void OnResumed() override;
48
49 void OnInputAttributeChanged(int index);
50
51 void OnCycleIndexChanged(int index);
52
53 void DumpShader();
54
55 /**
56 * Reload widget based on the current PICA200 state
57 * @param replace_vertex_data If true, invalidate all current vertex data
58 * @param vertex_data New vertex data to use, as passed to OnBreakPointHit. May be nullptr to
59 * specify that no valid vertex data can be retrieved currently. Only used if
60 * replace_vertex_data is true.
61 */
62 void Reload(bool replace_vertex_data = false, void* vertex_data = nullptr);
63
64private:
65 QLabel* instruction_description;
66 QTreeView* binary_list;
67 GraphicsVertexShaderModel* model;
68
69 /// TODO: Move these into a single struct
70 std::array<QLineEdit*, 4 * 16>
71 input_data; // A text box for each of the 4 components of up to 16 vertex attributes
72 std::array<QWidget*, 16>
73 input_data_container; // QWidget containing the QLayout containing each vertex attribute
74 std::array<QLabel*, 16> input_data_mapping; // A QLabel denoting the shader input attribute
75 // which the vertex attribute maps to
76
77 // Text to be shown when input vertex data is not retrievable
78 QLabel* breakpoint_warning;
79
80 QSpinBox* cycle_index;
81
82 nihstro::ShaderInfo info;
83 Pica::Shader::DebugData<true> debug_data;
84 Pica::Shader::InputVertex input_vertex;
85
86 friend class GraphicsVertexShaderModel;
87};