diff options
Diffstat (limited to 'src/yuzu/debugger')
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp | 27 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_breakpoint_observer.h | 33 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_breakpoints.cpp | 212 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_breakpoints.h | 46 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_breakpoints_p.h | 36 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_surface.cpp | 452 | ||||
| -rw-r--r-- | src/yuzu/debugger/graphics/graphics_surface.h | 97 | 
7 files changed, 903 insertions, 0 deletions
| diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..d6d61a739 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp @@ -0,0 +1,27 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMetaType> +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, +                                               const QString& title, QWidget* parent) +    : QDockWidget(title, parent), BreakPointObserver(debug_context) { +    qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::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(Tegra::DebugContext::Event, void*)), this, +            SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { +    emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnMaxwellResume() { +    emit Resumed(); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h new file mode 100644 index 000000000..9d05493cf --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. + * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, +                               protected Tegra::DebugContext::BreakPointObserver { +    Q_OBJECT + +public: +    BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title, +                           QWidget* parent = nullptr); + +    void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; +    void OnMaxwellResume() override; + +private slots: +    virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; +    virtual void OnResumed() = 0; + +signals: +    void Resumed(); +    void BreakPointHit(Tegra::DebugContext::Event event, void* data); +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp new file mode 100644 index 000000000..f98cc8152 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -0,0 +1,212 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_breakpoints_p.h" + +BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context, +                                 QObject* parent) +    : QAbstractListModel(parent), context_weak(debug_context), +      at_breakpoint(debug_context->at_breakpoint), +      active_breakpoint(debug_context->active_breakpoint) {} + +int BreakPointModel::columnCount(const QModelIndex& parent) const { +    return 1; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const { +    return static_cast<int>(Tegra::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const { +    const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + +    switch (role) { +    case Qt::DisplayRole: { +        if (index.column() == 0) { +            static const std::map<Tegra::DebugContext::Event, QString> map = { +                {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, +                {Tegra::DebugContext::Event::MaxwellCommandProcessed, +                 tr("Maxwell command processed")}, +                {Tegra::DebugContext::Event::IncomingPrimitiveBatch, +                 tr("Incoming primitive batch")}, +                {Tegra::DebugContext::Event::FinishedPrimitiveBatch, +                 tr("Finished primitive batch")}, +            }; + +            DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); +            return (map.find(event) != map.end()) ? map.at(event) : QString(); +        } + +        break; +    } + +    case Qt::CheckStateRole: { +        if (index.column() == 0) +            return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; +        break; +    } + +    case Qt::BackgroundRole: { +        if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { +            return QBrush(QColor(0xE0, 0xE0, 0x10)); +        } +        break; +    } + +    case Role_IsEnabled: { +        auto context = context_weak.lock(); +        return context && context->breakpoints[(int)event].enabled; +    } + +    default: +        break; +    } +    return QVariant(); +} + +Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { +    if (!index.isValid()) +        return 0; + +    Qt::ItemFlags flags = Qt::ItemIsEnabled; +    if (index.column() == 0) +        flags |= Qt::ItemIsUserCheckable; +    return flags; +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { +    const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + +    switch (role) { +    case Qt::CheckStateRole: { +        if (index.column() != 0) +            return false; + +        auto context = context_weak.lock(); +        if (!context) +            return false; + +        context->breakpoints[(int)event].enabled = value == Qt::Checked; +        QModelIndex changed_index = createIndex(index.row(), 0); +        emit dataChanged(changed_index, changed_index); +        return true; +    } +    } + +    return false; +} + +void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { +    auto context = context_weak.lock(); +    if (!context) +        return; + +    active_breakpoint = context->active_breakpoint; +    at_breakpoint = context->at_breakpoint; +    emit dataChanged(createIndex(static_cast<int>(event), 0), +                     createIndex(static_cast<int>(event), 0)); +} + +void BreakPointModel::OnResumed() { +    auto context = context_weak.lock(); +    if (!context) +        return; + +    at_breakpoint = context->at_breakpoint; +    emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), +                     createIndex(static_cast<int>(active_breakpoint), 0)); +    active_breakpoint = context->active_breakpoint; +} + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( +    std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) +    : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( +                                                          debug_context) { +    setObjectName("TegraBreakPointsWidget"); + +    status_text = new QLabel(tr("Emulation running")); +    resume_button = new QPushButton(tr("Resume")); +    resume_button->setEnabled(false); + +    breakpoint_model = new BreakPointModel(debug_context, this); +    breakpoint_list = new QTreeView; +    breakpoint_list->setRootIsDecorated(false); +    breakpoint_list->setHeaderHidden(true); +    breakpoint_list->setModel(breakpoint_model); + +    qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + +    connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, +            SLOT(OnItemDoubleClicked(const QModelIndex&))); + +    connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + +    connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, +            SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +    connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + +    connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, +            SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); +    connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + +    connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), +            breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + +    QWidget* main_widget = new QWidget; +    auto main_layout = new QVBoxLayout; +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(status_text); +        sub_layout->addWidget(resume_button); +        main_layout->addLayout(sub_layout); +    } +    main_layout->addWidget(breakpoint_list); +    main_widget->setLayout(main_layout); + +    setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { +    // Process in GUI thread +    emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { +    status_text->setText(tr("Emulation halted at breakpoint")); +    resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnMaxwellResume() { +    // Process in GUI thread +    emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() { +    status_text->setText(tr("Emulation running")); +    resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() { +    if (auto context = context_weak.lock()) +        context->Resume(); +} + +void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { +    if (!index.isValid()) +        return; + +    QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); +    QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); +    QVariant new_state = Qt::Unchecked; +    if (enabled == Qt::Unchecked) +        new_state = Qt::Checked; +    breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h new file mode 100644 index 000000000..ae0ede2e8 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h @@ -0,0 +1,46 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { +    Q_OBJECT + +    using Event = Tegra::DebugContext::Event; + +public: +    explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context, +                                       QWidget* parent = nullptr); + +    void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; +    void OnMaxwellResume() override; + +public slots: +    void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); +    void OnItemDoubleClicked(const QModelIndex&); +    void OnResumeRequested(); +    void OnResumed(); + +signals: +    void Resumed(); +    void BreakPointHit(Tegra::DebugContext::Event event, void* data); +    void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: +    QLabel* status_text; +    QPushButton* resume_button; + +    BreakPointModel* breakpoint_model; +    QTreeView* breakpoint_list; +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h new file mode 100644 index 000000000..35a6876ae --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h @@ -0,0 +1,36 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QAbstractListModel> +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { +    Q_OBJECT + +public: +    enum { +        Role_IsEnabled = Qt::UserRole, +    }; + +    BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent); + +    int columnCount(const QModelIndex& parent = QModelIndex()) const override; +    int rowCount(const QModelIndex& parent = QModelIndex()) const override; +    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; +    Qt::ItemFlags flags(const QModelIndex& index) const override; + +    bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + +public slots: +    void OnBreakPointHit(Tegra::DebugContext::Event event); +    void OnResumed(); + +private: +    std::weak_ptr<Tegra::DebugContext> context_weak; +    bool at_breakpoint; +    Tegra::DebugContext::Event active_breakpoint; +}; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..8e6509adc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,452 @@ +// 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 <QMouseEvent> +#include <QPushButton> +#include <QScrollArea> +#include <QSpinBox> +#include "core/core.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 "video_core/utils.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; +    default: +        UNIMPLEMENTED_MSG("Unimplemented RT format"); +    } +} + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) +    : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +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); + +    surface_format_control = new QComboBox; + +    // Color formats sorted by Maxwell texture format index +    surface_format_control->addItem(tr("None")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("A8R8G8B8")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("Unknown")); +    surface_format_control->addItem(tr("DXT1")); +    surface_format_control->addItem(tr("DXT23")); +    surface_format_control->addItem(tr("DXT45")); +    surface_format_control->addItem(tr("DXN1")); +    surface_format_control->addItem(tr("DXN2")); + +    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, SIGNAL(Update()), this, SLOT(OnUpdate())); +    connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, +            SLOT(OnSurfaceSourceChanged(int))); +    connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, +            SLOT(OnSurfaceAddressChanged(qint64))); +    connect(surface_width_control, SIGNAL(valueChanged(int)), this, +            SLOT(OnSurfaceWidthChanged(int))); +    connect(surface_height_control, SIGNAL(valueChanged(int)), this, +            SLOT(OnSurfaceHeightChanged(int))); +    connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, +            SLOT(OnSurfaceFormatChanged(int))); +    connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, +            SLOT(OnSurfacePickerXChanged(int))); +    connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, +            SLOT(OnSurfacePickerYChanged(int))); +    connect(save_surface, SIGNAL(clicked()), this, SLOT(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<Tegra::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... + +        auto& registers = gpu.Get3DEngine().regs; +        auto& rt = registers.rt[static_cast<size_t>(surface_source) - +                                static_cast<size_t>(Source::RenderTarget0)]; + +        surface_address = rt.Address(); +        surface_width = rt.horiz; +        surface_height = rt.vert; +        if (rt.format != 0) { +            surface_format = +                ConvertToTextureFormat(static_cast<Tegra::RenderTargetFormat>(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); +    VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + +    auto unswizzled_data = +        Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height); + +    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) { +            Math::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() { +    QString png_filter = tr("Portable Network Graphic (*.png)"); +    QString bin_filter = tr("Binary data (*.bin)"); + +    QString selectedFilter; +    QString filename = QFileDialog::getSaveFileName( +        this, tr("Save Surface"), +        QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), +        QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + +    if (filename.isEmpty()) { +        // If the user canceled the dialog, don't save anything. +        return; +    } + +    if (selectedFilter == png_filter) { +        const QPixmap* pixmap = surface_picture_label->pixmap(); +        ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + +        QFile file(filename); +        file.open(QIODevice::WriteOnly); +        if (pixmap) +            pixmap->save(&file, "PNG"); +    } else if (selectedFilter == bin_filter) { +        auto& gpu = Core::System::GetInstance().GPU(); +        VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + +        const u8* buffer = Memory::GetPointer(address); +        ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + +        QFile file(filename); +        file.open(QIODevice::WriteOnly); +        int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); +        QByteArray data(reinterpret_cast<const char*>(buffer), size); +        file.write(data); +    } else { +        UNREACHABLE_MSG("Unhandled filter selected"); +    } +} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h new file mode 100644 index 000000000..6a344bdfc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -0,0 +1,97 @@ +// 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(); + +protected slots: +    virtual void mouseMoveEvent(QMouseEvent* event); +    virtual void mousePressEvent(QMouseEvent* event); + +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(); + +private slots: +    void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; +    void OnResumed() override; + +    void SaveSurface(); + +signals: +    void Update(); + +private: +    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; +    Tegra::GPUVAddr surface_address; +    unsigned surface_width; +    unsigned surface_height; +    Tegra::Texture::TextureFormat surface_format; +    int surface_picker_x = 0; +    int surface_picker_y = 0; +}; | 
