citra-qt: Add pica framebuffer widget.
parent
2793619dce
commit
55ce9aca71
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
Loading…
Reference in New Issue