citra_qt: Replace 'Pica Framebuffer Debugger' with 'Pica Surface Viewer'
This commit is contained in:
		| @@ -11,7 +11,7 @@ set(SRCS | |||||||
|             debugger/graphics_breakpoint_observer.cpp |             debugger/graphics_breakpoint_observer.cpp | ||||||
|             debugger/graphics_breakpoints.cpp |             debugger/graphics_breakpoints.cpp | ||||||
|             debugger/graphics_cmdlists.cpp |             debugger/graphics_cmdlists.cpp | ||||||
|             debugger/graphics_framebuffer.cpp |             debugger/graphics_surface.cpp | ||||||
|             debugger/graphics_tracing.cpp |             debugger/graphics_tracing.cpp | ||||||
|             debugger/graphics_vertex_shader.cpp |             debugger/graphics_vertex_shader.cpp | ||||||
|             debugger/profiler.cpp |             debugger/profiler.cpp | ||||||
| @@ -42,7 +42,7 @@ set(HEADERS | |||||||
|             debugger/graphics_breakpoints.h |             debugger/graphics_breakpoints.h | ||||||
|             debugger/graphics_breakpoints_p.h |             debugger/graphics_breakpoints_p.h | ||||||
|             debugger/graphics_cmdlists.h |             debugger/graphics_cmdlists.h | ||||||
|             debugger/graphics_framebuffer.h |             debugger/graphics_surface.h | ||||||
|             debugger/graphics_tracing.h |             debugger/graphics_tracing.h | ||||||
|             debugger/graphics_vertex_shader.h |             debugger/graphics_vertex_shader.h | ||||||
|             debugger/profiler.h |             debugger/profiler.h | ||||||
|   | |||||||
| @@ -50,123 +50,6 @@ public: | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent) |  | ||||||
|     : QDockWidget(tr("Texture 0x%1").arg(info.physical_address, 8, 16, QLatin1Char('0'))), |  | ||||||
|       info(info) { |  | ||||||
|  |  | ||||||
|     QWidget* main_widget = new QWidget; |  | ||||||
|  |  | ||||||
|     QLabel* image_widget = new QLabel; |  | ||||||
|  |  | ||||||
|     connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&))); |  | ||||||
|  |  | ||||||
|     CSpinBox* phys_address_spinbox = new CSpinBox; |  | ||||||
|     phys_address_spinbox->SetBase(16); |  | ||||||
|     phys_address_spinbox->SetRange(0, 0xFFFFFFFF); |  | ||||||
|     phys_address_spinbox->SetPrefix("0x"); |  | ||||||
|     phys_address_spinbox->SetValue(info.physical_address); |  | ||||||
|     connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64))); |  | ||||||
|  |  | ||||||
|     QComboBox* format_choice = new QComboBox; |  | ||||||
|     format_choice->addItem(tr("RGBA8")); |  | ||||||
|     format_choice->addItem(tr("RGB8")); |  | ||||||
|     format_choice->addItem(tr("RGB5A1")); |  | ||||||
|     format_choice->addItem(tr("RGB565")); |  | ||||||
|     format_choice->addItem(tr("RGBA4")); |  | ||||||
|     format_choice->addItem(tr("IA8")); |  | ||||||
|     format_choice->addItem(tr("RG8")); |  | ||||||
|     format_choice->addItem(tr("I8")); |  | ||||||
|     format_choice->addItem(tr("A8")); |  | ||||||
|     format_choice->addItem(tr("IA4")); |  | ||||||
|     format_choice->addItem(tr("I4")); |  | ||||||
|     format_choice->addItem(tr("A4")); |  | ||||||
|     format_choice->addItem(tr("ETC1")); |  | ||||||
|     format_choice->addItem(tr("ETC1A4")); |  | ||||||
|     format_choice->setCurrentIndex(static_cast<int>(info.format)); |  | ||||||
|     connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); |  | ||||||
|  |  | ||||||
|     QSpinBox* width_spinbox = new QSpinBox; |  | ||||||
|     width_spinbox->setMaximum(65535); |  | ||||||
|     width_spinbox->setValue(info.width); |  | ||||||
|     connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int))); |  | ||||||
|  |  | ||||||
|     QSpinBox* height_spinbox = new QSpinBox; |  | ||||||
|     height_spinbox->setMaximum(65535); |  | ||||||
|     height_spinbox->setValue(info.height); |  | ||||||
|     connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int))); |  | ||||||
|  |  | ||||||
|     QSpinBox* stride_spinbox = new QSpinBox; |  | ||||||
|     stride_spinbox->setMaximum(65535 * 4); |  | ||||||
|     stride_spinbox->setValue(info.stride); |  | ||||||
|     connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int))); |  | ||||||
|  |  | ||||||
|     QVBoxLayout* main_layout = new QVBoxLayout; |  | ||||||
|     main_layout->addWidget(image_widget); |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|         QHBoxLayout* sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Source Address:"))); |  | ||||||
|         sub_layout->addWidget(phys_address_spinbox); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|         QHBoxLayout* sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Format"))); |  | ||||||
|         sub_layout->addWidget(format_choice); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|         QHBoxLayout* sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Width:"))); |  | ||||||
|         sub_layout->addWidget(width_spinbox); |  | ||||||
|         sub_layout->addStretch(); |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Height:"))); |  | ||||||
|         sub_layout->addWidget(height_spinbox); |  | ||||||
|         sub_layout->addStretch(); |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Stride:"))); |  | ||||||
|         sub_layout->addWidget(stride_spinbox); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     main_widget->setLayout(main_layout); |  | ||||||
|  |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
|  |  | ||||||
|     setWidget(main_widget); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void TextureInfoDockWidget::OnAddressChanged(qint64 value) { |  | ||||||
|     info.physical_address = value; |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void TextureInfoDockWidget::OnFormatChanged(int value) { |  | ||||||
|     info.format = static_cast<Pica::Regs::TextureFormat>(value); |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void TextureInfoDockWidget::OnWidthChanged(int value) { |  | ||||||
|     info.width = value; |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void TextureInfoDockWidget::OnHeightChanged(int value) { |  | ||||||
|     info.height = value; |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void TextureInfoDockWidget::OnStrideChanged(int value) { |  | ||||||
|     info.stride = value; |  | ||||||
|     emit UpdatePixmap(ReloadPixmap()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QPixmap TextureInfoDockWidget::ReloadPixmap() const { |  | ||||||
|     u8* src = Memory::GetPhysicalPointer(info.physical_address); |  | ||||||
|     return QPixmap::fromImage(LoadTexture(src, info)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { | GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { | |||||||
|             index = 0; |             index = 0; | ||||||
|         } else if (COMMAND_IN_RANGE(command_id, texture1)) { |         } else if (COMMAND_IN_RANGE(command_id, texture1)) { | ||||||
|             index = 1; |             index = 1; | ||||||
|         } else { |         } else if (COMMAND_IN_RANGE(command_id, texture2)) { | ||||||
|             index = 2; |             index = 2; | ||||||
|  |         } else { | ||||||
|  |             UNREACHABLE_MSG("Unknown texture command"); | ||||||
|         } |         } | ||||||
|         auto config = Pica::g_state.regs.GetTextures()[index].config; |         auto config = Pica::g_state.regs.GetTextures()[index].config; | ||||||
|         auto format = Pica::g_state.regs.GetTextures()[index].format; |         auto format = Pica::g_state.regs.GetTextures()[index].format; | ||||||
|         auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); |         auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); | ||||||
|  |  | ||||||
|         // TODO: Instead, emit a signal here to be caught by the main window widget. |         // TODO: Open a surface debugger | ||||||
|         auto main_window = static_cast<QMainWindow*>(parent()); |  | ||||||
|         main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,25 +61,3 @@ private: | |||||||
|     QWidget* command_info_widget; |     QWidget* command_info_widget; | ||||||
|     QPushButton* toggle_tracing; |     QPushButton* toggle_tracing; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class TextureInfoDockWidget : public QDockWidget { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void UpdatePixmap(const QPixmap& pixmap); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void OnAddressChanged(qint64 value); |  | ||||||
|     void OnFormatChanged(int value); |  | ||||||
|     void OnWidthChanged(int value); |  | ||||||
|     void OnHeightChanged(int value); |  | ||||||
|     void OnStrideChanged(int value); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QPixmap ReloadPixmap() const; |  | ||||||
|  |  | ||||||
|     Pica::DebugUtils::TextureInfo info; |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -1,356 +0,0 @@ | |||||||
| // Copyright 2014 Citra Emulator Project |  | ||||||
| // Licensed under GPLv2 or any later version |  | ||||||
| // Refer to the license.txt file included. |  | ||||||
|  |  | ||||||
| #include <QBoxLayout> |  | ||||||
| #include <QComboBox> |  | ||||||
| #include <QDebug> |  | ||||||
| #include <QLabel> |  | ||||||
| #include <QPushButton> |  | ||||||
| #include <QSpinBox> |  | ||||||
|  |  | ||||||
| #include "citra_qt/debugger/graphics_framebuffer.h" |  | ||||||
| #include "citra_qt/util/spinbox.h" |  | ||||||
|  |  | ||||||
| #include "common/color.h" |  | ||||||
|  |  | ||||||
| #include "core/memory.h" |  | ||||||
| #include "core/hw/gpu.h" |  | ||||||
|  |  | ||||||
| #include "video_core/pica.h" |  | ||||||
| #include "video_core/pica_state.h" |  | ||||||
| #include "video_core/utils.h" |  | ||||||
|  |  | ||||||
| GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, |  | ||||||
|                                                      QWidget* parent) |  | ||||||
|     : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), |  | ||||||
|       framebuffer_source(Source::PicaTarget) |  | ||||||
| { |  | ||||||
|     setObjectName("PicaFramebuffer"); |  | ||||||
|  |  | ||||||
|     framebuffer_source_list = new QComboBox; |  | ||||||
|     framebuffer_source_list->addItem(tr("Active Render Target")); |  | ||||||
|     framebuffer_source_list->addItem(tr("Active Depth Buffer")); |  | ||||||
|     framebuffer_source_list->addItem(tr("Custom")); |  | ||||||
|     framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); |  | ||||||
|  |  | ||||||
|     framebuffer_address_control = new CSpinBox; |  | ||||||
|     framebuffer_address_control->SetBase(16); |  | ||||||
|     framebuffer_address_control->SetRange(0, 0xFFFFFFFF); |  | ||||||
|     framebuffer_address_control->SetPrefix("0x"); |  | ||||||
|  |  | ||||||
|     framebuffer_width_control = new QSpinBox; |  | ||||||
|     framebuffer_width_control->setMinimum(1); |  | ||||||
|     framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum |  | ||||||
|  |  | ||||||
|     framebuffer_height_control = new QSpinBox; |  | ||||||
|     framebuffer_height_control->setMinimum(1); |  | ||||||
|     framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum |  | ||||||
|  |  | ||||||
|     framebuffer_format_control = new QComboBox; |  | ||||||
|     framebuffer_format_control->addItem(tr("RGBA8")); |  | ||||||
|     framebuffer_format_control->addItem(tr("RGB8")); |  | ||||||
|     framebuffer_format_control->addItem(tr("RGB5A1")); |  | ||||||
|     framebuffer_format_control->addItem(tr("RGB565")); |  | ||||||
|     framebuffer_format_control->addItem(tr("RGBA4")); |  | ||||||
|     framebuffer_format_control->addItem(tr("D16")); |  | ||||||
|     framebuffer_format_control->addItem(tr("D24")); |  | ||||||
|     framebuffer_format_control->addItem(tr("D24X8")); |  | ||||||
|     framebuffer_format_control->addItem(tr("X24S8")); |  | ||||||
|     framebuffer_format_control->addItem(tr("(unknown)")); |  | ||||||
|  |  | ||||||
|     // TODO: This QLabel should shrink the image to the available space rather than just expanding... |  | ||||||
|     framebuffer_picture_label = new QLabel; |  | ||||||
|  |  | ||||||
|     auto enlarge_button = new QPushButton(tr("Enlarge")); |  | ||||||
|  |  | ||||||
|     // Connections |  | ||||||
|     connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); |  | ||||||
|     connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); |  | ||||||
|     connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); |  | ||||||
|     connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); |  | ||||||
|     connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); |  | ||||||
|     connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); |  | ||||||
|  |  | ||||||
|     auto main_widget = new QWidget; |  | ||||||
|     auto main_layout = new QVBoxLayout; |  | ||||||
|     { |  | ||||||
|         auto sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Source:"))); |  | ||||||
|         sub_layout->addWidget(framebuffer_source_list); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|     { |  | ||||||
|         auto sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); |  | ||||||
|         sub_layout->addWidget(framebuffer_address_control); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|     { |  | ||||||
|         auto sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Width:"))); |  | ||||||
|         sub_layout->addWidget(framebuffer_width_control); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|     { |  | ||||||
|         auto sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Height:"))); |  | ||||||
|         sub_layout->addWidget(framebuffer_height_control); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|     { |  | ||||||
|         auto sub_layout = new QHBoxLayout; |  | ||||||
|         sub_layout->addWidget(new QLabel(tr("Format:"))); |  | ||||||
|         sub_layout->addWidget(framebuffer_format_control); |  | ||||||
|         main_layout->addLayout(sub_layout); |  | ||||||
|     } |  | ||||||
|     main_layout->addWidget(framebuffer_picture_label); |  | ||||||
|     main_layout->addWidget(enlarge_button); |  | ||||||
|     main_widget->setLayout(main_layout); |  | ||||||
|     setWidget(main_widget); |  | ||||||
|  |  | ||||||
|     // Load current data - TODO: Make sure this works when emulation is not running |  | ||||||
|     if (debug_context && debug_context->at_breakpoint) |  | ||||||
|         emit Update(); |  | ||||||
|     widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) |  | ||||||
| { |  | ||||||
|     emit Update(); |  | ||||||
|     widget()->setEnabled(true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnResumed() |  | ||||||
| { |  | ||||||
|     widget()->setEnabled(false); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) |  | ||||||
| { |  | ||||||
|     framebuffer_source = static_cast<Source>(new_value); |  | ||||||
|     emit Update(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) |  | ||||||
| { |  | ||||||
|     if (framebuffer_address != new_value) { |  | ||||||
|         framebuffer_address = static_cast<unsigned>(new_value); |  | ||||||
|  |  | ||||||
|         framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |  | ||||||
|         emit Update(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) |  | ||||||
| { |  | ||||||
|     if (framebuffer_width != static_cast<unsigned>(new_value)) { |  | ||||||
|         framebuffer_width = static_cast<unsigned>(new_value); |  | ||||||
|  |  | ||||||
|         framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |  | ||||||
|         emit Update(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) |  | ||||||
| { |  | ||||||
|     if (framebuffer_height != static_cast<unsigned>(new_value)) { |  | ||||||
|         framebuffer_height = static_cast<unsigned>(new_value); |  | ||||||
|  |  | ||||||
|         framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |  | ||||||
|         emit Update(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) |  | ||||||
| { |  | ||||||
|     if (framebuffer_format != static_cast<Format>(new_value)) { |  | ||||||
|         framebuffer_format = static_cast<Format>(new_value); |  | ||||||
|  |  | ||||||
|         framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |  | ||||||
|         emit Update(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GraphicsFramebufferWidget::OnUpdate() |  | ||||||
| { |  | ||||||
|     QPixmap pixmap; |  | ||||||
|  |  | ||||||
|     switch (framebuffer_source) { |  | ||||||
|     case Source::PicaTarget: |  | ||||||
|     { |  | ||||||
|         // TODO: Store a reference to the registers in the debug context instead of accessing them directly... |  | ||||||
|  |  | ||||||
|         const auto& framebuffer = Pica::g_state.regs.framebuffer; |  | ||||||
|  |  | ||||||
|         framebuffer_address = framebuffer.GetColorBufferPhysicalAddress(); |  | ||||||
|         framebuffer_width = framebuffer.GetWidth(); |  | ||||||
|         framebuffer_height = framebuffer.GetHeight(); |  | ||||||
|  |  | ||||||
|         switch (framebuffer.color_format) { |  | ||||||
|         case Pica::Regs::ColorFormat::RGBA8: |  | ||||||
|             framebuffer_format = Format::RGBA8; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::ColorFormat::RGB8: |  | ||||||
|             framebuffer_format = Format::RGB8; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::ColorFormat::RGB5A1: |  | ||||||
|             framebuffer_format = Format::RGB5A1; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::ColorFormat::RGB565: |  | ||||||
|             framebuffer_format = Format::RGB565; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::ColorFormat::RGBA4: |  | ||||||
|             framebuffer_format = Format::RGBA4; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         default: |  | ||||||
|             framebuffer_format = Format::Unknown; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     case Source::DepthBuffer: |  | ||||||
|     { |  | ||||||
|         const auto& framebuffer = Pica::g_state.regs.framebuffer; |  | ||||||
|  |  | ||||||
|         framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress(); |  | ||||||
|         framebuffer_width = framebuffer.GetWidth(); |  | ||||||
|         framebuffer_height = framebuffer.GetHeight(); |  | ||||||
|  |  | ||||||
|         switch (framebuffer.depth_format) { |  | ||||||
|         case Pica::Regs::DepthFormat::D16: |  | ||||||
|             framebuffer_format = Format::D16; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::DepthFormat::D24: |  | ||||||
|             framebuffer_format = Format::D24; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         case Pica::Regs::DepthFormat::D24S8: |  | ||||||
|             framebuffer_format = Format::D24X8; |  | ||||||
|             break; |  | ||||||
|  |  | ||||||
|         default: |  | ||||||
|             framebuffer_format = Format::Unknown; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     case Source::Custom: |  | ||||||
|     { |  | ||||||
|         // Keep user-specified values |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     default: |  | ||||||
|         qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO: Implement a good way to visualize alpha components! |  | ||||||
|     // TODO: Unify this decoding code with the texture decoder |  | ||||||
|     u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format); |  | ||||||
|  |  | ||||||
|     QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); |  | ||||||
|     u8* buffer = Memory::GetPhysicalPointer(framebuffer_address); |  | ||||||
|  |  | ||||||
|     for (unsigned int y = 0; y < framebuffer_height; ++y) { |  | ||||||
|         for (unsigned int x = 0; x < framebuffer_width; ++x) { |  | ||||||
|             const u32 coarse_y = y & ~7; |  | ||||||
|             u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel; |  | ||||||
|             const u8* pixel = buffer + offset; |  | ||||||
|             Math::Vec4<u8> color = { 0, 0, 0, 0 }; |  | ||||||
|  |  | ||||||
|             switch (framebuffer_format) { |  | ||||||
|             case Format::RGBA8: |  | ||||||
|                 color = Color::DecodeRGBA8(pixel); |  | ||||||
|                 break; |  | ||||||
|             case Format::RGB8: |  | ||||||
|                 color = Color::DecodeRGB8(pixel); |  | ||||||
|                 break; |  | ||||||
|             case Format::RGB5A1: |  | ||||||
|                 color = Color::DecodeRGB5A1(pixel); |  | ||||||
|                 break; |  | ||||||
|             case Format::RGB565: |  | ||||||
|                 color = Color::DecodeRGB565(pixel); |  | ||||||
|                 break; |  | ||||||
|             case Format::RGBA4: |  | ||||||
|                 color = Color::DecodeRGBA4(pixel); |  | ||||||
|                 break; |  | ||||||
|             case Format::D16: |  | ||||||
|             { |  | ||||||
|                 u32 data = Color::DecodeD16(pixel); |  | ||||||
|                 color.r() = data & 0xFF; |  | ||||||
|                 color.g() = (data >> 8) & 0xFF; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case Format::D24: |  | ||||||
|             { |  | ||||||
|                 u32 data = Color::DecodeD24(pixel); |  | ||||||
|                 color.r() = data & 0xFF; |  | ||||||
|                 color.g() = (data >> 8) & 0xFF; |  | ||||||
|                 color.b() = (data >> 16) & 0xFF; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case Format::D24X8: |  | ||||||
|             { |  | ||||||
|                 Math::Vec2<u32> data = Color::DecodeD24S8(pixel); |  | ||||||
|                 color.r() = data.x & 0xFF; |  | ||||||
|                 color.g() = (data.x >> 8) & 0xFF; |  | ||||||
|                 color.b() = (data.x >> 16) & 0xFF; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case Format::X24S8: |  | ||||||
|             { |  | ||||||
|                 Math::Vec2<u32> data = Color::DecodeD24S8(pixel); |  | ||||||
|                 color.r() = color.g() = color.b() = data.y; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             default: |  | ||||||
|                 qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     pixmap = QPixmap::fromImage(decoded_image); |  | ||||||
|  |  | ||||||
|     framebuffer_address_control->SetValue(framebuffer_address); |  | ||||||
|     framebuffer_width_control->setValue(framebuffer_width); |  | ||||||
|     framebuffer_height_control->setValue(framebuffer_height); |  | ||||||
|     framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); |  | ||||||
|     framebuffer_picture_label->setPixmap(pixmap); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) { |  | ||||||
|     switch (format) { |  | ||||||
|         case Format::RGBA8: |  | ||||||
|         case Format::D24X8: |  | ||||||
|         case Format::X24S8: |  | ||||||
|             return 4; |  | ||||||
|         case Format::RGB8: |  | ||||||
|         case Format::D24: |  | ||||||
|             return 3; |  | ||||||
|         case Format::RGB5A1: |  | ||||||
|         case Format::RGB565: |  | ||||||
|         case Format::RGBA4: |  | ||||||
|         case Format::D16: |  | ||||||
|             return 2; |  | ||||||
|         default: |  | ||||||
|             UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this " |  | ||||||
|                             "should not be reached as this function should " |  | ||||||
|                             "be given a format which is in " |  | ||||||
|                             "GraphicsFramebufferWidget::Format. Instead got %i", |  | ||||||
|                             static_cast<int>(format)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,76 +0,0 @@ | |||||||
| // Copyright 2014 Citra Emulator Project |  | ||||||
| // Licensed under GPLv2 or any later version |  | ||||||
| // Refer to the license.txt file included. |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "citra_qt/debugger/graphics_breakpoint_observer.h" |  | ||||||
|  |  | ||||||
| class QComboBox; |  | ||||||
| class QLabel; |  | ||||||
| class QSpinBox; |  | ||||||
|  |  | ||||||
| class CSpinBox; |  | ||||||
|  |  | ||||||
| class GraphicsFramebufferWidget : public BreakPointObserverDock { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
|     using Event = Pica::DebugContext::Event; |  | ||||||
|  |  | ||||||
|     enum class Source { |  | ||||||
|         PicaTarget   = 0, |  | ||||||
|         DepthBuffer  = 1, |  | ||||||
|         Custom       = 2, |  | ||||||
|  |  | ||||||
|         // TODO: Add GPU framebuffer sources! |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     enum class Format { |  | ||||||
|         RGBA8    = 0, |  | ||||||
|         RGB8     = 1, |  | ||||||
|         RGB5A1   = 2, |  | ||||||
|         RGB565   = 3, |  | ||||||
|         RGBA4    = 4, |  | ||||||
|         D16      = 5, |  | ||||||
|         D24      = 6, |  | ||||||
|         D24X8    = 7, |  | ||||||
|         X24S8    = 8, |  | ||||||
|         Unknown  = 9 |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     static u32 BytesPerPixel(Format format); |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); |  | ||||||
|  |  | ||||||
| public slots: |  | ||||||
|     void OnFramebufferSourceChanged(int new_value); |  | ||||||
|     void OnFramebufferAddressChanged(qint64 new_value); |  | ||||||
|     void OnFramebufferWidthChanged(int new_value); |  | ||||||
|     void OnFramebufferHeightChanged(int new_value); |  | ||||||
|     void OnFramebufferFormatChanged(int new_value); |  | ||||||
|     void OnUpdate(); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; |  | ||||||
|     void OnResumed() override; |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void Update(); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|  |  | ||||||
|     QComboBox* framebuffer_source_list; |  | ||||||
|     CSpinBox* framebuffer_address_control; |  | ||||||
|     QSpinBox* framebuffer_width_control; |  | ||||||
|     QSpinBox* framebuffer_height_control; |  | ||||||
|     QComboBox* framebuffer_format_control; |  | ||||||
|  |  | ||||||
|     QLabel* framebuffer_picture_label; |  | ||||||
|  |  | ||||||
|     Source framebuffer_source; |  | ||||||
|     unsigned framebuffer_address; |  | ||||||
|     unsigned framebuffer_width; |  | ||||||
|     unsigned framebuffer_height; |  | ||||||
|     Format framebuffer_format; |  | ||||||
| }; |  | ||||||
							
								
								
									
										736
									
								
								src/citra_qt/debugger/graphics_surface.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										736
									
								
								src/citra_qt/debugger/graphics_surface.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,736 @@ | |||||||
|  | // Copyright 2014 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <QBoxLayout> | ||||||
|  | #include <QComboBox> | ||||||
|  | #include <QDebug> | ||||||
|  | #include <QFileDialog> | ||||||
|  | #include <QLabel> | ||||||
|  | #include <QMouseEvent> | ||||||
|  | #include <QPushButton> | ||||||
|  | #include <QScrollArea> | ||||||
|  | #include <QSpinBox> | ||||||
|  |  | ||||||
|  | #include "citra_qt/debugger/graphics_surface.h" | ||||||
|  | #include "citra_qt/util/spinbox.h" | ||||||
|  |  | ||||||
|  | #include "common/color.h" | ||||||
|  |  | ||||||
|  | #include "core/memory.h" | ||||||
|  | #include "core/hw/gpu.h" | ||||||
|  |  | ||||||
|  | #include "video_core/pica.h" | ||||||
|  | #include "video_core/pica_state.h" | ||||||
|  | #include "video_core/utils.h" | ||||||
|  |  | ||||||
|  | SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {} | ||||||
|  | SurfacePicture::~SurfacePicture() {} | ||||||
|  |  | ||||||
|  | void SurfacePicture::mousePressEvent(QMouseEvent* event) | ||||||
|  | { | ||||||
|  |     // Only do something while the left mouse button is held down | ||||||
|  |     if (!(event->buttons() & Qt::LeftButton)) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     if (pixmap() == nullptr) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     if (surface_widget) | ||||||
|  |         surface_widget->Pick(event->x() * pixmap()->width() / width(), | ||||||
|  |                              event->y() * pixmap()->height() / height()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SurfacePicture::mouseMoveEvent(QMouseEvent* event) | ||||||
|  | { | ||||||
|  |     // We also want to handle the event if the user moves the mouse while holding down the LMB | ||||||
|  |     mousePressEvent(event); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, | ||||||
|  |                                                      QWidget* parent) | ||||||
|  |     : BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent), | ||||||
|  |       surface_source(Source::ColorBuffer) | ||||||
|  | { | ||||||
|  |     setObjectName("PicaSurface"); | ||||||
|  |  | ||||||
|  |     surface_source_list = new QComboBox; | ||||||
|  |     surface_source_list->addItem(tr("Color Buffer")); | ||||||
|  |     surface_source_list->addItem(tr("Depth Buffer")); | ||||||
|  |     surface_source_list->addItem(tr("Stencil Buffer")); | ||||||
|  |     surface_source_list->addItem(tr("Texture 0")); | ||||||
|  |     surface_source_list->addItem(tr("Texture 1")); | ||||||
|  |     surface_source_list->addItem(tr("Texture 2")); | ||||||
|  |     surface_source_list->addItem(tr("Custom")); | ||||||
|  |     surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); | ||||||
|  |  | ||||||
|  |     surface_address_control = new CSpinBox; | ||||||
|  |     surface_address_control->SetBase(16); | ||||||
|  |     surface_address_control->SetRange(0, 0xFFFFFFFF); | ||||||
|  |     surface_address_control->SetPrefix("0x"); | ||||||
|  |  | ||||||
|  |     unsigned max_dimension = 16384; // TODO: Find actual maximum | ||||||
|  |  | ||||||
|  |     surface_width_control = new QSpinBox; | ||||||
|  |     surface_width_control->setRange(0, max_dimension); | ||||||
|  |  | ||||||
|  |     surface_height_control = new QSpinBox; | ||||||
|  |     surface_height_control->setRange(0, max_dimension); | ||||||
|  |  | ||||||
|  |     surface_picker_x_control = new QSpinBox; | ||||||
|  |     surface_picker_x_control->setRange(0, max_dimension - 1); | ||||||
|  |  | ||||||
|  |     surface_picker_y_control = new QSpinBox; | ||||||
|  |     surface_picker_y_control->setRange(0, max_dimension - 1); | ||||||
|  |  | ||||||
|  |     surface_format_control = new QComboBox; | ||||||
|  |  | ||||||
|  |     // Color formats sorted by Pica texture format index | ||||||
|  |     surface_format_control->addItem(tr("RGBA8")); | ||||||
|  |     surface_format_control->addItem(tr("RGB8")); | ||||||
|  |     surface_format_control->addItem(tr("RGB5A1")); | ||||||
|  |     surface_format_control->addItem(tr("RGB565")); | ||||||
|  |     surface_format_control->addItem(tr("RGBA4")); | ||||||
|  |     surface_format_control->addItem(tr("IA8")); | ||||||
|  |     surface_format_control->addItem(tr("RG8")); | ||||||
|  |     surface_format_control->addItem(tr("I8")); | ||||||
|  |     surface_format_control->addItem(tr("A8")); | ||||||
|  |     surface_format_control->addItem(tr("IA4")); | ||||||
|  |     surface_format_control->addItem(tr("I4")); | ||||||
|  |     surface_format_control->addItem(tr("A4")); | ||||||
|  |     surface_format_control->addItem(tr("ETC1")); | ||||||
|  |     surface_format_control->addItem(tr("ETC1A4")); | ||||||
|  |     surface_format_control->addItem(tr("D16")); | ||||||
|  |     surface_format_control->addItem(tr("D24")); | ||||||
|  |     surface_format_control->addItem(tr("D24X8")); | ||||||
|  |     surface_format_control->addItem(tr("X24S8")); | ||||||
|  |     surface_format_control->addItem(tr("Unknown")); | ||||||
|  |  | ||||||
|  |     surface_info_label = new QLabel(); | ||||||
|  |     surface_info_label->setWordWrap(true); | ||||||
|  |  | ||||||
|  |     surface_picture_label = new SurfacePicture(0, this); | ||||||
|  |     surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | ||||||
|  |     surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); | ||||||
|  |     surface_picture_label->setScaledContents(false); | ||||||
|  |  | ||||||
|  |     auto scroll_area = new QScrollArea(); | ||||||
|  |     scroll_area->setBackgroundRole(QPalette::Dark); | ||||||
|  |     scroll_area->setWidgetResizable(false); | ||||||
|  |     scroll_area->setWidget(surface_picture_label); | ||||||
|  |  | ||||||
|  |     save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); | ||||||
|  |  | ||||||
|  |     // Connections | ||||||
|  |     connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); | ||||||
|  |     connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int))); | ||||||
|  |     connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64))); | ||||||
|  |     connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int))); | ||||||
|  |     connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int))); | ||||||
|  |     connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int))); | ||||||
|  |     connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int))); | ||||||
|  |     connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerYChanged(int))); | ||||||
|  |     connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); | ||||||
|  |  | ||||||
|  |     auto main_widget = new QWidget; | ||||||
|  |     auto main_layout = new QVBoxLayout; | ||||||
|  |     { | ||||||
|  |         auto sub_layout = new QHBoxLayout; | ||||||
|  |         sub_layout->addWidget(new QLabel(tr("Source:"))); | ||||||
|  |         sub_layout->addWidget(surface_source_list); | ||||||
|  |         main_layout->addLayout(sub_layout); | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |         auto sub_layout = new QHBoxLayout; | ||||||
|  |         sub_layout->addWidget(new QLabel(tr("Physical Address:"))); | ||||||
|  |         sub_layout->addWidget(surface_address_control); | ||||||
|  |         main_layout->addLayout(sub_layout); | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |         auto sub_layout = new QHBoxLayout; | ||||||
|  |         sub_layout->addWidget(new QLabel(tr("Width:"))); | ||||||
|  |         sub_layout->addWidget(surface_width_control); | ||||||
|  |         main_layout->addLayout(sub_layout); | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |         auto sub_layout = new QHBoxLayout; | ||||||
|  |         sub_layout->addWidget(new QLabel(tr("Height:"))); | ||||||
|  |         sub_layout->addWidget(surface_height_control); | ||||||
|  |         main_layout->addLayout(sub_layout); | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |         auto sub_layout = new QHBoxLayout; | ||||||
|  |         sub_layout->addWidget(new QLabel(tr("Format:"))); | ||||||
|  |         sub_layout->addWidget(surface_format_control); | ||||||
|  |         main_layout->addLayout(sub_layout); | ||||||
|  |     } | ||||||
|  |     main_layout->addWidget(scroll_area); | ||||||
|  |  | ||||||
|  |     auto info_layout = new QHBoxLayout; | ||||||
|  |     { | ||||||
|  |         auto xy_layout = new QVBoxLayout; | ||||||
|  |         { | ||||||
|  |             { | ||||||
|  |                 auto sub_layout = new QHBoxLayout; | ||||||
|  |                 sub_layout->addWidget(new QLabel(tr("X:"))); | ||||||
|  |                 sub_layout->addWidget(surface_picker_x_control); | ||||||
|  |                 xy_layout->addLayout(sub_layout); | ||||||
|  |             } | ||||||
|  |             { | ||||||
|  |                 auto sub_layout = new QHBoxLayout; | ||||||
|  |                 sub_layout->addWidget(new QLabel(tr("Y:"))); | ||||||
|  |                 sub_layout->addWidget(surface_picker_y_control); | ||||||
|  |                 xy_layout->addLayout(sub_layout); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         info_layout->addLayout(xy_layout); | ||||||
|  |         surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); | ||||||
|  |         info_layout->addWidget(surface_info_label); | ||||||
|  |     } | ||||||
|  |     main_layout->addLayout(info_layout); | ||||||
|  |  | ||||||
|  |     main_layout->addWidget(save_surface); | ||||||
|  |     main_widget->setLayout(main_layout); | ||||||
|  |     setWidget(main_widget); | ||||||
|  |  | ||||||
|  |     // Load current data - TODO: Make sure this works when emulation is not running | ||||||
|  |     if (debug_context && debug_context->at_breakpoint) { | ||||||
|  |         emit Update(); | ||||||
|  |         widget()->setEnabled(debug_context->at_breakpoint); | ||||||
|  |     } else { | ||||||
|  |         widget()->setEnabled(false); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) | ||||||
|  | { | ||||||
|  |     emit Update(); | ||||||
|  |     widget()->setEnabled(true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnResumed() | ||||||
|  | { | ||||||
|  |     widget()->setEnabled(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) | ||||||
|  | { | ||||||
|  |     surface_source = static_cast<Source>(new_value); | ||||||
|  |     emit Update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) | ||||||
|  | { | ||||||
|  |     if (surface_address != new_value) { | ||||||
|  |         surface_address = static_cast<unsigned>(new_value); | ||||||
|  |  | ||||||
|  |         surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||||||
|  |         emit Update(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) | ||||||
|  | { | ||||||
|  |     if (surface_width != static_cast<unsigned>(new_value)) { | ||||||
|  |         surface_width = static_cast<unsigned>(new_value); | ||||||
|  |  | ||||||
|  |         surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||||||
|  |         emit Update(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) | ||||||
|  | { | ||||||
|  |     if (surface_height != static_cast<unsigned>(new_value)) { | ||||||
|  |         surface_height = static_cast<unsigned>(new_value); | ||||||
|  |  | ||||||
|  |         surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||||||
|  |         emit Update(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) | ||||||
|  | { | ||||||
|  |     if (surface_format != static_cast<Format>(new_value)) { | ||||||
|  |         surface_format = static_cast<Format>(new_value); | ||||||
|  |  | ||||||
|  |         surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||||||
|  |         emit Update(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) | ||||||
|  | { | ||||||
|  |     if (surface_picker_x != new_value) { | ||||||
|  |         surface_picker_x = new_value; | ||||||
|  |         Pick(surface_picker_x, surface_picker_y); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) | ||||||
|  | { | ||||||
|  |     if (surface_picker_y != new_value) { | ||||||
|  |         surface_picker_y = new_value; | ||||||
|  |         Pick(surface_picker_x, surface_picker_y); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::Pick(int x, int y) | ||||||
|  | { | ||||||
|  |     surface_picker_x_control->setValue(x); | ||||||
|  |     surface_picker_y_control->setValue(y); | ||||||
|  |  | ||||||
|  |     if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) { | ||||||
|  |         surface_info_label->setText(tr("Pixel out of bounds")); | ||||||
|  |         surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     u8* buffer = Memory::GetPhysicalPointer(surface_address); | ||||||
|  |     if (buffer == nullptr) { | ||||||
|  |         surface_info_label->setText(tr("(unable to access pixel data)")); | ||||||
|  |         surface_info_label->setAlignment(Qt::AlignCenter); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); | ||||||
|  |     unsigned stride = nibbles_per_pixel * surface_width / 2; | ||||||
|  |  | ||||||
|  |     unsigned bytes_per_pixel; | ||||||
|  |     bool nibble_mode = (nibbles_per_pixel == 1); | ||||||
|  |     if (nibble_mode) { | ||||||
|  |         // As nibbles are contained in a byte we still need to access one byte per nibble | ||||||
|  |         bytes_per_pixel = 1; | ||||||
|  |     } else { | ||||||
|  |         bytes_per_pixel = nibbles_per_pixel / 2; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const u32 coarse_y = y & ~7; | ||||||
|  |     u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; | ||||||
|  |     const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset); | ||||||
|  |  | ||||||
|  |     auto GetText = [offset](Format format, const u8* pixel) { | ||||||
|  |         switch (format) { | ||||||
|  |         case Format::RGBA8: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeRGBA8(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.b(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.a(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::RGB8: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeRGB8(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2, Blue: %3") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.b(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::RGB5A1: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeRGB5A1(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.b(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.a(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::RGB565: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeRGB565(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2, Blue: %3") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.b(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::RGBA4: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeRGBA4(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.b(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.a(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::IA8: | ||||||
|  |             return QString("Index: %1, Alpha: %2") | ||||||
|  |                       .arg(pixel[0]) | ||||||
|  |                       .arg(pixel[1]); | ||||||
|  |         case Format::RG8: { | ||||||
|  |             auto value = Color::DecodeRG8(pixel) / 255.0f; | ||||||
|  |             return QString("Red: %1, Green: %2") | ||||||
|  |                       .arg(QString::number(value.r(), 'f', 2)) | ||||||
|  |                       .arg(QString::number(value.g(), 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::I8: | ||||||
|  |             return QString("Index: %1").arg(*pixel); | ||||||
|  |         case Format::A8: | ||||||
|  |             return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2)); | ||||||
|  |         case Format::IA4: | ||||||
|  |             return QString("Index: %1, Alpha: %2") | ||||||
|  |                       .arg(*pixel & 0xF) | ||||||
|  |                       .arg((*pixel & 0xF0) >> 4); | ||||||
|  |         case Format::I4: | ||||||
|  |         { | ||||||
|  |             u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF; | ||||||
|  |             return QString("Index: %1").arg(i); | ||||||
|  |         } | ||||||
|  |         case Format::A4: | ||||||
|  |         { | ||||||
|  |             u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF; | ||||||
|  |             return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2)); | ||||||
|  |         } | ||||||
|  |         case Format::ETC1: | ||||||
|  |         case Format::ETC1A4: | ||||||
|  |             // TODO: Display block information or channel values? | ||||||
|  |             return QString("Compressed data"); | ||||||
|  |         case Format::D16: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeD16(pixel); | ||||||
|  |             return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4)); | ||||||
|  |         } | ||||||
|  |         case Format::D24: | ||||||
|  |         { | ||||||
|  |             auto value = Color::DecodeD24(pixel); | ||||||
|  |             return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4)); | ||||||
|  |         } | ||||||
|  |         case Format::D24X8: | ||||||
|  |         case Format::X24S8: | ||||||
|  |         { | ||||||
|  |             auto values = Color::DecodeD24S8(pixel); | ||||||
|  |             return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]); | ||||||
|  |         } | ||||||
|  |         case Format::Unknown: | ||||||
|  |             return QString("Unknown format"); | ||||||
|  |         default: | ||||||
|  |             return QString("Unhandled format"); | ||||||
|  |         } | ||||||
|  |         return QString(""); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     QString nibbles = ""; | ||||||
|  |     for (unsigned i = 0; i < nibbles_per_pixel; i++) { | ||||||
|  |         unsigned nibble_index = i; | ||||||
|  |         if (nibble_mode) { | ||||||
|  |             nibble_index += (offset % 2) ? 0 : 1; | ||||||
|  |         } | ||||||
|  |         u8 byte = pixel[nibble_index / 2]; | ||||||
|  |         u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF; | ||||||
|  |         nibbles.append(QString::number(nibble, 16).toUpper()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel))); | ||||||
|  |     surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::OnUpdate() | ||||||
|  | { | ||||||
|  |     QPixmap pixmap; | ||||||
|  |  | ||||||
|  |     switch (surface_source) { | ||||||
|  |     case Source::ColorBuffer: | ||||||
|  |     { | ||||||
|  |         // TODO: Store a reference to the registers in the debug context instead of accessing them directly... | ||||||
|  |  | ||||||
|  |         const auto& framebuffer = Pica::g_state.regs.framebuffer; | ||||||
|  |  | ||||||
|  |         surface_address = framebuffer.GetColorBufferPhysicalAddress(); | ||||||
|  |         surface_width = framebuffer.GetWidth(); | ||||||
|  |         surface_height = framebuffer.GetHeight(); | ||||||
|  |  | ||||||
|  |         switch (framebuffer.color_format) { | ||||||
|  |         case Pica::Regs::ColorFormat::RGBA8: | ||||||
|  |             surface_format = Format::RGBA8; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::ColorFormat::RGB8: | ||||||
|  |             surface_format = Format::RGB8; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::ColorFormat::RGB5A1: | ||||||
|  |             surface_format = Format::RGB5A1; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::ColorFormat::RGB565: | ||||||
|  |             surface_format = Format::RGB565; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::ColorFormat::RGBA4: | ||||||
|  |             surface_format = Format::RGBA4; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |             surface_format = Format::Unknown; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     case Source::DepthBuffer: | ||||||
|  |     { | ||||||
|  |         const auto& framebuffer = Pica::g_state.regs.framebuffer; | ||||||
|  |  | ||||||
|  |         surface_address = framebuffer.GetDepthBufferPhysicalAddress(); | ||||||
|  |         surface_width = framebuffer.GetWidth(); | ||||||
|  |         surface_height = framebuffer.GetHeight(); | ||||||
|  |  | ||||||
|  |         switch (framebuffer.depth_format) { | ||||||
|  |         case Pica::Regs::DepthFormat::D16: | ||||||
|  |             surface_format = Format::D16; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::DepthFormat::D24: | ||||||
|  |             surface_format = Format::D24; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         case Pica::Regs::DepthFormat::D24S8: | ||||||
|  |             surface_format = Format::D24X8; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |             surface_format = Format::Unknown; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     case Source::StencilBuffer: | ||||||
|  |     { | ||||||
|  |         const auto& framebuffer = Pica::g_state.regs.framebuffer; | ||||||
|  |  | ||||||
|  |         surface_address = framebuffer.GetDepthBufferPhysicalAddress(); | ||||||
|  |         surface_width = framebuffer.GetWidth(); | ||||||
|  |         surface_height = framebuffer.GetHeight(); | ||||||
|  |  | ||||||
|  |         switch (framebuffer.depth_format) { | ||||||
|  |         case Pica::Regs::DepthFormat::D24S8: | ||||||
|  |             surface_format = Format::X24S8; | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |             surface_format = Format::Unknown; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     case Source::Texture0: | ||||||
|  |     case Source::Texture1: | ||||||
|  |     case Source::Texture2: | ||||||
|  |     { | ||||||
|  |         unsigned texture_index; | ||||||
|  |         if (surface_source == Source::Texture0) texture_index = 0; | ||||||
|  |         else if (surface_source == Source::Texture1) texture_index = 1; | ||||||
|  |         else if (surface_source == Source::Texture2) texture_index = 2; | ||||||
|  |         else { | ||||||
|  |             qDebug() << "Unknown texture source " << static_cast<int>(surface_source); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; | ||||||
|  |         auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format); | ||||||
|  |  | ||||||
|  |         surface_address = info.physical_address; | ||||||
|  |         surface_width = info.width; | ||||||
|  |         surface_height = info.height; | ||||||
|  |         surface_format = static_cast<Format>(info.format); | ||||||
|  |  | ||||||
|  |         if (surface_format > Format::MaxTextureFormat) { | ||||||
|  |             qDebug() << "Unknown texture format " << static_cast<int>(info.format); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     case Source::Custom: | ||||||
|  |     { | ||||||
|  |         // Keep user-specified values | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |         qDebug() << "Unknown surface source " << static_cast<int>(surface_source); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     surface_address_control->SetValue(surface_address); | ||||||
|  |     surface_width_control->setValue(surface_width); | ||||||
|  |     surface_height_control->setValue(surface_height); | ||||||
|  |     surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); | ||||||
|  |  | ||||||
|  |     // TODO: Implement a good way to visualize alpha components! | ||||||
|  |  | ||||||
|  |     QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); | ||||||
|  |     u8* buffer = Memory::GetPhysicalPointer(surface_address); | ||||||
|  |  | ||||||
|  |     if (buffer == nullptr) { | ||||||
|  |         surface_picture_label->hide(); | ||||||
|  |         surface_info_label->setText(tr("(invalid surface address)")); | ||||||
|  |         surface_info_label->setAlignment(Qt::AlignCenter); | ||||||
|  |         surface_picker_x_control->setEnabled(false); | ||||||
|  |         surface_picker_y_control->setEnabled(false); | ||||||
|  |         save_surface->setEnabled(false); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (surface_format == Format::Unknown) { | ||||||
|  |         surface_picture_label->hide(); | ||||||
|  |         surface_info_label->setText(tr("(unknown surface format)")); | ||||||
|  |         surface_info_label->setAlignment(Qt::AlignCenter); | ||||||
|  |         surface_picker_x_control->setEnabled(false); | ||||||
|  |         surface_picker_y_control->setEnabled(false); | ||||||
|  |         save_surface->setEnabled(false); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     surface_picture_label->show(); | ||||||
|  |  | ||||||
|  |     unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format); | ||||||
|  |     unsigned stride = nibbles_per_pixel * surface_width / 2; | ||||||
|  |  | ||||||
|  |     // We handle depth formats here because DebugUtils only supports TextureFormats | ||||||
|  |     if (surface_format <= Format::MaxTextureFormat) { | ||||||
|  |  | ||||||
|  |         // Generate a virtual texture | ||||||
|  |         Pica::DebugUtils::TextureInfo info; | ||||||
|  |         info.physical_address = surface_address; | ||||||
|  |         info.width = surface_width; | ||||||
|  |         info.height = surface_height; | ||||||
|  |         info.format = static_cast<Pica::Regs::TextureFormat>(surface_format); | ||||||
|  |         info.stride = stride; | ||||||
|  |  | ||||||
|  |         for (unsigned int y = 0; y < surface_height; ++y) { | ||||||
|  |             for (unsigned int x = 0; x < surface_width; ++x) { | ||||||
|  |                 Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true); | ||||||
|  |                 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } else { | ||||||
|  |  | ||||||
|  |         ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel"); | ||||||
|  |         unsigned bytes_per_pixel = nibbles_per_pixel / 2; | ||||||
|  |  | ||||||
|  |         for (unsigned int y = 0; y < surface_height; ++y) { | ||||||
|  |             for (unsigned int x = 0; x < surface_width; ++x) { | ||||||
|  |                 const u32 coarse_y = y & ~7; | ||||||
|  |                 u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride; | ||||||
|  |                 const u8* pixel = buffer + offset; | ||||||
|  |                 Math::Vec4<u8> color = { 0, 0, 0, 0 }; | ||||||
|  |  | ||||||
|  |                 switch(surface_format) { | ||||||
|  |                 case Format::D16: | ||||||
|  |                 { | ||||||
|  |                     u32 data = Color::DecodeD16(pixel); | ||||||
|  |                     color.r() = data & 0xFF; | ||||||
|  |                     color.g() = (data >> 8) & 0xFF; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 case Format::D24: | ||||||
|  |                 { | ||||||
|  |                     u32 data = Color::DecodeD24(pixel); | ||||||
|  |                     color.r() = data & 0xFF; | ||||||
|  |                     color.g() = (data >> 8) & 0xFF; | ||||||
|  |                     color.b() = (data >> 16) & 0xFF; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 case Format::D24X8: | ||||||
|  |                 { | ||||||
|  |                     Math::Vec2<u32> data = Color::DecodeD24S8(pixel); | ||||||
|  |                     color.r() = data.x & 0xFF; | ||||||
|  |                     color.g() = (data.x >> 8) & 0xFF; | ||||||
|  |                     color.b() = (data.x >> 16) & 0xFF; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 case Format::X24S8: | ||||||
|  |                 { | ||||||
|  |                     Math::Vec2<u32> data = Color::DecodeD24S8(pixel); | ||||||
|  |                     color.r() = color.g() = color.b() = data.y; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 default: | ||||||
|  |                     qDebug() << "Unknown surface format " << static_cast<int>(surface_format); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pixmap = QPixmap::fromImage(decoded_image); | ||||||
|  |     surface_picture_label->setPixmap(pixmap); | ||||||
|  |     surface_picture_label->resize(pixmap.size()); | ||||||
|  |  | ||||||
|  |     // Update the info with pixel data | ||||||
|  |     surface_picker_x_control->setEnabled(true); | ||||||
|  |     surface_picker_y_control->setEnabled(true); | ||||||
|  |     Pick(surface_picker_x, surface_picker_y); | ||||||
|  |  | ||||||
|  |     // Enable saving the converted pixmap to file | ||||||
|  |     save_surface->setEnabled(true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GraphicsSurfaceWidget::SaveSurface() { | ||||||
|  |     QString png_filter = tr("Portable Network Graphic (*.png)"); | ||||||
|  |     QString bin_filter = tr("Binary data (*.bin)"); | ||||||
|  |  | ||||||
|  |     QString selectedFilter; | ||||||
|  |     QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), | ||||||
|  |                                                     QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); | ||||||
|  |  | ||||||
|  |     if (filename.isEmpty()) { | ||||||
|  |         // If the user canceled the dialog, don't save anything. | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (selectedFilter == png_filter) { | ||||||
|  |         const QPixmap* pixmap = surface_picture_label->pixmap(); | ||||||
|  |         ASSERT_MSG(pixmap != nullptr, "No pixmap set"); | ||||||
|  |  | ||||||
|  |         QFile file(filename); | ||||||
|  |         file.open(QIODevice::WriteOnly); | ||||||
|  |         if (pixmap) | ||||||
|  |             pixmap->save(&file, "PNG"); | ||||||
|  |     } else if (selectedFilter == bin_filter) { | ||||||
|  |         const u8* buffer = Memory::GetPhysicalPointer(surface_address); | ||||||
|  |         ASSERT_MSG(buffer != nullptr, "Memory not accessible"); | ||||||
|  |  | ||||||
|  |         QFile file(filename); | ||||||
|  |         file.open(QIODevice::WriteOnly); | ||||||
|  |         int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2; | ||||||
|  |         QByteArray data(reinterpret_cast<const char*>(buffer), size); | ||||||
|  |         file.write(data); | ||||||
|  |     } else { | ||||||
|  |         UNREACHABLE_MSG("Unhandled filter selected"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) { | ||||||
|  |     if (format <= Format::MaxTextureFormat) { | ||||||
|  |         return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     switch (format) { | ||||||
|  |         case Format::D24X8: | ||||||
|  |         case Format::X24S8: | ||||||
|  |             return 4 * 2; | ||||||
|  |         case Format::D24: | ||||||
|  |             return 3 * 2; | ||||||
|  |         case Format::D16: | ||||||
|  |             return 2 * 2; | ||||||
|  |         default: | ||||||
|  |             UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this " | ||||||
|  |                             "should not be reached as this function should " | ||||||
|  |                             "be given a format which is in " | ||||||
|  |                             "GraphicsSurfaceWidget::Format. Instead got %i", | ||||||
|  |                             static_cast<int>(format)); | ||||||
|  |             return 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										120
									
								
								src/citra_qt/debugger/graphics_surface.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/citra_qt/debugger/graphics_surface.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | |||||||
|  | // Copyright 2014 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "citra_qt/debugger/graphics_breakpoint_observer.h" | ||||||
|  |  | ||||||
|  | #include <QLabel> | ||||||
|  | #include <QPushButton> | ||||||
|  |  | ||||||
|  | class QComboBox; | ||||||
|  | class QSpinBox; | ||||||
|  | class CSpinBox; | ||||||
|  |  | ||||||
|  | class GraphicsSurfaceWidget; | ||||||
|  |  | ||||||
|  | class SurfacePicture : public QLabel | ||||||
|  | { | ||||||
|  |     Q_OBJECT | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |     SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr); | ||||||
|  |     ~SurfacePicture(); | ||||||
|  |  | ||||||
|  | protected slots: | ||||||
|  |     virtual void mouseMoveEvent(QMouseEvent* event); | ||||||
|  |     virtual void mousePressEvent(QMouseEvent* event); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     GraphicsSurfaceWidget* surface_widget; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class GraphicsSurfaceWidget : public BreakPointObserverDock { | ||||||
|  |     Q_OBJECT | ||||||
|  |  | ||||||
|  |     using Event = Pica::DebugContext::Event; | ||||||
|  |  | ||||||
|  |     enum class Source { | ||||||
|  |         ColorBuffer   = 0, | ||||||
|  |         DepthBuffer   = 1, | ||||||
|  |         StencilBuffer = 2, | ||||||
|  |         Texture0      = 3, | ||||||
|  |         Texture1      = 4, | ||||||
|  |         Texture2      = 5, | ||||||
|  |         Custom        = 6, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     enum class Format { | ||||||
|  |         // These must match the TextureFormat type! | ||||||
|  |         RGBA8        =  0, | ||||||
|  |         RGB8         =  1, | ||||||
|  |         RGB5A1       =  2, | ||||||
|  |         RGB565       =  3, | ||||||
|  |         RGBA4        =  4, | ||||||
|  |         IA8          =  5, | ||||||
|  |         RG8          =  6,  ///< @note Also called HILO8 in 3DBrew. | ||||||
|  |         I8           =  7, | ||||||
|  |         A8           =  8, | ||||||
|  |         IA4          =  9, | ||||||
|  |         I4           = 10, | ||||||
|  |         A4           = 11, | ||||||
|  |         ETC1         = 12,  // compressed | ||||||
|  |         ETC1A4       = 13, | ||||||
|  |         MaxTextureFormat = 13, | ||||||
|  |         D16          = 14, | ||||||
|  |         D24          = 15, | ||||||
|  |         D24X8        = 16, | ||||||
|  |         X24S8        = 17, | ||||||
|  |         Unknown      = 18, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     static unsigned int NibblesPerPixel(Format format); | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |     GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); | ||||||
|  |     void Pick(int x, int y); | ||||||
|  |  | ||||||
|  | public slots: | ||||||
|  |     void OnSurfaceSourceChanged(int new_value); | ||||||
|  |     void OnSurfaceAddressChanged(qint64 new_value); | ||||||
|  |     void OnSurfaceWidthChanged(int new_value); | ||||||
|  |     void OnSurfaceHeightChanged(int new_value); | ||||||
|  |     void OnSurfaceFormatChanged(int new_value); | ||||||
|  |     void OnSurfacePickerXChanged(int new_value); | ||||||
|  |     void OnSurfacePickerYChanged(int new_value); | ||||||
|  |     void OnUpdate(); | ||||||
|  |  | ||||||
|  | private slots: | ||||||
|  |     void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; | ||||||
|  |     void OnResumed() override; | ||||||
|  |  | ||||||
|  |     void SaveSurface(); | ||||||
|  |  | ||||||
|  | signals: | ||||||
|  |     void Update(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |  | ||||||
|  |     QComboBox* surface_source_list; | ||||||
|  |     CSpinBox* surface_address_control; | ||||||
|  |     QSpinBox* surface_width_control; | ||||||
|  |     QSpinBox* surface_height_control; | ||||||
|  |     QComboBox* surface_format_control; | ||||||
|  |  | ||||||
|  |     SurfacePicture* surface_picture_label; | ||||||
|  |     QSpinBox* surface_picker_x_control; | ||||||
|  |     QSpinBox* surface_picker_y_control; | ||||||
|  |     QLabel* surface_info_label; | ||||||
|  |     QPushButton* save_surface; | ||||||
|  |  | ||||||
|  |     Source surface_source; | ||||||
|  |     unsigned surface_address; | ||||||
|  |     unsigned surface_width; | ||||||
|  |     unsigned surface_height; | ||||||
|  |     Format surface_format; | ||||||
|  |     int surface_picker_x = 0; | ||||||
|  |     int surface_picker_y = 0; | ||||||
|  | }; | ||||||
| @@ -26,7 +26,7 @@ | |||||||
| #include "citra_qt/debugger/graphics.h" | #include "citra_qt/debugger/graphics.h" | ||||||
| #include "citra_qt/debugger/graphics_breakpoints.h" | #include "citra_qt/debugger/graphics_breakpoints.h" | ||||||
| #include "citra_qt/debugger/graphics_cmdlists.h" | #include "citra_qt/debugger/graphics_cmdlists.h" | ||||||
| #include "citra_qt/debugger/graphics_framebuffer.h" | #include "citra_qt/debugger/graphics_surface.h" | ||||||
| #include "citra_qt/debugger/graphics_tracing.h" | #include "citra_qt/debugger/graphics_tracing.h" | ||||||
| #include "citra_qt/debugger/graphics_vertex_shader.h" | #include "citra_qt/debugger/graphics_vertex_shader.h" | ||||||
| #include "citra_qt/debugger/profiler.h" | #include "citra_qt/debugger/profiler.h" | ||||||
| @@ -98,10 +98,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) | |||||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); |     addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); | ||||||
|     graphicsBreakpointsWidget->hide(); |     graphicsBreakpointsWidget->hide(); | ||||||
|  |  | ||||||
|     auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this); |  | ||||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); |  | ||||||
|     graphicsFramebufferWidget->hide(); |  | ||||||
|  |  | ||||||
|     auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); |     auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this); | ||||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); |     addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); | ||||||
|     graphicsVertexShaderWidget->hide(); |     graphicsVertexShaderWidget->hide(); | ||||||
| @@ -110,7 +106,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) | |||||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); |     addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); | ||||||
|     graphicsTracingWidget->hide(); |     graphicsTracingWidget->hide(); | ||||||
|  |  | ||||||
|  |     auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this); | ||||||
|  |     connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer())); | ||||||
|  |  | ||||||
|     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); |     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); | ||||||
|  |     debug_menu->addAction(graphicsSurfaceViewerAction); | ||||||
|  |     debug_menu->addSeparator(); | ||||||
|     debug_menu->addAction(profilerWidget->toggleViewAction()); |     debug_menu->addAction(profilerWidget->toggleViewAction()); | ||||||
| #if MICROPROFILE_ENABLED | #if MICROPROFILE_ENABLED | ||||||
|     debug_menu->addAction(microProfileDialog->toggleViewAction()); |     debug_menu->addAction(microProfileDialog->toggleViewAction()); | ||||||
| @@ -121,7 +122,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) | |||||||
|     debug_menu->addAction(graphicsWidget->toggleViewAction()); |     debug_menu->addAction(graphicsWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); |     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); |     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); |  | ||||||
|     debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); |     debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); | ||||||
|     debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); |     debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); | ||||||
|  |  | ||||||
| @@ -498,6 +498,13 @@ void GMainWindow::OnConfigure() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void GMainWindow::OnCreateGraphicsSurfaceViewer() { | ||||||
|  |     auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this); | ||||||
|  |     addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget); | ||||||
|  |     // TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true); | ||||||
|  |     graphicsSurfaceViewerWidget->show(); | ||||||
|  | } | ||||||
|  |  | ||||||
| bool GMainWindow::ConfirmClose() { | bool GMainWindow::ConfirmClose() { | ||||||
|     if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) |     if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) | ||||||
|         return true; |         return true; | ||||||
|   | |||||||
| @@ -108,6 +108,7 @@ private slots: | |||||||
|     void OnConfigure(); |     void OnConfigure(); | ||||||
|     void OnDisplayTitleBars(bool); |     void OnDisplayTitleBars(bool); | ||||||
|     void ToggleWindowMode(); |     void ToggleWindowMode(); | ||||||
|  |     void OnCreateGraphicsSurfaceViewer(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     Ui::MainWindow ui; |     Ui::MainWindow ui; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jannik Vogel
					Jannik Vogel