summaryrefslogtreecommitdiff
path: root/src/citra_qt/debugger/graphics_surface.cpp
diff options
context:
space:
mode:
authorGravatar bunnei2016-06-07 18:26:24 -0400
committerGravatar bunnei2016-06-07 18:26:24 -0400
commit98b1436b8bcbcf2b130b4b8d660e2c4f7ac1abe4 (patch)
tree150839a42b9b6458fe228b84b2405d4fc1f163df /src/citra_qt/debugger/graphics_surface.cpp
parentMerge pull request #1873 from archshift/remove-config (diff)
parentcitra_qt: Replace 'Pica Framebuffer Debugger' with 'Pica Surface Viewer' (diff)
downloadyuzu-98b1436b8bcbcf2b130b4b8d660e2c4f7ac1abe4.tar.gz
yuzu-98b1436b8bcbcf2b130b4b8d660e2c4f7ac1abe4.tar.xz
yuzu-98b1436b8bcbcf2b130b4b8d660e2c4f7ac1abe4.zip
Merge pull request #1765 from JayFoxRox/debug-surface-viewer
Debugger: Pica surface viewer
Diffstat (limited to 'src/citra_qt/debugger/graphics_surface.cpp')
-rw-r--r--src/citra_qt/debugger/graphics_surface.cpp736
1 files changed, 736 insertions, 0 deletions
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}