summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/about_dialog.cpp2
-rw-r--r--src/yuzu/bootmanager.cpp6
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure_general.cpp21
-rw-r--r--src/yuzu/configuration/configure_general.ui111
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp27
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.h33
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp212
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.h46
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints_p.h36
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp453
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h97
-rw-r--r--src/yuzu/debugger/registers.cpp190
-rw-r--r--src/yuzu/debugger/registers.h42
-rw-r--r--src/yuzu/debugger/registers.ui40
-rw-r--r--src/yuzu/debugger/wait_tree.cpp141
-rw-r--r--src/yuzu/debugger/wait_tree.h54
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/main.cpp145
-rw-r--r--src/yuzu/main.h29
-rw-r--r--src/yuzu/ui_settings.h4
22 files changed, 1264 insertions, 457 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 0c4056c49..c662570d2 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -23,10 +23,15 @@ 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
- debugger/registers.h
debugger/wait_tree.cpp
debugger/wait_tree.h
game_list.cpp
@@ -53,7 +58,6 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_system.ui
- debugger/registers.ui
hotkeys.ui
main.ui
)
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/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 469988d63..5c17cd0d9 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -4,6 +4,8 @@
#include <QScreen>
#include <QWindow>
+#include <fmt/format.h>
+
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
@@ -102,8 +104,8 @@ private:
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
- std::string window_title = Common::StringFromFormat("yuzu %s| %s-%s", Common::g_build_name,
- Common::g_scm_branch, Common::g_scm_desc);
+ std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title));
InputCommon::Init();
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f9ddb9edc..8316db708 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -77,8 +77,8 @@ 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();
+ Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -94,6 +94,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 +108,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 +177,8 @@ 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->setValue("use_multi_core", Settings::values.use_multi_core);
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -188,6 +195,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..baa558667 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -13,9 +13,15 @@ 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_multi_core->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
}
ConfigureGeneral::~ConfigureGeneral() {}
@@ -23,13 +29,20 @@ 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_multi_core->setChecked(Settings::values.use_multi_core);
+ 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_multi_core = ui->use_multi_core->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..233adbe27 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,87 @@
</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>
+ <item>
+ <widget class="QCheckBox" name="use_multi_core">
+ <property name="text">
+ <string>Enable multi-core</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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>
- </widget>
+ </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..1fbca8ad0
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -0,0 +1,453 @@
+// 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;
+ case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
+ return Tegra::Texture::TextureFormat::A2B10G10R10;
+ 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);
+ boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(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();
+ boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(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/registers.cpp b/src/yuzu/debugger/registers.cpp
deleted file mode 100644
index 06e2d1647..000000000
--- a/src/yuzu/debugger/registers.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <QTreeWidgetItem>
-#include "core/arm/arm_interface.h"
-#include "core/core.h"
-#include "yuzu/debugger/registers.h"
-#include "yuzu/util/util.h"
-
-RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) {
- cpu_regs_ui.setupUi(this);
-
- tree = cpu_regs_ui.treeWidget;
- tree->addTopLevelItem(core_registers = new QTreeWidgetItem(QStringList(tr("Registers"))));
- tree->addTopLevelItem(vfp_registers = new QTreeWidgetItem(QStringList(tr("VFP Registers"))));
- tree->addTopLevelItem(vfp_system_registers =
- new QTreeWidgetItem(QStringList(tr("VFP System Registers"))));
- tree->addTopLevelItem(cpsr = new QTreeWidgetItem(QStringList("CPSR")));
-
- for (int i = 0; i < 16; ++i) {
- QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("R[%1]").arg(i)));
- core_registers->addChild(child);
- }
-
- for (int i = 0; i < 32; ++i) {
- QTreeWidgetItem* child = new QTreeWidgetItem(QStringList(QString("S[%1]").arg(i)));
- vfp_registers->addChild(child);
- }
-
- QFont font = GetMonospaceFont();
-
- CreateCPSRChildren();
- CreateVFPSystemRegisterChildren();
-
- // Set Registers to display in monospace font
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setFont(1, font);
-
- for (int i = 0; i < vfp_registers->childCount(); ++i)
- vfp_registers->child(i)->setFont(1, font);
-
- for (int i = 0; i < vfp_system_registers->childCount(); ++i) {
- vfp_system_registers->child(i)->setFont(1, font);
- for (int x = 0; x < vfp_system_registers->child(i)->childCount(); ++x) {
- vfp_system_registers->child(i)->child(x)->setFont(1, font);
- }
- }
- // Set CSPR to display in monospace font
- cpsr->setFont(1, font);
- for (int i = 0; i < cpsr->childCount(); ++i) {
- cpsr->child(i)->setFont(1, font);
- for (int x = 0; x < cpsr->child(i)->childCount(); ++x) {
- cpsr->child(i)->child(x)->setFont(1, font);
- }
- }
- setEnabled(false);
-}
-
-void RegistersWidget::OnDebugModeEntered() {
- if (!Core::System::GetInstance().IsPoweredOn())
- return;
-
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setText(
- 1, QString("0x%1").arg(Core::CPU().GetReg(i), 8, 16, QLatin1Char('0')));
-
- UpdateCPSRValues();
-}
-
-void RegistersWidget::OnDebugModeLeft() {}
-
-void RegistersWidget::OnEmulationStarting(EmuThread* emu_thread) {
- setEnabled(true);
-}
-
-void RegistersWidget::OnEmulationStopping() {
- // Reset widget text
- for (int i = 0; i < core_registers->childCount(); ++i)
- core_registers->child(i)->setText(1, QString(""));
-
- for (int i = 0; i < vfp_registers->childCount(); ++i)
- vfp_registers->child(i)->setText(1, QString(""));
-
- for (int i = 0; i < cpsr->childCount(); ++i)
- cpsr->child(i)->setText(1, QString(""));
-
- cpsr->setText(1, QString(""));
-
- // FPSCR
- for (int i = 0; i < vfp_system_registers->child(0)->childCount(); ++i)
- vfp_system_registers->child(0)->child(i)->setText(1, QString(""));
-
- // FPEXC
- for (int i = 0; i < vfp_system_registers->child(1)->childCount(); ++i)
- vfp_system_registers->child(1)->child(i)->setText(1, QString(""));
-
- vfp_system_registers->child(0)->setText(1, QString(""));
- vfp_system_registers->child(1)->setText(1, QString(""));
- vfp_system_registers->child(2)->setText(1, QString(""));
- vfp_system_registers->child(3)->setText(1, QString(""));
-
- setEnabled(false);
-}
-
-void RegistersWidget::CreateCPSRChildren() {
- cpsr->addChild(new QTreeWidgetItem(QStringList("M")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("T")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("F")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("I")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("A")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("E")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("IT")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("GE")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("DNM")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("J")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("Q")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("V")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("C")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("Z")));
- cpsr->addChild(new QTreeWidgetItem(QStringList("N")));
-}
-
-void RegistersWidget::UpdateCPSRValues() {
- const u32 cpsr_val = Core::CPU().GetCPSR();
-
- cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
- cpsr->child(0)->setText(
- 1, QString("b%1").arg(cpsr_val & 0x1F, 5, 2, QLatin1Char('0'))); // M - Mode
- cpsr->child(1)->setText(1, QString::number((cpsr_val >> 5) & 1)); // T - State
- cpsr->child(2)->setText(1, QString::number((cpsr_val >> 6) & 1)); // F - FIQ disable
- cpsr->child(3)->setText(1, QString::number((cpsr_val >> 7) & 1)); // I - IRQ disable
- cpsr->child(4)->setText(1, QString::number((cpsr_val >> 8) & 1)); // A - Imprecise abort
- cpsr->child(5)->setText(1, QString::number((cpsr_val >> 9) & 1)); // E - Data endianness
- cpsr->child(6)->setText(1,
- QString::number((cpsr_val >> 10) & 0x3F)); // IT - If-Then state (DNM)
- cpsr->child(7)->setText(1,
- QString::number((cpsr_val >> 16) & 0xF)); // GE - Greater-than-or-Equal
- cpsr->child(8)->setText(1, QString::number((cpsr_val >> 20) & 0xF)); // DNM - Do not modify
- cpsr->child(9)->setText(1, QString::number((cpsr_val >> 24) & 1)); // J - Jazelle
- cpsr->child(10)->setText(1, QString::number((cpsr_val >> 27) & 1)); // Q - Saturation
- cpsr->child(11)->setText(1, QString::number((cpsr_val >> 28) & 1)); // V - Overflow
- cpsr->child(12)->setText(1, QString::number((cpsr_val >> 29) & 1)); // C - Carry/Borrow/Extend
- cpsr->child(13)->setText(1, QString::number((cpsr_val >> 30) & 1)); // Z - Zero
- cpsr->child(14)->setText(1, QString::number((cpsr_val >> 31) & 1)); // N - Negative/Less than
-}
-
-void RegistersWidget::CreateVFPSystemRegisterChildren() {
- QTreeWidgetItem* const fpscr = new QTreeWidgetItem(QStringList("FPSCR"));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IOC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DZC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("OFC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("UFC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IXC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IDC")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IOE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DZE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("OFE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("UFE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IXE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("IDE")));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Length"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Vector Stride"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList(tr("Rounding Mode"))));
- fpscr->addChild(new QTreeWidgetItem(QStringList("FZ")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("DN")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("V")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("C")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("Z")));
- fpscr->addChild(new QTreeWidgetItem(QStringList("N")));
-
- QTreeWidgetItem* const fpexc = new QTreeWidgetItem(QStringList("FPEXC"));
- fpexc->addChild(new QTreeWidgetItem(QStringList("IOC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("OFC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("UFC")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("INV")));
- fpexc->addChild(new QTreeWidgetItem(QStringList(tr("Vector Iteration Count"))));
- fpexc->addChild(new QTreeWidgetItem(QStringList("FP2V")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("EN")));
- fpexc->addChild(new QTreeWidgetItem(QStringList("EX")));
-
- vfp_system_registers->addChild(fpscr);
- vfp_system_registers->addChild(fpexc);
- vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST")));
- vfp_system_registers->addChild(new QTreeWidgetItem(QStringList("FPINST2")));
-}
-
-void RegistersWidget::UpdateVFPSystemRegisterValues() {
- UNIMPLEMENTED();
-}
diff --git a/src/yuzu/debugger/registers.h b/src/yuzu/debugger/registers.h
deleted file mode 100644
index 55bda5b59..000000000
--- a/src/yuzu/debugger/registers.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <QDockWidget>
-#include "ui_registers.h"
-
-class QTreeWidget;
-class QTreeWidgetItem;
-class EmuThread;
-
-class RegistersWidget : public QDockWidget {
- Q_OBJECT
-
-public:
- explicit RegistersWidget(QWidget* parent = nullptr);
-
-public slots:
- void OnDebugModeEntered();
- void OnDebugModeLeft();
-
- void OnEmulationStarting(EmuThread* emu_thread);
- void OnEmulationStopping();
-
-private:
- void CreateCPSRChildren();
- void UpdateCPSRValues();
-
- void CreateVFPSystemRegisterChildren();
- void UpdateVFPSystemRegisterValues();
-
- Ui::ARMRegisters cpu_regs_ui;
-
- QTreeWidget* tree;
-
- QTreeWidgetItem* core_registers;
- QTreeWidgetItem* vfp_registers;
- QTreeWidgetItem* vfp_system_registers;
- QTreeWidgetItem* cpsr;
-};
diff --git a/src/yuzu/debugger/registers.ui b/src/yuzu/debugger/registers.ui
deleted file mode 100644
index c81ae03f9..000000000
--- a/src/yuzu/debugger/registers.ui
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ARMRegisters</class>
- <widget class="QDockWidget" name="ARMRegisters">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>300</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>ARM Registers</string>
- </property>
- <widget class="QWidget" name="dockWidgetContents">
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTreeWidget" name="treeWidget">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <column>
- <property name="text">
- <string>Register</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Value</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 7a62f57b5..017bef13c 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -6,8 +6,8 @@
#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/handle_table.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
@@ -51,13 +51,21 @@ std::size_t WaitTreeItem::Row() const {
}
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
- 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) {
- item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
- item_list.back()->row = i;
- }
+ std::size_t row = 0;
+ auto add_threads = [&](const std::vector<Kernel::SharedPtr<Kernel::Thread>>& threads) {
+ for (std::size_t i = 0; i < threads.size(); ++i) {
+ item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
+ item_list.back()->row = row;
+ ++row;
+ }
+ };
+
+ add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList());
+ add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList());
+
return item_list;
}
@@ -67,6 +75,53 @@ QString WaitTreeText::GetText() const {
return text;
}
+WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
+ mutex_value = Memory::Read32(mutex_address);
+ owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
+ owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle);
+}
+
+QString WaitTreeMutexInfo::GetText() const {
+ return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0'));
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
+ std::vector<std::unique_ptr<WaitTreeItem>> list;
+
+ bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
+
+ list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
+ list.push_back(std::make_unique<WaitTreeText>(
+ tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0'))));
+ if (owner != nullptr)
+ list.push_back(std::make_unique<WaitTreeThread>(*owner));
+ return list;
+}
+
+WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {}
+
+QString WaitTreeCallstack::GetText() const {
+ return tr("Call stack");
+}
+
+std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const {
+ std::vector<std::unique_ptr<WaitTreeItem>> list;
+
+ constexpr size_t BaseRegister = 29;
+ u64 base_pointer = thread.context.cpu_registers[BaseRegister];
+
+ while (base_pointer != 0) {
+ u64 lr = Memory::Read64(base_pointer + sizeof(u64));
+ if (lr == 0)
+ break;
+ list.push_back(
+ std::make_unique<WaitTreeText>(tr("0x%1").arg(lr - sizeof(u32), 16, 16, QChar('0'))));
+ base_pointer = Memory::Read64(base_pointer);
+ }
+
+ return list;
+}
+
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
bool WaitTreeExpandableItem::IsExpandable() const {
@@ -84,11 +139,6 @@ std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitO
switch (object.GetHandleType()) {
case Kernel::HandleType::Event:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
- case Kernel::HandleType::Mutex:
- return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
- case Kernel::HandleType::ConditionVariable:
- return std::make_unique<WaitTreeConditionVariable>(
- static_cast<const Kernel::ConditionVariable&>(object));
case Kernel::HandleType::Timer:
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
case Kernel::HandleType::Thread:
@@ -150,8 +200,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");
@@ -160,6 +210,9 @@ QString WaitTreeThread::GetText() const {
case THREADSTATUS_WAIT_SYNCH_ANY:
status = tr("waiting for objects");
break;
+ case THREADSTATUS_WAIT_MUTEX:
+ status = tr("waiting for mutex");
+ break;
case THREADSTATUS_DORMANT:
status = tr("dormant");
break;
@@ -180,12 +233,13 @@ 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);
case THREADSTATUS_WAIT_SYNCH_ALL:
case THREADSTATUS_WAIT_SYNCH_ANY:
+ case THREADSTATUS_WAIT_MUTEX:
return QColor(Qt::GlobalColor::red);
case THREADSTATUS_DORMANT:
return QColor(Qt::GlobalColor::darkCyan);
@@ -218,6 +272,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
}
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
+ list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
+ list.push_back(
+ std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask)));
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
.arg(thread.current_priority)
@@ -225,17 +282,19 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
list.push_back(std::make_unique<WaitTreeText>(
tr("last running ticks = %1").arg(thread.last_running_ticks)));
- if (thread.held_mutexes.empty()) {
- list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
- } else {
- list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
- }
+ if (thread.mutex_wait_address != 0)
+ list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address));
+ else
+ list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
+
if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
thread.IsSleepingOnWaitAll()));
}
+ list.push_back(std::make_unique<WaitTreeCallstack>(thread));
+
return list;
}
@@ -250,33 +309,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
return list;
}
-WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
-
- const auto& mutex = static_cast<const Kernel::Mutex&>(object);
- if (mutex.GetHasWaiters()) {
- list.push_back(std::make_unique<WaitTreeText>(tr("locked by thread:")));
- list.push_back(std::make_unique<WaitTreeThread>(*mutex.GetHoldingThread()));
- } else {
- list.push_back(std::make_unique<WaitTreeText>(tr("free")));
- }
- return list;
-}
-
-WaitTreeConditionVariable::WaitTreeConditionVariable(const Kernel::ConditionVariable& object)
- : WaitTreeWaitObject(object) {}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeConditionVariable::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
-
- const auto& condition_variable = static_cast<const Kernel::ConditionVariable&>(object);
- list.push_back(std::make_unique<WaitTreeText>(
- tr("available count = %1").arg(condition_variable.GetAvailableCount())));
- return list;
-}
-
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
@@ -293,21 +325,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
return list;
}
-WaitTreeMutexList::WaitTreeMutexList(
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list)
- : mutex_list(list) {}
-
-QString WaitTreeMutexList::GetText() const {
- return tr("holding mutexes");
-}
-
-std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const {
- std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size());
- std::transform(mutex_list.begin(), mutex_list.end(), list.begin(),
- [](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); });
- return list;
-}
-
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
: thread_list(list) {}
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index e538174eb..10fc9e968 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -16,8 +16,6 @@ class EmuThread;
namespace Kernel {
class WaitObject;
class Event;
-class Mutex;
-class ConditionVariable;
class Thread;
class Timer;
} // namespace Kernel
@@ -61,6 +59,31 @@ public:
bool IsExpandable() const override;
};
+class WaitTreeMutexInfo : public WaitTreeExpandableItem {
+ Q_OBJECT
+public:
+ explicit WaitTreeMutexInfo(VAddr mutex_address);
+ QString GetText() const override;
+ std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+ VAddr mutex_address;
+ u32 mutex_value;
+ Kernel::Handle owner_handle;
+ Kernel::SharedPtr<Kernel::Thread> owner;
+};
+
+class WaitTreeCallstack : public WaitTreeExpandableItem {
+ Q_OBJECT
+public:
+ explicit WaitTreeCallstack(const Kernel::Thread& thread);
+ QString GetText() const override;
+ std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
+
+private:
+ const Kernel::Thread& thread;
+};
+
class WaitTreeWaitObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
@@ -104,20 +127,6 @@ public:
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
-class WaitTreeMutex : public WaitTreeWaitObject {
- Q_OBJECT
-public:
- explicit WaitTreeMutex(const Kernel::Mutex& object);
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-};
-
-class WaitTreeConditionVariable : public WaitTreeWaitObject {
- Q_OBJECT
-public:
- explicit WaitTreeConditionVariable(const Kernel::ConditionVariable& object);
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-};
-
class WaitTreeTimer : public WaitTreeWaitObject {
Q_OBJECT
public:
@@ -125,19 +134,6 @@ public:
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
-class WaitTreeMutexList : public WaitTreeExpandableItem {
- Q_OBJECT
-public:
- explicit WaitTreeMutexList(
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list);
-
- QString GetText() const override;
- std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
-
-private:
- const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list;
-};
-
class WaitTreeThreadList : public WaitTreeExpandableItem {
Q_OBJECT
public:
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 76ced4de4..bbd681eae 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -315,7 +315,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
if (!FileUtil::Exists(dir_path.toStdString()) ||
!FileUtil::IsDirectory(dir_path.toStdString())) {
- LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data());
+ NGLOG_ERROR(Frontend, "Could not find game list folder at {}",
+ dir_path.toLocal8Bit().data());
search_field->setFilterResult(0, 0);
return;
}
@@ -364,7 +365,7 @@ static bool HasSupportedFileExtension(const std::string& file_name) {
void GameList::RefreshGameDirectory() {
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
- LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
+ NGLOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
search_field->clear();
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 5802b9855..aa9028399 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,12 +25,14 @@
#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"
#include "yuzu/game_list.h"
#include "yuzu/hotkeys.h"
@@ -42,6 +43,15 @@
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
+#ifdef _WIN32
+extern "C" {
+// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
+// graphics
+__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
+__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+}
+#endif
+
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -69,10 +79,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();
@@ -152,14 +168,15 @@ void GMainWindow::InitializeDebugWidgets() {
debug_menu->addAction(microProfileDialog->toggleViewAction());
#endif
- registersWidget = new RegistersWidget(this);
- addDockWidget(Qt::RightDockWidgetArea, registersWidget);
- registersWidget->hide();
- debug_menu->addAction(registersWidget->toggleViewAction());
- connect(this, &GMainWindow::EmulationStarting, registersWidget,
- &RegistersWidget::OnEmulationStarting);
- 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);
@@ -308,6 +325,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+bool GMainWindow::SupportsRequiredGLExtensions() {
+ QStringList unsupported_ext;
+
+ if (!GLAD_GL_ARB_program_interface_query)
+ unsupported_ext.append("ARB_program_interface_query");
+ if (!GLAD_GL_ARB_separate_shader_objects)
+ unsupported_ext.append("ARB_separate_shader_objects");
+ if (!GLAD_GL_ARB_shader_storage_buffer_object)
+ unsupported_ext.append("ARB_shader_storage_buffer_object");
+ if (!GLAD_GL_ARB_vertex_attrib_binding)
+ unsupported_ext.append("ARB_vertex_attrib_binding");
+
+ for (const QString& ext : unsupported_ext)
+ NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+
+ return unsupported_ext.empty();
+}
+
bool GMainWindow::LoadROM(const QString& filename) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
@@ -323,23 +358,34 @@ bool GMainWindow::LoadROM(const QString& filename) {
return false;
}
+ if (!SupportsRequiredGLExtensions()) {
+ QMessageBox::critical(
+ this, tr("Error while initializing OpenGL Core!"),
+ tr("Your GPU may not support one or more required OpenGL extensions. Please "
+ "ensure you have the latest graphics driver. See the log for more details."));
+ return false;
+ }
+
Core::System& system{Core::System::GetInstance()};
- const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
+ system.SetGPUDebugContext(debug_context);
- Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
+ const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
if (result != Core::System::ResultStatus::Success) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:
- LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!",
- filename.toStdString().c_str());
+ NGLOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("The ROM format is not supported."));
break;
-
+ case Core::System::ResultStatus::ErrorUnsupportedArch:
+ NGLOG_CRITICAL(Frontend, "Unsupported architecture detected!", filename.toStdString());
+ QMessageBox::critical(this, tr("Error while loading ROM!"),
+ tr("The ROM uses currently unusable 32-bit architecture"));
+ break;
case Core::System::ResultStatus::ErrorSystemMode:
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
+ NGLOG_CRITICAL(Frontend, "Failed to load ROM!");
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("Could not determine the system mode."));
break;
@@ -351,9 +397,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;
@@ -384,11 +430,12 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
return false;
}
+ Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
void GMainWindow::BootGame(const QString& filename) {
- LOG_INFO(Frontend, "yuzu starting...");
+ NGLOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
if (!LoadROM(filename))
@@ -403,17 +450,12 @@ void GMainWindow::BootGame(const QString& filename) {
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(), &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();
if (ui.action_Single_Window_Mode->isChecked()) {
game_list->hide();
}
@@ -636,6 +678,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ UpdateUITheme();
config->Save();
}
}
@@ -674,18 +717,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 {
@@ -700,7 +743,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);
@@ -751,9 +794,11 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
return;
}
- UISettings::values.geometry = saveGeometry();
+ if (ui.action_Fullscreen->isChecked()) {
+ UISettings::values.geometry = saveGeometry();
+ UISettings::values.renderwindow_geometry = render_window->saveGeometry();
+ }
UISettings::values.state = saveState();
- UISettings::values.renderwindow_geometry = render_window->saveGeometry();
#if MICROPROFILE_ENABLED
UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
UISettings::values.microprofile_visible = microProfileDialog->isVisible();
@@ -816,6 +861,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()) {
+ NGLOG_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..3e29d5fc4 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,17 @@ 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 +63,9 @@ signals:
*/
void EmulationStopping();
+ // Signal that tells widgets to update icons to use the current theme
+ void UpdateThemedIcons();
+
private:
void InitializeWidgets();
void InitializeDebugWidgets();
@@ -76,6 +78,7 @@ private:
void ConnectWidgetEvents();
void ConnectMenuEvents();
+ bool SupportsRequiredGLExtensions();
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
void ShutdownGame();
@@ -138,6 +141,8 @@ private:
Ui::MainWindow ui;
+ std::shared_ptr<Tegra::DebugContext> debug_context;
+
GRenderWindow* render_window;
GameList* game_list;
@@ -150,22 +155,24 @@ 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;
// Debugger panes
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;