diff options
Diffstat (limited to 'src/yuzu')
-rw-r--r-- | src/yuzu/CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/yuzu/about_dialog.cpp | 2 | ||||
-rw-r--r-- | src/yuzu/configuration/config.cpp | 15 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_general.cpp | 18 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_general.ui | 104 | ||||
-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 | 451 | ||||
-rw-r--r-- | src/yuzu/debugger/graphics/graphics_surface.h | 97 | ||||
-rw-r--r-- | src/yuzu/debugger/wait_tree.cpp | 6 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 78 | ||||
-rw-r--r-- | src/yuzu/main.h | 26 | ||||
-rw-r--r-- | src/yuzu/ui_settings.h | 4 |
16 files changed, 1096 insertions, 66 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/about_dialog.cpp b/src/yuzu/about_dialog.cpp index da3429822..d6647eeea 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QIcon> #include "common/scm_rev.h" #include "ui_aboutdialog.h" #include "yuzu/about_dialog.h" AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); + ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index f9ddb9edc..8843f2078 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -77,8 +77,7 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt()); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -94,6 +93,10 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); + qt_config->beginGroup("System"); + Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); qt_config->endGroup(); @@ -104,6 +107,8 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("UI"); + UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -171,7 +176,7 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core)); + qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -188,6 +193,10 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); + qt_config->beginGroup("System"); + qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 92fd6ab02..2d73fc5aa 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,9 +13,14 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->setupUi(this); + for (auto theme : UISettings::themes) { + ui->theme_combobox->addItem(theme.first, theme.second); + } + this->setConfiguration(); - ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } ConfigureGeneral::~ConfigureGeneral() {} @@ -23,13 +28,18 @@ ConfigureGeneral::~ConfigureGeneral() {} void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); - ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core)); + ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex()); + UISettings::values.theme = + ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + + Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 573c4cb0e..1775c4d40 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -13,17 +13,17 @@ <property name="windowTitle"> <string>Form</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="HorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="VerticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="GeneralGroupBox"> <property name="title"> <string>General</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> @@ -44,40 +44,80 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>CPU Core</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QComboBox" name="cpu_core_combobox"> - <item> - <property name="text"> - <string>Unicorn</string> - </property> - </item> - <item> - <property name="text"> - <string>Dynarmic</string> - </property> - </item> - </widget> - </item> - </layout> - </item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_cpu_jit"> + <property name="text"> + <string>Enable CPU JIT</string> + </property> + </widget> + </item> </layout> - </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="EmulationGroupBox"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="EmulationVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Enable docked mode</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="theme_group_box"> + <property name="title"> + <string>Theme</string> + </property> + <layout class="QHBoxLayout" name="theme_qhbox_layout"> + <item> + <layout class="QVBoxLayout" name="theme_qvbox_layout"> + <item> + <layout class="QHBoxLayout" name="theme_qhbox_layout_2"> + <item> + <widget class="QLabel" name="theme_label"> + <property name="text"> + <string>Theme:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="theme_combobox"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> + <widget class="QGroupBox" name="HotKeysGroupBox"> <property name="title"> <string>Hotkeys</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <layout class="QHBoxLayout" name="HotKeysHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="HotKeysVerticalLayout"> <item> <widget class="GHotkeysDialog" name="widget" native="true"/> </item> 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..1e4844b57 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,451 @@ +// 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.width; + surface_height = rt.height; + if (rt.format != Tegra::RenderTargetFormat::NONE) { + surface_format = ConvertToTextureFormat(rt.format); + } + + break; + } + + case Source::Custom: { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast<int>(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); + + if (surface_address == 0) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + 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/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 7a62f57b5..cae2864e5 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -150,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"); @@ -180,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/main.cpp b/src/yuzu/main.cpp index 9b2047e04..793d9d739 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,10 +71,16 @@ 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(); + default_theme_paths = QIcon::themeSearchPaths(); + UpdateUITheme(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -161,6 +169,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(); @@ -187,7 +205,8 @@ 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), &QShortcut::activated, this, @@ -324,6 +343,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"); @@ -350,9 +371,9 @@ bool GMainWindow::LoadROM(const QString& filename) { "yuzu. A real Switch is required.<br/><br/>" "For more information on dumping and decrypting games, please see the following " "wiki pages: <ul>" - "<li><a href='https://citra-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " + "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " "Cartridges</a></li>" - "<li><a href='https://citra-emu.org/wiki/dumping-installed-titles/'>Dumping " + "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping " "Installed Titles</a></li>" "</ul>")); break; @@ -635,6 +656,7 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); + UpdateUITheme(); config->Save(); } } @@ -673,18 +695,18 @@ void GMainWindow::UpdateStatusBar() { void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) { QMessageBox::StandardButton answer; QString status_message; - const QString common_message = - tr("The game you are trying to load requires additional files from your 3DS to be dumped " - "before playing.<br/><br/>For more information on dumping these files, please see the " - "following wiki page: <a " - "href='https://citra-emu.org/wiki/" - "dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>Dumping System " - "Archives and the Shared Fonts from a 3DS Console</a>.<br/><br/>Would you like to quit " - "back to the game list? Continuing emulation may result in crashes, corrupted save " - "data, or other bugs."); + const QString common_message = tr( + "The game you are trying to load requires additional files from your Switch to be dumped " + "before playing.<br/><br/>For more information on dumping these files, please see the " + "following wiki page: <a " + "href='https://yuzu-emu.org/wiki/" + "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System " + "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit " + "back to the game list? Continuing emulation may result in crashes, corrupted save " + "data, or other bugs."); switch (result) { case Core::System::ResultStatus::ErrorSystemFiles: { - QString message = "Citra was unable to locate a 3DS system archive"; + QString message = "yuzu was unable to locate a Switch system archive"; if (!details.empty()) { message.append(tr(": %1. ").arg(details.c_str())); } else { @@ -699,7 +721,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det } case Core::System::ResultStatus::ErrorSharedFont: { - QString message = tr("Citra was unable to locate the 3DS shared fonts. "); + QString message = tr("yuzu was unable to locate the Switch shared fonts. "); message.append(common_message); answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); @@ -815,6 +837,32 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +void GMainWindow::UpdateUITheme() { + QStringList theme_paths(default_theme_paths); + if (UISettings::values.theme != UISettings::themes[0].second && + !UISettings::values.theme.isEmpty()) { + QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + QFile f(theme_uri); + if (!f.exists()) { + LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); + } else { + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + GMainWindow::setStyleSheet(ts.readAll()); + } + theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); + QIcon::setThemeName(":/icons/" + UISettings::values.theme); + } else { + qApp->setStyleSheet(""); + GMainWindow::setStyleSheet(""); + theme_paths.append(QStringList{":/icons/default"}); + QIcon::setThemeName(":/icons/default"); + } + QIcon::setThemeSearchPaths(theme_paths); + emit UpdateThemedIcons(); +} + #ifdef main #undef main #endif diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..20ff65314 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -2,8 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#ifndef _CITRA_QT_MAIN_HXX_ -#define _CITRA_QT_MAIN_HXX_ +#pragma once #include <memory> #include <QMainWindow> @@ -15,17 +14,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 @@ -64,6 +64,9 @@ signals: */ void EmulationStopping(); + // Signal that tells widgets to update icons to use the current theme + void UpdateThemedIcons(); + private: void InitializeWidgets(); void InitializeDebugWidgets(); @@ -138,6 +141,8 @@ private: Ui::MainWindow ui; + std::shared_ptr<Tegra::DebugContext> debug_context; + GRenderWindow* render_window; GameList* game_list; @@ -150,7 +155,7 @@ private: std::unique_ptr<Config> config; - // Whether emulation is currently running in Citra. + // Whether emulation is currently running in yuzu. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; @@ -158,14 +163,17 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + // stores default icon theme search paths for the platform + QStringList default_theme_paths; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; }; - -#endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 9036ce2c1..8e215a002 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,6 +15,10 @@ namespace UISettings { using ContextualShortcut = std::pair<QString, int>; using Shortcut = std::pair<QString, ContextualShortcut>; +static const std::array<std::pair<QString, QString>, 2> themes = { + {std::make_pair(QString("Default"), QString("default")), + std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; + struct Values { QByteArray geometry; QByteArray state; |