2014-10-26 15:38:40 +00:00
|
|
|
// Copyright 2014 Citra Emulator Project
|
2014-12-17 05:38:14 +00:00
|
|
|
// Licensed under GPLv2 or any later version
|
2014-10-26 15:38:40 +00:00
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include <QBoxLayout>
|
|
|
|
#include <QComboBox>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QSpinBox>
|
|
|
|
|
2015-02-23 23:24:35 +00:00
|
|
|
#include "core/hw/gpu.h"
|
2014-12-31 14:17:07 +00:00
|
|
|
#include "video_core/color.h"
|
2014-10-26 15:38:40 +00:00
|
|
|
#include "video_core/pica.h"
|
2015-02-23 23:24:35 +00:00
|
|
|
#include "video_core/utils.h"
|
2014-10-26 15:38:40 +00:00
|
|
|
|
2015-01-03 23:51:14 +00:00
|
|
|
#include "graphics_framebuffer.h"
|
2014-10-26 15:38:40 +00:00
|
|
|
|
2015-01-03 23:51:14 +00:00
|
|
|
#include "util/spinbox.h"
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
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"));
|
2015-03-09 01:07:02 +00:00
|
|
|
framebuffer_source_list->addItem(tr("Active Depth Buffer"));
|
2014-10-26 15:38:40 +00:00
|
|
|
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"));
|
2015-02-26 03:44:55 +00:00
|
|
|
framebuffer_format_control->addItem(tr("RGB5A1"));
|
2014-10-26 15:38:40 +00:00
|
|
|
framebuffer_format_control->addItem(tr("RGB565"));
|
|
|
|
framebuffer_format_control->addItem(tr("RGBA4"));
|
2015-03-09 01:07:02 +00:00
|
|
|
framebuffer_format_control->addItem(tr("D16"));
|
|
|
|
framebuffer_format_control->addItem(tr("D24"));
|
|
|
|
framebuffer_format_control->addItem(tr("D24S8"));
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
// 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
|
2014-12-15 21:09:48 +00:00
|
|
|
if (debug_context && debug_context->at_breakpoint)
|
|
|
|
emit Update();
|
2014-10-26 15:38:40 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-10 20:35:26 +00:00
|
|
|
void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value)
|
2014-10-26 15:38:40 +00:00
|
|
|
{
|
2015-01-10 20:55:52 +00:00
|
|
|
if (framebuffer_width != static_cast<unsigned>(new_value)) {
|
|
|
|
framebuffer_width = static_cast<unsigned>(new_value);
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
|
|
|
emit Update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-10 20:35:26 +00:00
|
|
|
void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
|
2014-10-26 15:38:40 +00:00
|
|
|
{
|
2015-01-30 19:20:34 +00:00
|
|
|
if (framebuffer_height != static_cast<unsigned>(new_value)) {
|
|
|
|
framebuffer_height = static_cast<unsigned>(new_value);
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
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...
|
|
|
|
|
2015-03-09 02:31:15 +00:00
|
|
|
const auto& framebuffer = Pica::registers.framebuffer;
|
2014-10-26 15:38:40 +00:00
|
|
|
|
2014-12-15 20:28:45 +00:00
|
|
|
framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
|
2014-10-26 15:38:40 +00:00
|
|
|
framebuffer_width = framebuffer.GetWidth();
|
|
|
|
framebuffer_height = framebuffer.GetHeight();
|
2014-12-21 01:47:44 +00:00
|
|
|
// TODO: It's unknown how this format is actually specified
|
|
|
|
framebuffer_format = Format::RGBA8;
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-03-09 01:07:02 +00:00
|
|
|
case Source::DepthBuffer:
|
|
|
|
{
|
2015-03-09 02:31:15 +00:00
|
|
|
const auto& framebuffer = Pica::registers.framebuffer;
|
2015-03-09 01:07:02 +00:00
|
|
|
|
|
|
|
framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress();
|
|
|
|
framebuffer_width = framebuffer.GetWidth();
|
|
|
|
framebuffer_height = framebuffer.GetHeight();
|
|
|
|
framebuffer_format = Format::D16;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-10-26 15:38:40 +00:00
|
|
|
case Source::Custom:
|
|
|
|
{
|
|
|
|
// Keep user-specified values
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-12-04 18:41:03 +00:00
|
|
|
// TODO: Implement a good way to visualize alpha components!
|
|
|
|
// TODO: Unify this decoding code with the texture decoder
|
2015-03-09 01:07:02 +00:00
|
|
|
u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format);
|
2015-02-23 23:24:35 +00:00
|
|
|
|
2015-02-26 03:44:55 +00:00
|
|
|
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
|
2015-05-09 06:08:11 +00:00
|
|
|
u8* buffer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(framebuffer_address));
|
2015-03-09 01:07:02 +00:00
|
|
|
|
2015-02-26 03:44:55 +00:00
|
|
|
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;
|
2015-03-09 01:07:02 +00:00
|
|
|
const u8* pixel = buffer + offset;
|
2015-02-26 03:44:55 +00:00
|
|
|
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;
|
2015-03-09 01:07:02 +00:00
|
|
|
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::D24S8:
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2015-02-26 03:44:55 +00:00
|
|
|
default:
|
|
|
|
qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format);
|
|
|
|
break;
|
2014-10-26 15:38:40 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 03:44:55 +00:00
|
|
|
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
|
2014-10-26 15:38:40 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-26 03:44:55 +00:00
|
|
|
pixmap = QPixmap::fromImage(decoded_image);
|
2014-10-26 15:38:40 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2015-03-09 01:07:02 +00:00
|
|
|
|
|
|
|
u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) {
|
|
|
|
switch (format) {
|
|
|
|
case Format::RGBA8:
|
|
|
|
case Format::D24S8:
|
|
|
|
return 4;
|
|
|
|
case Format::RGB8:
|
|
|
|
case Format::D24:
|
|
|
|
return 3;
|
|
|
|
case Format::RGB5A1:
|
|
|
|
case Format::RGB565:
|
|
|
|
case Format::RGBA4:
|
|
|
|
case Format::D16:
|
|
|
|
return 2;
|
|
|
|
}
|
2015-03-09 02:31:15 +00:00
|
|
|
}
|