diff options
Diffstat (limited to 'src/yuzu')
22 files changed, 1083 insertions, 119 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0c4056c49..5af3154d7 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -23,6 +23,13 @@ add_executable(yuzu configuration/configure_input.h configuration/configure_system.cpp configuration/configure_system.h + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_surface.h debugger/profiler.cpp debugger/profiler.h debugger/registers.cpp diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index b9dc4943a..469988d63 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -289,6 +289,6 @@ void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); // windowHandle() is not initialized until the Window is shown, so we connect it here. - connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, - SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection); + connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, + Qt::UniqueConnection); } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 6a40f035c..71dc58e5d 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -26,10 +26,18 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ { - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_E, + Qt::Key_Up, + Qt::Key_Down, + Qt::Key_Left, + Qt::Key_Right, + Qt::Key_E, }, { - Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_R, + Qt::Key_I, + Qt::Key_K, + Qt::Key_J, + Qt::Key_L, + Qt::Key_R, }, }}; @@ -70,7 +78,7 @@ void Config::ReadValues() { qt_config->beginGroup("Core"); Settings::values.cpu_core = - static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt()); + static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 1).toInt()); qt_config->endGroup(); qt_config->beginGroup("Renderer"); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index babd583a2..c5303851c 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>740</width> + <width>461</width> <height>500</height> </rect> </property> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 10043e6e8..78559e2bb 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -14,7 +14,11 @@ const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> ConfigureInput::analog_sub_buttons{{ - "up", "down", "left", "right", "modifier", + "up", + "down", + "left", + "right", + "modifier", }}; static QString getKeyName(int key_code) { @@ -36,7 +40,8 @@ static void SetAnalogButton(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, const std::string& button_name) { if (analog_param.Get("engine", "") != "analog_from_button") { analog_param = { - {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, + {"engine", "analog_from_button"}, + {"modifier_scale", "0.5"}, }; } analog_param.Set(button_name, input_param.Serialize()); @@ -107,11 +112,17 @@ ConfigureInput::ConfigureInput(QWidget* parent) analog_map_buttons = {{ { - ui->buttonLStickUp, ui->buttonLStickDown, ui->buttonLStickLeft, ui->buttonLStickRight, + ui->buttonLStickUp, + ui->buttonLStickDown, + ui->buttonLStickLeft, + ui->buttonLStickRight, ui->buttonLStickMod, }, { - ui->buttonRStickUp, ui->buttonRStickDown, ui->buttonRStickLeft, ui->buttonRStickRight, + ui->buttonRStickUp, + ui->buttonRStickDown, + ui->buttonRStickLeft, + ui->buttonRStickRight, ui->buttonRStickMod, }, }}; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index c162ca02c..377b79c77 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -15,9 +15,9 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> - <layout class="QGridLayout" name="gridLayout_7"> + <layout class="QGridLayout" name="buttons"> <item row="3" column="1"> - <widget class="QGroupBox" name="faceButtons_6"> + <widget class="QGroupBox" name="misc"> <property name="title"> <string>Misc.</string> </property> @@ -29,9 +29,9 @@ </property> <layout class="QGridLayout" name="gridLayout_6"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_25"> + <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> <item> - <widget class="QLabel" name="label_29"> + <widget class="QLabel" name="labelPlus"> <property name="text"> <string>Plus:</string> </property> @@ -47,9 +47,9 @@ </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_26"> + <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> <item> - <widget class="QLabel" name="label_30"> + <widget class="QLabel" name="labelMinus"> <property name="text"> <string>Minus:</string> </property> @@ -65,9 +65,9 @@ </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_27"> + <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> <item> - <widget class="QLabel" name="label_31"> + <widget class="QLabel" name="labelHome"> <property name="text"> <string>Home:</string> </property> @@ -83,9 +83,9 @@ </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_28"> + <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> <item> - <widget class="QLabel" name="label_11"> + <widget class="QLabel" name="labelScrCap"> <property name="text"> <string>Screen Capture:</string> @@ -130,9 +130,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="labelA"> <property name="text"> <string>A:</string> </property> @@ -148,9 +148,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> <item> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="labelB"> <property name="text"> <string>B:</string> </property> @@ -166,9 +166,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> <item> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="labelX"> <property name="text"> <string>X:</string> </property> @@ -184,9 +184,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> <item> - <widget class="QLabel" name="label_4"> + <widget class="QLabel" name="labelY"> <property name="text"> <string>Y:</string> </property> @@ -205,7 +205,7 @@ Capture:</string> </widget> </item> <item row="0" column="1"> - <widget class="QGroupBox" name="faceButtons_2"> + <widget class="QGroupBox" name="Dpad"> <property name="title"> <string>Directional Pad</string> </property> @@ -217,9 +217,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_2"> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_12"> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> <item> - <widget class="QLabel" name="label_34"> + <widget class="QLabel" name="labelDpadUp"> <property name="text"> <string>Up:</string> </property> @@ -235,9 +235,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_9"> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> <item> - <widget class="QLabel" name="label_35"> + <widget class="QLabel" name="labelDpadDown"> <property name="text"> <string>Down:</string> </property> @@ -253,9 +253,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_10"> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_32"> + <widget class="QLabel" name="labelDpadLeft"> <property name="text"> <string>Left:</string> </property> @@ -271,9 +271,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_11"> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> <item> - <widget class="QLabel" name="label_33"> + <widget class="QLabel" name="labelDpadRight"> <property name="text"> <string>Right:</string> </property> @@ -292,7 +292,7 @@ Capture:</string> </widget> </item> <item row="3" column="0"> - <widget class="QGroupBox" name="faceButtons_3"> + <widget class="QGroupBox" name="shoulderButtons"> <property name="title"> <string>Shoulder Buttons</string> </property> @@ -304,9 +304,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_13"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> <item> - <widget class="QLabel" name="label_17"> + <widget class="QLabel" name="labelL"> <property name="text"> <string>L:</string> </property> @@ -322,9 +322,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_14"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> <item> - <widget class="QLabel" name="label_19"> + <widget class="QLabel" name="labelR"> <property name="text"> <string>R:</string> </property> @@ -340,9 +340,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_15"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> <item> - <widget class="QLabel" name="label_20"> + <widget class="QLabel" name="labelZL"> <property name="text"> <string>ZL:</string> </property> @@ -358,9 +358,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_16"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> <item> - <widget class="QLabel" name="label_18"> + <widget class="QLabel" name="labelZR"> <property name="text"> <string>ZR:</string> </property> @@ -376,9 +376,9 @@ Capture:</string> </layout> </item> <item row="2" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_8"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> <item> - <widget class="QLabel" name="label_7"> + <widget class="QLabel" name="labelSL"> <property name="text"> <string>SL:</string> </property> @@ -394,9 +394,9 @@ Capture:</string> </layout> </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_29"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> <item> - <widget class="QLabel" name="label_8"> + <widget class="QLabel" name="labelSR"> <property name="text"> <string>SR:</string> </property> @@ -415,7 +415,7 @@ Capture:</string> </widget> </item> <item row="1" column="1"> - <widget class="QGroupBox" name="faceButtons_5"> + <widget class="QGroupBox" name="RStick"> <property name="title"> <string>Right Stick</string> </property> @@ -430,9 +430,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_5"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_24"> + <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> <item> - <widget class="QLabel" name="label_26"> + <widget class="QLabel" name="labelRStickDown"> <property name="text"> <string>Down:</string> </property> @@ -448,9 +448,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_22"> + <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> <item> - <widget class="QLabel" name="label_27"> + <widget class="QLabel" name="labelRStickRight"> <property name="text"> <string>Right:</string> </property> @@ -473,9 +473,9 @@ Capture:</string> </widget> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_21"> + <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_25"> + <widget class="QLabel" name="labelRStickLeft"> <property name="text"> <string>Left:</string> </property> @@ -491,9 +491,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_25"> + <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> <item> - <widget class="QLabel" name="label_28"> + <widget class="QLabel" name="labelRStickUp"> <property name="text"> <string>Up:</string> </property> @@ -509,9 +509,9 @@ Capture:</string> </layout> </item> <item row="2" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> <item> - <widget class="QLabel" name="label_5"> + <widget class="QLabel" name="labelRStickPressed"> <property name="text"> <string>Pressed:</string> </property> @@ -527,9 +527,9 @@ Capture:</string> </layout> </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_32"> + <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> <item> - <widget class="QLabel" name="label_10"> + <widget class="QLabel" name="labelRStickMod"> <property name="text"> <string>Modifier:</string> </property> @@ -548,7 +548,7 @@ Capture:</string> </widget> </item> <item row="1" column="0"> - <widget class="QGroupBox" name="faceButtons_4"> + <widget class="QGroupBox" name="LStick"> <property name="title"> <string>Left Stick</string> </property> @@ -560,9 +560,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_4"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_20"> + <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> <item> - <widget class="QLabel" name="label_22"> + <widget class="QLabel" name="labelLStickDown"> <property name="text"> <string>Down:</string> </property> @@ -585,9 +585,9 @@ Capture:</string> </widget> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_18"> + <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> <item> - <widget class="QLabel" name="label_23"> + <widget class="QLabel" name="labelLStickRight"> <property name="text"> <string>Right:</string> </property> @@ -603,9 +603,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_17"> + <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_21"> + <widget class="QLabel" name="labelLStickLeft"> <property name="text"> <string>Left:</string> </property> @@ -621,9 +621,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_19"> + <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> <item> - <widget class="QLabel" name="label_24"> + <widget class="QLabel" name="labelLStickUp"> <property name="text"> <string>Up:</string> </property> @@ -639,9 +639,9 @@ Capture:</string> </layout> </item> <item row="3" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_31"> + <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> <item> - <widget class="QLabel" name="label_9"> + <widget class="QLabel" name="labelLStickMod"> <property name="text"> <string>Modifier:</string> </property> @@ -657,9 +657,9 @@ Capture:</string> </layout> </item> <item row="3" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_7" stretch="0,0"> + <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> <item> - <widget class="QLabel" name="label_6"> + <widget class="QLabel" name="labelLStickPressed"> <property name="text"> <string>Pressed:</string> </property> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 89e783687..d09505a0f 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -9,7 +9,18 @@ #include "yuzu/ui_settings.h" static const std::array<int, 12> days_in_month = {{ - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + 31, + 29, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, }}; ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { 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; +}; diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index cc9babe84..8b30e0a85 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -74,7 +74,7 @@ QAction* MicroProfileDialog::toggleViewAction() { toggle_view_action = new QAction(windowTitle(), this); toggle_view_action->setCheckable(true); toggle_view_action->setChecked(isVisible()); - connect(toggle_view_action, SIGNAL(toggled(bool)), SLOT(setVisible(bool))); + connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible); } return toggle_view_action; @@ -107,7 +107,8 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { MicroProfileSetDisplayMode(1); // Timers screen MicroProfileInitUI(); - connect(&update_timer, SIGNAL(timeout()), SLOT(update())); + connect(&update_timer, &QTimer::timeout, this, + static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update)); } void MicroProfileWidget::paintEvent(QPaintEvent* ev) { diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 70471923e..cae2864e5 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -5,6 +5,7 @@ #include "yuzu/debugger/wait_tree.h" #include "yuzu/util/util.h" +#include "core/core.h" #include "core/hle/kernel/condition_variable.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" @@ -50,7 +51,7 @@ std::size_t WaitTreeItem::Row() const { } std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { - const auto& threads = Kernel::GetThreadList(); + const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList(); std::vector<std::unique_ptr<WaitTreeThread>> item_list; item_list.reserve(threads.size()); for (std::size_t i = 0; i < threads.size(); ++i) { @@ -149,8 +150,8 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_READY: status = tr("ready"); break; - case THREADSTATUS_WAIT_ARB: - status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0')); + case THREADSTATUS_WAIT_HLE_EVENT: + status = tr("waiting for HLE return"); break; case THREADSTATUS_WAIT_SLEEP: status = tr("sleeping"); @@ -168,7 +169,7 @@ QString WaitTreeThread::GetText() const { } QString pc_info = tr(" PC = 0x%1 LR = 0x%2") .arg(thread.context.pc, 8, 16, QLatin1Char('0')) - .arg(thread.context.cpu_registers[31], 8, 16, QLatin1Char('0')); + .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0')); return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; } @@ -179,7 +180,7 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkGreen); case THREADSTATUS_READY: return QColor(Qt::GlobalColor::darkBlue); - case THREADSTATUS_WAIT_ARB: + case THREADSTATUS_WAIT_HLE_EVENT: return QColor(Qt::GlobalColor::darkRed); case THREADSTATUS_WAIT_SLEEP: return QColor(Qt::GlobalColor::darkYellow); diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 4034e909b..e538174eb 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -20,7 +20,7 @@ class Mutex; class ConditionVariable; class Thread; class Timer; -} +} // namespace Kernel class WaitTreeThread; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 6d7c409d0..76ced4de4 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -114,8 +114,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { edit_filter->setPlaceholderText(tr("Enter pattern to filter")); edit_filter->installEventFilter(keyReleaseEater); edit_filter->setClearButtonEnabled(true); - connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, - SLOT(onTextChanged(const QString&))); + connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged); label_filter_result = new QLabel; button_filter_close = new QToolButton(this); button_filter_close->setText("X"); @@ -124,7 +123,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { "#000000; font-weight: bold; background: #F0F0F0; }" "QToolButton:hover{ border: none; padding: 0px; color: " "#EEEEEE; font-weight: bold; background: #E81123}"); - connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); + connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked); layout_filter->setSpacing(10); layout_filter->addWidget(label_filter); layout_filter->addWidget(edit_filter); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 31f2825ee..bd323870b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -18,7 +18,6 @@ #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/microprofile.h" -#include "common/platform.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" @@ -26,10 +25,13 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" @@ -69,6 +71,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + + debug_context = Tegra::DebugContext::Construct(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); @@ -161,6 +166,16 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + 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); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); @@ -175,7 +190,7 @@ void GMainWindow::InitializeRecentFileMenuActions() { for (int i = 0; i < max_recent_files_item; ++i) { actions_recent_files[i] = new QAction(this); actions_recent_files[i]->setVisible(false); - connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile())); + connect(actions_recent_files[i], &QAction::triggered, this, &GMainWindow::OnMenuRecentFile); ui.menu_recent_files->addAction(actions_recent_files[i]); } @@ -187,13 +202,14 @@ void GMainWindow::InitializeHotkeys() { RegisterHotkey("Main Window", "Load File", QKeySequence::Open); RegisterHotkey("Main Window", "Start Emulation"); RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); - RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence::Cancel, Qt::ApplicationShortcut); + RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), + Qt::ApplicationShortcut); LoadHotkeys(); - connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this, - SLOT(OnMenuLoadFile())); - connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this, - SLOT(OnStartGame())); + connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this, + &GMainWindow::OnMenuLoadFile); + connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this, + &GMainWindow::OnStartGame); connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger); connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously, @@ -245,13 +261,14 @@ void GMainWindow::RestoreUIState() { } void GMainWindow::ConnectWidgetEvents() { - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); - connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, - SLOT(OnGameListOpenSaveFolder(u64))); + connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); + connect(game_list, &GameList::OpenSaveFolderRequested, this, + &GMainWindow::OnGameListOpenSaveFolder); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); + connect(this, &GMainWindow::EmulationStarting, render_window, + &GRenderWindow::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, render_window, + &GRenderWindow::OnEmulationStopping); connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } @@ -323,6 +340,8 @@ bool GMainWindow::LoadROM(const QString& filename) { Core::System& system{Core::System::GetInstance()}; + system.SetGPUDebugContext(debug_context); + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); @@ -398,17 +417,17 @@ void GMainWindow::BootGame(const QString& filename) { render_window->moveContext(); emu_thread->start(); - connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); + connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, - SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget, - SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget, + &RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget, + &WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget, + &RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget, + &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); // Update the GUI registersWidget->OnDebugModeEntered(); @@ -437,7 +456,7 @@ void GMainWindow::ShutdownGame() { emu_thread = nullptr; // The emulation is stopped, so closing the window or not does not matter anymore - disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); + disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); // Update the GUI ui.action_Start->setEnabled(false); @@ -548,8 +567,7 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); - connect(emu_thread.get(), SIGNAL(ErrorThrown(Core::System::ResultStatus, std::string)), this, - SLOT(OnCoreError(Core::System::ResultStatus, std::string))); + connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); ui.action_Start->setText(tr("Continue")); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..2471caf83 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -15,17 +15,18 @@ class Config; class EmuThread; class GameList; class GImageInfo; -class GPUCommandStreamWidget; -class GPUCommandListWidget; class GraphicsBreakPointsWidget; -class GraphicsTracingWidget; -class GraphicsVertexShaderWidget; +class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; class RegistersWidget; class WaitTreeWidget; +namespace Tegra { +class DebugContext; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -138,6 +139,8 @@ private: Ui::MainWindow ui; + std::shared_ptr<Tegra::DebugContext> debug_context; + GRenderWindow* render_window; GameList* game_list; @@ -158,6 +161,8 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index d093da641..9036ce2c1 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -50,4 +50,4 @@ struct Values { }; extern Values values; -} +} // namespace UISettings diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp index 92753ec1c..14ef1e884 100644 --- a/src/yuzu/util/spinbox.cpp +++ b/src/yuzu/util/spinbox.cpp @@ -39,7 +39,7 @@ CSpinBox::CSpinBox(QWidget* parent) // TODO: Might be nice to not immediately call the slot. // Think of an address that is being replaced by a different one, in which case a lot // invalid intermediate addresses would be read from during editing. - connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished())); + connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished); UpdateText(); } |