mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	citra-qt: Add pica framebuffer widget.
This commit is contained in:
		
							parent
							
								
									2793619dce
								
							
						
					
					
						commit
						55ce9aca71
					
				
					 4 changed files with 382 additions and 0 deletions
				
			
		|  | @ -10,6 +10,7 @@ set(SRCS | |||
|             debugger/graphics.cpp | ||||
|             debugger/graphics_breakpoints.cpp | ||||
|             debugger/graphics_cmdlists.cpp | ||||
|             debugger/graphics_framebuffer.cpp | ||||
|             debugger/ramview.cpp | ||||
|             debugger/registers.cpp | ||||
|             util/spinbox.cpp | ||||
|  | @ -27,6 +28,7 @@ set(HEADERS | |||
|             debugger/graphics.hxx | ||||
|             debugger/graphics_breakpoints.hxx | ||||
|             debugger/graphics_cmdlists.hxx | ||||
|             debugger/graphics_framebuffer.hxx | ||||
|             debugger/ramview.hxx | ||||
|             debugger/registers.hxx | ||||
|             util/spinbox.hxx | ||||
|  |  | |||
							
								
								
									
										282
									
								
								src/citra_qt/debugger/graphics_framebuffer.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								src/citra_qt/debugger/graphics_framebuffer.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,282 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QBoxLayout> | ||||
| #include <QComboBox> | ||||
| #include <QDebug> | ||||
| #include <QLabel> | ||||
| #include <QMetaType> | ||||
| #include <QPushButton> | ||||
| #include <QSpinBox> | ||||
| 
 | ||||
| #include "video_core/pica.h" | ||||
| 
 | ||||
| #include "graphics_framebuffer.hxx" | ||||
| 
 | ||||
| #include "util/spinbox.hxx" | ||||
| 
 | ||||
| BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, | ||||
|                                                const QString& title, QWidget* parent) | ||||
|     : QDockWidget(title, parent), BreakPointObserver(debug_context) | ||||
| { | ||||
|     qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); | ||||
| 
 | ||||
|     connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); | ||||
| 
 | ||||
|     // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
 | ||||
|     //       care of delaying its handling to the GUI thread.
 | ||||
|     connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), | ||||
|             this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), | ||||
|             Qt::BlockingQueuedConnection); | ||||
| } | ||||
| 
 | ||||
| void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) | ||||
| { | ||||
|     emit BreakPointHit(event, data); | ||||
| } | ||||
| 
 | ||||
| void BreakPointObserverDock::OnPicaResume() | ||||
| { | ||||
|     emit Resumed(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 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("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("RGBA5551")); | ||||
|     framebuffer_format_control->addItem(tr("RGB565")); | ||||
|     framebuffer_format_control->addItem(tr("RGBA4")); | ||||
| 
 | ||||
|     // 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
 | ||||
|     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 != new_value) { | ||||
|         framebuffer_width = new_value; | ||||
| 
 | ||||
|         framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||||
|         emit Update(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) | ||||
| { | ||||
|     if (framebuffer_height != new_value) { | ||||
|         framebuffer_height = 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...
 | ||||
| 
 | ||||
|         auto framebuffer = Pica::registers.framebuffer; | ||||
|         using Framebuffer = decltype(framebuffer); | ||||
| 
 | ||||
|         framebuffer_address = framebuffer.GetColorBufferAddress(); | ||||
|         framebuffer_width = framebuffer.GetWidth(); | ||||
|         framebuffer_height = framebuffer.GetHeight(); | ||||
|         framebuffer_format = static_cast<Format>(framebuffer.color_format); | ||||
| 
 | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case Source::Custom: | ||||
|     { | ||||
|         // Keep user-specified values
 | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     default: | ||||
|         qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     switch (framebuffer_format) { | ||||
|     case Format::RGBA8: | ||||
|     { | ||||
|         // TODO: Implement a good way to visualize the alpha component
 | ||||
| 
 | ||||
|         QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); | ||||
|         u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); | ||||
|         for (int y = 0; y < framebuffer_height; ++y) { | ||||
|             for (int x = 0; x < framebuffer_width; ++x) { | ||||
|                 u32 value = *(color_buffer + x + y * framebuffer_width); | ||||
| 
 | ||||
|                 decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); | ||||
|             } | ||||
|         } | ||||
|         pixmap = QPixmap::fromImage(decoded_image); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case Format::RGB8: | ||||
|     { | ||||
|         QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); | ||||
|         u8* color_buffer = Memory::GetPointer(framebuffer_address); | ||||
|         for (int y = 0; y < framebuffer_height; ++y) { | ||||
|             for (int x = 0; x < framebuffer_width; ++x) { | ||||
|                 u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; | ||||
| 
 | ||||
|                 decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); | ||||
|             } | ||||
|         } | ||||
|         pixmap = QPixmap::fromImage(decoded_image); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case Format::RGBA5551: | ||||
|     { | ||||
|         QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); | ||||
|         u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); | ||||
|         for (int y = 0; y < framebuffer_height; ++y) { | ||||
|             for (int x = 0; x < framebuffer_width; ++x) { | ||||
|                 u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); | ||||
|                 u8 r = (value >> 11) & 0x1F; | ||||
|                 u8 g = (value >> 6) & 0x1F; | ||||
|                 u8 b = (value >> 1) & 0x1F; | ||||
|                 u8 a = value & 1; | ||||
| 
 | ||||
|                 decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); | ||||
|             } | ||||
|         } | ||||
|         pixmap = QPixmap::fromImage(decoded_image); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     default: | ||||
|         qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
| } | ||||
							
								
								
									
										92
									
								
								src/citra_qt/debugger/graphics_framebuffer.hxx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/citra_qt/debugger/graphics_framebuffer.hxx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <QDockWidget> | ||||
| 
 | ||||
| #include "video_core/debug_utils/debug_utils.h" | ||||
| 
 | ||||
| class QComboBox; | ||||
| class QLabel; | ||||
| class QSpinBox; | ||||
| 
 | ||||
| class CSpinBox; | ||||
| 
 | ||||
| // Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots.
 | ||||
| // This is because the Pica breakpoint callbacks will called on a non-GUI thread, while
 | ||||
| // the widget usually wants to perform reactions in the GUI thread.
 | ||||
| class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, | ||||
|                            QWidget* parent = nullptr); | ||||
| 
 | ||||
|     void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; | ||||
|     void OnPicaResume() override; | ||||
| 
 | ||||
| private slots: | ||||
|     virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; | ||||
|     virtual void OnResumed() = 0; | ||||
| 
 | ||||
| signals: | ||||
|     void Resumed(); | ||||
|     void BreakPointHit(Pica::DebugContext::Event event, void* data); | ||||
| }; | ||||
| 
 | ||||
| class GraphicsFramebufferWidget : public BreakPointObserverDock { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
|     using Event = Pica::DebugContext::Event; | ||||
| 
 | ||||
|     enum class Source { | ||||
|         PicaTarget = 0, | ||||
|         Custom = 1, | ||||
| 
 | ||||
|         // TODO: Add GPU framebuffer sources!
 | ||||
|     }; | ||||
| 
 | ||||
|     enum class Format { | ||||
|         RGBA8    = 0, | ||||
|         RGB8     = 1, | ||||
|         RGBA5551 = 2, | ||||
|         RGB565   = 3, | ||||
|         RGBA4    = 4, | ||||
|     }; | ||||
| 
 | ||||
| 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; | ||||
| }; | ||||
|  | @ -22,6 +22,7 @@ | |||
| #include "debugger/graphics.hxx" | ||||
| #include "debugger/graphics_breakpoints.hxx" | ||||
| #include "debugger/graphics_cmdlists.hxx" | ||||
| #include "debugger/graphics_framebuffer.hxx" | ||||
| 
 | ||||
| #include "core/settings.h" | ||||
| #include "core/system.h" | ||||
|  | @ -74,6 +75,10 @@ GMainWindow::GMainWindow() | |||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); | ||||
|     graphicsBreakpointsWidget->hide(); | ||||
| 
 | ||||
|     auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this); | ||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); | ||||
|     graphicsFramebufferWidget->hide(); | ||||
| 
 | ||||
|     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); | ||||
|     debug_menu->addAction(disasmWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(registersWidget->toggleViewAction()); | ||||
|  | @ -81,6 +86,7 @@ GMainWindow::GMainWindow() | |||
|     debug_menu->addAction(graphicsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); | ||||
| 
 | ||||
|     // Set default UI state
 | ||||
|     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue