mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	citra_qt: Replace 'Pica Framebuffer Debugger' with 'Pica Surface Viewer'
This commit is contained in:
		
							parent
							
								
									75cbfeee58
								
							
						
					
					
						commit
						7d7849d71a
					
				
					 9 changed files with 876 additions and 583 deletions
				
			
		|  | @ -11,7 +11,7 @@ set(SRCS | |||
|             debugger/graphics_breakpoint_observer.cpp | ||||
|             debugger/graphics_breakpoints.cpp | ||||
|             debugger/graphics_cmdlists.cpp | ||||
|             debugger/graphics_framebuffer.cpp | ||||
|             debugger/graphics_surface.cpp | ||||
|             debugger/graphics_tracing.cpp | ||||
|             debugger/graphics_vertex_shader.cpp | ||||
|             debugger/profiler.cpp | ||||
|  | @ -42,7 +42,7 @@ set(HEADERS | |||
|             debugger/graphics_breakpoints.h | ||||
|             debugger/graphics_breakpoints_p.h | ||||
|             debugger/graphics_cmdlists.h | ||||
|             debugger/graphics_framebuffer.h | ||||
|             debugger/graphics_surface.h | ||||
|             debugger/graphics_tracing.h | ||||
|             debugger/graphics_vertex_shader.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) { | ||||
| 
 | ||||
| } | ||||
|  | @ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { | |||
|             index = 0; | ||||
|         } else if (COMMAND_IN_RANGE(command_id, texture1)) { | ||||
|             index = 1; | ||||
|         } else { | ||||
|         } else if (COMMAND_IN_RANGE(command_id, texture2)) { | ||||
|             index = 2; | ||||
|         } else { | ||||
|             UNREACHABLE_MSG("Unknown texture command"); | ||||
|         } | ||||
|         auto config = Pica::g_state.regs.GetTextures()[index].config; | ||||
|         auto format = Pica::g_state.regs.GetTextures()[index].format; | ||||
|         auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); | ||||
| 
 | ||||
|         // TODO: Instead, emit a signal here to be caught by the main window widget.
 | ||||
|         auto main_window = static_cast<QMainWindow*>(parent()); | ||||
|         main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); | ||||
|         // TODO: Open a surface debugger
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -61,25 +61,3 @@ private: | |||
|     QWidget* command_info_widget; | ||||
|     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_breakpoints.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_vertex_shader.h" | ||||
| #include "citra_qt/debugger/profiler.h" | ||||
|  | @ -98,10 +98,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) | |||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); | ||||
|     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); | ||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); | ||||
|     graphicsVertexShaderWidget->hide(); | ||||
|  | @ -110,7 +106,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) | |||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); | ||||
|     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")); | ||||
|     debug_menu->addAction(graphicsSurfaceViewerAction); | ||||
|     debug_menu->addSeparator(); | ||||
|     debug_menu->addAction(profilerWidget->toggleViewAction()); | ||||
| #if MICROPROFILE_ENABLED | ||||
|     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(graphicsCommandsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsVertexShaderWidget->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() { | ||||
|     if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) | ||||
|         return true; | ||||
|  |  | |||
|  | @ -108,6 +108,7 @@ private slots: | |||
|     void OnConfigure(); | ||||
|     void OnDisplayTitleBars(bool); | ||||
|     void ToggleWindowMode(); | ||||
|     void OnCreateGraphicsSurfaceViewer(); | ||||
| 
 | ||||
| private: | ||||
|     Ui::MainWindow ui; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue