graphics_surface.cpp 26 KB

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