summaryrefslogtreecommitdiffstats
path: root/src/citra_qt
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/citra_qt/CMakeLists.txt2
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.cpp282
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.hxx92
-rw-r--r--src/citra_qt/main.cpp6
4 files changed, 382 insertions, 0 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 999724102..cd4a04eac 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -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
diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp
new file mode 100644
index 000000000..b91db5433
--- /dev/null
+++ b/src/citra_qt/debugger/graphics_framebuffer.cpp
@@ -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);
+}
diff --git a/src/citra_qt/debugger/graphics_framebuffer.hxx b/src/citra_qt/debugger/graphics_framebuffer.hxx
new file mode 100644
index 000000000..bb73b2f72
--- /dev/null
+++ b/src/citra_qt/debugger/graphics_framebuffer.hxx
@@ -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;
+};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 84afe59d8..b4e3ad964 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -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