graphics_surface.cpp 17 KB

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