graphics_surface.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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 "common/vector_math.h"
  14. #include "core/core.h"
  15. #include "core/memory.h"
  16. #include "video_core/engines/maxwell_3d.h"
  17. #include "video_core/gpu.h"
  18. #include "video_core/textures/decoders.h"
  19. #include "video_core/textures/texture.h"
  20. #include "yuzu/debugger/graphics/graphics_surface.h"
  21. #include "yuzu/util/spinbox.h"
  22. static Tegra::Texture::TextureFormat ConvertToTextureFormat(
  23. Tegra::RenderTargetFormat render_target_format) {
  24. switch (render_target_format) {
  25. case Tegra::RenderTargetFormat::RGBA8_UNORM:
  26. return Tegra::Texture::TextureFormat::A8R8G8B8;
  27. case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
  28. return Tegra::Texture::TextureFormat::A2B10G10R10;
  29. default:
  30. UNIMPLEMENTED_MSG("Unimplemented RT format");
  31. return Tegra::Texture::TextureFormat::A8R8G8B8;
  32. }
  33. }
  34. SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
  35. : QLabel(parent), surface_widget(surface_widget_) {}
  36. SurfacePicture::~SurfacePicture() = default;
  37. void SurfacePicture::mousePressEvent(QMouseEvent* event) {
  38. // Only do something while the left mouse button is held down
  39. if (!(event->buttons() & Qt::LeftButton))
  40. return;
  41. if (pixmap() == nullptr)
  42. return;
  43. if (surface_widget)
  44. surface_widget->Pick(event->x() * pixmap()->width() / width(),
  45. event->y() * pixmap()->height() / height());
  46. }
  47. void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
  48. // We also want to handle the event if the user moves the mouse while holding down the LMB
  49. mousePressEvent(event);
  50. }
  51. GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
  52. QWidget* parent)
  53. : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
  54. surface_source(Source::RenderTarget0) {
  55. setObjectName("MaxwellSurface");
  56. surface_source_list = new QComboBox;
  57. surface_source_list->addItem(tr("Render Target 0"));
  58. surface_source_list->addItem(tr("Render Target 1"));
  59. surface_source_list->addItem(tr("Render Target 2"));
  60. surface_source_list->addItem(tr("Render Target 3"));
  61. surface_source_list->addItem(tr("Render Target 4"));
  62. surface_source_list->addItem(tr("Render Target 5"));
  63. surface_source_list->addItem(tr("Render Target 6"));
  64. surface_source_list->addItem(tr("Render Target 7"));
  65. surface_source_list->addItem(tr("Z Buffer"));
  66. surface_source_list->addItem(tr("Custom"));
  67. surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
  68. surface_address_control = new CSpinBox;
  69. surface_address_control->SetBase(16);
  70. surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
  71. surface_address_control->SetPrefix("0x");
  72. unsigned max_dimension = 16384; // TODO: Find actual maximum
  73. surface_width_control = new QSpinBox;
  74. surface_width_control->setRange(0, max_dimension);
  75. surface_height_control = new QSpinBox;
  76. surface_height_control->setRange(0, max_dimension);
  77. surface_picker_x_control = new QSpinBox;
  78. surface_picker_x_control->setRange(0, max_dimension - 1);
  79. surface_picker_y_control = new QSpinBox;
  80. surface_picker_y_control->setRange(0, max_dimension - 1);
  81. // clang-format off
  82. // Color formats sorted by Maxwell texture format index
  83. const QStringList surface_formats{
  84. tr("None"),
  85. QStringLiteral("R32_G32_B32_A32"),
  86. QStringLiteral("R32_G32_B32"),
  87. QStringLiteral("R16_G16_B16_A16"),
  88. QStringLiteral("R32_G32"),
  89. QStringLiteral("R32_B24G8"),
  90. QStringLiteral("ETC2_RGB"),
  91. QStringLiteral("X8B8G8R8"),
  92. QStringLiteral("A8R8G8B8"),
  93. QStringLiteral("A2B10G10R10"),
  94. QStringLiteral("ETC2_RGB_PTA"),
  95. QStringLiteral("ETC2_RGBA"),
  96. QStringLiteral("R16_G16"),
  97. QStringLiteral("G8R24"),
  98. QStringLiteral("G24R8"),
  99. QStringLiteral("R32"),
  100. QStringLiteral("BC6H_SF16"),
  101. QStringLiteral("BC6H_UF16"),
  102. QStringLiteral("A4B4G4R4"),
  103. QStringLiteral("A5B5G5R1"),
  104. QStringLiteral("A1B5G5R5"),
  105. QStringLiteral("B5G6R5"),
  106. QStringLiteral("B6G5R5"),
  107. QStringLiteral("BC7U"),
  108. QStringLiteral("G8R8"),
  109. QStringLiteral("EAC"),
  110. QStringLiteral("EACX2"),
  111. QStringLiteral("R16"),
  112. QStringLiteral("Y8_VIDEO"),
  113. QStringLiteral("R8"),
  114. QStringLiteral("G4R4"),
  115. QStringLiteral("R1"),
  116. QStringLiteral("E5B9G9R9_SHAREDEXP"),
  117. QStringLiteral("BF10GF11RF11"),
  118. QStringLiteral("G8B8G8R8"),
  119. QStringLiteral("B8G8R8G8"),
  120. QStringLiteral("DXT1"),
  121. QStringLiteral("DXT23"),
  122. QStringLiteral("DXT45"),
  123. QStringLiteral("DXN1"),
  124. QStringLiteral("DXN2"),
  125. QStringLiteral("Z24S8"),
  126. QStringLiteral("X8Z24"),
  127. QStringLiteral("S8Z24"),
  128. QStringLiteral("X4V4Z24__COV4R4V"),
  129. QStringLiteral("X4V4Z24__COV8R8V"),
  130. QStringLiteral("V8Z24__COV4R12V"),
  131. QStringLiteral("ZF32"),
  132. QStringLiteral("ZF32_X24S8"),
  133. QStringLiteral("X8Z24_X20V4S8__COV4R4V"),
  134. QStringLiteral("X8Z24_X20V4S8__COV8R8V"),
  135. QStringLiteral("ZF32_X20V4X8__COV4R4V"),
  136. QStringLiteral("ZF32_X20V4X8__COV8R8V"),
  137. QStringLiteral("ZF32_X20V4S8__COV4R4V"),
  138. QStringLiteral("ZF32_X20V4S8__COV8R8V"),
  139. QStringLiteral("X8Z24_X16V8S8__COV4R12V"),
  140. QStringLiteral("ZF32_X16V8X8__COV4R12V"),
  141. QStringLiteral("ZF32_X16V8S8__COV4R12V"),
  142. QStringLiteral("Z16"),
  143. QStringLiteral("V8Z24__COV8R24V"),
  144. QStringLiteral("X8Z24_X16V8S8__COV8R24V"),
  145. QStringLiteral("ZF32_X16V8X8__COV8R24V"),
  146. QStringLiteral("ZF32_X16V8S8__COV8R24V"),
  147. QStringLiteral("ASTC_2D_4X4"),
  148. QStringLiteral("ASTC_2D_5X5"),
  149. QStringLiteral("ASTC_2D_6X6"),
  150. QStringLiteral("ASTC_2D_8X8"),
  151. QStringLiteral("ASTC_2D_10X10"),
  152. QStringLiteral("ASTC_2D_12X12"),
  153. QStringLiteral("ASTC_2D_5X4"),
  154. QStringLiteral("ASTC_2D_6X5"),
  155. QStringLiteral("ASTC_2D_8X6"),
  156. QStringLiteral("ASTC_2D_10X8"),
  157. QStringLiteral("ASTC_2D_12X10"),
  158. QStringLiteral("ASTC_2D_8X5"),
  159. QStringLiteral("ASTC_2D_10X5"),
  160. QStringLiteral("ASTC_2D_10X6"),
  161. };
  162. // clang-format on
  163. surface_format_control = new QComboBox;
  164. surface_format_control->addItems(surface_formats);
  165. surface_info_label = new QLabel();
  166. surface_info_label->setWordWrap(true);
  167. surface_picture_label = new SurfacePicture(0, this);
  168. surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  169. surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
  170. surface_picture_label->setScaledContents(false);
  171. auto scroll_area = new QScrollArea();
  172. scroll_area->setBackgroundRole(QPalette::Dark);
  173. scroll_area->setWidgetResizable(false);
  174. scroll_area->setWidget(surface_picture_label);
  175. save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
  176. // Connections
  177. connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
  178. connect(surface_source_list,
  179. static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
  180. &GraphicsSurfaceWidget::OnSurfaceSourceChanged);
  181. connect(surface_address_control, &CSpinBox::ValueChanged, this,
  182. &GraphicsSurfaceWidget::OnSurfaceAddressChanged);
  183. connect(surface_width_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
  184. this, &GraphicsSurfaceWidget::OnSurfaceWidthChanged);
  185. connect(surface_height_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
  186. this, &GraphicsSurfaceWidget::OnSurfaceHeightChanged);
  187. connect(surface_format_control,
  188. static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
  189. &GraphicsSurfaceWidget::OnSurfaceFormatChanged);
  190. connect(surface_picker_x_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
  191. this, &GraphicsSurfaceWidget::OnSurfacePickerXChanged);
  192. connect(surface_picker_y_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
  193. this, &GraphicsSurfaceWidget::OnSurfacePickerYChanged);
  194. connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface);
  195. auto main_widget = new QWidget;
  196. auto main_layout = new QVBoxLayout;
  197. {
  198. auto sub_layout = new QHBoxLayout;
  199. sub_layout->addWidget(new QLabel(tr("Source:")));
  200. sub_layout->addWidget(surface_source_list);
  201. main_layout->addLayout(sub_layout);
  202. }
  203. {
  204. auto sub_layout = new QHBoxLayout;
  205. sub_layout->addWidget(new QLabel(tr("GPU Address:")));
  206. sub_layout->addWidget(surface_address_control);
  207. main_layout->addLayout(sub_layout);
  208. }
  209. {
  210. auto sub_layout = new QHBoxLayout;
  211. sub_layout->addWidget(new QLabel(tr("Width:")));
  212. sub_layout->addWidget(surface_width_control);
  213. main_layout->addLayout(sub_layout);
  214. }
  215. {
  216. auto sub_layout = new QHBoxLayout;
  217. sub_layout->addWidget(new QLabel(tr("Height:")));
  218. sub_layout->addWidget(surface_height_control);
  219. main_layout->addLayout(sub_layout);
  220. }
  221. {
  222. auto sub_layout = new QHBoxLayout;
  223. sub_layout->addWidget(new QLabel(tr("Format:")));
  224. sub_layout->addWidget(surface_format_control);
  225. main_layout->addLayout(sub_layout);
  226. }
  227. main_layout->addWidget(scroll_area);
  228. auto info_layout = new QHBoxLayout;
  229. {
  230. auto xy_layout = new QVBoxLayout;
  231. {
  232. {
  233. auto sub_layout = new QHBoxLayout;
  234. sub_layout->addWidget(new QLabel(tr("X:")));
  235. sub_layout->addWidget(surface_picker_x_control);
  236. xy_layout->addLayout(sub_layout);
  237. }
  238. {
  239. auto sub_layout = new QHBoxLayout;
  240. sub_layout->addWidget(new QLabel(tr("Y:")));
  241. sub_layout->addWidget(surface_picker_y_control);
  242. xy_layout->addLayout(sub_layout);
  243. }
  244. }
  245. info_layout->addLayout(xy_layout);
  246. surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
  247. info_layout->addWidget(surface_info_label);
  248. }
  249. main_layout->addLayout(info_layout);
  250. main_layout->addWidget(save_surface);
  251. main_widget->setLayout(main_layout);
  252. setWidget(main_widget);
  253. // Load current data - TODO: Make sure this works when emulation is not running
  254. if (debug_context && debug_context->at_breakpoint) {
  255. emit Update();
  256. widget()->setEnabled(debug_context->at_breakpoint);
  257. } else {
  258. widget()->setEnabled(false);
  259. }
  260. }
  261. void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
  262. emit Update();
  263. widget()->setEnabled(true);
  264. }
  265. void GraphicsSurfaceWidget::OnResumed() {
  266. widget()->setEnabled(false);
  267. }
  268. void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
  269. surface_source = static_cast<Source>(new_value);
  270. emit Update();
  271. }
  272. void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
  273. if (surface_address != new_value) {
  274. surface_address = static_cast<GPUVAddr>(new_value);
  275. surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
  276. emit Update();
  277. }
  278. }
  279. void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
  280. if (surface_width != static_cast<unsigned>(new_value)) {
  281. surface_width = static_cast<unsigned>(new_value);
  282. surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
  283. emit Update();
  284. }
  285. }
  286. void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
  287. if (surface_height != static_cast<unsigned>(new_value)) {
  288. surface_height = static_cast<unsigned>(new_value);
  289. surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
  290. emit Update();
  291. }
  292. }
  293. void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
  294. if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
  295. surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
  296. surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
  297. emit Update();
  298. }
  299. }
  300. void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
  301. if (surface_picker_x != new_value) {
  302. surface_picker_x = new_value;
  303. Pick(surface_picker_x, surface_picker_y);
  304. }
  305. }
  306. void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
  307. if (surface_picker_y != new_value) {
  308. surface_picker_y = new_value;
  309. Pick(surface_picker_x, surface_picker_y);
  310. }
  311. }
  312. void GraphicsSurfaceWidget::Pick(int x, int y) {
  313. surface_picker_x_control->setValue(x);
  314. surface_picker_y_control->setValue(y);
  315. if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
  316. y >= static_cast<int>(surface_height)) {
  317. surface_info_label->setText(tr("Pixel out of bounds"));
  318. surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  319. return;
  320. }
  321. surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
  322. surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  323. }
  324. void GraphicsSurfaceWidget::OnUpdate() {
  325. auto& gpu = Core::System::GetInstance().GPU();
  326. QPixmap pixmap;
  327. switch (surface_source) {
  328. case Source::RenderTarget0:
  329. case Source::RenderTarget1:
  330. case Source::RenderTarget2:
  331. case Source::RenderTarget3:
  332. case Source::RenderTarget4:
  333. case Source::RenderTarget5:
  334. case Source::RenderTarget6:
  335. case Source::RenderTarget7: {
  336. // TODO: Store a reference to the registers in the debug context instead of accessing them
  337. // directly...
  338. const auto& registers = gpu.Maxwell3D().regs;
  339. const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
  340. static_cast<std::size_t>(Source::RenderTarget0)];
  341. surface_address = rt.Address();
  342. surface_width = rt.width;
  343. surface_height = rt.height;
  344. if (rt.format != Tegra::RenderTargetFormat::NONE) {
  345. surface_format = ConvertToTextureFormat(rt.format);
  346. }
  347. break;
  348. }
  349. case Source::Custom: {
  350. // Keep user-specified values
  351. break;
  352. }
  353. default:
  354. qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
  355. break;
  356. }
  357. surface_address_control->SetValue(surface_address);
  358. surface_width_control->setValue(surface_width);
  359. surface_height_control->setValue(surface_height);
  360. surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
  361. if (surface_address == 0) {
  362. surface_picture_label->hide();
  363. surface_info_label->setText(tr("(invalid surface address)"));
  364. surface_info_label->setAlignment(Qt::AlignCenter);
  365. surface_picker_x_control->setEnabled(false);
  366. surface_picker_y_control->setEnabled(false);
  367. save_surface->setEnabled(false);
  368. return;
  369. }
  370. // TODO: Implement a good way to visualize alpha components!
  371. QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
  372. // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
  373. // Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
  374. auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
  375. gpu.MemoryManager().GetPointer(surface_address), 1, 1,
  376. Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
  377. auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
  378. surface_width, surface_height);
  379. surface_picture_label->show();
  380. for (unsigned int y = 0; y < surface_height; ++y) {
  381. for (unsigned int x = 0; x < surface_width; ++x) {
  382. Common::Vec4<u8> color;
  383. color[0] = texture_data[x + y * surface_width + 0];
  384. color[1] = texture_data[x + y * surface_width + 1];
  385. color[2] = texture_data[x + y * surface_width + 2];
  386. color[3] = texture_data[x + y * surface_width + 3];
  387. decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
  388. }
  389. }
  390. pixmap = QPixmap::fromImage(decoded_image);
  391. surface_picture_label->setPixmap(pixmap);
  392. surface_picture_label->resize(pixmap.size());
  393. // Update the info with pixel data
  394. surface_picker_x_control->setEnabled(true);
  395. surface_picker_y_control->setEnabled(true);
  396. Pick(surface_picker_x, surface_picker_y);
  397. // Enable saving the converted pixmap to file
  398. save_surface->setEnabled(true);
  399. }
  400. void GraphicsSurfaceWidget::SaveSurface() {
  401. QString png_filter = tr("Portable Network Graphic (*.png)");
  402. QString bin_filter = tr("Binary data (*.bin)");
  403. QString selectedFilter;
  404. QString filename = QFileDialog::getSaveFileName(
  405. this, tr("Save Surface"),
  406. QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
  407. QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
  408. if (filename.isEmpty()) {
  409. // If the user canceled the dialog, don't save anything.
  410. return;
  411. }
  412. if (selectedFilter == png_filter) {
  413. const QPixmap* pixmap = surface_picture_label->pixmap();
  414. ASSERT_MSG(pixmap != nullptr, "No pixmap set");
  415. QFile file(filename);
  416. file.open(QIODevice::WriteOnly);
  417. if (pixmap)
  418. pixmap->save(&file, "PNG");
  419. } else if (selectedFilter == bin_filter) {
  420. auto& gpu = Core::System::GetInstance().GPU();
  421. std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
  422. const u8* buffer = Memory::GetPointer(*address);
  423. ASSERT_MSG(buffer != nullptr, "Memory not accessible");
  424. QFile file(filename);
  425. file.open(QIODevice::WriteOnly);
  426. int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
  427. QByteArray data(reinterpret_cast<const char*>(buffer), size);
  428. file.write(data);
  429. } else {
  430. UNREACHABLE_MSG("Unhandled filter selected");
  431. }
  432. }