graphics_surface.cpp 26 KB

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