graphics_surface.cpp 26 KB

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