summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/citra_qt/CMakeLists.txt4
-rw-r--r--src/citra_qt/debugger/graphics_cmdlists.cpp125
-rw-r--r--src/citra_qt/debugger/graphics_cmdlists.h22
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.cpp356
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.h76
-rw-r--r--src/citra_qt/debugger/graphics_surface.cpp736
-rw-r--r--src/citra_qt/debugger/graphics_surface.h120
-rw-r--r--src/citra_qt/main.cpp19
-rw-r--r--src/citra_qt/main.h1
9 files changed, 876 insertions, 583 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 585686627..43a766053 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -9,7 +9,7 @@ set(SRCS
9 debugger/graphics_breakpoint_observer.cpp 9 debugger/graphics_breakpoint_observer.cpp
10 debugger/graphics_breakpoints.cpp 10 debugger/graphics_breakpoints.cpp
11 debugger/graphics_cmdlists.cpp 11 debugger/graphics_cmdlists.cpp
12 debugger/graphics_framebuffer.cpp 12 debugger/graphics_surface.cpp
13 debugger/graphics_tracing.cpp 13 debugger/graphics_tracing.cpp
14 debugger/graphics_vertex_shader.cpp 14 debugger/graphics_vertex_shader.cpp
15 debugger/profiler.cpp 15 debugger/profiler.cpp
@@ -39,7 +39,7 @@ set(HEADERS
39 debugger/graphics_breakpoints.h 39 debugger/graphics_breakpoints.h
40 debugger/graphics_breakpoints_p.h 40 debugger/graphics_breakpoints_p.h
41 debugger/graphics_cmdlists.h 41 debugger/graphics_cmdlists.h
42 debugger/graphics_framebuffer.h 42 debugger/graphics_surface.h
43 debugger/graphics_tracing.h 43 debugger/graphics_tracing.h
44 debugger/graphics_vertex_shader.h 44 debugger/graphics_vertex_shader.h
45 debugger/profiler.h 45 debugger/profiler.h
diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp
index 5186d2b44..3e0a0a145 100644
--- a/src/citra_qt/debugger/graphics_cmdlists.cpp
+++ b/src/citra_qt/debugger/graphics_cmdlists.cpp
@@ -50,123 +50,6 @@ public:
50 } 50 }
51}; 51};
52 52
53TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent)
54 : QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))),
55 info(info) {
56
57 QWidget* main_widget = new QWidget;
58
59 QLabel* image_widget = new QLabel;
60
61 connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&)));
62
63 CSpinBox* phys_address_spinbox = new CSpinBox;
64 phys_address_spinbox->SetBase(16);
65 phys_address_spinbox->SetRange(0, 0xFFFFFFFF);
66 phys_address_spinbox->SetPrefix("0x");
67 phys_address_spinbox->SetValue(info.physical_address);
68 connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64)));
69
70 QComboBox* format_choice = new QComboBox;
71 format_choice->addItem(tr("RGBA8"));
72 format_choice->addItem(tr("RGB8"));
73 format_choice->addItem(tr("RGB5A1"));
74 format_choice->addItem(tr("RGB565"));
75 format_choice->addItem(tr("RGBA4"));
76 format_choice->addItem(tr("IA8"));
77 format_choice->addItem(tr("RG8"));
78 format_choice->addItem(tr("I8"));
79 format_choice->addItem(tr("A8"));
80 format_choice->addItem(tr("IA4"));
81 format_choice->addItem(tr("I4"));
82 format_choice->addItem(tr("A4"));
83 format_choice->addItem(tr("ETC1"));
84 format_choice->addItem(tr("ETC1A4"));
85 format_choice->setCurrentIndex(static_cast<int>(info.format));
86 connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int)));
87
88 QSpinBox* width_spinbox = new QSpinBox;
89 width_spinbox->setMaximum(65535);
90 width_spinbox->setValue(info.width);
91 connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int)));
92
93 QSpinBox* height_spinbox = new QSpinBox;
94 height_spinbox->setMaximum(65535);
95 height_spinbox->setValue(info.height);
96 connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int)));
97
98 QSpinBox* stride_spinbox = new QSpinBox;
99 stride_spinbox->setMaximum(65535 * 4);
100 stride_spinbox->setValue(info.stride);
101 connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int)));
102
103 QVBoxLayout* main_layout = new QVBoxLayout;
104 main_layout->addWidget(image_widget);
105
106 {
107 QHBoxLayout* sub_layout = new QHBoxLayout;
108 sub_layout->addWidget(new QLabel(tr("Source Address:")));
109 sub_layout->addWidget(phys_address_spinbox);
110 main_layout->addLayout(sub_layout);
111 }
112
113 {
114 QHBoxLayout* sub_layout = new QHBoxLayout;
115 sub_layout->addWidget(new QLabel(tr("Format")));
116 sub_layout->addWidget(format_choice);
117 main_layout->addLayout(sub_layout);
118 }
119
120 {
121 QHBoxLayout* sub_layout = new QHBoxLayout;
122 sub_layout->addWidget(new QLabel(tr("Width:")));
123 sub_layout->addWidget(width_spinbox);
124 sub_layout->addStretch();
125 sub_layout->addWidget(new QLabel(tr("Height:")));
126 sub_layout->addWidget(height_spinbox);
127 sub_layout->addStretch();
128 sub_layout->addWidget(new QLabel(tr("Stride:")));
129 sub_layout->addWidget(stride_spinbox);
130 main_layout->addLayout(sub_layout);
131 }
132
133 main_widget->setLayout(main_layout);
134
135 emit UpdatePixmap(ReloadPixmap());
136
137 setWidget(main_widget);
138}
139
140void TextureInfoDockWidget::OnAddressChanged(qint64 value) {
141 info.physical_address = value;
142 emit UpdatePixmap(ReloadPixmap());
143}
144
145void TextureInfoDockWidget::OnFormatChanged(int value) {
146 info.format = static_cast<Pica::Regs::TextureFormat>(value);
147 emit UpdatePixmap(ReloadPixmap());
148}
149
150void TextureInfoDockWidget::OnWidthChanged(int value) {
151 info.width = value;
152 emit UpdatePixmap(ReloadPixmap());
153}
154
155void TextureInfoDockWidget::OnHeightChanged(int value) {
156 info.height = value;
157 emit UpdatePixmap(ReloadPixmap());
158}
159
160void TextureInfoDockWidget::OnStrideChanged(int value) {
161 info.stride = value;
162 emit UpdatePixmap(ReloadPixmap());
163}
164
165QPixmap TextureInfoDockWidget::ReloadPixmap() const {
166 u8* src = Memory::GetPhysicalPointer(info.physical_address);
167 return QPixmap::fromImage(LoadTexture(src, info));
168}
169
170GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { 53GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
171 54
172} 55}
@@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
249 index = 0; 132 index = 0;
250 } else if (COMMAND_IN_RANGE(command_id, texture1)) { 133 } else if (COMMAND_IN_RANGE(command_id, texture1)) {
251 index = 1; 134 index = 1;
252 } else { 135 } else if (COMMAND_IN_RANGE(command_id, texture2)) {
253 index = 2; 136 index = 2;
137 } else {
138 UNREACHABLE_MSG("Unknown texture command");
254 } 139 }
255 auto config = Pica::g_state.regs.GetTextures()[index].config; 140 auto config = Pica::g_state.regs.GetTextures()[index].config;
256 auto format = Pica::g_state.regs.GetTextures()[index].format; 141 auto format = Pica::g_state.regs.GetTextures()[index].format;
257 auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); 142 auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
258 143
259 // TODO: Instead, emit a signal here to be caught by the main window widget. 144 // TODO: Open a surface debugger
260 auto main_window = static_cast<QMainWindow*>(parent());
261 main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window));
262 } 145 }
263} 146}
264 147
diff --git a/src/citra_qt/debugger/graphics_cmdlists.h b/src/citra_qt/debugger/graphics_cmdlists.h
index 586cc7239..8a2a294b9 100644
--- a/src/citra_qt/debugger/graphics_cmdlists.h
+++ b/src/citra_qt/debugger/graphics_cmdlists.h
@@ -61,25 +61,3 @@ private:
61 QWidget* command_info_widget; 61 QWidget* command_info_widget;
62 QPushButton* toggle_tracing; 62 QPushButton* toggle_tracing;
63}; 63};
64
65class TextureInfoDockWidget : public QDockWidget {
66 Q_OBJECT
67
68public:
69 TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr);
70
71signals:
72 void UpdatePixmap(const QPixmap& pixmap);
73
74private slots:
75 void OnAddressChanged(qint64 value);
76 void OnFormatChanged(int value);
77 void OnWidthChanged(int value);
78 void OnHeightChanged(int value);
79 void OnStrideChanged(int value);
80
81private:
82 QPixmap ReloadPixmap() const;
83
84 Pica::DebugUtils::TextureInfo info;
85};
diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp
deleted file mode 100644
index 68cff78b2..000000000
--- a/src/citra_qt/debugger/graphics_framebuffer.cpp
+++ /dev/null
@@ -1,356 +0,0 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QBoxLayout>
6#include <QComboBox>
7#include <QDebug>
8#include <QLabel>
9#include <QPushButton>
10#include <QSpinBox>
11
12#include "citra_qt/debugger/graphics_framebuffer.h"
13#include "citra_qt/util/spinbox.h"
14
15#include "common/color.h"
16
17#include "core/memory.h"
18#include "core/hw/gpu.h"
19
20#include "video_core/pica.h"
21#include "video_core/pica_state.h"
22#include "video_core/utils.h"
23
24GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context,
25 QWidget* parent)
26 : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent),
27 framebuffer_source(Source::PicaTarget)
28{
29 setObjectName("PicaFramebuffer");
30
31 framebuffer_source_list = new QComboBox;
32 framebuffer_source_list->addItem(tr("Active Render Target"));
33 framebuffer_source_list->addItem(tr("Active Depth Buffer"));
34 framebuffer_source_list->addItem(tr("Custom"));
35 framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source));
36
37 framebuffer_address_control = new CSpinBox;
38 framebuffer_address_control->SetBase(16);
39 framebuffer_address_control->SetRange(0, 0xFFFFFFFF);
40 framebuffer_address_control->SetPrefix("0x");
41
42 framebuffer_width_control = new QSpinBox;
43 framebuffer_width_control->setMinimum(1);
44 framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
45
46 framebuffer_height_control = new QSpinBox;
47 framebuffer_height_control->setMinimum(1);
48 framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
49
50 framebuffer_format_control = new QComboBox;
51 framebuffer_format_control->addItem(tr("RGBA8"));
52 framebuffer_format_control->addItem(tr("RGB8"));
53 framebuffer_format_control->addItem(tr("RGB5A1"));
54 framebuffer_format_control->addItem(tr("RGB565"));
55 framebuffer_format_control->addItem(tr("RGBA4"));
56 framebuffer_format_control->addItem(tr("D16"));
57 framebuffer_format_control->addItem(tr("D24"));
58 framebuffer_format_control->addItem(tr("D24X8"));
59 framebuffer_format_control->addItem(tr("X24S8"));
60 framebuffer_format_control->addItem(tr("(unknown)"));
61
62 // TODO: This QLabel should shrink the image to the available space rather than just expanding...
63 framebuffer_picture_label = new QLabel;
64
65 auto enlarge_button = new QPushButton(tr("Enlarge"));
66
67 // Connections
68 connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
69 connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int)));
70 connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64)));
71 connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int)));
72 connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int)));
73 connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int)));
74
75 auto main_widget = new QWidget;
76 auto main_layout = new QVBoxLayout;
77 {
78 auto sub_layout = new QHBoxLayout;
79 sub_layout->addWidget(new QLabel(tr("Source:")));
80 sub_layout->addWidget(framebuffer_source_list);
81 main_layout->addLayout(sub_layout);
82 }
83 {
84 auto sub_layout = new QHBoxLayout;
85 sub_layout->addWidget(new QLabel(tr("Virtual Address:")));
86 sub_layout->addWidget(framebuffer_address_control);
87 main_layout->addLayout(sub_layout);
88 }
89 {
90 auto sub_layout = new QHBoxLayout;
91 sub_layout->addWidget(new QLabel(tr("Width:")));
92 sub_layout->addWidget(framebuffer_width_control);
93 main_layout->addLayout(sub_layout);
94 }
95 {
96 auto sub_layout = new QHBoxLayout;
97 sub_layout->addWidget(new QLabel(tr("Height:")));
98 sub_layout->addWidget(framebuffer_height_control);
99 main_layout->addLayout(sub_layout);
100 }
101 {
102 auto sub_layout = new QHBoxLayout;
103 sub_layout->addWidget(new QLabel(tr("Format:")));
104 sub_layout->addWidget(framebuffer_format_control);
105 main_layout->addLayout(sub_layout);
106 }
107 main_layout->addWidget(framebuffer_picture_label);
108 main_layout->addWidget(enlarge_button);
109 main_widget->setLayout(main_layout);
110 setWidget(main_widget);
111
112 // Load current data - TODO: Make sure this works when emulation is not running
113 if (debug_context && debug_context->at_breakpoint)
114 emit Update();
115 widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint
116}
117
118void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
119{
120 emit Update();
121 widget()->setEnabled(true);
122}
123
124void GraphicsFramebufferWidget::OnResumed()
125{
126 widget()->setEnabled(false);
127}
128
129void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value)
130{
131 framebuffer_source = static_cast<Source>(new_value);
132 emit Update();
133}
134
135void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value)
136{
137 if (framebuffer_address != new_value) {
138 framebuffer_address = static_cast<unsigned>(new_value);
139
140 framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
141 emit Update();
142 }
143}
144
145void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value)
146{
147 if (framebuffer_width != static_cast<unsigned>(new_value)) {
148 framebuffer_width = static_cast<unsigned>(new_value);
149
150 framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
151 emit Update();
152 }
153}
154
155void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
156{
157 if (framebuffer_height != static_cast<unsigned>(new_value)) {
158 framebuffer_height = static_cast<unsigned>(new_value);
159
160 framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
161 emit Update();
162 }
163}
164
165void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value)
166{
167 if (framebuffer_format != static_cast<Format>(new_value)) {
168 framebuffer_format = static_cast<Format>(new_value);
169
170 framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
171 emit Update();
172 }
173}
174
175void GraphicsFramebufferWidget::OnUpdate()
176{
177 QPixmap pixmap;
178
179 switch (framebuffer_source) {
180 case Source::PicaTarget:
181 {
182 // TODO: Store a reference to the registers in the debug context instead of accessing them directly...
183
184 const auto& framebuffer = Pica::g_state.regs.framebuffer;
185
186 framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
187 framebuffer_width = framebuffer.GetWidth();
188 framebuffer_height = framebuffer.GetHeight();
189
190 switch (framebuffer.color_format) {
191 case Pica::Regs::ColorFormat::RGBA8:
192 framebuffer_format = Format::RGBA8;
193 break;
194
195 case Pica::Regs::ColorFormat::RGB8:
196 framebuffer_format = Format::RGB8;
197 break;
198
199 case Pica::Regs::ColorFormat::RGB5A1:
200 framebuffer_format = Format::RGB5A1;
201 break;
202
203 case Pica::Regs::ColorFormat::RGB565:
204 framebuffer_format = Format::RGB565;
205 break;
206
207 case Pica::Regs::ColorFormat::RGBA4:
208 framebuffer_format = Format::RGBA4;
209 break;
210
211 default:
212 framebuffer_format = Format::Unknown;
213 break;
214 }
215
216 break;
217 }
218
219 case Source::DepthBuffer:
220 {
221 const auto& framebuffer = Pica::g_state.regs.framebuffer;
222
223 framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress();
224 framebuffer_width = framebuffer.GetWidth();
225 framebuffer_height = framebuffer.GetHeight();
226
227 switch (framebuffer.depth_format) {
228 case Pica::Regs::DepthFormat::D16:
229 framebuffer_format = Format::D16;
230 break;
231
232 case Pica::Regs::DepthFormat::D24:
233 framebuffer_format = Format::D24;
234 break;
235
236 case Pica::Regs::DepthFormat::D24S8:
237 framebuffer_format = Format::D24X8;
238 break;
239
240 default:
241 framebuffer_format = Format::Unknown;
242 break;
243 }
244
245 break;
246 }
247
248 case Source::Custom:
249 {
250 // Keep user-specified values
251 break;
252 }
253
254 default:
255 qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source);
256 break;
257 }
258
259 // TODO: Implement a good way to visualize alpha components!
260 // TODO: Unify this decoding code with the texture decoder
261 u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format);
262
263 QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
264 u8* buffer = Memory::GetPhysicalPointer(framebuffer_address);
265
266 for (unsigned int y = 0; y < framebuffer_height; ++y) {
267 for (unsigned int x = 0; x < framebuffer_width; ++x) {
268 const u32 coarse_y = y & ~7;
269 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel;
270 const u8* pixel = buffer + offset;
271 Math::Vec4<u8> color = { 0, 0, 0, 0 };
272
273 switch (framebuffer_format) {
274 case Format::RGBA8:
275 color = Color::DecodeRGBA8(pixel);
276 break;
277 case Format::RGB8:
278 color = Color::DecodeRGB8(pixel);
279 break;
280 case Format::RGB5A1:
281 color = Color::DecodeRGB5A1(pixel);
282 break;
283 case Format::RGB565:
284 color = Color::DecodeRGB565(pixel);
285 break;
286 case Format::RGBA4:
287 color = Color::DecodeRGBA4(pixel);
288 break;
289 case Format::D16:
290 {
291 u32 data = Color::DecodeD16(pixel);
292 color.r() = data & 0xFF;
293 color.g() = (data >> 8) & 0xFF;
294 break;
295 }
296 case Format::D24:
297 {
298 u32 data = Color::DecodeD24(pixel);
299 color.r() = data & 0xFF;
300 color.g() = (data >> 8) & 0xFF;
301 color.b() = (data >> 16) & 0xFF;
302 break;
303 }
304 case Format::D24X8:
305 {
306 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
307 color.r() = data.x & 0xFF;
308 color.g() = (data.x >> 8) & 0xFF;
309 color.b() = (data.x >> 16) & 0xFF;
310 break;
311 }
312 case Format::X24S8:
313 {
314 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
315 color.r() = color.g() = color.b() = data.y;
316 break;
317 }
318 default:
319 qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format);
320 break;
321 }
322
323 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
324 }
325 }
326 pixmap = QPixmap::fromImage(decoded_image);
327
328 framebuffer_address_control->SetValue(framebuffer_address);
329 framebuffer_width_control->setValue(framebuffer_width);
330 framebuffer_height_control->setValue(framebuffer_height);
331 framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format));
332 framebuffer_picture_label->setPixmap(pixmap);
333}
334
335u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) {
336 switch (format) {
337 case Format::RGBA8:
338 case Format::D24X8:
339 case Format::X24S8:
340 return 4;
341 case Format::RGB8:
342 case Format::D24:
343 return 3;
344 case Format::RGB5A1:
345 case Format::RGB565:
346 case Format::RGBA4:
347 case Format::D16:
348 return 2;
349 default:
350 UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this "
351 "should not be reached as this function should "
352 "be given a format which is in "
353 "GraphicsFramebufferWidget::Format. Instead got %i",
354 static_cast<int>(format));
355 }
356}
diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h
deleted file mode 100644
index 5cd96f2e9..000000000
--- a/src/citra_qt/debugger/graphics_framebuffer.h
+++ /dev/null
@@ -1,76 +0,0 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "citra_qt/debugger/graphics_breakpoint_observer.h"
8
9class QComboBox;
10class QLabel;
11class QSpinBox;
12
13class CSpinBox;
14
15class GraphicsFramebufferWidget : public BreakPointObserverDock {
16 Q_OBJECT
17
18 using Event = Pica::DebugContext::Event;
19
20 enum class Source {
21 PicaTarget = 0,
22 DepthBuffer = 1,
23 Custom = 2,
24
25 // TODO: Add GPU framebuffer sources!
26 };
27
28 enum class Format {
29 RGBA8 = 0,
30 RGB8 = 1,
31 RGB5A1 = 2,
32 RGB565 = 3,
33 RGBA4 = 4,
34 D16 = 5,
35 D24 = 6,
36 D24X8 = 7,
37 X24S8 = 8,
38 Unknown = 9
39 };
40
41 static u32 BytesPerPixel(Format format);
42
43public:
44 GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
45
46public slots:
47 void OnFramebufferSourceChanged(int new_value);
48 void OnFramebufferAddressChanged(qint64 new_value);
49 void OnFramebufferWidthChanged(int new_value);
50 void OnFramebufferHeightChanged(int new_value);
51 void OnFramebufferFormatChanged(int new_value);
52 void OnUpdate();
53
54private slots:
55 void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
56 void OnResumed() override;
57
58signals:
59 void Update();
60
61private:
62
63 QComboBox* framebuffer_source_list;
64 CSpinBox* framebuffer_address_control;
65 QSpinBox* framebuffer_width_control;
66 QSpinBox* framebuffer_height_control;
67 QComboBox* framebuffer_format_control;
68
69 QLabel* framebuffer_picture_label;
70
71 Source framebuffer_source;
72 unsigned framebuffer_address;
73 unsigned framebuffer_width;
74 unsigned framebuffer_height;
75 Format framebuffer_format;
76};
diff --git a/src/citra_qt/debugger/graphics_surface.cpp b/src/citra_qt/debugger/graphics_surface.cpp
new file mode 100644
index 000000000..ac2d6f89b
--- /dev/null
+++ b/src/citra_qt/debugger/graphics_surface.cpp
@@ -0,0 +1,736 @@
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
15#include "citra_qt/debugger/graphics_surface.h"
16#include "citra_qt/util/spinbox.h"
17
18#include "common/color.h"
19
20#include "core/memory.h"
21#include "core/hw/gpu.h"
22
23#include "video_core/pica.h"
24#include "video_core/pica_state.h"
25#include "video_core/utils.h"
26
27SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {}
28SurfacePicture::~SurfacePicture() {}
29
30void SurfacePicture::mousePressEvent(QMouseEvent* event)
31{
32 // Only do something while the left mouse button is held down
33 if (!(event->buttons() & Qt::LeftButton))
34 return;
35
36 if (pixmap() == nullptr)
37 return;
38
39 if (surface_widget)
40 surface_widget->Pick(event->x() * pixmap()->width() / width(),
41 event->y() * pixmap()->height() / height());
42}
43
44void SurfacePicture::mouseMoveEvent(QMouseEvent* event)
45{
46 // We also want to handle the event if the user moves the mouse while holding down the LMB
47 mousePressEvent(event);
48}
49
50
51GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
52 QWidget* parent)
53 : BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
54 surface_source(Source::ColorBuffer)
55{
56 setObjectName("PicaSurface");
57
58 surface_source_list = new QComboBox;
59 surface_source_list->addItem(tr("Color Buffer"));
60 surface_source_list->addItem(tr("Depth Buffer"));
61 surface_source_list->addItem(tr("Stencil Buffer"));
62 surface_source_list->addItem(tr("Texture 0"));
63 surface_source_list->addItem(tr("Texture 1"));
64 surface_source_list->addItem(tr("Texture 2"));
65 surface_source_list->addItem(tr("Custom"));
66 surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
67
68 surface_address_control = new CSpinBox;
69 surface_address_control->SetBase(16);
70 surface_address_control->SetRange(0, 0xFFFFFFFF);
71 surface_address_control->SetPrefix("0x");
72
73 unsigned max_dimension = 16384; // TODO: Find actual maximum
74
75 surface_width_control = new QSpinBox;
76 surface_width_control->setRange(0, max_dimension);
77
78 surface_height_control = new QSpinBox;
79 surface_height_control->setRange(0, max_dimension);
80
81 surface_picker_x_control = new QSpinBox;
82 surface_picker_x_control->setRange(0, max_dimension - 1);
83
84 surface_picker_y_control = new QSpinBox;
85 surface_picker_y_control->setRange(0, max_dimension - 1);
86
87 surface_format_control = new QComboBox;
88
89 // Color formats sorted by Pica texture format index
90 surface_format_control->addItem(tr("RGBA8"));
91 surface_format_control->addItem(tr("RGB8"));
92 surface_format_control->addItem(tr("RGB5A1"));
93 surface_format_control->addItem(tr("RGB565"));
94 surface_format_control->addItem(tr("RGBA4"));
95 surface_format_control->addItem(tr("IA8"));
96 surface_format_control->addItem(tr("RG8"));
97 surface_format_control->addItem(tr("I8"));
98 surface_format_control->addItem(tr("A8"));
99 surface_format_control->addItem(tr("IA4"));
100 surface_format_control->addItem(tr("I4"));
101 surface_format_control->addItem(tr("A4"));
102 surface_format_control->addItem(tr("ETC1"));
103 surface_format_control->addItem(tr("ETC1A4"));
104 surface_format_control->addItem(tr("D16"));
105 surface_format_control->addItem(tr("D24"));
106 surface_format_control->addItem(tr("D24X8"));
107 surface_format_control->addItem(tr("X24S8"));
108 surface_format_control->addItem(tr("Unknown"));
109
110 surface_info_label = new QLabel();
111 surface_info_label->setWordWrap(true);
112
113 surface_picture_label = new SurfacePicture(0, this);
114 surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
115 surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
116 surface_picture_label->setScaledContents(false);
117
118 auto scroll_area = new QScrollArea();
119 scroll_area->setBackgroundRole(QPalette::Dark);
120 scroll_area->setWidgetResizable(false);
121 scroll_area->setWidget(surface_picture_label);
122
123 save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
124
125 // Connections
126 connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
127 connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int)));
128 connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64)));
129 connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int)));
130 connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int)));
131 connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int)));
132 connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int)));
133 connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, 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{
208 emit Update();
209 widget()->setEnabled(true);
210}
211
212void GraphicsSurfaceWidget::OnResumed()
213{
214 widget()->setEnabled(false);
215}
216
217void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value)
218{
219 surface_source = static_cast<Source>(new_value);
220 emit Update();
221}
222
223void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value)
224{
225 if (surface_address != new_value) {
226 surface_address = static_cast<unsigned>(new_value);
227
228 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
229 emit Update();
230 }
231}
232
233void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value)
234{
235 if (surface_width != static_cast<unsigned>(new_value)) {
236 surface_width = static_cast<unsigned>(new_value);
237
238 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
239 emit Update();
240 }
241}
242
243void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value)
244{
245 if (surface_height != static_cast<unsigned>(new_value)) {
246 surface_height = static_cast<unsigned>(new_value);
247
248 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
249 emit Update();
250 }
251}
252
253void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value)
254{
255 if (surface_format != static_cast<Format>(new_value)) {
256 surface_format = static_cast<Format>(new_value);
257
258 surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
259 emit Update();
260 }
261}
262
263void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value)
264{
265 if (surface_picker_x != new_value) {
266 surface_picker_x = new_value;
267 Pick(surface_picker_x, surface_picker_y);
268 }
269}
270
271void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value)
272{
273 if (surface_picker_y != new_value) {
274 surface_picker_y = new_value;
275 Pick(surface_picker_x, surface_picker_y);
276 }
277}
278
279void GraphicsSurfaceWidget::Pick(int x, int y)
280{
281 surface_picker_x_control->setValue(x);
282 surface_picker_y_control->setValue(y);
283
284 if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) {
285 surface_info_label->setText(tr("Pixel out of bounds"));
286 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
287 return;
288 }
289
290 u8* buffer = Memory::GetPhysicalPointer(surface_address);
291 if (buffer == nullptr) {
292 surface_info_label->setText(tr("(unable to access pixel data)"));
293 surface_info_label->setAlignment(Qt::AlignCenter);
294 return;
295 }
296
297 unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
298 unsigned stride = nibbles_per_pixel * surface_width / 2;
299
300 unsigned bytes_per_pixel;
301 bool nibble_mode = (nibbles_per_pixel == 1);
302 if (nibble_mode) {
303 // As nibbles are contained in a byte we still need to access one byte per nibble
304 bytes_per_pixel = 1;
305 } else {
306 bytes_per_pixel = nibbles_per_pixel / 2;
307 }
308
309 const u32 coarse_y = y & ~7;
310 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
311 const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset);
312
313 auto GetText = [offset](Format format, const u8* pixel) {
314 switch (format) {
315 case Format::RGBA8:
316 {
317 auto value = Color::DecodeRGBA8(pixel) / 255.0f;
318 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
319 .arg(QString::number(value.r(), 'f', 2))
320 .arg(QString::number(value.g(), 'f', 2))
321 .arg(QString::number(value.b(), 'f', 2))
322 .arg(QString::number(value.a(), 'f', 2));
323 }
324 case Format::RGB8:
325 {
326 auto value = Color::DecodeRGB8(pixel) / 255.0f;
327 return QString("Red: %1, Green: %2, Blue: %3")
328 .arg(QString::number(value.r(), 'f', 2))
329 .arg(QString::number(value.g(), 'f', 2))
330 .arg(QString::number(value.b(), 'f', 2));
331 }
332 case Format::RGB5A1:
333 {
334 auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
335 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
336 .arg(QString::number(value.r(), 'f', 2))
337 .arg(QString::number(value.g(), 'f', 2))
338 .arg(QString::number(value.b(), 'f', 2))
339 .arg(QString::number(value.a(), 'f', 2));
340 }
341 case Format::RGB565:
342 {
343 auto value = Color::DecodeRGB565(pixel) / 255.0f;
344 return QString("Red: %1, Green: %2, Blue: %3")
345 .arg(QString::number(value.r(), 'f', 2))
346 .arg(QString::number(value.g(), 'f', 2))
347 .arg(QString::number(value.b(), 'f', 2));
348 }
349 case Format::RGBA4:
350 {
351 auto value = Color::DecodeRGBA4(pixel) / 255.0f;
352 return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
353 .arg(QString::number(value.r(), 'f', 2))
354 .arg(QString::number(value.g(), 'f', 2))
355 .arg(QString::number(value.b(), 'f', 2))
356 .arg(QString::number(value.a(), 'f', 2));
357 }
358 case Format::IA8:
359 return QString("Index: %1, Alpha: %2")
360 .arg(pixel[0])
361 .arg(pixel[1]);
362 case Format::RG8: {
363 auto value = Color::DecodeRG8(pixel) / 255.0f;
364 return QString("Red: %1, Green: %2")
365 .arg(QString::number(value.r(), 'f', 2))
366 .arg(QString::number(value.g(), 'f', 2));
367 }
368 case Format::I8:
369 return QString("Index: %1").arg(*pixel);
370 case Format::A8:
371 return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
372 case Format::IA4:
373 return QString("Index: %1, Alpha: %2")
374 .arg(*pixel & 0xF)
375 .arg((*pixel & 0xF0) >> 4);
376 case Format::I4:
377 {
378 u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
379 return QString("Index: %1").arg(i);
380 }
381 case Format::A4:
382 {
383 u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
384 return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
385 }
386 case Format::ETC1:
387 case Format::ETC1A4:
388 // TODO: Display block information or channel values?
389 return QString("Compressed data");
390 case Format::D16:
391 {
392 auto value = Color::DecodeD16(pixel);
393 return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
394 }
395 case Format::D24:
396 {
397 auto value = Color::DecodeD24(pixel);
398 return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
399 }
400 case Format::D24X8:
401 case Format::X24S8:
402 {
403 auto values = Color::DecodeD24S8(pixel);
404 return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]);
405 }
406 case Format::Unknown:
407 return QString("Unknown format");
408 default:
409 return QString("Unhandled format");
410 }
411 return QString("");
412 };
413
414 QString nibbles = "";
415 for (unsigned i = 0; i < nibbles_per_pixel; i++) {
416 unsigned nibble_index = i;
417 if (nibble_mode) {
418 nibble_index += (offset % 2) ? 0 : 1;
419 }
420 u8 byte = pixel[nibble_index / 2];
421 u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF;
422 nibbles.append(QString::number(nibble, 16).toUpper());
423 }
424
425 surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
426 surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
427}
428
429void GraphicsSurfaceWidget::OnUpdate()
430{
431 QPixmap pixmap;
432
433 switch (surface_source) {
434 case Source::ColorBuffer:
435 {
436 // TODO: Store a reference to the registers in the debug context instead of accessing them directly...
437
438 const auto& framebuffer = Pica::g_state.regs.framebuffer;
439
440 surface_address = framebuffer.GetColorBufferPhysicalAddress();
441 surface_width = framebuffer.GetWidth();
442 surface_height = framebuffer.GetHeight();
443
444 switch (framebuffer.color_format) {
445 case Pica::Regs::ColorFormat::RGBA8:
446 surface_format = Format::RGBA8;
447 break;
448
449 case Pica::Regs::ColorFormat::RGB8:
450 surface_format = Format::RGB8;
451 break;
452
453 case Pica::Regs::ColorFormat::RGB5A1:
454 surface_format = Format::RGB5A1;
455 break;
456
457 case Pica::Regs::ColorFormat::RGB565:
458 surface_format = Format::RGB565;
459 break;
460
461 case Pica::Regs::ColorFormat::RGBA4:
462 surface_format = Format::RGBA4;
463 break;
464
465 default:
466 surface_format = Format::Unknown;
467 break;
468 }
469
470 break;
471 }
472
473 case Source::DepthBuffer:
474 {
475 const auto& framebuffer = Pica::g_state.regs.framebuffer;
476
477 surface_address = framebuffer.GetDepthBufferPhysicalAddress();
478 surface_width = framebuffer.GetWidth();
479 surface_height = framebuffer.GetHeight();
480
481 switch (framebuffer.depth_format) {
482 case Pica::Regs::DepthFormat::D16:
483 surface_format = Format::D16;
484 break;
485
486 case Pica::Regs::DepthFormat::D24:
487 surface_format = Format::D24;
488 break;
489
490 case Pica::Regs::DepthFormat::D24S8:
491 surface_format = Format::D24X8;
492 break;
493
494 default:
495 surface_format = Format::Unknown;
496 break;
497 }
498
499 break;
500 }
501
502 case Source::StencilBuffer:
503 {
504 const auto& framebuffer = Pica::g_state.regs.framebuffer;
505
506 surface_address = framebuffer.GetDepthBufferPhysicalAddress();
507 surface_width = framebuffer.GetWidth();
508 surface_height = framebuffer.GetHeight();
509
510 switch (framebuffer.depth_format) {
511 case Pica::Regs::DepthFormat::D24S8:
512 surface_format = Format::X24S8;
513 break;
514
515 default:
516 surface_format = Format::Unknown;
517 break;
518 }
519
520 break;
521 }
522
523 case Source::Texture0:
524 case Source::Texture1:
525 case Source::Texture2:
526 {
527 unsigned texture_index;
528 if (surface_source == Source::Texture0) texture_index = 0;
529 else if (surface_source == Source::Texture1) texture_index = 1;
530 else if (surface_source == Source::Texture2) texture_index = 2;
531 else {
532 qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
533 break;
534 }
535
536 const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
537 auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
538
539 surface_address = info.physical_address;
540 surface_width = info.width;
541 surface_height = info.height;
542 surface_format = static_cast<Format>(info.format);
543
544 if (surface_format > Format::MaxTextureFormat) {
545 qDebug() << "Unknown texture format " << static_cast<int>(info.format);
546 }
547 break;
548 }
549
550 case Source::Custom:
551 {
552 // Keep user-specified values
553 break;
554 }
555
556 default:
557 qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
558 break;
559 }
560
561 surface_address_control->SetValue(surface_address);
562 surface_width_control->setValue(surface_width);
563 surface_height_control->setValue(surface_height);
564 surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
565
566 // TODO: Implement a good way to visualize alpha components!
567
568 QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
569 u8* buffer = Memory::GetPhysicalPointer(surface_address);
570
571 if (buffer == nullptr) {
572 surface_picture_label->hide();
573 surface_info_label->setText(tr("(invalid surface address)"));
574 surface_info_label->setAlignment(Qt::AlignCenter);
575 surface_picker_x_control->setEnabled(false);
576 surface_picker_y_control->setEnabled(false);
577 save_surface->setEnabled(false);
578 return;
579 }
580
581 if (surface_format == Format::Unknown) {
582 surface_picture_label->hide();
583 surface_info_label->setText(tr("(unknown surface format)"));
584 surface_info_label->setAlignment(Qt::AlignCenter);
585 surface_picker_x_control->setEnabled(false);
586 surface_picker_y_control->setEnabled(false);
587 save_surface->setEnabled(false);
588 return;
589 }
590
591 surface_picture_label->show();
592
593 unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
594 unsigned stride = nibbles_per_pixel * surface_width / 2;
595
596 // We handle depth formats here because DebugUtils only supports TextureFormats
597 if (surface_format <= Format::MaxTextureFormat) {
598
599 // Generate a virtual texture
600 Pica::DebugUtils::TextureInfo info;
601 info.physical_address = surface_address;
602 info.width = surface_width;
603 info.height = surface_height;
604 info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
605 info.stride = stride;
606
607 for (unsigned int y = 0; y < surface_height; ++y) {
608 for (unsigned int x = 0; x < surface_width; ++x) {
609 Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true);
610 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
611 }
612 }
613
614 } else {
615
616 ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel");
617 unsigned bytes_per_pixel = nibbles_per_pixel / 2;
618
619 for (unsigned int y = 0; y < surface_height; ++y) {
620 for (unsigned int x = 0; x < surface_width; ++x) {
621 const u32 coarse_y = y & ~7;
622 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
623 const u8* pixel = buffer + offset;
624 Math::Vec4<u8> color = { 0, 0, 0, 0 };
625
626 switch(surface_format) {
627 case Format::D16:
628 {
629 u32 data = Color::DecodeD16(pixel);
630 color.r() = data & 0xFF;
631 color.g() = (data >> 8) & 0xFF;
632 break;
633 }
634 case Format::D24:
635 {
636 u32 data = Color::DecodeD24(pixel);
637 color.r() = data & 0xFF;
638 color.g() = (data >> 8) & 0xFF;
639 color.b() = (data >> 16) & 0xFF;
640 break;
641 }
642 case Format::D24X8:
643 {
644 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
645 color.r() = data.x & 0xFF;
646 color.g() = (data.x >> 8) & 0xFF;
647 color.b() = (data.x >> 16) & 0xFF;
648 break;
649 }
650 case Format::X24S8:
651 {
652 Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
653 color.r() = color.g() = color.b() = data.y;
654 break;
655 }
656 default:
657 qDebug() << "Unknown surface format " << static_cast<int>(surface_format);
658 break;
659 }
660
661 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
662 }
663 }
664
665 }
666
667 pixmap = QPixmap::fromImage(decoded_image);
668 surface_picture_label->setPixmap(pixmap);
669 surface_picture_label->resize(pixmap.size());
670
671 // Update the info with pixel data
672 surface_picker_x_control->setEnabled(true);
673 surface_picker_y_control->setEnabled(true);
674 Pick(surface_picker_x, surface_picker_y);
675
676 // Enable saving the converted pixmap to file
677 save_surface->setEnabled(true);
678}
679
680void GraphicsSurfaceWidget::SaveSurface() {
681 QString png_filter = tr("Portable Network Graphic (*.png)");
682 QString bin_filter = tr("Binary data (*.bin)");
683
684 QString selectedFilter;
685 QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
686 QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
687
688 if (filename.isEmpty()) {
689 // If the user canceled the dialog, don't save anything.
690 return;
691 }
692
693 if (selectedFilter == png_filter) {
694 const QPixmap* pixmap = surface_picture_label->pixmap();
695 ASSERT_MSG(pixmap != nullptr, "No pixmap set");
696
697 QFile file(filename);
698 file.open(QIODevice::WriteOnly);
699 if (pixmap)
700 pixmap->save(&file, "PNG");
701 } else if (selectedFilter == bin_filter) {
702 const u8* buffer = Memory::GetPhysicalPointer(surface_address);
703 ASSERT_MSG(buffer != nullptr, "Memory not accessible");
704
705 QFile file(filename);
706 file.open(QIODevice::WriteOnly);
707 int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2;
708 QByteArray data(reinterpret_cast<const char*>(buffer), size);
709 file.write(data);
710 } else {
711 UNREACHABLE_MSG("Unhandled filter selected");
712 }
713}
714
715unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) {
716 if (format <= Format::MaxTextureFormat) {
717 return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format));
718 }
719
720 switch (format) {
721 case Format::D24X8:
722 case Format::X24S8:
723 return 4 * 2;
724 case Format::D24:
725 return 3 * 2;
726 case Format::D16:
727 return 2 * 2;
728 default:
729 UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this "
730 "should not be reached as this function should "
731 "be given a format which is in "
732 "GraphicsSurfaceWidget::Format. Instead got %i",
733 static_cast<int>(format));
734 return 0;
735 }
736}
diff --git a/src/citra_qt/debugger/graphics_surface.h b/src/citra_qt/debugger/graphics_surface.h
new file mode 100644
index 000000000..7c7f50e38
--- /dev/null
+++ b/src/citra_qt/debugger/graphics_surface.h
@@ -0,0 +1,120 @@
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 "citra_qt/debugger/graphics_breakpoint_observer.h"
8
9#include <QLabel>
10#include <QPushButton>
11
12class QComboBox;
13class QSpinBox;
14class CSpinBox;
15
16class GraphicsSurfaceWidget;
17
18class SurfacePicture : public QLabel
19{
20 Q_OBJECT
21
22public:
23 SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr);
24 ~SurfacePicture();
25
26protected slots:
27 virtual void mouseMoveEvent(QMouseEvent* event);
28 virtual void mousePressEvent(QMouseEvent* event);
29
30private:
31 GraphicsSurfaceWidget* surface_widget;
32
33};
34
35class GraphicsSurfaceWidget : public BreakPointObserverDock {
36 Q_OBJECT
37
38 using Event = Pica::DebugContext::Event;
39
40 enum class Source {
41 ColorBuffer = 0,
42 DepthBuffer = 1,
43 StencilBuffer = 2,
44 Texture0 = 3,
45 Texture1 = 4,
46 Texture2 = 5,
47 Custom = 6,
48 };
49
50 enum class Format {
51 // These must match the TextureFormat type!
52 RGBA8 = 0,
53 RGB8 = 1,
54 RGB5A1 = 2,
55 RGB565 = 3,
56 RGBA4 = 4,
57 IA8 = 5,
58 RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
59 I8 = 7,
60 A8 = 8,
61 IA4 = 9,
62 I4 = 10,
63 A4 = 11,
64 ETC1 = 12, // compressed
65 ETC1A4 = 13,
66 MaxTextureFormat = 13,
67 D16 = 14,
68 D24 = 15,
69 D24X8 = 16,
70 X24S8 = 17,
71 Unknown = 18,
72 };
73
74 static unsigned int NibblesPerPixel(Format format);
75
76public:
77 GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
78 void Pick(int x, int y);
79
80public slots:
81 void OnSurfaceSourceChanged(int new_value);
82 void OnSurfaceAddressChanged(qint64 new_value);
83 void OnSurfaceWidthChanged(int new_value);
84 void OnSurfaceHeightChanged(int new_value);
85 void OnSurfaceFormatChanged(int new_value);
86 void OnSurfacePickerXChanged(int new_value);
87 void OnSurfacePickerYChanged(int new_value);
88 void OnUpdate();
89
90private slots:
91 void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
92 void OnResumed() override;
93
94 void SaveSurface();
95
96signals:
97 void Update();
98
99private:
100
101 QComboBox* surface_source_list;
102 CSpinBox* surface_address_control;
103 QSpinBox* surface_width_control;
104 QSpinBox* surface_height_control;
105 QComboBox* surface_format_control;
106
107 SurfacePicture* surface_picture_label;
108 QSpinBox* surface_picker_x_control;
109 QSpinBox* surface_picker_y_control;
110 QLabel* surface_info_label;
111 QPushButton* save_surface;
112
113 Source surface_source;
114 unsigned surface_address;
115 unsigned surface_width;
116 unsigned surface_height;
117 Format surface_format;
118 int surface_picker_x = 0;
119 int surface_picker_y = 0;
120};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 6239160bc..0ed1ffa5a 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -29,7 +29,7 @@
29#include "citra_qt/debugger/graphics.h" 29#include "citra_qt/debugger/graphics.h"
30#include "citra_qt/debugger/graphics_breakpoints.h" 30#include "citra_qt/debugger/graphics_breakpoints.h"
31#include "citra_qt/debugger/graphics_cmdlists.h" 31#include "citra_qt/debugger/graphics_cmdlists.h"
32#include "citra_qt/debugger/graphics_framebuffer.h" 32#include "citra_qt/debugger/graphics_surface.h"
33#include "citra_qt/debugger/graphics_tracing.h" 33#include "citra_qt/debugger/graphics_tracing.h"
34#include "citra_qt/debugger/graphics_vertex_shader.h" 34#include "citra_qt/debugger/graphics_vertex_shader.h"
35#include "citra_qt/debugger/profiler.h" 35#include "citra_qt/debugger/profiler.h"
@@ -101,10 +101,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
101 addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); 101 addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
102 graphicsBreakpointsWidget->hide(); 102 graphicsBreakpointsWidget->hide();
103 103
104 auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this);
105 addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget);
106 graphicsFramebufferWidget->hide();
107
108 auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); 104 auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
109 addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); 105 addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
110 graphicsVertexShaderWidget->hide(); 106 graphicsVertexShaderWidget->hide();
@@ -113,7 +109,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
113 addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); 109 addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
114 graphicsTracingWidget->hide(); 110 graphicsTracingWidget->hide();
115 111
112 auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this);
113 connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer()));
114
116 QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); 115 QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
116 debug_menu->addAction(graphicsSurfaceViewerAction);
117 debug_menu->addSeparator();
117 debug_menu->addAction(profilerWidget->toggleViewAction()); 118 debug_menu->addAction(profilerWidget->toggleViewAction());
118#if MICROPROFILE_ENABLED 119#if MICROPROFILE_ENABLED
119 debug_menu->addAction(microProfileDialog->toggleViewAction()); 120 debug_menu->addAction(microProfileDialog->toggleViewAction());
@@ -124,7 +125,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
124 debug_menu->addAction(graphicsWidget->toggleViewAction()); 125 debug_menu->addAction(graphicsWidget->toggleViewAction());
125 debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); 126 debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
126 debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); 127 debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
127 debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction());
128 debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); 128 debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
129 debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); 129 debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
130 130
@@ -517,6 +517,13 @@ void GMainWindow::OnConfigure() {
517 } 517 }
518} 518}
519 519
520void GMainWindow::OnCreateGraphicsSurfaceViewer() {
521 auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
522 addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);
523 // TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true);
524 graphicsSurfaceViewerWidget->show();
525}
526
520bool GMainWindow::ConfirmClose() { 527bool GMainWindow::ConfirmClose() {
521 if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) 528 if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
522 return true; 529 return true;
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 477db5c5c..b836b13fb 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -108,6 +108,7 @@ private slots:
108 void OnConfigure(); 108 void OnConfigure();
109 void OnDisplayTitleBars(bool); 109 void OnDisplayTitleBars(bool);
110 void ToggleWindowMode(); 110 void ToggleWindowMode();
111 void OnCreateGraphicsSurfaceViewer();
111 112
112private: 113private:
113 Ui::MainWindow ui; 114 Ui::MainWindow ui;