Merge pull request #2353 from lioncash/surface
yuzu/debugger: Remove graphics surface viewer
This commit is contained in:
commit
f46c3164e7
|
@ -56,8 +56,6 @@ add_executable(yuzu
|
||||||
debugger/graphics/graphics_breakpoints.cpp
|
debugger/graphics/graphics_breakpoints.cpp
|
||||||
debugger/graphics/graphics_breakpoints.h
|
debugger/graphics/graphics_breakpoints.h
|
||||||
debugger/graphics/graphics_breakpoints_p.h
|
debugger/graphics/graphics_breakpoints_p.h
|
||||||
debugger/graphics/graphics_surface.cpp
|
|
||||||
debugger/graphics/graphics_surface.h
|
|
||||||
debugger/console.cpp
|
debugger/console.cpp
|
||||||
debugger/console.h
|
debugger/console.h
|
||||||
debugger/profiler.cpp
|
debugger/profiler.cpp
|
||||||
|
|
|
@ -1,516 +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 <QFileDialog>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QMouseEvent>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QSpinBox>
|
|
||||||
#include "common/vector_math.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/memory.h"
|
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
|
||||||
#include "video_core/gpu.h"
|
|
||||||
#include "video_core/textures/decoders.h"
|
|
||||||
#include "video_core/textures/texture.h"
|
|
||||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
|
||||||
#include "yuzu/util/spinbox.h"
|
|
||||||
|
|
||||||
static Tegra::Texture::TextureFormat ConvertToTextureFormat(
|
|
||||||
Tegra::RenderTargetFormat render_target_format) {
|
|
||||||
switch (render_target_format) {
|
|
||||||
case Tegra::RenderTargetFormat::RGBA8_UNORM:
|
|
||||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
|
||||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
|
||||||
return Tegra::Texture::TextureFormat::A2B10G10R10;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unimplemented RT format");
|
|
||||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
|
|
||||||
: QLabel(parent), surface_widget(surface_widget_) {}
|
|
||||||
|
|
||||||
SurfacePicture::~SurfacePicture() = default;
|
|
||||||
|
|
||||||
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<Tegra::DebugContext> debug_context,
|
|
||||||
QWidget* parent)
|
|
||||||
: BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
|
|
||||||
surface_source(Source::RenderTarget0) {
|
|
||||||
setObjectName("MaxwellSurface");
|
|
||||||
|
|
||||||
surface_source_list = new QComboBox;
|
|
||||||
surface_source_list->addItem(tr("Render Target 0"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 1"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 2"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 3"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 4"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 5"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 6"));
|
|
||||||
surface_source_list->addItem(tr("Render Target 7"));
|
|
||||||
surface_source_list->addItem(tr("Z Buffer"));
|
|
||||||
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, 0x7FFFFFFFFFFFFFFF);
|
|
||||||
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);
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
// Color formats sorted by Maxwell texture format index
|
|
||||||
const QStringList surface_formats{
|
|
||||||
tr("None"),
|
|
||||||
QStringLiteral("R32_G32_B32_A32"),
|
|
||||||
QStringLiteral("R32_G32_B32"),
|
|
||||||
QStringLiteral("R16_G16_B16_A16"),
|
|
||||||
QStringLiteral("R32_G32"),
|
|
||||||
QStringLiteral("R32_B24G8"),
|
|
||||||
QStringLiteral("ETC2_RGB"),
|
|
||||||
QStringLiteral("X8B8G8R8"),
|
|
||||||
QStringLiteral("A8R8G8B8"),
|
|
||||||
QStringLiteral("A2B10G10R10"),
|
|
||||||
QStringLiteral("ETC2_RGB_PTA"),
|
|
||||||
QStringLiteral("ETC2_RGBA"),
|
|
||||||
QStringLiteral("R16_G16"),
|
|
||||||
QStringLiteral("G8R24"),
|
|
||||||
QStringLiteral("G24R8"),
|
|
||||||
QStringLiteral("R32"),
|
|
||||||
QStringLiteral("BC6H_SF16"),
|
|
||||||
QStringLiteral("BC6H_UF16"),
|
|
||||||
QStringLiteral("A4B4G4R4"),
|
|
||||||
QStringLiteral("A5B5G5R1"),
|
|
||||||
QStringLiteral("A1B5G5R5"),
|
|
||||||
QStringLiteral("B5G6R5"),
|
|
||||||
QStringLiteral("B6G5R5"),
|
|
||||||
QStringLiteral("BC7U"),
|
|
||||||
QStringLiteral("G8R8"),
|
|
||||||
QStringLiteral("EAC"),
|
|
||||||
QStringLiteral("EACX2"),
|
|
||||||
QStringLiteral("R16"),
|
|
||||||
QStringLiteral("Y8_VIDEO"),
|
|
||||||
QStringLiteral("R8"),
|
|
||||||
QStringLiteral("G4R4"),
|
|
||||||
QStringLiteral("R1"),
|
|
||||||
QStringLiteral("E5B9G9R9_SHAREDEXP"),
|
|
||||||
QStringLiteral("BF10GF11RF11"),
|
|
||||||
QStringLiteral("G8B8G8R8"),
|
|
||||||
QStringLiteral("B8G8R8G8"),
|
|
||||||
QStringLiteral("DXT1"),
|
|
||||||
QStringLiteral("DXT23"),
|
|
||||||
QStringLiteral("DXT45"),
|
|
||||||
QStringLiteral("DXN1"),
|
|
||||||
QStringLiteral("DXN2"),
|
|
||||||
QStringLiteral("Z24S8"),
|
|
||||||
QStringLiteral("X8Z24"),
|
|
||||||
QStringLiteral("S8Z24"),
|
|
||||||
QStringLiteral("X4V4Z24__COV4R4V"),
|
|
||||||
QStringLiteral("X4V4Z24__COV8R8V"),
|
|
||||||
QStringLiteral("V8Z24__COV4R12V"),
|
|
||||||
QStringLiteral("ZF32"),
|
|
||||||
QStringLiteral("ZF32_X24S8"),
|
|
||||||
QStringLiteral("X8Z24_X20V4S8__COV4R4V"),
|
|
||||||
QStringLiteral("X8Z24_X20V4S8__COV8R8V"),
|
|
||||||
QStringLiteral("ZF32_X20V4X8__COV4R4V"),
|
|
||||||
QStringLiteral("ZF32_X20V4X8__COV8R8V"),
|
|
||||||
QStringLiteral("ZF32_X20V4S8__COV4R4V"),
|
|
||||||
QStringLiteral("ZF32_X20V4S8__COV8R8V"),
|
|
||||||
QStringLiteral("X8Z24_X16V8S8__COV4R12V"),
|
|
||||||
QStringLiteral("ZF32_X16V8X8__COV4R12V"),
|
|
||||||
QStringLiteral("ZF32_X16V8S8__COV4R12V"),
|
|
||||||
QStringLiteral("Z16"),
|
|
||||||
QStringLiteral("V8Z24__COV8R24V"),
|
|
||||||
QStringLiteral("X8Z24_X16V8S8__COV8R24V"),
|
|
||||||
QStringLiteral("ZF32_X16V8X8__COV8R24V"),
|
|
||||||
QStringLiteral("ZF32_X16V8S8__COV8R24V"),
|
|
||||||
QStringLiteral("ASTC_2D_4X4"),
|
|
||||||
QStringLiteral("ASTC_2D_5X5"),
|
|
||||||
QStringLiteral("ASTC_2D_6X6"),
|
|
||||||
QStringLiteral("ASTC_2D_8X8"),
|
|
||||||
QStringLiteral("ASTC_2D_10X10"),
|
|
||||||
QStringLiteral("ASTC_2D_12X12"),
|
|
||||||
QStringLiteral("ASTC_2D_5X4"),
|
|
||||||
QStringLiteral("ASTC_2D_6X5"),
|
|
||||||
QStringLiteral("ASTC_2D_8X6"),
|
|
||||||
QStringLiteral("ASTC_2D_10X8"),
|
|
||||||
QStringLiteral("ASTC_2D_12X10"),
|
|
||||||
QStringLiteral("ASTC_2D_8X5"),
|
|
||||||
QStringLiteral("ASTC_2D_10X5"),
|
|
||||||
QStringLiteral("ASTC_2D_10X6"),
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
surface_format_control = new QComboBox;
|
|
||||||
surface_format_control->addItems(surface_formats);
|
|
||||||
|
|
||||||
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, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
|
|
||||||
connect(surface_source_list, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfaceSourceChanged);
|
|
||||||
connect(surface_address_control, &CSpinBox::ValueChanged, this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfaceAddressChanged);
|
|
||||||
connect(surface_width_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfaceWidthChanged);
|
|
||||||
connect(surface_height_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfaceHeightChanged);
|
|
||||||
connect(surface_format_control, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfaceFormatChanged);
|
|
||||||
connect(surface_picker_x_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfacePickerXChanged);
|
|
||||||
connect(surface_picker_y_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
|
||||||
&GraphicsSurfaceWidget::OnSurfacePickerYChanged);
|
|
||||||
connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::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("GPU 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(Tegra::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<GPUVAddr>(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<Tegra::Texture::TextureFormat>(new_value)) {
|
|
||||||
surface_format = static_cast<Tegra::Texture::TextureFormat>(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 >= static_cast<int>(surface_width) || y < 0 ||
|
|
||||||
y >= static_cast<int>(surface_height)) {
|
|
||||||
surface_info_label->setText(tr("Pixel out of bounds"));
|
|
||||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
|
|
||||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsSurfaceWidget::OnUpdate() {
|
|
||||||
auto& gpu = Core::System::GetInstance().GPU();
|
|
||||||
|
|
||||||
QPixmap pixmap;
|
|
||||||
|
|
||||||
switch (surface_source) {
|
|
||||||
case Source::RenderTarget0:
|
|
||||||
case Source::RenderTarget1:
|
|
||||||
case Source::RenderTarget2:
|
|
||||||
case Source::RenderTarget3:
|
|
||||||
case Source::RenderTarget4:
|
|
||||||
case Source::RenderTarget5:
|
|
||||||
case Source::RenderTarget6:
|
|
||||||
case Source::RenderTarget7: {
|
|
||||||
// TODO: Store a reference to the registers in the debug context instead of accessing them
|
|
||||||
// directly...
|
|
||||||
|
|
||||||
const auto& registers = gpu.Maxwell3D().regs;
|
|
||||||
const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
|
|
||||||
static_cast<std::size_t>(Source::RenderTarget0)];
|
|
||||||
|
|
||||||
surface_address = rt.Address();
|
|
||||||
surface_width = rt.width;
|
|
||||||
surface_height = rt.height;
|
|
||||||
if (rt.format != Tegra::RenderTargetFormat::NONE) {
|
|
||||||
surface_format = ConvertToTextureFormat(rt.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));
|
|
||||||
|
|
||||||
if (surface_address == 0) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement a good way to visualize alpha components!
|
|
||||||
|
|
||||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
|
||||||
|
|
||||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
|
||||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
|
||||||
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
|
|
||||||
gpu.MemoryManager().GetPointer(surface_address), 1, 1,
|
|
||||||
Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
|
|
||||||
|
|
||||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
|
||||||
surface_width, surface_height);
|
|
||||||
|
|
||||||
surface_picture_label->show();
|
|
||||||
|
|
||||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
|
||||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
|
||||||
Common::Vec4<u8> color;
|
|
||||||
color[0] = texture_data[x + y * surface_width + 0];
|
|
||||||
color[1] = texture_data[x + y * surface_width + 1];
|
|
||||||
color[2] = texture_data[x + y * surface_width + 2];
|
|
||||||
color[3] = texture_data[x + y * surface_width + 3];
|
|
||||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
const QString png_filter = tr("Portable Network Graphic (*.png)");
|
|
||||||
const QString bin_filter = tr("Binary data (*.bin)");
|
|
||||||
|
|
||||||
QString selected_filter;
|
|
||||||
const QString filename = QFileDialog::getSaveFileName(
|
|
||||||
this, tr("Save Surface"),
|
|
||||||
QStringLiteral("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
|
||||||
QStringLiteral("%1;;%2").arg(png_filter, bin_filter), &selected_filter);
|
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
|
||||||
// If the user canceled the dialog, don't save anything.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected_filter == png_filter) {
|
|
||||||
const QPixmap* const pixmap = surface_picture_label->pixmap();
|
|
||||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
|
||||||
|
|
||||||
QFile file{filename};
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pixmap->save(&file, "PNG")) {
|
|
||||||
QMessageBox::warning(this, tr("Error"),
|
|
||||||
tr("Failed to save surface data to file '%1'").arg(filename));
|
|
||||||
}
|
|
||||||
} else if (selected_filter == bin_filter) {
|
|
||||||
auto& gpu = Core::System::GetInstance().GPU();
|
|
||||||
const std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
|
||||||
|
|
||||||
const u8* const buffer = Memory::GetPointer(*address);
|
|
||||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
|
||||||
|
|
||||||
QFile file{filename};
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int size =
|
|
||||||
surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
|
|
||||||
const QByteArray data(reinterpret_cast<const char*>(buffer), size);
|
|
||||||
if (file.write(data) != data.size()) {
|
|
||||||
QMessageBox::warning(
|
|
||||||
this, tr("Error"),
|
|
||||||
tr("Failed to completely write surface data to file. The saved data will "
|
|
||||||
"likely be corrupt."));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
UNREACHABLE_MSG("Unhandled filter selected");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include "video_core/memory_manager.h"
|
|
||||||
#include "video_core/textures/texture.h"
|
|
||||||
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
|
|
||||||
|
|
||||||
class QComboBox;
|
|
||||||
class QSpinBox;
|
|
||||||
class CSpinBox;
|
|
||||||
|
|
||||||
class GraphicsSurfaceWidget;
|
|
||||||
|
|
||||||
class SurfacePicture : public QLabel {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SurfacePicture(QWidget* parent = nullptr,
|
|
||||||
GraphicsSurfaceWidget* surface_widget = nullptr);
|
|
||||||
~SurfacePicture() override;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void mouseMoveEvent(QMouseEvent* event) override;
|
|
||||||
void mousePressEvent(QMouseEvent* event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GraphicsSurfaceWidget* surface_widget;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using Event = Tegra::DebugContext::Event;
|
|
||||||
|
|
||||||
enum class Source {
|
|
||||||
RenderTarget0 = 0,
|
|
||||||
RenderTarget1 = 1,
|
|
||||||
RenderTarget2 = 2,
|
|
||||||
RenderTarget3 = 3,
|
|
||||||
RenderTarget4 = 4,
|
|
||||||
RenderTarget5 = 5,
|
|
||||||
RenderTarget6 = 6,
|
|
||||||
RenderTarget7 = 7,
|
|
||||||
ZBuffer = 8,
|
|
||||||
Custom = 9,
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::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();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void Update();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
|
||||||
void OnResumed() override;
|
|
||||||
|
|
||||||
void SaveSurface();
|
|
||||||
|
|
||||||
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;
|
|
||||||
GPUVAddr surface_address;
|
|
||||||
unsigned surface_width;
|
|
||||||
unsigned surface_height;
|
|
||||||
Tegra::Texture::TextureFormat surface_format;
|
|
||||||
int surface_picker_x = 0;
|
|
||||||
int surface_picker_y = 0;
|
|
||||||
};
|
|
|
@ -90,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||||
#include "yuzu/configuration/configure_dialog.h"
|
#include "yuzu/configuration/configure_dialog.h"
|
||||||
#include "yuzu/debugger/console.h"
|
#include "yuzu/debugger/console.h"
|
||||||
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
|
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
|
||||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
|
||||||
#include "yuzu/debugger/profiler.h"
|
#include "yuzu/debugger/profiler.h"
|
||||||
#include "yuzu/debugger/wait_tree.h"
|
#include "yuzu/debugger/wait_tree.h"
|
||||||
#include "yuzu/discord.h"
|
#include "yuzu/discord.h"
|
||||||
|
@ -483,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() {
|
||||||
graphicsBreakpointsWidget->hide();
|
graphicsBreakpointsWidget->hide();
|
||||||
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
|
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
|
||||||
|
|
||||||
graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
|
|
||||||
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
|
|
||||||
graphicsSurfaceWidget->hide();
|
|
||||||
debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
|
|
||||||
|
|
||||||
waitTreeWidget = new WaitTreeWidget(this);
|
waitTreeWidget = new WaitTreeWidget(this);
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
||||||
waitTreeWidget->hide();
|
waitTreeWidget->hide();
|
||||||
|
|
|
@ -23,7 +23,6 @@ class EmuThread;
|
||||||
class GameList;
|
class GameList;
|
||||||
class GImageInfo;
|
class GImageInfo;
|
||||||
class GraphicsBreakPointsWidget;
|
class GraphicsBreakPointsWidget;
|
||||||
class GraphicsSurfaceWidget;
|
|
||||||
class GRenderWindow;
|
class GRenderWindow;
|
||||||
class LoadingScreen;
|
class LoadingScreen;
|
||||||
class MicroProfileDialog;
|
class MicroProfileDialog;
|
||||||
|
@ -240,7 +239,6 @@ private:
|
||||||
ProfilerWidget* profilerWidget;
|
ProfilerWidget* profilerWidget;
|
||||||
MicroProfileDialog* microProfileDialog;
|
MicroProfileDialog* microProfileDialog;
|
||||||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
||||||
GraphicsSurfaceWidget* graphicsSurfaceWidget;
|
|
||||||
WaitTreeWidget* waitTreeWidget;
|
WaitTreeWidget* waitTreeWidget;
|
||||||
|
|
||||||
QAction* actions_recent_files[max_recent_files_item];
|
QAction* actions_recent_files[max_recent_files_item];
|
||||||
|
|
Reference in New Issue