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