diff options
Diffstat (limited to 'src/citra_qt/debugger')
| -rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.cpp | 282 | ||||
| -rw-r--r-- | src/citra_qt/debugger/graphics_framebuffer.hxx | 92 | 
2 files changed, 374 insertions, 0 deletions
| diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp new file mode 100644 index 000000000..b91db5433 --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -0,0 +1,282 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QSpinBox> + +#include "video_core/pica.h" + +#include "graphics_framebuffer.hxx" + +#include "util/spinbox.hxx" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, +                                               const QString& title, QWidget* parent) +    : QDockWidget(title, parent), BreakPointObserver(debug_context) +{ +    qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); + +    connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + +    // NOTE: This signal is emitted from a non-GUI thread, but connect() takes +    //       care of delaying its handling to the GUI thread. +    connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), +            this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), +            Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) +{ +    emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnPicaResume() +{ +    emit Resumed(); +} + + +GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, +                                                     QWidget* parent) +    : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), +      framebuffer_source(Source::PicaTarget) +{ +    setObjectName("PicaFramebuffer"); + +    framebuffer_source_list = new QComboBox; +    framebuffer_source_list->addItem(tr("Active Render Target")); +    framebuffer_source_list->addItem(tr("Custom")); +    framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); + +    framebuffer_address_control = new CSpinBox; +    framebuffer_address_control->SetBase(16); +    framebuffer_address_control->SetRange(0, 0xFFFFFFFF); +    framebuffer_address_control->SetPrefix("0x"); + +    framebuffer_width_control = new QSpinBox; +    framebuffer_width_control->setMinimum(1); +    framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum + +    framebuffer_height_control = new QSpinBox; +    framebuffer_height_control->setMinimum(1); +    framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum + +    framebuffer_format_control = new QComboBox; +    framebuffer_format_control->addItem(tr("RGBA8")); +    framebuffer_format_control->addItem(tr("RGB8")); +    framebuffer_format_control->addItem(tr("RGBA5551")); +    framebuffer_format_control->addItem(tr("RGB565")); +    framebuffer_format_control->addItem(tr("RGBA4")); + +    // TODO: This QLabel should shrink the image to the available space rather than just expanding... +    framebuffer_picture_label = new QLabel; + +    auto enlarge_button = new QPushButton(tr("Enlarge")); + +    // Connections +    connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); +    connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); +    connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); +    connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); +    connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); +    connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); + +    auto main_widget = new QWidget; +    auto main_layout = new QVBoxLayout; +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(new QLabel(tr("Source:"))); +        sub_layout->addWidget(framebuffer_source_list); +        main_layout->addLayout(sub_layout); +    } +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); +        sub_layout->addWidget(framebuffer_address_control); +        main_layout->addLayout(sub_layout); +    } +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(new QLabel(tr("Width:"))); +        sub_layout->addWidget(framebuffer_width_control); +        main_layout->addLayout(sub_layout); +    } +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(new QLabel(tr("Height:"))); +        sub_layout->addWidget(framebuffer_height_control); +        main_layout->addLayout(sub_layout); +    } +    { +        auto sub_layout = new QHBoxLayout; +        sub_layout->addWidget(new QLabel(tr("Format:"))); +        sub_layout->addWidget(framebuffer_format_control); +        main_layout->addLayout(sub_layout); +    } +    main_layout->addWidget(framebuffer_picture_label); +    main_layout->addWidget(enlarge_button); +    main_widget->setLayout(main_layout); +    setWidget(main_widget); + +    // Load current data - TODO: Make sure this works when emulation is not running +    emit Update(); +    widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint +} + +void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ +    emit Update(); +    widget()->setEnabled(true); +} + +void GraphicsFramebufferWidget::OnResumed() +{ +    widget()->setEnabled(false); +} + +void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) +{ +    framebuffer_source = static_cast<Source>(new_value); +    emit Update(); +} + +void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) +{ +    if (framebuffer_address != new_value) { +        framebuffer_address = static_cast<unsigned>(new_value); + +        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); +        emit Update(); +    } +} + +void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) +{ +    if (framebuffer_width != new_value) { +        framebuffer_width = new_value; + +        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); +        emit Update(); +    } +} + +void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) +{ +    if (framebuffer_height != new_value) { +        framebuffer_height = new_value; + +        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); +        emit Update(); +    } +} + +void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) +{ +    if (framebuffer_format != static_cast<Format>(new_value)) { +        framebuffer_format = static_cast<Format>(new_value); + +        framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); +        emit Update(); +    } +} + +void GraphicsFramebufferWidget::OnUpdate() +{ +    QPixmap pixmap; + +    switch (framebuffer_source) { +    case Source::PicaTarget: +    { +        // TODO: Store a reference to the registers in the debug context instead of accessing them directly... + +        auto framebuffer = Pica::registers.framebuffer; +        using Framebuffer = decltype(framebuffer); + +        framebuffer_address = framebuffer.GetColorBufferAddress(); +        framebuffer_width = framebuffer.GetWidth(); +        framebuffer_height = framebuffer.GetHeight(); +        framebuffer_format = static_cast<Format>(framebuffer.color_format); + +        break; +    } + +    case Source::Custom: +    { +        // Keep user-specified values +        break; +    } + +    default: +        qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); +        break; +    } + +    switch (framebuffer_format) { +    case Format::RGBA8: +    { +        // TODO: Implement a good way to visualize the alpha component + +        QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); +        u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); +        for (int y = 0; y < framebuffer_height; ++y) { +            for (int x = 0; x < framebuffer_width; ++x) { +                u32 value = *(color_buffer + x + y * framebuffer_width); + +                decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); +            } +        } +        pixmap = QPixmap::fromImage(decoded_image); +        break; +    } + +    case Format::RGB8: +    { +        QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); +        u8* color_buffer = Memory::GetPointer(framebuffer_address); +        for (int y = 0; y < framebuffer_height; ++y) { +            for (int x = 0; x < framebuffer_width; ++x) { +                u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; + +                decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); +            } +        } +        pixmap = QPixmap::fromImage(decoded_image); +        break; +    } + +    case Format::RGBA5551: +    { +        QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); +        u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); +        for (int y = 0; y < framebuffer_height; ++y) { +            for (int x = 0; x < framebuffer_width; ++x) { +                u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); +                u8 r = (value >> 11) & 0x1F; +                u8 g = (value >> 6) & 0x1F; +                u8 b = (value >> 1) & 0x1F; +                u8 a = value & 1; + +                decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); +            } +        } +        pixmap = QPixmap::fromImage(decoded_image); +        break; +    } + +    default: +        qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); +        break; +    } + +    framebuffer_address_control->SetValue(framebuffer_address); +    framebuffer_width_control->setValue(framebuffer_width); +    framebuffer_height_control->setValue(framebuffer_height); +    framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); +    framebuffer_picture_label->setPixmap(pixmap); +} diff --git a/src/citra_qt/debugger/graphics_framebuffer.hxx b/src/citra_qt/debugger/graphics_framebuffer.hxx new file mode 100644 index 000000000..bb73b2f72 --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.hxx @@ -0,0 +1,92 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> + +#include "video_core/debug_utils/debug_utils.h" + +class QComboBox; +class QLabel; +class QSpinBox; + +class CSpinBox; + +// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. +// This is because the Pica breakpoint callbacks will called on a non-GUI thread, while +// the widget usually wants to perform reactions in the GUI thread. +class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { +    Q_OBJECT + +public: +    BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, +                           QWidget* parent = nullptr); + +    void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; +    void OnPicaResume() override; + +private slots: +    virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; +    virtual void OnResumed() = 0; + +signals: +    void Resumed(); +    void BreakPointHit(Pica::DebugContext::Event event, void* data); +}; + +class GraphicsFramebufferWidget : public BreakPointObserverDock { +    Q_OBJECT + +    using Event = Pica::DebugContext::Event; + +    enum class Source { +        PicaTarget = 0, +        Custom = 1, + +        // TODO: Add GPU framebuffer sources! +    }; + +    enum class Format { +        RGBA8    = 0, +        RGB8     = 1, +        RGBA5551 = 2, +        RGB565   = 3, +        RGBA4    = 4, +    }; + +public: +    GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + +public slots: +    void OnFramebufferSourceChanged(int new_value); +    void OnFramebufferAddressChanged(qint64 new_value); +    void OnFramebufferWidthChanged(int new_value); +    void OnFramebufferHeightChanged(int new_value); +    void OnFramebufferFormatChanged(int new_value); +    void OnUpdate(); + +private slots: +    void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; +    void OnResumed() override; + +signals: +    void Update(); + +private: + +    QComboBox* framebuffer_source_list; +    CSpinBox* framebuffer_address_control; +    QSpinBox* framebuffer_width_control; +    QSpinBox* framebuffer_height_control; +    QComboBox* framebuffer_format_control; + +    QLabel* framebuffer_picture_label; + +    Source framebuffer_source; +    unsigned framebuffer_address; +    unsigned framebuffer_width; +    unsigned framebuffer_height; +    Format framebuffer_format; +}; | 
