summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt14
-rw-r--r--src/yuzu/applets/profile_select.cpp1
-rw-r--r--src/yuzu/applets/profile_select.h2
-rw-r--r--src/yuzu/bootmanager.cpp208
-rw-r--r--src/yuzu/bootmanager.h29
-rw-r--r--src/yuzu/configuration/config.cpp69
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure.ui19
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp16
-rw-r--r--src/yuzu/configuration/configure_dialog.h3
-rw-r--r--src/yuzu/configuration/configure_general.cpp8
-rw-r--r--src/yuzu/configuration/configure_general.h1
-rw-r--r--src/yuzu/configuration/configure_general.ui24
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp3
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp122
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h47
-rw-r--r--src/yuzu/configuration/configure_hotkeys.ui42
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp460
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h96
-rw-r--r--src/yuzu/game_list.cpp18
-rw-r--r--src/yuzu/game_list.h8
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/game_list_worker.cpp125
-rw-r--r--src/yuzu/game_list_worker.h16
-rw-r--r--src/yuzu/hotkeys.cpp73
-rw-r--r--src/yuzu/hotkeys.h42
-rw-r--r--src/yuzu/hotkeys.ui46
-rw-r--r--src/yuzu/loading_screen.cpp7
-rw-r--r--src/yuzu/main.cpp169
-rw-r--r--src/yuzu/main.h15
-rw-r--r--src/yuzu/ui_settings.cpp1
-rw-r--r--src/yuzu/ui_settings.h11
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp37
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.h24
35 files changed, 794 insertions, 974 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 4cab599b4..31b65c04c 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -31,6 +31,8 @@ add_executable(yuzu
configuration/configure_general.h
configuration/configure_graphics.cpp
configuration/configure_graphics.h
+ configuration/configure_hotkeys.cpp
+ configuration/configure_hotkeys.h
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input_player.cpp
@@ -54,8 +56,6 @@ add_executable(yuzu
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/console.cpp
debugger/console.h
debugger/profiler.cpp
@@ -78,6 +78,8 @@ add_executable(yuzu
ui_settings.h
util/limitable_input_dialog.cpp
util/limitable_input_dialog.h
+ util/sequence_dialog/sequence_dialog.cpp
+ util/sequence_dialog/sequence_dialog.h
util/spinbox.cpp
util/spinbox.h
util/util.cpp
@@ -95,6 +97,7 @@ set(UIS
configuration/configure_gamelist.ui
configuration/configure_general.ui
configuration/configure_graphics.ui
+ configuration/configure_hotkeys.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_input_simple.ui
@@ -105,7 +108,6 @@ set(UIS
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
compatdb.ui
- hotkeys.ui
loading_screen.ui
main.ui
)
@@ -149,6 +151,12 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
+target_compile_definitions(yuzu PRIVATE
+ # Use QStringBuilder for string concatenation to reduce
+ # the overall number of temporary strings created.
+ -DQT_USE_QSTRINGBUILDER
+)
+
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
endif()
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index f95f7fe3c..743b24d76 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -4,6 +4,7 @@
#include <mutex>
#include <QDialogButtonBox>
+#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h
index 868573324..1c2922e54 100644
--- a/src/yuzu/applets/profile_select.h
+++ b/src/yuzu/applets/profile_select.h
@@ -7,6 +7,7 @@
#include <vector>
#include <QDialog>
#include <QList>
+#include <QTreeView>
#include "core/frontend/applets/profile_select.h"
class GMainWindow;
@@ -16,7 +17,6 @@ class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
-class QTreeView;
class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 7438fbc0a..c29f2d2dc 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,6 +1,13 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
#include <QApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
+#include <QOffscreenSurface>
+#include <QOpenGLWindow>
+#include <QPainter>
#include <QScreen>
#include <QWindow>
#include <fmt/format.h>
@@ -82,13 +89,36 @@ void EmuThread::run() {
render_window->moveContext();
}
+class GGLContext : public Core::Frontend::GraphicsContext {
+public:
+ explicit GGLContext(QOpenGLContext* shared_context) : surface() {
+ context = std::make_unique<QOpenGLContext>(shared_context);
+ surface.setFormat(shared_context->format());
+ surface.create();
+ }
+
+ void MakeCurrent() override {
+ context->makeCurrent(&surface);
+ }
+
+ void DoneCurrent() override {
+ context->doneCurrent();
+ }
+
+ void SwapBuffers() override {}
+
+private:
+ std::unique_ptr<QOpenGLContext> context;
+ QOffscreenSurface surface;
+};
+
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
// context.
// The corresponding functionality is handled in EmuThread instead
-class GGLWidgetInternal : public QGLWidget {
+class GGLWidgetInternal : public QOpenGLWindow {
public:
- GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
- : QGLWidget(fmt, parent), parent(parent) {}
+ GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
+ : QOpenGLWindow(shared_context), parent(parent) {}
void paintEvent(QPaintEvent* ev) override {
if (do_painting) {
@@ -101,9 +131,51 @@ public:
parent->OnFramebufferSizeChanged();
}
+ void keyPressEvent(QKeyEvent* event) override {
+ InputCommon::GetKeyboard()->PressKey(event->key());
+ }
+
+ void keyReleaseEvent(QKeyEvent* event) override {
+ InputCommon::GetKeyboard()->ReleaseKey(event->key());
+ }
+
+ void mousePressEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchBeginEvent
+
+ const auto pos{event->pos()};
+ if (event->button() == Qt::LeftButton) {
+ const auto [x, y] = parent->ScaleTouch(pos);
+ parent->TouchPressed(x, y);
+ } else if (event->button() == Qt::RightButton) {
+ InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ }
+ }
+
+ void mouseMoveEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchUpdateEvent
+
+ const auto pos{event->pos()};
+ const auto [x, y] = parent->ScaleTouch(pos);
+ parent->TouchMoved(x, y);
+ InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+ }
+
+ void mouseReleaseEvent(QMouseEvent* event) override {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchEndEvent
+
+ if (event->button() == Qt::LeftButton)
+ parent->TouchReleased();
+ else if (event->button() == Qt::RightButton)
+ InputCommon::GetMotionEmu()->EndTilt();
+ }
+
void DisablePainting() {
do_painting = false;
}
+
void EnablePainting() {
do_painting = true;
}
@@ -114,7 +186,7 @@ private:
};
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
- : QWidget(parent), child(nullptr), emu_thread(emu_thread) {
+ : QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
@@ -137,19 +209,19 @@ void GRenderWindow::moveContext() {
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
? emu_thread
: qApp->thread();
- child->context()->moveToThread(thread);
+ context->moveToThread(thread);
}
void GRenderWindow::SwapBuffers() {
- // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
+ // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,
// since we never call `doneCurrent` in this thread.
// However:
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
// since the last time `swapBuffers` was executed;
// - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
- child->makeCurrent();
+ context->makeCurrent(child);
- child->swapBuffers();
+ context->swapBuffers(child);
if (!first_frame) {
emit FirstFrameDisplayed();
first_frame = true;
@@ -157,11 +229,11 @@ void GRenderWindow::SwapBuffers() {
}
void GRenderWindow::MakeCurrent() {
- child->makeCurrent();
+ context->makeCurrent(child);
}
void GRenderWindow::DoneCurrent() {
- child->doneCurrent();
+ context->doneCurrent();
}
void GRenderWindow::PollEvents() {}
@@ -174,14 +246,26 @@ void GRenderWindow::PollEvents() {}
void GRenderWindow::OnFramebufferSizeChanged() {
// Screen changes potentially incur a change in screen DPI, hence we should update the
// framebuffer size
- qreal pixelRatio = windowPixelRatio();
+ qreal pixelRatio = GetWindowPixelRatio();
unsigned width = child->QPaintDevice::width() * pixelRatio;
unsigned height = child->QPaintDevice::height() * pixelRatio;
UpdateCurrentFramebufferLayout(width, height);
}
+void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
+ if (child) {
+ child->keyPressEvent(event);
+ }
+}
+
+void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
+ if (child) {
+ child->keyReleaseEvent(event);
+ }
+}
+
void GRenderWindow::BackupGeometry() {
- geometry = ((QGLWidget*)this)->saveGeometry();
+ geometry = ((QWidget*)this)->saveGeometry();
}
void GRenderWindow::RestoreGeometry() {
@@ -199,18 +283,18 @@ QByteArray GRenderWindow::saveGeometry() {
// If we are a top-level widget, store the current geometry
// otherwise, store the last backup
if (parent() == nullptr)
- return ((QGLWidget*)this)->saveGeometry();
+ return ((QWidget*)this)->saveGeometry();
else
return geometry;
}
-qreal GRenderWindow::windowPixelRatio() const {
+qreal GRenderWindow::GetWindowPixelRatio() const {
// windowHandle() might not be accessible until the window is displayed to screen.
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
}
std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
- const qreal pixel_ratio = windowPixelRatio();
+ const qreal pixel_ratio = GetWindowPixelRatio();
return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
}
@@ -220,47 +304,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
-void GRenderWindow::keyPressEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->PressKey(event->key());
-}
-
-void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->ReleaseKey(event->key());
-}
-
-void GRenderWindow::mousePressEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchBeginEvent
-
- auto pos = event->pos();
- if (event->button() == Qt::LeftButton) {
- const auto [x, y] = ScaleTouch(pos);
- this->TouchPressed(x, y);
- } else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
- }
-}
-
-void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchUpdateEvent
-
- auto pos = event->pos();
- const auto [x, y] = ScaleTouch(pos);
- this->TouchMoved(x, y);
- InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
-}
-
-void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
- if (event->source() == Qt::MouseEventSynthesizedBySystem)
- return; // touch input is handled in TouchEndEvent
-
- if (event->button() == Qt::LeftButton)
- this->TouchReleased();
- else if (event->button() == Qt::RightButton)
- InputCommon::GetMotionEmu()->EndTilt();
-}
-
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
// TouchBegin always has exactly one touch point, so take the .first()
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
@@ -313,35 +356,60 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
NotifyClientAreaSizeChanged(std::make_pair(width, height));
}
+std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
+ return std::make_unique<GGLContext>(shared_context.get());
+}
+
void GRenderWindow::InitRenderTarget() {
- if (child) {
- delete child;
- }
+ shared_context.reset();
+ context.reset();
- if (layout()) {
- delete layout();
- }
+ delete child;
+ child = nullptr;
+
+ delete container;
+ container = nullptr;
+
+ delete layout();
first_frame = false;
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
- QGLFormat fmt;
+ QSurfaceFormat fmt;
fmt.setVersion(4, 3);
- fmt.setProfile(QGLFormat::CoreProfile);
+ fmt.setProfile(QSurfaceFormat::CoreProfile);
+ // TODO: expose a setting for buffer value (ie default/single/double/triple)
+ fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
+ shared_context = std::make_unique<QOpenGLContext>();
+ shared_context->setFormat(fmt);
+ shared_context->create();
+ context = std::make_unique<QOpenGLContext>();
+ context->setShareContext(shared_context.get());
+ context->setFormat(fmt);
+ context->create();
fmt.setSwapInterval(false);
- // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
- fmt.setOption(QGL::NoDeprecatedFunctions);
+ child = new GGLWidgetInternal(this, shared_context.get());
+ container = QWidget::createWindowContainer(child, this);
- child = new GGLWidgetInternal(fmt, this);
QBoxLayout* layout = new QHBoxLayout(this);
-
- resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
- layout->addWidget(child);
+ layout->addWidget(container);
layout->setMargin(0);
setLayout(layout);
+ // Reset minimum size to avoid unwanted resizes when this function is called for a second time.
+ setMinimumSize(1, 1);
+
+ // Show causes the window to actually be created and the OpenGL context as well, but we don't
+ // want the widget to be shown yet, so immediately hide it.
+ show();
+ hide();
+
+ resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 3183621bc..9608b959f 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,9 +7,9 @@
#include <atomic>
#include <condition_variable>
#include <mutex>
-#include <QGLWidget>
#include <QImage>
#include <QThread>
+#include <QWidget>
#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
@@ -21,6 +21,8 @@ class QTouchEvent;
class GGLWidgetInternal;
class GMainWindow;
class GRenderWindow;
+class QSurface;
+class QOpenGLContext;
namespace VideoCore {
enum class LoadCallbackStage;
@@ -121,25 +123,21 @@ public:
void MakeCurrent() override;
void DoneCurrent() override;
void PollEvents() override;
+ std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
+
+ void ForwardKeyPressEvent(QKeyEvent* event);
+ void ForwardKeyReleaseEvent(QKeyEvent* event);
void BackupGeometry();
void RestoreGeometry();
void restoreGeometry(const QByteArray& geometry); // overridden
QByteArray saveGeometry(); // overridden
- qreal windowPixelRatio() const;
+ qreal GetWindowPixelRatio() const;
+ std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
void closeEvent(QCloseEvent* event) override;
-
- void keyPressEvent(QKeyEvent* event) override;
- void keyReleaseEvent(QKeyEvent* event) override;
-
- void mousePressEvent(QMouseEvent* event) override;
- void mouseMoveEvent(QMouseEvent* event) override;
- void mouseReleaseEvent(QMouseEvent* event) override;
-
bool event(QEvent* event) override;
-
void focusOutEvent(QFocusEvent* event) override;
void OnClientAreaResized(unsigned width, unsigned height);
@@ -161,7 +159,6 @@ signals:
void FirstFrameDisplayed();
private:
- std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
@@ -169,11 +166,17 @@ private:
void OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) override;
- GGLWidgetInternal* child;
+ QWidget* container = nullptr;
+ GGLWidgetInternal* child = nullptr;
QByteArray geometry;
EmuThread* emu_thread;
+ // Context that backs the GGLWidgetInternal (and will be used by core to render)
+ std::unique_ptr<QOpenGLContext> context;
+ // Context that will be shared between all newly created contexts. This should never be made
+ // current
+ std::unique_ptr<QOpenGLContext> shared_context;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index dead9f807..8725a78dc 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <QKeySequence>
#include <QSettings>
#include "common/file_util.h"
#include "configure_input_simple.h"
@@ -9,7 +11,6 @@
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "yuzu/configuration/config.h"
-#include "yuzu/ui_settings.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -17,7 +18,6 @@ Config::Config() {
FileUtil::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
-
Reload();
}
@@ -205,6 +205,27 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
};
+// This shouldn't have anything except static initializers (no functions). So
+// QKeySequnce(...).toString() is NOT ALLOWED HERE.
+// This must be in alphabetical order according to action name as it must have the same order as
+// UISetting::values.shortcuts, which is alphabetically ordered.
+const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
+ {{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
+ {"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
+ {"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
+ {"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
+ {"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
+ {"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
+ {"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
+ {"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
+ {"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
+ {"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
+ {"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
+ {"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
+ {"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
+ {"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
+ {"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
+
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p];
@@ -373,6 +394,7 @@ void Config::ReadValues() {
ReadSetting("use_accurate_gpu_emulation", false).toBool();
Settings::values.use_asynchronous_gpu_emulation =
ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
+ Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();
Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
@@ -502,26 +524,21 @@ void Config::ReadValues() {
qt_config->beginGroup("Paths");
UISettings::values.roms_path = ReadSetting("romsPath").toString();
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
- UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString();
- UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool();
+ UISettings::values.game_directory_path = ReadSetting("gameListRootDir", ".").toString();
+ UISettings::values.game_directory_deepscan = ReadSetting("gameListDeepScan", false).toBool();
UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
- QStringList groups = qt_config->childGroups();
- for (auto group : groups) {
+ for (auto [name, group, shortcut] : default_hotkeys) {
+ auto [keyseq, context] = shortcut;
qt_config->beginGroup(group);
-
- QStringList hotkeys = qt_config->childGroups();
- for (auto hotkey : hotkeys) {
- qt_config->beginGroup(hotkey);
- UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
- group + "/" + hotkey,
- UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
- ReadSetting("Context").toInt())));
- qt_config->endGroup();
- }
-
+ qt_config->beginGroup(name);
+ UISettings::values.shortcuts.push_back(
+ {name,
+ group,
+ {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
+ qt_config->endGroup();
qt_config->endGroup();
}
qt_config->endGroup();
@@ -648,6 +665,7 @@ void Config::SaveValues() {
WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
false);
+ WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);
// Cast to double because Qt's written float values are not human-readable
WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
@@ -752,15 +770,22 @@ void Config::SaveValues() {
WriteSetting("romsPath", UISettings::values.roms_path);
WriteSetting("symbolsPath", UISettings::values.symbols_path);
WriteSetting("screenshotPath", UISettings::values.screenshot_path);
- WriteSetting("gameListRootDir", UISettings::values.gamedir, ".");
- WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false);
+ WriteSetting("gameListRootDir", UISettings::values.game_directory_path, ".");
+ WriteSetting("gameListDeepScan", UISettings::values.game_directory_deepscan, false);
WriteSetting("recentFiles", UISettings::values.recent_files);
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
- for (auto shortcut : UISettings::values.shortcuts) {
- WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
- WriteSetting(shortcut.first + "/Context", shortcut.second.second);
+ // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
+ // However, their ordering must also be the same.
+ for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
+ auto [name, group, shortcut] = UISettings::values.shortcuts[i];
+ qt_config->beginGroup(group);
+ qt_config->beginGroup(name);
+ WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
+ WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
+ qt_config->endGroup();
+ qt_config->endGroup();
}
qt_config->endGroup();
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index f4185db18..221d2364c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
#include <string>
#include <QVariant>
#include "core/settings.h"
+#include "yuzu/ui_settings.h"
class QSettings;
@@ -47,6 +48,8 @@ private:
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
+ static const std::array<UISettings::Shortcut, 15> default_hotkeys;
+
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
};
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 3f03f0b77..267717bc9 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -7,9 +7,15 @@
<x>0</x>
<y>0</y>
<width>382</width>
- <height>241</height>
+ <height>650</height>
</rect>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>650</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>yuzu Configuration</string>
</property>
@@ -62,6 +68,11 @@
<string>Input</string>
</attribute>
</widget>
+ <widget class="ConfigureHotkeys" name="hotkeysTab">
+ <attribute name="title">
+ <string>Hotkeys</string>
+ </attribute>
+ </widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
@@ -150,6 +161,12 @@
<header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureHotkeys</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_hotkeys.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 777050405..a5218b051 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -8,20 +8,22 @@
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
-ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
- : QDialog(parent), ui(new Ui::ConfigureDialog) {
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
+ : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
ui->setupUi(this);
- ui->generalTab->PopulateHotkeyList(registry);
+ ui->hotkeysTab->Populate(registry);
this->setConfiguration();
this->PopulateSelectionList();
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
-
adjustSize();
-
ui->selectorList->setCurrentRow(0);
+
+ // Synchronise lists upon initialisation
+ ui->hotkeysTab->EmitHotkeysChanged();
}
ConfigureDialog::~ConfigureDialog() = default;
@@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
ui->systemTab->applyConfiguration();
ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration();
+ ui->hotkeysTab->applyConfiguration(registry);
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
@@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
- {tr("Controls"), {tr("Input")}}}};
+ {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
@@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{tr("System"), ui->systemTab},
{tr("Profiles"), ui->profileManagerTab},
{tr("Input"), ui->inputTab},
+ {tr("Hotkeys"), ui->hotkeysTab},
{tr("Graphics"), ui->graphicsTab},
{tr("Audio"), ui->audioTab},
{tr("Debug"), ui->debugTab},
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 243d9fa09..2363ba584 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
~ConfigureDialog() override;
void applyConfiguration();
@@ -28,4 +28,5 @@ private:
void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui;
+ HotkeyRegistry& registry;
};
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 389fcf667..e48f4f5a3 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -28,19 +28,15 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::setConfiguration() {
- ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
+ ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
}
-void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
- ui->widget->Populate(registry);
-}
-
void ConfigureGeneral::applyConfiguration() {
- UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
+ UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.theme =
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 59738af40..df41d995b 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -20,7 +20,6 @@ public:
explicit ConfigureGeneral(QWidget* parent = nullptr);
~ConfigureGeneral() override;
- void PopulateHotkeyList(const HotkeyRegistry& registry);
void applyConfiguration();
private:
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 01d1c0b8e..1a5721fe7 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -98,22 +98,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="HotKeysGroupBox">
- <property name="title">
- <string>Hotkeys</string>
- </property>
- <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
- <item>
- <widget class="GHotkeysDialog" name="widget" native="true"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -130,14 +114,6 @@
</item>
</layout>
</widget>
- <customwidgets>
- <customwidget>
- <class>GHotkeysDialog</class>
- <extends>QWidget</extends>
- <header>hotkeys.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index dd1d67488..0a9883d37 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -77,6 +77,8 @@ void ConfigureGraphics::setConfiguration() {
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
+ ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue));
}
@@ -90,6 +92,7 @@ void ConfigureGraphics::applyConfiguration() {
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.use_asynchronous_gpu_emulation =
ui->use_asynchronous_gpu_emulation->isChecked();
+ Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index c6767e0ca..15ab18ecd 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -71,6 +71,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="force_30fps_mode">
+ <property name="text">
+ <string>Force 30 FPS mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
new file mode 100644
index 000000000..a7a8752e5
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -0,0 +1,122 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include "core/settings.h"
+#include "ui_configure_hotkeys.h"
+#include "yuzu/configuration/configure_hotkeys.h"
+#include "yuzu/hotkeys.h"
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+
+ model = new QStandardItemModel(this);
+ model->setColumnCount(3);
+ model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
+
+ connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
+ ui->hotkey_list->setModel(model);
+
+ // TODO(Kloen): Make context configurable as well (hiding the column for now)
+ ui->hotkey_list->hideColumn(2);
+
+ ui->hotkey_list->setColumnWidth(0, 200);
+ ui->hotkey_list->resizeColumnToContents(1);
+}
+
+ConfigureHotkeys::~ConfigureHotkeys() = default;
+
+void ConfigureHotkeys::EmitHotkeysChanged() {
+ emit HotkeysChanged(GetUsedKeyList());
+}
+
+QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
+ QList<QKeySequence> list;
+ for (int r = 0; r < model->rowCount(); r++) {
+ const QStandardItem* parent = model->item(r, 0);
+ for (int r2 = 0; r2 < parent->rowCount(); r2++) {
+ const QStandardItem* keyseq = parent->child(r2, 1);
+ list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
+ }
+ }
+ return list;
+}
+
+void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
+ for (const auto& group : registry.hotkey_groups) {
+ auto* parent_item = new QStandardItem(group.first);
+ parent_item->setEditable(false);
+ for (const auto& hotkey : group.second) {
+ auto* action = new QStandardItem(hotkey.first);
+ auto* keyseq =
+ new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
+ action->setEditable(false);
+ keyseq->setEditable(false);
+ parent_item->appendRow({action, keyseq});
+ }
+ model->appendRow(parent_item);
+ }
+
+ ui->hotkey_list->expandAll();
+}
+
+void ConfigureHotkeys::Configure(QModelIndex index) {
+ if (!index.parent().isValid()) {
+ return;
+ }
+
+ index = index.sibling(index.row(), 1);
+ auto* const model = ui->hotkey_list->model();
+ const auto previous_key = model->data(index);
+
+ SequenceDialog hotkey_dialog{this};
+
+ const int return_code = hotkey_dialog.exec();
+ const auto key_sequence = hotkey_dialog.GetSequence();
+ if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {
+ return;
+ }
+
+ if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
+ QMessageBox::critical(this, tr("Error in inputted key"),
+ tr("You're using a key that's already bound."));
+ } else {
+ model->setData(index, key_sequence.toString(QKeySequence::NativeText));
+ EmitHotkeysChanged();
+ }
+}
+
+bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
+ return GetUsedKeyList().contains(key_sequence);
+}
+
+void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
+ for (int key_id = 0; key_id < model->rowCount(); key_id++) {
+ const QStandardItem* parent = model->item(key_id, 0);
+ for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
+ const QStandardItem* action = parent->child(key_column_id, 0);
+ const QStandardItem* keyseq = parent->child(key_column_id, 1);
+ for (auto& [group, sub_actions] : registry.hotkey_groups) {
+ if (group != parent->text())
+ continue;
+ for (auto& [action_name, hotkey] : sub_actions) {
+ if (action_name != action->text())
+ continue;
+ hotkey.keyseq = QKeySequence(keyseq->text());
+ }
+ }
+ }
+ }
+
+ registry.SaveHotkeys();
+ Settings::Apply();
+}
+
+void ConfigureHotkeys::retranslateUi() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
new file mode 100644
index 000000000..73fb8a175
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -0,0 +1,47 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureHotkeys;
+}
+
+class HotkeyRegistry;
+class QStandardItemModel;
+
+class ConfigureHotkeys : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureHotkeys(QWidget* parent = nullptr);
+ ~ConfigureHotkeys() override;
+
+ void applyConfiguration(HotkeyRegistry& registry);
+ void retranslateUi();
+
+ void EmitHotkeysChanged();
+
+ /**
+ * Populates the hotkey list widget using data from the provided registry.
+ * Called everytime the Configure dialog is opened.
+ * @param registry The HotkeyRegistry whose data is used to populate the list.
+ */
+ void Populate(const HotkeyRegistry& registry);
+
+signals:
+ void HotkeysChanged(QList<QKeySequence> new_key_list);
+
+private:
+ void Configure(QModelIndex index);
+ bool IsUsedKey(QKeySequence key_sequence) const;
+ QList<QKeySequence> GetUsedKeyList() const;
+
+ std::unique_ptr<Ui::ConfigureHotkeys> ui;
+
+ QStandardItemModel* model;
+};
diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui
new file mode 100644
index 000000000..0d0b70f38
--- /dev/null
+++ b/src/yuzu/configuration/configure_hotkeys.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureHotkeys</class>
+ <widget class="QWidget" name="ConfigureHotkeys">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>363</width>
+ <height>388</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Hotkey Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Double-click on a binding to change it.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="hotkey_list">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui> \ No newline at end of file
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
deleted file mode 100644
index 11023ed63..000000000
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ /dev/null
@@ -1,460 +0,0 @@
-// 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 "common/vector_math.h"
-#include "core/core.h"
-#include "core/memory.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 "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");
- return Tegra::Texture::TextureFormat::A8R8G8B8;
- }
-}
-
-SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
- : QLabel(parent), surface_widget(surface_widget_) {}
-
-SurfacePicture::~SurfacePicture() = default;
-
-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, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
- connect(surface_source_list,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &GraphicsSurfaceWidget::OnSurfaceSourceChanged);
- connect(surface_address_control, &CSpinBox::ValueChanged, this,
- &GraphicsSurfaceWidget::OnSurfaceAddressChanged);
- connect(surface_width_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfaceWidthChanged);
- connect(surface_height_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfaceHeightChanged);
- connect(surface_format_control,
- static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &GraphicsSurfaceWidget::OnSurfaceFormatChanged);
- connect(surface_picker_x_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfacePickerXChanged);
- connect(surface_picker_y_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- this, &GraphicsSurfaceWidget::OnSurfacePickerYChanged);
- connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::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<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...
-
- const auto& registers = gpu.Maxwell3D().regs;
- const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
- static_cast<std::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);
-
- // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
- // Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
- auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
- gpu.MemoryManager().GetPointer(surface_address), 1, 1,
- Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
-
- 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) {
- Common::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();
- std::optional<VAddr> address = gpu.MemoryManager().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
deleted file mode 100644
index 89445b18f..000000000
--- a/src/yuzu/debugger/graphics/graphics_surface.h
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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() override;
-
-protected slots:
- void mouseMoveEvent(QMouseEvent* event) override;
- void mousePressEvent(QMouseEvent* event) override;
-
-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();
-
-signals:
- void Update();
-
-private:
- void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
- void OnResumed() override;
-
- void SaveSurface();
-
- 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;
- 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/game_list.cpp b/src/yuzu/game_list.cpp
index c0e3c5fa9..b0ca766ec 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -18,6 +18,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
@@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
-GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
- : QWidget{parent}, vfs(std::move(vfs)) {
+GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -329,6 +331,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QMenu context_menu;
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
+ QAction* open_transferable_shader_cache =
+ context_menu.addAction(tr("Open Transferable Shader Cache"));
context_menu.addSeparator();
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
@@ -344,6 +348,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
connect(open_lfs_location, &QAction::triggered,
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
+ connect(open_transferable_shader_cache, &QAction::triggered,
+ [&]() { emit OpenTransferableShaderCacheRequested(program_id); });
connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered,
@@ -428,7 +434,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
emit ShouldCancelWorker();
- GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
+ GameListWorker* worker =
+ new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
@@ -460,9 +467,10 @@ void GameList::LoadInterfaceLayout() {
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
void GameList::RefreshGameDirectory() {
- if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
+ if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
search_field->clear();
- PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index b317eb2fc..56007eef8 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -26,8 +26,9 @@ class GameListSearchField;
class GMainWindow;
namespace FileSys {
+class ManualContentProvider;
class VfsFilesystem;
-}
+} // namespace FileSys
enum class GameListOpenTarget {
SaveData,
@@ -47,7 +48,8 @@ public:
COLUMN_COUNT, // Number of columns
};
- explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
+ explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
void clearFilter();
@@ -66,6 +68,7 @@ signals:
void GameChosen(QString game_path);
void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
+ void OpenTransferableShaderCacheRequested(u64 program_id);
void DumpRomFSRequested(u64 program_id, const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
@@ -85,6 +88,7 @@ private:
void RefreshGameDirectory();
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
GMainWindow* main_window = nullptr;
QVBoxLayout* layout = nullptr;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 3db0e90da..2cf5c58a0 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -95,7 +95,7 @@ public:
if (row2.isEmpty())
return row1;
- return row1 + "\n " + row2;
+ return QString(row1 + "\n " + row2);
}
return GameListItem::data(role);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index b37710f59..8687e7c5a 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -12,12 +12,15 @@
#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
+#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/submission_package.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "yuzu/compatibility_list.h"
@@ -119,20 +122,25 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
}
} // Anonymous namespace
-GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
- const CompatibilityList& compatibility_list)
- : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
+GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path,
+ bool deep_scan, const CompatibilityList& compatibility_list)
+ : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
compatibility_list(compatibility_list) {}
GameListWorker::~GameListWorker() = default;
-void GameListWorker::AddInstalledTitlesToGameList() {
- const auto cache = Service::FileSystem::GetUnionContents();
- const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Program);
+void GameListWorker::AddTitlesToGameList() {
+ const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
+ Core::System::GetInstance().GetContentProvider());
+ const auto installed_games = cache.ListEntriesFilterOrigin(
+ std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
- for (const auto& game : installed_games) {
- const auto file = cache.GetEntryUnparsed(game);
+ for (const auto& [slot, game] : installed_games) {
+ if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
+ continue;
+
+ const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
@@ -150,45 +158,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch));
}
-
- const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Control);
-
- for (const auto& entry : control_data) {
- auto nca = cache.GetEntry(entry);
- if (nca != nullptr) {
- nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
- }
- }
}
-void GameListWorker::FillControlMap(const std::string& dir_path) {
- const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- if (stop_processing) {
- // Breaks the callback loop
- return false;
- }
-
- const std::string physical_name = directory + DIR_SEP + virtual_name;
- const QFileInfo file_info(QString::fromStdString(physical_name));
- if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
- auto nca =
- std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (nca->GetType() == FileSys::NCAContentType::Control) {
- const u64 title_id = nca->GetTitleId();
- nca_control_map.insert_or_assign(title_id, std::move(nca));
- }
- }
- return true;
- };
-
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
-}
-
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
- const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
+ unsigned int recursion) {
+ const auto callback = [this, target, recursion](u64* num_entries_out,
+ const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
@@ -198,7 +174,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
const bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
- auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
+ auto loader = Loader::GetLoader(file);
if (!loader) {
return true;
}
@@ -209,31 +186,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
return true;
}
- std::vector<u8> icon;
- const auto res1 = loader->ReadIcon(icon);
-
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
- std::string name = " ";
- const auto res3 = loader->ReadTitle(name);
+ if (target == ScanTarget::FillManualContentProvider) {
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI ||
+ file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+ } else {
+ std::vector<u8> icon;
+ const auto res1 = loader->ReadIcon(icon);
- const FileSys::PatchManager patch{program_id};
+ std::string name = " ";
+ const auto res3 = loader->ReadTitle(name);
- if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
- res2 == Loader::ResultStatus::Success) {
- // Use from metadata pool.
- if (nca_control_map.find(program_id) != nca_control_map.end()) {
- const auto& nca = nca_control_map[program_id];
- GetMetadataFromControlNCA(patch, *nca, icon, name);
- }
- }
+ const FileSys::PatchManager patch{program_id};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
- compatibility_list, patch));
+ emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
+ compatibility_list, patch));
+ }
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
- AddFstEntriesToGameList(physical_name, recursion - 1);
+ ScanFileSystem(target, physical_name, recursion - 1);
}
return true;
@@ -245,10 +233,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
- FillControlMap(dir_path.toStdString());
- AddInstalledTitlesToGameList();
- AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
- nca_control_map.clear();
+ provider->ClearAllEntries();
+ ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
+ deep_scan ? 256 : 0);
+ AddTitlesToGameList();
+ ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
emit Finished(watch_list);
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 0e42d0bde..7c3074af9 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -33,7 +33,8 @@ class GameListWorker : public QObject, public QRunnable {
Q_OBJECT
public:
- GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
+ GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
const CompatibilityList& compatibility_list);
~GameListWorker() override;
@@ -58,12 +59,17 @@ signals:
void Finished(QStringList watch_list);
private:
- void AddInstalledTitlesToGameList();
- void FillControlMap(const std::string& dir_path);
- void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
+ void AddTitlesToGameList();
+
+ enum class ScanTarget {
+ FillManualContentProvider,
+ PopulateGameList,
+ };
+
+ void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
- std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
+ FileSys::ManualContentProvider* provider;
QStringList watch_list;
QString dir_path;
bool deep_scan;
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index dce399774..4582e7f21 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <QKeySequence>
#include <QShortcut>
#include <QTreeWidgetItem>
@@ -13,47 +12,32 @@
HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
-void HotkeyRegistry::LoadHotkeys() {
- // Make sure NOT to use a reference here because it would become invalid once we call
- // beginGroup()
- for (auto shortcut : UISettings::values.shortcuts) {
- const QStringList cat = shortcut.first.split('/');
- Q_ASSERT(cat.size() >= 2);
-
- // RegisterHotkey assigns default keybindings, so use old values as default parameters
- Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
- if (!shortcut.second.first.isEmpty()) {
- hk.keyseq = QKeySequence::fromString(shortcut.second.first);
- hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
- }
- if (hk.shortcut)
- hk.shortcut->setKey(hk.keyseq);
- }
-}
-
void HotkeyRegistry::SaveHotkeys() {
UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) {
for (const auto& hotkey : group.second) {
- UISettings::values.shortcuts.emplace_back(
- UISettings::Shortcut(group.first + '/' + hotkey.first,
- UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
- hotkey.second.context)));
+ UISettings::values.shortcuts.push_back(
+ {hotkey.first, group.first,
+ UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
+ hotkey.second.context)});
}
}
}
-void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq,
- Qt::ShortcutContext default_context) {
- auto& hotkey_group = hotkey_groups[group];
- if (hotkey_group.find(action) != hotkey_group.end()) {
- return;
+void HotkeyRegistry::LoadHotkeys() {
+ // Make sure NOT to use a reference here because it would become invalid once we call
+ // beginGroup()
+ for (auto shortcut : UISettings::values.shortcuts) {
+ Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
+ if (!shortcut.shortcut.first.isEmpty()) {
+ hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
+ hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
+ }
+ if (hk.shortcut) {
+ hk.shortcut->disconnect();
+ hk.shortcut->setKey(hk.keyseq);
+ }
}
-
- auto& hotkey_action = hotkey_groups[group][action];
- hotkey_action.keyseq = default_keyseq;
- hotkey_action.context = default_context;
}
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
return hk.shortcut;
}
-GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
- ui.setupUi(this);
+QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
+ return hotkey_groups[group][action].keyseq;
}
-void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
- for (const auto& group : registry.hotkey_groups) {
- QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
- for (const auto& hotkey : group.second) {
- QStringList columns;
- columns << hotkey.first << hotkey.second.keyseq.toString();
- QTreeWidgetItem* item = new QTreeWidgetItem(columns);
- toplevel_item->addChild(item);
- }
- ui.treeWidget->addTopLevelItem(toplevel_item);
- }
- // TODO: Make context configurable as well (hiding the column for now)
- ui.treeWidget->setColumnCount(2);
-
- ui.treeWidget->resizeColumnToContents(0);
- ui.treeWidget->resizeColumnToContents(1);
+Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
+ const QString& action) {
+ return hotkey_groups[group][action].context;
}
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index f38e6c002..4f526dc7e 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -5,7 +5,6 @@
#pragma once
#include <map>
-#include "ui_hotkeys.h"
class QDialog;
class QKeySequence;
@@ -14,7 +13,7 @@ class QShortcut;
class HotkeyRegistry final {
public:
- friend class GHotkeysDialog;
+ friend class ConfigureHotkeys;
explicit HotkeyRegistry();
~HotkeyRegistry();
@@ -49,22 +48,27 @@ public:
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
/**
- * Register a hotkey.
+ * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
*
- * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
- * @param action Name of the action (e.g. "Start Emulation", "Load Image")
- * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
- * settings file before
- * @param default_context Default context to assign if the hotkey wasn't present in the settings
- * file before
- * @warning Both the group and action strings will be displayed in the hotkey settings dialog
+ * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
+ */
+ QKeySequence GetKeySequence(const QString& group, const QString& action);
+
+ /**
+ * Returns a Qt::ShortcutContext object who can be connected to other
+ * QAction::setShortcutContext.
+ *
+ * @param group General group this shortcut context belongs to (e.g. "Main Window",
+ * "Debugger").
+ * @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
- void RegisterHotkey(const QString& group, const QString& action,
- const QKeySequence& default_keyseq = {},
- Qt::ShortcutContext default_context = Qt::WindowShortcut);
+ Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
private:
struct Hotkey {
+ Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
+
QKeySequence keyseq;
QShortcut* shortcut = nullptr;
Qt::ShortcutContext context = Qt::WindowShortcut;
@@ -75,15 +79,3 @@ private:
HotkeyGroupMap hotkey_groups;
};
-
-class GHotkeysDialog : public QWidget {
- Q_OBJECT
-
-public:
- explicit GHotkeysDialog(QWidget* parent = nullptr);
-
- void Populate(const HotkeyRegistry& registry);
-
-private:
- Ui::hotkeys ui;
-};
diff --git a/src/yuzu/hotkeys.ui b/src/yuzu/hotkeys.ui
deleted file mode 100644
index 050fe064e..000000000
--- a/src/yuzu/hotkeys.ui
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>hotkeys</class>
- <widget class="QWidget" name="hotkeys">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>363</width>
- <height>388</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Hotkey Settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTreeWidget" name="treeWidget">
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectItems</enum>
- </property>
- <property name="headerHidden">
- <bool>false</bool>
- </property>
- <column>
- <property name="text">
- <string>Action</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Hotkey</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Context</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index 86f6d0165..4e2d988cd 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -192,7 +192,12 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
}
// update labels and progress bar
- ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ if (stage == VideoCore::LoadCallbackStage::Decompile ||
+ stage == VideoCore::LoadCallbackStage::Build) {
+ ui->stage->setText(stage_translations[stage].arg(value).arg(total));
+ } else {
+ ui->stage->setText(stage_translations[stage]);
+ }
ui->value->setText(estimate);
ui->progress_bar->setValue(static_cast<int>(value));
previous_time = now;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 41ba3c4c6..bdee44b04 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -37,14 +37,20 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <glad/glad.h>
#define QT_NO_OPENGL
+#include <QClipboard>
+#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
+#include <QInputDialog>
#include <QMessageBox>
+#include <QProgressBar>
+#include <QProgressDialog>
+#include <QShortcut>
+#include <QStatusBar>
#include <QtConcurrent/QtConcurrent>
-#include <QtGui>
-#include <QtWidgets>
+
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/detached_tasks.h"
@@ -55,11 +61,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
-#include "common/string_util.h"
#include "common/telemetry.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
-#include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
@@ -71,7 +75,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/filesystem/fsp_ldr.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
@@ -87,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/debugger/console.h"
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
-#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/discord.h"
@@ -168,7 +170,8 @@ static void InitializeLogging() {
GMainWindow::GMainWindow()
: config(new Config()), emu_thread(nullptr),
- vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
+ vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
+ provider(std::make_unique<FileSys::ManualContentProvider>()) {
InitializeLogging();
debug_context = Tegra::DebugContext::Construct();
@@ -200,13 +203,18 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
show();
+ Core::System::GetInstance().SetContentProvider(
+ std::make_unique<FileSys::ContentProviderUnion>());
+ Core::System::GetInstance().RegisterContentProvider(
+ FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
+ Service::FileSystem::CreateFactories(*vfs);
+
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
- // Necessary to load titles from nand in gamelist.
- Service::FileSystem::CreateFactories(*vfs);
game_list->LoadCompatibilityList();
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
// Show one-time "callout" messages to the user
ShowTelemetryCallout();
@@ -416,7 +424,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
- game_list = new GameList(vfs, this);
+ game_list = new GameList(vfs, provider.get(), this);
ui.horizontalLayout->addWidget(game_list);
loading_screen = new LoadingScreen(this);
@@ -475,11 +483,6 @@ void GMainWindow::InitializeDebugWidgets() {
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();
@@ -511,33 +514,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
}
void GMainWindow::InitializeHotkeys() {
- hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
- hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
- hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
- hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
- hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
- hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
- Qt::ApplicationShortcut);
- hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
- QKeySequence(QKeySequence::Print));
- hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
-
hotkey_registry.LoadHotkeys();
+ ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
+ ui.action_Load_File->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Load File"));
+
+ ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
+ ui.action_Exit->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
+
+ ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
+ ui.action_Stop->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
+
+ ui.action_Show_Filter_Bar->setShortcut(
+ hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
+ ui.action_Show_Filter_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
+
+ ui.action_Show_Status_Bar->setShortcut(
+ hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
+ ui.action_Show_Status_Bar->setShortcutContext(
+ hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
+
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
this, &GMainWindow::OnMenuLoadFile);
- connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
- &QShortcut::activated, this, &GMainWindow::OnStartGame);
- connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
- this, [&] {
+ connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
+ &QShortcut::activated, this, [&] {
if (emulation_running) {
if (emu_thread->IsRunning()) {
OnPauseGame();
@@ -546,8 +550,8 @@ void GMainWindow::InitializeHotkeys() {
}
}
});
- connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
- [this] {
+ connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
+ &QShortcut::activated, this, [this] {
if (!Core::System::GetInstance().IsPoweredOn())
return;
BootGame(QString(game_path));
@@ -648,6 +652,8 @@ void GMainWindow::RestoreUIState() {
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
+ connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
+ &GMainWindow::OnTransferableShaderCacheOpenFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
@@ -692,7 +698,6 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ToggleWindowMode);
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
&GMainWindow::OnDisplayTitleBars);
- ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
@@ -1082,6 +1087,39 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
}
+void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
+ ASSERT(program_id != 0);
+
+ const QString tranferable_shader_cache_folder_path =
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) + "opengl" +
+ DIR_SEP + "transferable";
+
+ const QString transferable_shader_cache_file_path =
+ tranferable_shader_cache_folder_path + DIR_SEP +
+ QString::fromStdString(fmt::format("{:016X}.bin", program_id));
+
+ if (!QFile::exists(transferable_shader_cache_file_path)) {
+ QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+
+ // Windows supports opening a folder with selecting a specified file in explorer. On every other
+ // OS we just open the transferable shader cache folder without preselecting the transferable
+ // shader cache file for the selected game.
+#if defined(Q_OS_WIN)
+ const QString explorer = QStringLiteral("explorer");
+ QStringList param;
+ if (!QFileInfo(transferable_shader_cache_file_path).isDir()) {
+ param << QStringLiteral("/select,");
+ }
+ param << QDir::toNativeSeparators(transferable_shader_cache_file_path);
+ QProcess::startDetached(explorer, param);
+#else
+ QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path));
+#endif
+}
+
static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
std::size_t out = 0;
@@ -1141,7 +1179,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
if (!romfs_title_id) {
@@ -1241,8 +1279,8 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
- game_list->PopulateAsync(UISettings::values.gamedir,
- UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
config->Save();
@@ -1330,7 +1368,8 @@ void GMainWindow::OnMenuInstallToNAND() {
const auto success = [this]() {
QMessageBox::information(this, tr("Successfully Installed"),
tr("The file was successfully installed."));
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
};
const auto failed = [this]() {
@@ -1457,8 +1496,8 @@ void GMainWindow::OnMenuInstallToNAND() {
void GMainWindow::OnMenuSelectGameListRoot() {
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!dir_path.isEmpty()) {
- UISettings::values.gamedir = dir_path;
- game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan);
+ UISettings::values.game_directory_path = dir_path;
+ game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
}
}
@@ -1480,7 +1519,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
Service::FileSystem::CreateFactories(*vfs);
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
@@ -1624,6 +1664,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ InitializeHotkeys();
if (UISettings::values.theme != old_theme)
UpdateUITheme();
if (UISettings::values.enable_discord_presence != old_discord_presence)
@@ -1631,8 +1672,8 @@ void GMainWindow::OnConfigure() {
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
- game_list->PopulateAsync(UISettings::values.gamedir,
- UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
config->Save();
@@ -1882,18 +1923,19 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
Service::FileSystem::CreateFactories(*vfs);
if (behavior == ReinitializeKeyBehavior::Warning) {
- game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_directory_path,
+ UISettings::values.game_directory_deepscan);
}
}
-std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
- const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
+std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
+ u64 program_id) {
const auto dlc_entries =
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
- std::vector<FileSys::RegisteredCacheEntry> dlc_match;
+ std::vector<FileSys::ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
+ [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
@@ -1989,6 +2031,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
event->acceptProposedAction();
}
+void GMainWindow::keyPressEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyPressEvent(event);
+ }
+}
+
+void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
+ if (render_window) {
+ render_window->ForwardKeyReleaseEvent(event);
+ }
+}
+
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -2056,7 +2110,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName("yuzu team");
QCoreApplication::setApplicationName("yuzu");
- QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
+ // Enables the core to make the qt created contexts current on std::threads
+ QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
// Qt changes the locale and causes issues in float conversion using std::to_string() when
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e07c892cf..ce5045819 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -23,7 +23,6 @@ class EmuThread;
class GameList;
class GImageInfo;
class GraphicsBreakPointsWidget;
-class GraphicsSurfaceWidget;
class GRenderWindow;
class LoadingScreen;
class MicroProfileDialog;
@@ -37,7 +36,8 @@ struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
namespace FileSys {
-class RegisteredCacheUnion;
+class ContentProvider;
+class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
@@ -120,7 +120,6 @@ private:
void InitializeWidgets();
void InitializeDebugWidgets();
void InitializeRecentFileMenuActions();
- void InitializeHotkeys();
void SetDefaultUIGeometry();
void RestoreUIState();
@@ -176,6 +175,7 @@ private slots:
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
+ void OnTransferableShaderCacheOpenFile(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
@@ -195,6 +195,7 @@ private slots:
void OnAbout();
void OnToggleFilterBar();
void OnDisplayTitleBars(bool);
+ void InitializeHotkeys();
void ToggleFullscreen();
void ShowFullscreen();
void HideFullscreen();
@@ -204,7 +205,7 @@ private slots:
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
+ std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
void UpdateStatusBar();
Ui::MainWindow ui;
@@ -232,12 +233,12 @@ private:
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ std::unique_ptr<FileSys::ManualContentProvider> provider;
// Debugger panes
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
- GraphicsSurfaceWidget* graphicsSurfaceWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];
@@ -251,4 +252,8 @@ protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
+
+ // Overrides used to forward signals to the render window when the focus moves out.
+ void keyPressEvent(QKeyEvent* event) override;
+ void keyReleaseEvent(QKeyEvent* event) override;
};
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp
index a314493fc..4bdc302e0 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/ui_settings.cpp
@@ -12,5 +12,4 @@ const Themes themes{{
}};
Values values = {};
-
} // namespace UISettings
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 82aaeedb0..dbd318e20 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,7 +15,12 @@
namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
-using Shortcut = std::pair<QString, ContextualShortcut>;
+
+struct Shortcut {
+ QString name;
+ QString group;
+ ContextualShortcut shortcut;
+};
using Themes = std::array<std::pair<const char*, const char*>, 2>;
extern const Themes themes;
@@ -50,8 +55,8 @@ struct Values {
QString roms_path;
QString symbols_path;
QString screenshot_path;
- QString gamedir;
- bool gamedir_deepscan;
+ QString game_directory_path;
+ bool game_directory_deepscan;
QStringList recent_files;
QString theme;
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
new file mode 100644
index 000000000..d3edf6ec3
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -0,0 +1,37 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDialogButtonBox>
+#include <QKeySequenceEdit>
+#include <QVBoxLayout>
+#include "yuzu/util/sequence_dialog/sequence_dialog.h"
+
+SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
+ setWindowTitle(tr("Enter a hotkey"));
+ auto* layout = new QVBoxLayout(this);
+ key_sequence = new QKeySequenceEdit;
+ layout->addWidget(key_sequence);
+ auto* buttons =
+ new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
+ buttons->setCenterButtons(true);
+ layout->addWidget(buttons);
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+}
+
+SequenceDialog::~SequenceDialog() = default;
+
+QKeySequence SequenceDialog::GetSequence() const {
+ // Only the first key is returned. The other 3, if present, are ignored.
+ return QKeySequence(key_sequence->keySequence()[0]);
+}
+
+bool SequenceDialog::focusNextPrevChild(bool next) {
+ return false;
+}
+
+void SequenceDialog::closeEvent(QCloseEvent*) {
+ reject();
+}
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h
new file mode 100644
index 000000000..969c77740
--- /dev/null
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h
@@ -0,0 +1,24 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+class QKeySequenceEdit;
+
+class SequenceDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit SequenceDialog(QWidget* parent = nullptr);
+ ~SequenceDialog() override;
+
+ QKeySequence GetSequence() const;
+ void closeEvent(QCloseEvent*) override;
+
+private:
+ QKeySequenceEdit* key_sequence;
+ bool focusNextPrevChild(bool next) override;
+};