diff options
Diffstat (limited to 'src/yuzu')
37 files changed, 1959 insertions, 792 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 8b9404718..6b25a7fa0 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(yuzu compatibility_list.h configuration/config.cpp configuration/config.h + configuration/configuration_shared.cpp + configuration/configuration_shared.h configuration/configure.ui configuration/configure_audio.cpp configuration/configure_audio.h @@ -60,9 +62,12 @@ add_executable(yuzu configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h configuration/configure_mouse_advanced.ui - configuration/configure_per_general.cpp - configuration/configure_per_general.h - configuration/configure_per_general.ui + configuration/configure_per_game.cpp + configuration/configure_per_game.h + configuration/configure_per_game.ui + configuration/configure_per_game_addons.cpp + configuration/configure_per_game_addons.h + configuration/configure_per_game_addons.ui configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui @@ -147,7 +152,7 @@ endif() create_target_directory_groups(yuzu) 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 Boost::boost glad Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (ENABLE_VULKAN AND NOT WIN32) @@ -208,6 +213,10 @@ if (MSVC) copy_yuzu_unicorn_deps(yuzu) endif() +if (NOT APPLE) + target_compile_definitions(yuzu PRIVATE HAS_OPENGL) +endif() + if (ENABLE_VULKAN) target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) target_compile_definitions(yuzu PRIVATE HAS_VULKAN) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 1adf8932b..5738787ac 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -8,13 +8,16 @@ #include <QHBoxLayout> #include <QKeyEvent> #include <QMessageBox> -#include <QOffscreenSurface> -#include <QOpenGLContext> #include <QPainter> #include <QScreen> #include <QStringList> #include <QWindow> +#ifdef HAS_OPENGL +#include <QOffscreenSurface> +#include <QOpenGLContext> +#endif + #if !defined(WIN32) && HAS_VULKAN #include <qpa/qplatformnativeinterface.h> #endif @@ -41,49 +44,65 @@ EmuThread::EmuThread() = default; EmuThread::~EmuThread() = default; void EmuThread::run() { - MicroProfileOnThreadCreate("EmuThread"); + std::string name = "yuzu:EmuControlThread"; + MicroProfileOnThreadCreate(name.c_str()); + Common::SetCurrentThreadName(name.c_str()); + + auto& system = Core::System::GetInstance(); + + system.RegisterHostThread(); + + auto& gpu = system.GPU(); // Main process has been loaded. Make the context current to this thread and begin GPU and CPU // execution. - Core::System::GetInstance().GPU().Start(); + gpu.Start(); + + gpu.ObtainContext(); emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( + system.Renderer().Rasterizer().LoadDiskResources( stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); }); emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + gpu.ReleaseContext(); + // Holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step bool was_active = false; while (!stop_run) { if (running) { - if (!was_active) + if (was_active) { emit DebugModeLeft(); + } - Core::System::ResultStatus result = Core::System::GetInstance().RunLoop(); + running_guard = true; + Core::System::ResultStatus result = system.Run(); if (result != Core::System::ResultStatus::Success) { + running_guard = false; this->SetRunning(false); - emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); + emit ErrorThrown(result, system.GetStatusDetails()); } + running_wait.Wait(); + result = system.Pause(); + if (result != Core::System::ResultStatus::Success) { + running_guard = false; + this->SetRunning(false); + emit ErrorThrown(result, system.GetStatusDetails()); + } + running_guard = false; - was_active = running || exec_step; - if (!was_active && !stop_run) + if (!stop_run) { + was_active = true; emit DebugModeEntered(); + } } else if (exec_step) { - if (!was_active) - emit DebugModeLeft(); - - exec_step = false; - Core::System::GetInstance().SingleStep(); - emit DebugModeEntered(); - yieldCurrentThread(); - - was_active = false; + UNIMPLEMENTED(); } else { std::unique_lock lock{running_mutex}; running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); @@ -91,13 +110,14 @@ void EmuThread::run() { } // Shutdown the core emulation - Core::System::GetInstance().Shutdown(); + system.Shutdown(); #if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); #endif } +#ifdef HAS_OPENGL class OpenGLSharedContext : public Core::Frontend::GraphicsContext { public: /// Create the original context that should be shared from @@ -106,6 +126,9 @@ public: format.setVersion(4, 3); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); + if (Settings::values.renderer_debug) { + format.setOption(QSurfaceFormat::FormatOption::DebugContext); + } // TODO: expose a setting for buffer value (ie default/single/double/triple) format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); format.setSwapInterval(0); @@ -122,7 +145,7 @@ public: // disable vsync for any shared contexts auto format = share_context->format(); - format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0); + format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0); context = std::make_unique<QOpenGLContext>(); context->setShareContext(share_context); @@ -180,6 +203,7 @@ private: std::unique_ptr<QOffscreenSurface> offscreen_surface{}; QSurface* surface; }; +#endif class DummyContext : public Core::Frontend::GraphicsContext {}; @@ -352,7 +376,7 @@ QByteArray GRenderWindow::saveGeometry() { } qreal GRenderWindow::windowPixelRatio() const { - return devicePixelRatio(); + return devicePixelRatioF(); } std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF& pos) const { @@ -470,13 +494,15 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) { } std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { - if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { +#ifdef HAS_OPENGL + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { auto c = static_cast<OpenGLSharedContext*>(main_context.get()); // Bind the shared contexts to the main surface in case the backend wants to take over // presentation return std::make_unique<OpenGLSharedContext>(c->GetShareContext(), child_widget->windowHandle()); } +#endif return std::make_unique<DummyContext>(); } @@ -485,7 +511,7 @@ bool GRenderWindow::InitRenderTarget() { first_frame = false; - switch (Settings::values.renderer_backend) { + switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: if (!InitializeOpenGL()) { return false; @@ -512,7 +538,7 @@ bool GRenderWindow::InitRenderTarget() { OnFramebufferSizeChanged(); BackupGeometry(); - if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { if (!LoadOpenGL()) { return false; } @@ -557,6 +583,7 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal } bool GRenderWindow::InitializeOpenGL() { +#ifdef HAS_OPENGL // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose auto child = new OpenGLRenderWidget(this); @@ -568,6 +595,11 @@ bool GRenderWindow::InitializeOpenGL() { std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle())); return true; +#else + QMessageBox::warning(this, tr("OpenGL not available!"), + tr("yuzu has not been compiled with OpenGL support.")); + return false; +#endif } bool GRenderWindow::InitializeVulkan() { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 3626604ca..6c59b4d5c 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -59,6 +59,12 @@ public: this->running = running; lock.unlock(); running_cv.notify_all(); + if (!running) { + running_wait.Set(); + /// Wait until effectively paused + while (running_guard) + ; + } } /** @@ -84,6 +90,8 @@ private: std::atomic_bool stop_run{false}; std::mutex running_mutex; std::condition_variable running_cv; + Common::Event running_wait{}; + std::atomic_bool running_guard{false}; signals: /** diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index b08b87426..1b2b1b2bb 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -13,17 +13,20 @@ #include "input_common/udp/client.h" #include "yuzu/configuration/config.h" -Config::Config() { +Config::Config(const std::string& config_file, bool is_global) { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; + qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file; FileUtil::CreateFullPath(qt_config_loc); qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); + global = is_global; Reload(); } Config::~Config() { - Save(); + if (global) { + Save(); + } } const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { @@ -211,8 +214,8 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default // 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. // clang-format off -const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{{ - {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}}, +const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ + {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, @@ -220,8 +223,9 @@ const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{{ {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, - {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}}, - {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}}, + {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, @@ -401,16 +405,19 @@ void Config::ApplyDefaultProfileIfInputInvalid() { void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); - Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) - .toString() - .toStdString(); - Settings::values.enable_audio_stretching = - ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool(); - Settings::values.audio_device_id = - ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) - .toString() - .toStdString(); - Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat(); + if (global) { + Settings::values.sink_id = + ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) + .toString() + .toStdString(); + Settings::values.audio_device_id = + ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) + .toString() + .toStdString(); + } + ReadSettingGlobal(Settings::values.enable_audio_stretching, + QStringLiteral("enable_audio_stretching"), true); + ReadSettingGlobal(Settings::values.volume, QStringLiteral("volume"), 1); qt_config->endGroup(); } @@ -439,6 +446,8 @@ void Config::ReadControlValues() { .toInt()); Settings::values.udp_pad_index = static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); + Settings::values.use_docked_mode = + ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); qt_config->endGroup(); } @@ -446,7 +455,7 @@ void Config::ReadControlValues() { void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); + ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false); qt_config->endGroup(); } @@ -533,6 +542,8 @@ void Config::ReadDebuggingValues() { Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); Settings::values.disable_cpu_opt = ReadSetting(QStringLiteral("disable_cpu_opt"), false).toBool(); + Settings::values.disable_macro_jit = + ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool(); qt_config->endGroup(); } @@ -625,34 +636,28 @@ void Config::ReadPathValues() { void Config::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); - Settings::values.renderer_backend = - static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt()); - Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool(); - Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt(); - Settings::values.resolution_factor = - ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat(); - Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt(); - Settings::values.max_anisotropy = ReadSetting(QStringLiteral("max_anisotropy"), 0).toInt(); - Settings::values.use_frame_limit = - ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); - Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); - Settings::values.use_disk_shader_cache = - ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool(); - const int gpu_accuracy_level = ReadSetting(QStringLiteral("gpu_accuracy"), 0).toInt(); - Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); - Settings::values.use_asynchronous_gpu_emulation = - ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); - Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); - Settings::values.use_assembly_shaders = - ReadSetting(QStringLiteral("use_assembly_shaders"), false).toBool(); - Settings::values.use_fast_gpu_time = - ReadSetting(QStringLiteral("use_fast_gpu_time"), true).toBool(); - Settings::values.force_30fps_mode = - ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); - - Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat(); - Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat(); - Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat(); + ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0); + ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false); + ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0); + ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0); + ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0); + ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true); + ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100); + ReadSettingGlobal(Settings::values.use_disk_shader_cache, + QStringLiteral("use_disk_shader_cache"), true); + ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0); + ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation, + QStringLiteral("use_asynchronous_gpu_emulation"), false); + ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); + ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), + false); + ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), + true); + ReadSettingGlobal(Settings::values.force_30fps_mode, QStringLiteral("force_30fps_mode"), false); + + ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0); + ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0); + ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); qt_config->endGroup(); } @@ -664,11 +669,13 @@ void Config::ReadShortcutValues() { const auto& [keyseq, context] = shortcut; qt_config->beginGroup(group); qt_config->beginGroup(name); + // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 + // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open + // a file dialog in windowed mode UISettings::values.shortcuts.push_back( {name, group, - {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), - ReadSetting(QStringLiteral("Context"), context).toInt()}}); + {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}}); qt_config->endGroup(); qt_config->endGroup(); } @@ -679,35 +686,45 @@ void Config::ReadShortcutValues() { void Config::ReadSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - Settings::values.use_docked_mode = - ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); - - Settings::values.current_user = std::clamp<int>( - ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1); + ReadSettingGlobal(Settings::values.current_user, QStringLiteral("current_user"), 0); + Settings::values.current_user = + std::clamp<int>(Settings::values.current_user, 0, Service::Account::MAX_USERS - 1); - Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.language_index, QStringLiteral("language_index"), 1); - Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.region_index, QStringLiteral("region_index"), 1); - Settings::values.time_zone_index = ReadSetting(QStringLiteral("time_zone_index"), 0).toInt(); + ReadSettingGlobal(Settings::values.time_zone_index, QStringLiteral("time_zone_index"), 0); - const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool(); - if (rng_seed_enabled) { - Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong(); - } else { - Settings::values.rng_seed = std::nullopt; + bool rng_seed_enabled; + ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false); + bool rng_seed_global = + global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool(); + Settings::values.rng_seed.SetGlobal(rng_seed_global); + if (global || !rng_seed_global) { + if (rng_seed_enabled) { + Settings::values.rng_seed.SetValue( + ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong()); + } else { + Settings::values.rng_seed.SetValue(std::nullopt); + } } - const auto custom_rtc_enabled = - ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); - if (custom_rtc_enabled) { - Settings::values.custom_rtc = - std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); - } else { - Settings::values.custom_rtc = std::nullopt; + bool custom_rtc_enabled; + ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false); + bool custom_rtc_global = + global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool(); + Settings::values.custom_rtc.SetGlobal(custom_rtc_global); + if (global || !custom_rtc_global) { + if (custom_rtc_enabled) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong())); + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } } - Settings::values.sound_index = ReadSetting(QStringLiteral("sound_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.sound_index, QStringLiteral("sound_index"), 1); qt_config->endGroup(); } @@ -720,8 +737,6 @@ void Config::ReadUIValues() { .toString(); UISettings::values.enable_discord_presence = ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool(); - UISettings::values.screenshot_resolution_factor = - static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt()); UISettings::values.select_user_on_boot = ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool(); @@ -803,18 +818,20 @@ void Config::ReadWebServiceValues() { } void Config::ReadValues() { - ReadControlValues(); + if (global) { + ReadControlValues(); + ReadDataStorageValues(); + ReadDebuggingValues(); + ReadDisabledAddOnValues(); + ReadServiceValues(); + ReadUIValues(); + ReadWebServiceValues(); + ReadMiscellaneousValues(); + } ReadCoreValues(); ReadRendererValues(); ReadAudioValues(); - ReadDataStorageValues(); ReadSystemValues(); - ReadMiscellaneousValues(); - ReadDebuggingValues(); - ReadWebServiceValues(); - ReadServiceValues(); - ReadDisabledAddOnValues(); - ReadUIValues(); } void Config::SavePlayerValues() { @@ -901,30 +918,35 @@ void Config::SaveTouchscreenValues() { } void Config::SaveValues() { - SaveControlValues(); + if (global) { + SaveControlValues(); + SaveDataStorageValues(); + SaveDebuggingValues(); + SaveDisabledAddOnValues(); + SaveServiceValues(); + SaveUIValues(); + SaveWebServiceValues(); + SaveMiscellaneousValues(); + } SaveCoreValues(); SaveRendererValues(); SaveAudioValues(); - SaveDataStorageValues(); SaveSystemValues(); - SaveMiscellaneousValues(); - SaveDebuggingValues(); - SaveWebServiceValues(); - SaveServiceValues(); - SaveDisabledAddOnValues(); - SaveUIValues(); } void Config::SaveAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); - WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id), - QStringLiteral("auto")); - WriteSetting(QStringLiteral("enable_audio_stretching"), - Settings::values.enable_audio_stretching, true); - WriteSetting(QStringLiteral("output_device"), - QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto")); - WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f); + if (global) { + WriteSetting(QStringLiteral("output_engine"), + QString::fromStdString(Settings::values.sink_id), QStringLiteral("auto")); + WriteSetting(QStringLiteral("output_device"), + QString::fromStdString(Settings::values.audio_device_id), + QStringLiteral("auto")); + } + WriteSettingGlobal(QStringLiteral("enable_audio_stretching"), + Settings::values.enable_audio_stretching, true); + WriteSettingGlobal(QStringLiteral("volume"), Settings::values.volume, 1.0f); qt_config->endGroup(); } @@ -947,6 +969,7 @@ void Config::SaveControlValues() { WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, InputCommon::CemuhookUDP::DEFAULT_PORT); WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); + WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); qt_config->endGroup(); } @@ -954,7 +977,7 @@ void Config::SaveControlValues() { void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); + WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); qt_config->endGroup(); } @@ -1011,6 +1034,7 @@ void Config::SaveDebuggingValues() { WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); WriteSetting(QStringLiteral("disable_cpu_opt"), Settings::values.disable_cpu_opt, false); + WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false); qt_config->endGroup(); } @@ -1076,31 +1100,34 @@ void Config::SavePathValues() { void Config::SaveRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); - WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0); + WriteSettingGlobal(QStringLiteral("backend"), + static_cast<int>(Settings::values.renderer_backend.GetValue(global)), + Settings::values.renderer_backend.UsingGlobal(), 0); WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false); - WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); - WriteSetting(QStringLiteral("resolution_factor"), - static_cast<double>(Settings::values.resolution_factor), 1.0); - WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); - WriteSetting(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); - WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); - WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); - WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, - true); - WriteSetting(QStringLiteral("gpu_accuracy"), static_cast<int>(Settings::values.gpu_accuracy), - 0); - WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), - Settings::values.use_asynchronous_gpu_emulation, false); - WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); - WriteSetting(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, - false); - WriteSetting(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, true); - WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); + WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); + WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); + WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); + WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); + WriteSettingGlobal(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); + WriteSettingGlobal(QStringLiteral("use_disk_shader_cache"), + Settings::values.use_disk_shader_cache, true); + WriteSettingGlobal(QStringLiteral("gpu_accuracy"), + static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)), + Settings::values.gpu_accuracy.UsingGlobal(), 0); + WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"), + Settings::values.use_asynchronous_gpu_emulation, false); + WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); + WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), + Settings::values.use_assembly_shaders, false); + WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, + true); + WriteSettingGlobal(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, + false); // Cast to double because Qt's written float values are not human-readable - WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0); - WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0); - WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0); + WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0); + WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); + WriteSettingGlobal(QStringLiteral("bg_blue"), Settings::values.bg_blue, 0.0); qt_config->endGroup(); } @@ -1128,23 +1155,28 @@ void Config::SaveShortcutValues() { void Config::SaveSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); - WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); - WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1); - WriteSetting(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); - - WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); - WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); - - WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), - false); - WriteSetting(QStringLiteral("custom_rtc"), - QVariant::fromValue<long long>( - Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), - 0); - - WriteSetting(QStringLiteral("sound_index"), Settings::values.sound_index, 1); + WriteSettingGlobal(QStringLiteral("language_index"), Settings::values.language_index, 1); + WriteSettingGlobal(QStringLiteral("region_index"), Settings::values.region_index, 1); + WriteSettingGlobal(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); + + WriteSettingGlobal(QStringLiteral("rng_seed_enabled"), + Settings::values.rng_seed.GetValue(global).has_value(), + Settings::values.rng_seed.UsingGlobal(), false); + WriteSettingGlobal(QStringLiteral("rng_seed"), + Settings::values.rng_seed.GetValue(global).value_or(0), + Settings::values.rng_seed.UsingGlobal(), 0); + + WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"), + Settings::values.custom_rtc.GetValue(global).has_value(), + Settings::values.custom_rtc.UsingGlobal(), false); + WriteSettingGlobal( + QStringLiteral("custom_rtc"), + QVariant::fromValue<long long>( + Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()), + Settings::values.custom_rtc.UsingGlobal(), 0); + + WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1); qt_config->endGroup(); } @@ -1156,8 +1188,6 @@ void Config::SaveUIValues() { QString::fromUtf8(UISettings::themes[0].second)); WriteSetting(QStringLiteral("enable_discord_presence"), UISettings::values.enable_discord_presence, true); - WriteSetting(QStringLiteral("screenshot_resolution_factor"), - UISettings::values.screenshot_resolution_factor, 0); WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot, false); @@ -1238,6 +1268,34 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) return result; } +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name) { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + setting.SetGlobal(use_global); + if (global || !use_global) { + setting.SetValue(ReadSetting(name).value<Type>()); + } +} + +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, + const QVariant& default_value) { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + setting.SetGlobal(use_global); + if (global || !use_global) { + setting.SetValue(ReadSetting(name, default_value).value<Type>()); + } +} + +template <typename Type> +void Config::ReadSettingGlobal(Type& setting, const QString& name, + const QVariant& default_value) const { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + if (global || !use_global) { + setting = ReadSetting(name, default_value).value<Type>(); + } +} + void Config::WriteSetting(const QString& name, const QVariant& value) { qt_config->setValue(name, value); } @@ -1248,6 +1306,40 @@ void Config::WriteSetting(const QString& name, const QVariant& value, qt_config->setValue(name, value); } +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); + } + if (global || !setting.UsingGlobal()) { + qt_config->setValue(name, setting.GetValue(global)); + } +} + +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, + const QVariant& default_value) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); + } + if (global || !setting.UsingGlobal()) { + qt_config->setValue(name + QStringLiteral("/default"), + setting.GetValue(global) == default_value.value<Type>()); + qt_config->setValue(name, setting.GetValue(global)); + } +} + +void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, + const QVariant& default_value) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), use_global); + } + if (global || !use_global) { + qt_config->setValue(name + QStringLiteral("/default"), value == default_value); + qt_config->setValue(name, value); + } +} + void Config::Reload() { ReadValues(); // To apply default value changes diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 5cd2a5feb..681f0bca5 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -7,6 +7,7 @@ #include <array> #include <memory> #include <string> +#include <QMetaType> #include <QVariant> #include "core/settings.h" #include "yuzu/uisettings.h" @@ -15,7 +16,7 @@ class QSettings; class Config { public: - Config(); + explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true); ~Config(); void Reload(); @@ -27,7 +28,7 @@ public: default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; - static const std::array<UISettings::Shortcut, 15> default_hotkeys; + static const std::array<UISettings::Shortcut, 16> default_hotkeys; private: void ReadValues(); @@ -82,9 +83,33 @@ private: QVariant ReadSetting(const QString& name) const; QVariant ReadSetting(const QString& name, const QVariant& default_value) const; + // Templated ReadSettingGlobal functions will also look for the use_global setting and set + // both the value and the global state properly + template <typename Type> + void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name); + template <typename Type> + void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, + const QVariant& default_value); + template <typename Type> + void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const; + // Templated WriteSettingGlobal functions will also write the global state if needed and will + // skip writing the actual setting if it defers to the global value void WriteSetting(const QString& name, const QVariant& value); void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); + template <typename Type> + void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting); + template <typename Type> + void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, + const QVariant& default_value); + void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, + const QVariant& default_value); std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; + + bool global; }; + +// These metatype declarations cannot be in core/settings.h because core is devoid of QT +Q_DECLARE_METATYPE(Settings::RendererBackend); +Q_DECLARE_METATYPE(Settings::GPUAccuracy); diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp new file mode 100644 index 000000000..bb47c3933 --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -0,0 +1,76 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QCheckBox> +#include <QComboBox> +#include "core/settings.h" +#include "yuzu/configuration/configuration_shared.h" +#include "yuzu/configuration/configure_per_game.h" + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, + const QCheckBox* checkbox) { + if (checkbox->checkState() == Qt::PartiallyChecked) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(checkbox->checkState() == Qt::Checked); + } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, + const QComboBox* combobox) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); + } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, + const QComboBox* combobox) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(static_cast<Settings::RendererBackend>( + combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); + } +} + +void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, + const Settings::Setting<bool>* setting) { + if (setting->UsingGlobal()) { + checkbox->setCheckState(Qt::PartiallyChecked); + } else { + checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked); + } +} + +void ConfigurationShared::SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<int>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() + ? ConfigurationShared::USE_GLOBAL_INDEX + : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( + QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast<int>(setting->GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( + QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast<int>(setting->GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::InsertGlobalItem(QComboBox* combobox) { + const QString use_global_text = ConfigurePerGame::tr("Use global configuration"); + combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text); + combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX); +} diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h new file mode 100644 index 000000000..b11b1b950 --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.h @@ -0,0 +1,36 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QCheckBox> +#include <QComboBox> +#include <QString> +#include "core/settings.h" + +namespace ConfigurationShared { + +constexpr int USE_GLOBAL_INDEX = 0; +constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; +constexpr int USE_GLOBAL_OFFSET = 2; + +// Global-aware apply and set functions + +void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox); +void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, + const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting, + const QComboBox* combobox); + +void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); +void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting); +void SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<Settings::RendererBackend>* setting); +void SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<Settings::GPUAccuracy>* setting); + +void InsertGlobalItem(QComboBox* combobox); + +} // namespace ConfigurationShared diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index f370c690f..cc021beec 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -11,6 +11,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_audio.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_audio.h" ConfigureAudio::ConfigureAudio(QWidget* parent) @@ -24,6 +25,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); + ui->volume_label->setVisible(Settings::configuring_global); + ui->volume_combo_box->setVisible(!Settings::configuring_global); + + SetupPerGameUI(); + SetConfiguration(); const bool is_powered_on = Core::System::GetInstance().IsPoweredOn(); @@ -41,8 +47,22 @@ void ConfigureAudio::SetConfiguration() { SetAudioDeviceFromDeviceID(); - ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); - ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); + ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum()); + + if (Settings::configuring_global) { + ui->toggle_audio_stretching->setChecked( + Settings::values.enable_audio_stretching.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->toggle_audio_stretching, + &Settings::values.enable_audio_stretching); + if (Settings::values.volume.UsingGlobal()) { + ui->volume_combo_box->setCurrentIndex(0); + ui->volume_slider->setEnabled(false); + } else { + ui->volume_combo_box->setCurrentIndex(1); + ui->volume_slider->setEnabled(true); + } + } SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } @@ -80,15 +100,36 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - Settings::values.sink_id = - ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) - .toStdString(); - Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); - Settings::values.audio_device_id = - ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) - .toStdString(); - Settings::values.volume = - static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); + if (Settings::configuring_global) { + Settings::values.sink_id = + ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) + .toStdString(); + Settings::values.audio_device_id = + ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) + .toStdString(); + + // Guard if during game and set to game-specific value + if (Settings::values.enable_audio_stretching.UsingGlobal()) { + Settings::values.enable_audio_stretching.SetValue( + ui->toggle_audio_stretching->isChecked()); + } + if (Settings::values.volume.UsingGlobal()) { + Settings::values.volume.SetValue( + static_cast<float>(ui->volume_slider->sliderPosition()) / + ui->volume_slider->maximum()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, + ui->toggle_audio_stretching); + if (ui->volume_combo_box->currentIndex() == 0) { + Settings::values.volume.SetGlobal(true); + } else { + Settings::values.volume.SetGlobal(false); + Settings::values.volume.SetValue( + static_cast<float>(ui->volume_slider->sliderPosition()) / + ui->volume_slider->maximum()); + } + } } void ConfigureAudio::changeEvent(QEvent* event) { @@ -122,3 +163,22 @@ void ConfigureAudio::RetranslateUI() { ui->retranslateUi(this); SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } + +void ConfigureAudio::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); + ui->toggle_audio_stretching->setEnabled( + Settings::values.enable_audio_stretching.UsingGlobal()); + + return; + } + + ui->toggle_audio_stretching->setTristate(true); + connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), + this, [this](int index) { ui->volume_slider->setEnabled(index == 1); }); + + ui->output_sink_combo_box->setVisible(false); + ui->output_sink_label->setVisible(false); + ui->audio_device_combo_box->setVisible(false); + ui->audio_device_label->setVisible(false); +} diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index ea83bd72d..d84f4a682 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -34,5 +34,7 @@ private: void SetAudioDeviceFromDeviceID(); void SetVolumeIndicatorText(int percentage); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureAudio> ui; }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a098b9acc..862ccb988 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>188</width> - <height>246</height> + <width>367</width> + <height>368</height> </rect> </property> <layout class="QVBoxLayout"> @@ -18,9 +18,9 @@ </property> <layout class="QVBoxLayout"> <item> - <layout class="QHBoxLayout"> + <layout class="QHBoxLayout" name="_3"> <item> - <widget class="QLabel" name="label_1"> + <widget class="QLabel" name="output_sink_label"> <property name="text"> <string>Output Engine:</string> </property> @@ -31,20 +31,20 @@ </item> </layout> </item> - <item> - <widget class="QCheckBox" name="toggle_audio_stretching"> - <property name="toolTip"> - <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> - </property> - <property name="text"> - <string>Enable audio stretching</string> - </property> - </widget> - </item> <item> - <layout class="QHBoxLayout"> + <widget class="QCheckBox" name="toggle_audio_stretching"> + <property name="toolTip"> + <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> + </property> + <property name="text"> + <string>Enable audio stretching</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="_2"> <item> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="audio_device_label"> <property name="text"> <string>Audio Device:</string> </property> @@ -61,7 +61,21 @@ <number>0</number> </property> <item> - <widget class="QLabel" name="label_3"> + <widget class="QComboBox" name="volume_combo_box"> + <item> + <property name="text"> + <string>Use global volume</string> + </property> + </item> + <item> + <property name="text"> + <string>Set volume:</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="volume_label"> <property name="text"> <string>Volume:</string> </property> @@ -74,7 +88,7 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>30</width> <height>20</height> </size> </property> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index c2026763e..2c77441fd 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -39,6 +39,8 @@ void ConfigureDebug::SetConfiguration() { ui->disable_cpu_opt->setChecked(Settings::values.disable_cpu_opt); ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); + ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); } void ConfigureDebug::ApplyConfiguration() { @@ -51,6 +53,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.disable_cpu_opt = ui->disable_cpu_opt->isChecked(); Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); + Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index e0d4c4a44..46f0208c6 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -148,6 +148,19 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="disable_macro_jit"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="whatsThis"> + <string>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</string> + </property> + <property name="text"> + <string>Disable Macro JIT</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index df4473b46..5918e9972 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -14,6 +14,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { + Settings::configuring_global = true; + ui->setupUi(this); ui->hotkeysTab->Populate(registry); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index cb95423e0..1fb62d1cf 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -7,40 +7,79 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_general.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_general.h" #include "yuzu/uisettings.h" ConfigureGeneral::ConfigureGeneral(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGeneral) { - ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); - connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); + connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, [this]() { + ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked); + }); } ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::SetConfiguration() { + const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); + + ui->use_multi_core->setEnabled(runtime_lock); + ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); + ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse); - ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); - ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); - ui->frame_limit->setValue(Settings::values.frame_limit); + ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); + ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); + + if (!Settings::configuring_global) { + if (Settings::values.use_multi_core.UsingGlobal()) { + ui->use_multi_core->setCheckState(Qt::PartiallyChecked); + } + if (Settings::values.use_frame_limit.UsingGlobal()) { + ui->toggle_frame_limit->setCheckState(Qt::PartiallyChecked); + } + } + + ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked && + ui->toggle_frame_limit->isEnabled()); } void ConfigureGeneral::ApplyConfiguration() { - UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); - UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); - UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); + if (Settings::configuring_global) { + UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); + UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); + UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); + UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); + + // Guard if during game and set to game-specific value + if (Settings::values.use_frame_limit.UsingGlobal()) { + Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == + Qt::Checked); + Settings::values.frame_limit.SetValue(ui->frame_limit->value()); + Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, + ui->use_multi_core); - Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); - Settings::values.frame_limit = ui->frame_limit->value(); + bool global_frame_limit = ui->toggle_frame_limit->checkState() == Qt::PartiallyChecked; + Settings::values.use_frame_limit.SetGlobal(global_frame_limit); + Settings::values.frame_limit.SetGlobal(global_frame_limit); + if (!global_frame_limit) { + Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == + Qt::Checked); + Settings::values.frame_limit.SetValue(ui->frame_limit->value()); + } + } } void ConfigureGeneral::changeEvent(QEvent* event) { @@ -54,3 +93,20 @@ void ConfigureGeneral::changeEvent(QEvent* event) { void ConfigureGeneral::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureGeneral::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); + ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); + + return; + } + + ui->toggle_check_exit->setVisible(false); + ui->toggle_user_on_boot->setVisible(false); + ui->toggle_background_pause->setVisible(false); + ui->toggle_hide_mouse->setVisible(false); + + ui->toggle_frame_limit->setTristate(true); + ui->use_multi_core->setTristate(true); +} diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index ef05ce065..9c785c22e 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -28,5 +28,7 @@ private: void SetConfiguration(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureGeneral> ui; }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index fc3b7e65a..2711116a2 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -52,6 +52,13 @@ </layout> </item> <item> + <widget class="QCheckBox" name="use_multi_core"> + <property name="text"> + <string>Multicore CPU Emulation</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="toggle_check_exit"> <property name="text"> <string>Confirm exit while emulation is running</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index ea667caef..cb4706bd6 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -13,65 +13,27 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #ifdef HAS_VULKAN #include "video_core/renderer_vulkan/renderer_vulkan.h" #endif -namespace { -enum class Resolution : int { - Auto, - Scale1x, - Scale2x, - Scale3x, - Scale4x, -}; - -float ToResolutionFactor(Resolution option) { - switch (option) { - case Resolution::Auto: - return 0.f; - case Resolution::Scale1x: - return 1.f; - case Resolution::Scale2x: - return 2.f; - case Resolution::Scale3x: - return 3.f; - case Resolution::Scale4x: - return 4.f; - } - return 0.f; -} - -Resolution FromResolutionFactor(float factor) { - if (factor == 0.f) { - return Resolution::Auto; - } else if (factor == 1.f) { - return Resolution::Scale1x; - } else if (factor == 2.f) { - return Resolution::Scale2x; - } else if (factor == 3.f) { - return Resolution::Scale3x; - } else if (factor == 4.f) { - return Resolution::Scale4x; - } - return Resolution::Auto; -} -} // Anonymous namespace - ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { - vulkan_device = Settings::values.vulkan_device; + vulkan_device = Settings::values.vulkan_device.GetValue(); RetrieveVulkanDevices(); ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); - connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateDeviceComboBox(); }); - connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, + connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) { UpdateDeviceSelection(device); }); connect(ui->bg_button, &QPushButton::clicked, this, [this] { @@ -81,6 +43,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) } UpdateBackgroundColorButton(new_bg_color); }); + + ui->bg_label->setVisible(Settings::configuring_global); + ui->bg_combobox->setVisible(!Settings::configuring_global); } void ConfigureGraphics::UpdateDeviceSelection(int device) { @@ -98,31 +63,95 @@ void ConfigureGraphics::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); ui->api->setEnabled(runtime_lock); - ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend)); - ui->resolution_factor_combobox->setCurrentIndex( - static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); - ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio); - ui->use_disk_shader_cache->setEnabled(runtime_lock); - ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); - ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); - UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, - Settings::values.bg_blue)); + ui->use_disk_shader_cache->setEnabled(runtime_lock); + + if (Settings::configuring_global) { + ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); + ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); + ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); + ui->use_asynchronous_gpu_emulation->setChecked( + Settings::values.use_asynchronous_gpu_emulation.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->use_disk_shader_cache, + &Settings::values.use_disk_shader_cache); + ConfigurationShared::SetPerGameSetting(ui->use_asynchronous_gpu_emulation, + &Settings::values.use_asynchronous_gpu_emulation); + + ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); + ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, + &Settings::values.aspect_ratio); + + ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); + ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); + } + + UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(), + Settings::values.bg_green.GetValue(), + Settings::values.bg_blue.GetValue())); UpdateDeviceComboBox(); } void ConfigureGraphics::ApplyConfiguration() { - Settings::values.renderer_backend = GetCurrentGraphicsBackend(); - Settings::values.vulkan_device = vulkan_device; - Settings::values.resolution_factor = - ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); - Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex(); - Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); - Settings::values.use_asynchronous_gpu_emulation = - ui->use_asynchronous_gpu_emulation->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()); + if (Settings::configuring_global) { + // Guard if during game and set to game-specific value + if (Settings::values.renderer_backend.UsingGlobal()) { + Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); + } + if (Settings::values.vulkan_device.UsingGlobal()) { + Settings::values.vulkan_device.SetValue(vulkan_device); + } + if (Settings::values.aspect_ratio.UsingGlobal()) { + Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); + } + if (Settings::values.use_disk_shader_cache.UsingGlobal()) { + Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked()); + } + if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) { + Settings::values.use_asynchronous_gpu_emulation.SetValue( + ui->use_asynchronous_gpu_emulation->isChecked()); + } + if (Settings::values.bg_red.UsingGlobal()) { + Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); + Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); + Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); + } + } else { + if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.renderer_backend.SetGlobal(true); + Settings::values.vulkan_device.SetGlobal(true); + } else { + Settings::values.renderer_backend.SetGlobal(false); + Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); + if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) { + Settings::values.vulkan_device.SetGlobal(false); + Settings::values.vulkan_device.SetValue(vulkan_device); + } else { + Settings::values.vulkan_device.SetGlobal(true); + } + } + + ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, + ui->aspect_ratio_combobox); + + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, + ui->use_disk_shader_cache); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, + ui->use_asynchronous_gpu_emulation); + + if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.bg_red.SetGlobal(true); + Settings::values.bg_green.SetGlobal(true); + Settings::values.bg_blue.SetGlobal(true); + } else { + Settings::values.bg_red.SetGlobal(false); + Settings::values.bg_green.SetGlobal(false); + Settings::values.bg_blue.SetGlobal(false); + Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); + Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); + Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); + } + } } void ConfigureGraphics::changeEvent(QEvent* event) { @@ -151,19 +180,27 @@ void ConfigureGraphics::UpdateDeviceComboBox() { ui->device->clear(); bool enabled = false; + + if (!Settings::configuring_global && + ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + vulkan_device = Settings::values.vulkan_device.GetValue(); + } switch (GetCurrentGraphicsBackend()) { case Settings::RendererBackend::OpenGL: ui->device->addItem(tr("OpenGL Graphics Device")); enabled = false; break; case Settings::RendererBackend::Vulkan: - for (const auto device : vulkan_devices) { + for (const auto& device : vulkan_devices) { ui->device->addItem(device); } ui->device->setCurrentIndex(vulkan_device); enabled = !vulkan_devices.empty(); break; } + // If in per-game config and use global is selected, don't enable. + enabled &= !(!Settings::configuring_global && + ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX); ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn()); } @@ -177,5 +214,37 @@ void ConfigureGraphics::RetrieveVulkanDevices() { } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { - return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); + if (Settings::configuring_global) { + return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); + } + + if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.renderer_backend.SetGlobal(true); + return Settings::values.renderer_backend.GetValue(); + } + Settings::values.renderer_backend.SetGlobal(false); + return static_cast<Settings::RendererBackend>(ui->api->currentIndex() - + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigureGraphics::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); + ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); + ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); + ui->use_asynchronous_gpu_emulation->setEnabled( + Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); + ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); + ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); + + return; + } + + connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, + [this](int index) { ui->bg_button->setEnabled(index == 1); }); + + ui->use_disk_shader_cache->setTristate(true); + ui->use_asynchronous_gpu_emulation->setTristate(true); + ConfigurationShared::InsertGlobalItem(ui->aspect_ratio_combobox); + ConfigurationShared::InsertGlobalItem(ui->api); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 7e0596d9c..24f01c739 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -35,6 +35,8 @@ private: void RetrieveVulkanDevices(); + void SetupPerGameUI(); + Settings::RendererBackend GetCurrentGraphicsBackend() const; std::unique_ptr<Ui::ConfigureGraphics> ui; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index c816d6108..62418fc14 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -85,39 +85,34 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="ar_label"> <property name="text"> - <string>Internal Resolution:</string> + <string>Aspect Ratio:</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="resolution_factor_combobox"> - <item> - <property name="text"> - <string>Auto (Window Size)</string> - </property> - </item> + <widget class="QComboBox" name="aspect_ratio_combobox"> <item> <property name="text"> - <string>Native (1280x720)</string> + <string>Default (16:9)</string> </property> </item> <item> <property name="text"> - <string>2x Native (2560x1440)</string> + <string>Force 4:3</string> </property> </item> <item> <property name="text"> - <string>3x Native (3840x2160)</string> + <string>Force 21:9</string> </property> </item> <item> <property name="text"> - <string>4x Native (5120x2880)</string> + <string>Stretch to Window</string> </property> </item> </widget> @@ -125,42 +120,30 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_6"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> - <widget class="QLabel" name="ar_label"> - <property name="text"> - <string>Aspect Ratio:</string> + <widget class="QComboBox" name="bg_combobox"> + <property name="currentText"> + <string>Use global background color</string> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="maxVisibleItems"> + <number>10</number> </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="aspect_ratio_combobox"> - <item> - <property name="text"> - <string>Default (16:9)</string> - </property> - </item> - <item> - <property name="text"> - <string>Force 4:3</string> - </property> - </item> <item> <property name="text"> - <string>Force 21:9</string> + <string>Use global background color</string> </property> </item> <item> <property name="text"> - <string>Stretch to Window</string> + <string>Set background color:</string> </property> </item> </widget> </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <widget class="QLabel" name="bg_label"> <property name="text"> @@ -169,6 +152,19 @@ </widget> </item> <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> <widget class="QPushButton" name="bg_button"> <property name="maximumSize"> <size> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 37aadf7f8..7c0fa7ec5 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -5,6 +5,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics_advanced.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_advanced.h" ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) @@ -12,8 +13,7 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) ui->setupUi(this); - // TODO: Remove this after assembly shaders are fully integrated - ui->use_assembly_shaders->setVisible(false); + SetupPerGameUI(); SetConfiguration(); } @@ -22,26 +22,81 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default; void ConfigureGraphicsAdvanced::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); - ui->gpu_accuracy->setCurrentIndex(static_cast<int>(Settings::values.gpu_accuracy)); ui->use_vsync->setEnabled(runtime_lock); - ui->use_vsync->setChecked(Settings::values.use_vsync); ui->use_assembly_shaders->setEnabled(runtime_lock); - ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders); - ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time); ui->force_30fps_mode->setEnabled(runtime_lock); - ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); - ui->anisotropic_filtering_combobox->setCurrentIndex(Settings::values.max_anisotropy); + + if (Settings::configuring_global) { + ui->gpu_accuracy->setCurrentIndex( + static_cast<int>(Settings::values.gpu_accuracy.GetValue())); + ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); + ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode.GetValue()); + ui->anisotropic_filtering_combobox->setCurrentIndex( + Settings::values.max_anisotropy.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); + ConfigurationShared::SetPerGameSetting(ui->use_vsync, &Settings::values.use_vsync); + ConfigurationShared::SetPerGameSetting(ui->use_assembly_shaders, + &Settings::values.use_assembly_shaders); + ConfigurationShared::SetPerGameSetting(ui->use_fast_gpu_time, + &Settings::values.use_fast_gpu_time); + ConfigurationShared::SetPerGameSetting(ui->force_30fps_mode, + &Settings::values.force_30fps_mode); + ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, + &Settings::values.max_anisotropy); + } } void ConfigureGraphicsAdvanced::ApplyConfiguration() { - auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(ui->gpu_accuracy->currentIndex()); - Settings::values.gpu_accuracy = gpu_accuracy; - Settings::values.use_vsync = ui->use_vsync->isChecked(); - Settings::values.use_assembly_shaders = ui->use_assembly_shaders->isChecked(); - Settings::values.use_fast_gpu_time = ui->use_fast_gpu_time->isChecked(); - Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); - Settings::values.max_anisotropy = ui->anisotropic_filtering_combobox->currentIndex(); + // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) + const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( + ui->gpu_accuracy->currentIndex() - + ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + + if (Settings::configuring_global) { + // Must guard in case of a during-game configuration when set to be game-specific. + if (Settings::values.gpu_accuracy.UsingGlobal()) { + Settings::values.gpu_accuracy.SetValue(gpu_accuracy); + } + if (Settings::values.use_vsync.UsingGlobal()) { + Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked()); + } + if (Settings::values.use_assembly_shaders.UsingGlobal()) { + Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); + } + if (Settings::values.use_fast_gpu_time.UsingGlobal()) { + Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); + } + if (Settings::values.force_30fps_mode.UsingGlobal()) { + Settings::values.force_30fps_mode.SetValue(ui->force_30fps_mode->isChecked()); + } + if (Settings::values.max_anisotropy.UsingGlobal()) { + Settings::values.max_anisotropy.SetValue( + ui->anisotropic_filtering_combobox->currentIndex()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, + ui->anisotropic_filtering_combobox); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, + ui->use_assembly_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, + ui->use_fast_gpu_time); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.force_30fps_mode, + ui->force_30fps_mode); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, + ui->anisotropic_filtering_combobox); + + if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.gpu_accuracy.SetGlobal(true); + } else { + Settings::values.gpu_accuracy.SetGlobal(false); + Settings::values.gpu_accuracy.SetValue(gpu_accuracy); + } + } } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -55,3 +110,25 @@ void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { void ConfigureGraphicsAdvanced::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureGraphicsAdvanced::SetupPerGameUI() { + // Disable if not global (only happens during game) + if (Settings::configuring_global) { + ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); + ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); + ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); + ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->force_30fps_mode->setEnabled(Settings::values.force_30fps_mode.UsingGlobal()); + ui->anisotropic_filtering_combobox->setEnabled( + Settings::values.max_anisotropy.UsingGlobal()); + + return; + } + + ConfigurationShared::InsertGlobalItem(ui->gpu_accuracy); + ui->use_vsync->setTristate(true); + ui->use_assembly_shaders->setTristate(true); + ui->use_fast_gpu_time->setTristate(true); + ui->force_30fps_mode->setTristate(true); + ConfigurationShared::InsertGlobalItem(ui->anisotropic_filtering_combobox); +} diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bbc9d4355..c043588ff 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -26,5 +26,7 @@ private: void SetConfiguration(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; }; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index e4eb5594b..00433926d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -70,6 +70,20 @@ static QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "gcpad") { + if (param.Has("axis")) { + const QString axis_str = QString::fromStdString(param.Get("axis", "")); + const QString direction_str = QString::fromStdString(param.Get("direction", "")); + + return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); + } + if (param.Has("button")) { + const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); + return QObject::tr("GC Button %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -126,6 +140,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return {}; } + if (param.Get("engine", "") == "gcpad") { + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left" || dir == "right") { + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + + return QObject::tr("GC Axis %1").arg(axis_x_str); + } + + if (dir == "up" || dir == "down") { + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + + return QObject::tr("GC Axis %1").arg(axis_y_str); + } + + return {}; + } return QObject::tr("[unknown]"); } @@ -332,7 +365,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); - if (analogs_param[analog_id].Get("engine", "") == "sdl") { + if (analogs_param[analog_id].Get("engine", "") == "sdl" || + analogs_param[analog_id].Get("engine", "") == "gcpad") { analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( tr("Deadzone: %1%").arg(slider_value)); analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); @@ -352,6 +386,20 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(poll_timer.get(), &QTimer::timeout, [this] { Common::ParamPackage params; + if (InputCommon::GetGCButtons()->IsPolling()) { + params = InputCommon::GetGCButtons()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } + if (InputCommon::GetGCAnalogs()->IsPolling()) { + params = InputCommon::GetGCAnalogs()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); if (params.Has("engine")) { @@ -480,7 +528,9 @@ void ConfigureInputPlayer::RestoreDefaults() { SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); } } + UpdateButtonLabels(); + ApplyConfiguration(); } void ConfigureInputPlayer::ClearAll() { @@ -505,6 +555,7 @@ void ConfigureInputPlayer::ClearAll() { } UpdateButtonLabels(); + ApplyConfiguration(); } void ConfigureInputPlayer::UpdateButtonLabels() { @@ -531,7 +582,7 @@ void ConfigureInputPlayer::UpdateButtonLabels() { analog_map_deadzone_and_modifier_slider_label[analog_id]; if (param.Has("engine")) { - if (param.Get("engine", "") == "sdl") { + if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") { if (!param.Has("deadzone")) { param.Set("deadzone", 0.1f); } @@ -580,6 +631,11 @@ void ConfigureInputPlayer::HandleClick( grabKeyboard(); grabMouse(); + if (type == InputCommon::Polling::DeviceType::Button) { + InputCommon::GetGCButtons()->BeginConfiguration(); + } else { + InputCommon::GetGCAnalogs()->BeginConfiguration(); + } timeout_timer->start(5000); // Cancel after 5 seconds poll_timer->start(200); // Check for new inputs every 200ms } @@ -593,6 +649,9 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, poller->Stop(); } + InputCommon::GetGCButtons()->EndConfiguration(); + InputCommon::GetGCAnalogs()->EndConfiguration(); + if (!abort) { (*input_setter)(params); } diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp new file mode 100644 index 000000000..1e49f0787 --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -0,0 +1,140 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QCheckBox> +#include <QHeaderView> +#include <QMenu> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_game.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_game.h" +#include "yuzu/uisettings.h" +#include "yuzu/util/util.h" + +ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) + : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { + game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false); + + Settings::configuring_global = false; + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("Properties")); + + ui->addonsTab->SetTitleId(title_id); + + scene = new QGraphicsScene; + ui->icon_view->setScene(scene); + + LoadConfiguration(); +} + +ConfigurePerGame::~ConfigurePerGame() = default; + +void ConfigurePerGame::ApplyConfiguration() { + ui->addonsTab->ApplyConfiguration(); + ui->generalTab->ApplyConfiguration(); + ui->systemTab->ApplyConfiguration(); + ui->graphicsTab->ApplyConfiguration(); + ui->graphicsAdvancedTab->ApplyConfiguration(); + ui->audioTab->ApplyConfiguration(); + + Settings::Apply(); + Settings::LogSettings(); + + game_config->Save(); +} + +void ConfigurePerGame::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigurePerGame::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + LoadConfiguration(); +} + +void ConfigurePerGame::LoadConfiguration() { + if (file == nullptr) { + return; + } + + ui->addonsTab->LoadFromFile(file); + + ui->display_title_id->setText( + QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); + + FileSys::PatchManager pm{title_id}; + const auto control = pm.GetControlMetadata(); + const auto loader = Loader::GetLoader(file); + + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); + + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + if (control.second != nullptr) { + scene->clear(); + + QPixmap map; + const auto bytes = control.second->ReadAllBytes(); + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector<u8> bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + scene->clear(); + + QPixmap map; + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + ui->display_filename->setText(QString::fromStdString(file->GetName())); + + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h new file mode 100644 index 000000000..5f9a08cef --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.h @@ -0,0 +1,51 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QDialog> +#include <QList> + +#include "core/file_sys/vfs_types.h" +#include "yuzu/configuration/config.h" + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Ui { +class ConfigurePerGame; +} + +class ConfigurePerGame : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGame(QWidget* parent, u64 title_id); + ~ConfigurePerGame() override; + + /// Save all button configurations to settings file + void ApplyConfiguration(); + + void LoadFromFile(FileSys::VirtualFile file); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void LoadConfiguration(); + + std::unique_ptr<Ui::ConfigurePerGame> ui; + FileSys::VirtualFile file; + u64 title_id; + + QGraphicsScene* scene; + + std::unique_ptr<Config> game_config; +}; diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui new file mode 100644 index 000000000..d2057c4ab --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.ui @@ -0,0 +1,350 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGame</class> + <widget class="QDialog" name="ConfigurePerGame"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Info</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item alignment="Qt::AlignHCenter"> + <widget class="QGraphicsView" name="icon_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>256</width> + <height>256</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>256</width> + <height>256</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="6" column="1"> + <widget class="QLineEdit" name="display_size"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="display_version"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Title ID</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="display_title_id"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLineEdit" name="display_filename"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLineEdit" name="display_format"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Filename</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_name"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="display_developer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Developer</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"/> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="usesScrollButtons"> + <bool>true</bool> + </property> + <property name="documentMode"> + <bool>false</bool> + </property> + <property name="tabsClosable"> + <bool>false</bool> + </property> + <widget class="ConfigurePerGameAddons" name="addonsTab"> + <attribute name="title"> + <string>Add-Ons</string> + </attribute> + </widget> + <widget class="ConfigureGeneral" name="generalTab"> + <attribute name="title"> + <string>General</string> + </attribute> + </widget> + <widget class="ConfigureSystem" name="systemTab"> + <attribute name="title"> + <string>System</string> + </attribute> + </widget> + <widget class="ConfigureGraphics" name="graphicsTab"> + <attribute name="title"> + <string>Graphics</string> + </attribute> + </widget> + <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab"> + <attribute name="title"> + <string>Adv. Graphics</string> + </attribute> + </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigureGeneral</class> + <extends>QWidget</extends> + <header>configuration/configure_general.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureSystem</class> + <extends>QWidget</extends> + <header>configuration/configure_system.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureAudio</class> + <extends>QWidget</extends> + <header>configuration/configure_audio.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureGraphics</class> + <extends>QWidget</extends> + <header>configuration/configure_graphics.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureGraphicsAdvanced</class> + <extends>QWidget</extends> + <header>configuration/configure_graphics_advanced.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigurePerGameAddons</class> + <extends>QWidget</extends> + <header>configuration/configure_per_game_addons.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigurePerGame</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigurePerGame</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index d7f259f12..478d5d3a1 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -15,23 +15,20 @@ #include "common/common_paths.h" #include "common/file_util.h" -#include "core/file_sys/control_metadata.h" +#include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" -#include "ui_configure_per_general.h" +#include "ui_configure_per_game_addons.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/uisettings.h" #include "yuzu/util/util.h" -ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) - : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { - +ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigurePerGameAddons) { ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - setWindowTitle(tr("Properties")); layout = new QVBoxLayout; tree_view = new QTreeView; @@ -52,7 +49,7 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrells of custom types. + // with signals/slots. In this case, QList falls under the umbrella of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->setContentsMargins(0, 0, 0, 0); @@ -61,18 +58,15 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) ui->scrollArea->setLayout(layout); - scene = new QGraphicsScene; - ui->icon_view->setScene(scene); + ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn()); connect(item_model, &QStandardItemModel::itemChanged, [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); - - LoadConfiguration(); } -ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; +ConfigurePerGameAddons::~ConfigurePerGameAddons() = default; -void ConfigurePerGameGeneral::ApplyConfiguration() { +void ConfigurePerGameAddons::ApplyConfiguration() { std::vector<std::string> disabled_addons; for (const auto& item : list_items) { @@ -92,72 +86,35 @@ void ConfigurePerGameGeneral::ApplyConfiguration() { Settings::values.disabled_addons[title_id] = disabled_addons; } -void ConfigurePerGameGeneral::changeEvent(QEvent* event) { +void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + LoadConfiguration(); +} + +void ConfigurePerGameAddons::SetTitleId(u64 id) { + this->title_id = id; +} + +void ConfigurePerGameAddons::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); } - QDialog::changeEvent(event); + QWidget::changeEvent(event); } -void ConfigurePerGameGeneral::RetranslateUI() { +void ConfigurePerGameAddons::RetranslateUI() { ui->retranslateUi(this); } -void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) { - this->file = std::move(file); - LoadConfiguration(); -} - -void ConfigurePerGameGeneral::LoadConfiguration() { +void ConfigurePerGameAddons::LoadConfiguration() { if (file == nullptr) { return; } - ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id))); - FileSys::PatchManager pm{title_id}; - const auto control = pm.GetControlMetadata(); const auto loader = Loader::GetLoader(file); - if (control.first != nullptr) { - ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); - ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); - ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); - } else { - std::string title; - if (loader->ReadTitle(title) == Loader::ResultStatus::Success) - ui->display_name->setText(QString::fromStdString(title)); - - FileSys::NACP nacp; - if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) - ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); - - ui->display_version->setText(QStringLiteral("1.0.0")); - } - - if (control.second != nullptr) { - scene->clear(); - - QPixmap map; - const auto bytes = control.second->ReadAllBytes(); - map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } else { - std::vector<u8> bytes; - if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { - scene->clear(); - - QPixmap map; - map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } - } - FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); @@ -182,12 +139,4 @@ void ConfigurePerGameGeneral::LoadConfiguration() { } tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); - - ui->display_filename->setText(QString::fromStdString(file->GetName())); - - ui->display_format->setText( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); - - const auto valueText = ReadableByteSize(file->GetSize()); - ui->display_size->setText(valueText); } diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_game_addons.h index a3b2cdeff..a00ec3539 100644 --- a/src/yuzu/configuration/configure_per_general.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,7 +7,6 @@ #include <memory> #include <vector> -#include <QDialog> #include <QList> #include "core/file_sys/vfs_types.h" @@ -19,35 +18,36 @@ class QTreeView; class QVBoxLayout; namespace Ui { -class ConfigurePerGameGeneral; +class ConfigurePerGameAddons; } -class ConfigurePerGameGeneral : public QDialog { +class ConfigurePerGameAddons : public QWidget { Q_OBJECT public: - explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); - ~ConfigurePerGameGeneral() override; + explicit ConfigurePerGameAddons(QWidget* parent = nullptr); + ~ConfigurePerGameAddons() override; /// Save all button configurations to settings file void ApplyConfiguration(); void LoadFromFile(FileSys::VirtualFile file); + void SetTitleId(u64 id); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); void LoadConfiguration(); - std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; + std::unique_ptr<Ui::ConfigurePerGameAddons> ui; FileSys::VirtualFile file; u64 title_id; QVBoxLayout* layout; QTreeView* tree_view; QStandardItemModel* item_model; - QGraphicsScene* scene; std::vector<QList<QStandardItem*>> list_items; }; diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui new file mode 100644 index 000000000..aefdebfcd --- /dev/null +++ b/src/yuzu/configuration/configure_per_game_addons.ui @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameAddons</class> + <widget class="QWidget" name="ConfigurePerGameAddons"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>380</width> + <height>280</height> + </rect> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui deleted file mode 100644 index 8fdd96fa4..000000000 --- a/src/yuzu/configuration/configure_per_general.ui +++ /dev/null @@ -1,276 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConfigurePerGameGeneral</class> - <widget class="QDialog" name="ConfigurePerGameGeneral"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>520</height> - </rect> - </property> - <property name="windowTitle"> - <string>ConfigurePerGameGeneral</string> - </property> - <layout class="QHBoxLayout" name="HorizontalLayout"> - <item> - <layout class="QVBoxLayout" name="VerticalLayout"> - <item> - <widget class="QGroupBox" name="GeneralGroupBox"> - <property name="title"> - <string>Info</string> - </property> - <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> - <item> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="6" column="1" colspan="2"> - <widget class="QLineEdit" name="display_filename"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="display_name"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Developer</string> - </property> - </widget> - </item> - <item row="5" column="1" colspan="2"> - <widget class="QLineEdit" name="display_size"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Name</string> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>Filename</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Version</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Format</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="display_version"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QLineEdit" name="display_format"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Size</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="display_developer"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Title ID</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLineEdit" name="display_title_id"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="2" rowspan="5"> - <widget class="QGraphicsView" name="icon_view"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>128</width> - <height>128</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>128</width> - <height>128</height> - </size> - </property> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="sizeAdjustPolicy"> - <enum>QAbstractScrollArea::AdjustToContents</enum> - </property> - <property name="interactive"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="PerformanceGroupBox"> - <property name="title"> - <string>Add-Ons</string> - </property> - <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> - <item> - <widget class="QScrollArea" name="scrollArea"> - <property name="widgetResizable"> - <bool>true</bool> - </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>350</width> - <height>169</height> - </rect> - </property> - </widget> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ConfigurePerGameGeneral</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>269</x> - <y>567</y> - </hint> - <hint type="destinationlabel"> - <x>269</x> - <y>294</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>ConfigurePerGameGeneral</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>269</x> - <y>567</y> - </hint> - <hint type="destinationlabel"> - <x>269</x> - <y>294</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp index 06566e981..0de7a4f0b 100644 --- a/src/yuzu/configuration/configure_service.cpp +++ b/src/yuzu/configuration/configure_service.cpp @@ -68,6 +68,7 @@ void ConfigureService::SetConfiguration() { } std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { +#ifdef YUZU_ENABLE_BOXCAT std::optional<std::string> global; std::map<std::string, Service::BCAT::EventStatus> map; const auto res = Service::BCAT::Boxcat::GetStatus(global, map); @@ -105,7 +106,10 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { .arg(QString::fromStdString(key)) .arg(FormatEventStatusString(value)); } - return {QStringLiteral("Current Boxcat Events"), std::move(out)}; + return {tr("Current Boxcat Events"), std::move(out)}; +#else + return {tr("Current Boxcat Events"), tr("There are currently no events on boxcat.")}; +#endif } void ConfigureService::OnBCATImplChanged() { diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 10315e7a6..68e02738b 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -14,6 +14,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_system.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_system.h" ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { @@ -21,20 +22,25 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::RefreshConsoleID); - connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { - ui->rng_seed_edit->setEnabled(checked); - if (!checked) { + connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) { + ui->rng_seed_edit->setEnabled(state == Qt::Checked); + if (state != Qt::Checked) { ui->rng_seed_edit->setText(QStringLiteral("00000000")); } }); - connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { - ui->custom_rtc_edit->setEnabled(checked); - if (!checked) { + connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) { + ui->custom_rtc_edit->setEnabled(state == Qt::Checked); + if (state != Qt::Checked) { ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); } }); + ui->label_console_id->setVisible(Settings::configuring_global); + ui->button_regenerate_console_id->setVisible(Settings::configuring_global); + + SetupPerGameUI(); + SetConfiguration(); } @@ -54,26 +60,58 @@ void ConfigureSystem::RetranslateUI() { void ConfigureSystem::SetConfiguration() { enabled = !Core::System::GetInstance().IsPoweredOn(); + const auto rng_seed = + QStringLiteral("%1") + .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) + .toUpper(); + const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( + std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); - ui->combo_language->setCurrentIndex(Settings::values.language_index); - ui->combo_region->setCurrentIndex(Settings::values.region_index); - ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index); - ui->combo_sound->setCurrentIndex(Settings::values.sound_index); - - ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); - ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); - - const auto rng_seed = QStringLiteral("%1") - .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}) - .toUpper(); - ui->rng_seed_edit->setText(rng_seed); - - ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); + if (Settings::configuring_global) { + ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); + ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue()); + ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue()); + ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue()); + + ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->rng_seed_edit->setText(rng_seed); + + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + } else { + ConfigurationShared::SetPerGameSetting(ui->combo_language, + &Settings::values.language_index); + ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index); + ConfigurationShared::SetPerGameSetting(ui->combo_time_zone, + &Settings::values.time_zone_index); + ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); + + if (Settings::values.rng_seed.UsingGlobal()) { + ui->rng_seed_checkbox->setCheckState(Qt::PartiallyChecked); + } else { + ui->rng_seed_checkbox->setCheckState( + Settings::values.rng_seed.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value()); + if (Settings::values.rng_seed.GetValue().has_value()) { + ui->rng_seed_edit->setText(rng_seed); + } + } - const auto rtc_time = Settings::values.custom_rtc.value_or( - std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); - ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + if (Settings::values.custom_rtc.UsingGlobal()) { + ui->custom_rtc_checkbox->setCheckState(Qt::PartiallyChecked); + } else { + ui->custom_rtc_checkbox->setCheckState( + Settings::values.custom_rtc.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value()); + if (Settings::values.custom_rtc.GetValue().has_value()) { + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + } + } + } } void ConfigureSystem::ReadSystemSettings() {} @@ -83,22 +121,78 @@ void ConfigureSystem::ApplyConfiguration() { return; } - Settings::values.language_index = ui->combo_language->currentIndex(); - Settings::values.region_index = ui->combo_region->currentIndex(); - Settings::values.time_zone_index = ui->combo_time_zone->currentIndex(); - Settings::values.sound_index = ui->combo_sound->currentIndex(); + if (Settings::configuring_global) { + // Guard if during game and set to game-specific value + if (Settings::values.language_index.UsingGlobal()) { + Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); + } + if (Settings::values.region_index.UsingGlobal()) { + Settings::values.region_index.SetValue(ui->combo_region->currentIndex()); + } + if (Settings::values.time_zone_index.UsingGlobal()) { + Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex()); + } + if (Settings::values.sound_index.UsingGlobal()) { + Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex()); + } + + if (Settings::values.rng_seed.UsingGlobal()) { + if (ui->rng_seed_checkbox->isChecked()) { + Settings::values.rng_seed.SetValue( + ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + } else { + Settings::values.rng_seed.SetValue(std::nullopt); + } + } - if (ui->rng_seed_checkbox->isChecked()) { - Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); + if (Settings::values.custom_rtc.UsingGlobal()) { + if (ui->custom_rtc_checkbox->isChecked()) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } + } } else { - Settings::values.rng_seed = std::nullopt; - } + ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, + ui->combo_language); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, + ui->combo_time_zone); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); + + switch (ui->rng_seed_checkbox->checkState()) { + case Qt::Checked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + break; + case Qt::Unchecked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(std::nullopt); + break; + case Qt::PartiallyChecked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(std::nullopt); + Settings::values.rng_seed.SetGlobal(true); + break; + } - if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc = - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); - } else { - Settings::values.custom_rtc = std::nullopt; + switch (ui->custom_rtc_checkbox->checkState()) { + case Qt::Checked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + break; + case Qt::Unchecked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue(std::nullopt); + break; + case Qt::PartiallyChecked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue(std::nullopt); + Settings::values.custom_rtc.SetGlobal(true); + break; + } } Settings::Apply(); @@ -120,3 +214,25 @@ void ConfigureSystem::RefreshConsoleID() { ui->label_console_id->setText( tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } + +void ConfigureSystem::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal()); + ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal()); + ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal()); + ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); + ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal()); + + return; + } + + ConfigurationShared::InsertGlobalItem(ui->combo_language); + ConfigurationShared::InsertGlobalItem(ui->combo_region); + ConfigurationShared::InsertGlobalItem(ui->combo_time_zone); + ConfigurationShared::InsertGlobalItem(ui->combo_sound); + ui->rng_seed_checkbox->setTristate(true); + ui->custom_rtc_checkbox->setTristate(true); +} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 26d42d5c5..f317ef8b5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -32,6 +32,8 @@ private: void RefreshConsoleID(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index c1ea25fb8..9bb0a0109 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -2,10 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <fmt/format.h> + #include "yuzu/debugger/wait_tree.h" #include "yuzu/util/util.h" #include "common/assert.h" +#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/mutex.h" @@ -59,8 +62,10 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() std::size_t row = 0; auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::Thread>>& threads) { for (std::size_t i = 0; i < threads.size(); ++i) { - item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); - item_list.back()->row = row; + if (!threads[i]->IsHLEThread()) { + item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); + item_list.back()->row = row; + } ++row; } }; @@ -114,20 +119,21 @@ QString WaitTreeCallstack::GetText() const { std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const { std::vector<std::unique_ptr<WaitTreeItem>> list; - constexpr std::size_t BaseRegister = 29; - auto& memory = Core::System::GetInstance().Memory(); - u64 base_pointer = thread.GetContext64().cpu_registers[BaseRegister]; + if (thread.IsHLEThread()) { + return list; + } - while (base_pointer != 0) { - const u64 lr = memory.Read64(base_pointer + sizeof(u64)); - if (lr == 0) { - break; - } + if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) { + return list; + } - list.push_back(std::make_unique<WaitTreeText>( - tr("0x%1").arg(lr - sizeof(u32), 16, 16, QLatin1Char{'0'}))); + auto backtrace = Core::ARM_Interface::GetBacktraceFromContext(Core::System::GetInstance(), + thread.GetContext64()); - base_pointer = memory.Read64(base_pointer); + for (auto& entry : backtrace) { + std::string s = fmt::format("{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, + entry.original_address, entry.offset, entry.name); + list.push_back(std::make_unique<WaitTreeText>(QString::fromStdString(s))); } return list; @@ -206,7 +212,15 @@ QString WaitTreeThread::GetText() const { status = tr("running"); break; case Kernel::ThreadStatus::Ready: - status = tr("ready"); + if (!thread.IsPaused()) { + if (thread.WasRunning()) { + status = tr("running"); + } else { + status = tr("ready"); + } + } else { + status = tr("paused"); + } break; case Kernel::ThreadStatus::Paused: status = tr("paused"); @@ -254,7 +268,15 @@ QColor WaitTreeThread::GetColor() const { case Kernel::ThreadStatus::Running: return QColor(Qt::GlobalColor::darkGreen); case Kernel::ThreadStatus::Ready: - return QColor(Qt::GlobalColor::darkBlue); + if (!thread.IsPaused()) { + if (thread.WasRunning()) { + return QColor(Qt::GlobalColor::darkGreen); + } else { + return QColor(Qt::GlobalColor::darkBlue); + } + } else { + return QColor(Qt::GlobalColor::lightGray); + } case Kernel::ThreadStatus::Paused: return QColor(Qt::GlobalColor::lightGray); case Kernel::ThreadStatus::WaitHLEEvent: @@ -319,7 +341,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) { list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetSynchronizationObjects(), - thread.IsSleepingOnWait())); + thread.IsWaitingSync())); } list.push_back(std::make_unique<WaitTreeCallstack>(thread)); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 270cccc77..4d501a8f9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -16,7 +16,7 @@ #include "applets/software_keyboard.h" #include "applets/web_browser.h" #include "configuration/configure_input.h" -#include "configuration/configure_per_general.h" +#include "configuration/configure_per_game.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/general_frontend.h" @@ -56,6 +56,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QShortcut> #include <QStatusBar> #include <QSysInfo> +#include <QUrl> #include <QtConcurrent/QtConcurrent> #include <fmt/format.h> @@ -217,7 +218,20 @@ GMainWindow::GMainWindow() LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", yuzu_build_version, Common::g_scm_branch, Common::g_scm_desc); #ifdef ARCHITECTURE_x86_64 - LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string); + const auto& caps = Common::GetCPUCaps(); + std::string cpu_string = caps.cpu_string; + if (caps.avx || caps.avx2 || caps.avx512) { + cpu_string += " | AVX"; + if (caps.avx512) { + cpu_string += "512"; + } else if (caps.avx2) { + cpu_string += '2'; + } + if (caps.fma || caps.fma4) { + cpu_string += " | FMA"; + } + } + LOG_INFO(Frontend, "Host CPU: {}", cpu_string); #endif LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString()); LOG_INFO(Frontend, "Host RAM: {:.2f} GB", @@ -520,14 +534,36 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - Settings::values.use_asynchronous_gpu_emulation = - !Settings::values.use_asynchronous_gpu_emulation; - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); + bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue(); + Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); Settings::Apply(); }); async_status_button->setText(tr("ASYNC")); async_status_button->setCheckable(true); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); + + // Setup Multicore button + multicore_status_button = new QPushButton(); + multicore_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); + multicore_status_button->setFocusPolicy(Qt::NoFocus); + connect(multicore_status_button, &QPushButton::clicked, [&] { + if (emulation_running) { + return; + } + Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); + bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue(); + Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + Settings::Apply(); + }); + multicore_status_button->setText(tr("MULTICORE")); + multicore_status_button->setCheckable(true); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, async_status_button); // Setup Renderer API button @@ -545,16 +581,16 @@ void GMainWindow::InitializeWidgets() { renderer_status_button->setCheckable(false); renderer_status_button->setDisabled(true); #else - renderer_status_button->setChecked(Settings::values.renderer_backend == + renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); connect(renderer_status_button, &QPushButton::clicked, [=] { if (emulation_running) { return; } if (renderer_status_button->isChecked()) { - Settings::values.renderer_backend = Settings::RendererBackend::Vulkan; + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan); } else { - Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL); } Settings::Apply(); @@ -653,6 +689,11 @@ void GMainWindow::InitializeHotkeys() { ui.action_Capture_Screenshot->setShortcutContext( hotkey_registry.GetShortcutContext(main_window, capture_screenshot)); + ui.action_Fullscreen->setShortcut( + hotkey_registry.GetHotkey(main_window, fullscreen, this)->key()); + ui.action_Fullscreen->setShortcutContext( + hotkey_registry.GetShortcutContext(main_window, fullscreen)); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this), &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile); connect( @@ -686,24 +727,24 @@ void GMainWindow::InitializeHotkeys() { }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this), &QShortcut::activated, this, [&] { - Settings::values.use_frame_limit = !Settings::values.use_frame_limit; + Settings::values.use_frame_limit.SetValue( + !Settings::values.use_frame_limit.GetValue()); UpdateStatusBar(); }); - // TODO: Remove this comment/static whenever the next major release of - // MSVC occurs and we make it a requirement (see: - // https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html) - static constexpr u16 SPEED_LIMIT_STEP = 5; + constexpr u16 SPEED_LIMIT_STEP = 5; connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this), &QShortcut::activated, this, [&] { - if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) { - Settings::values.frame_limit += SPEED_LIMIT_STEP; + if (Settings::values.frame_limit.GetValue() < 9999 - SPEED_LIMIT_STEP) { + Settings::values.frame_limit.SetValue(SPEED_LIMIT_STEP + + Settings::values.frame_limit.GetValue()); UpdateStatusBar(); } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this), &QShortcut::activated, this, [&] { - if (Settings::values.frame_limit > SPEED_LIMIT_STEP) { - Settings::values.frame_limit -= SPEED_LIMIT_STEP; + if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) { + Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() - + SPEED_LIMIT_STEP); UpdateStatusBar(); } }); @@ -715,7 +756,7 @@ void GMainWindow::InitializeHotkeys() { }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this), &QShortcut::activated, this, [&] { - if (emu_thread->IsRunning()) { + if (emu_thread != nullptr && emu_thread->IsRunning()) { OnCaptureScreenshot(); } }); @@ -726,6 +767,9 @@ void GMainWindow::InitializeHotkeys() { Settings::values.use_docked_mode); dock_status_button->setChecked(Settings::values.use_docked_mode); }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this), + &QShortcut::activated, this, + [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); } void GMainWindow::SetDefaultUIGeometry() { @@ -826,6 +870,10 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); connect(ui.action_Report_Compatibility, &QAction::triggered, this, &GMainWindow::OnMenuReportCompatibility); + connect(ui.action_Open_Mods_Page, &QAction::triggered, this, &GMainWindow::OnOpenModsPage); + connect(ui.action_Open_Quickstart_Guide, &QAction::triggered, this, + &GMainWindow::OnOpenQuickstartGuide); + connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); @@ -839,10 +887,6 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize); // Fullscreen - ui.action_Fullscreen->setShortcut( - hotkey_registry - .GetHotkey(QStringLiteral("Main Window"), QStringLiteral("Fullscreen"), this) - ->key()); connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); // Movie @@ -910,6 +954,8 @@ bool GMainWindow::LoadROM(const QString& filename) { nullptr, // E-Commerce }); + system.RegisterHostThread(); + const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; const auto drd_callout = @@ -996,6 +1042,17 @@ void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list + u64 title_id{0}; + + const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); + const auto loader = Loader::GetLoader(v_file); + if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { + // Load per game settings + Config per_game_config(fmt::format("{:016X}.ini", title_id), false); + } + + Settings::LogSettings(); + if (UISettings::values.select_user_on_boot) { SelectAndSetCurrentUser(); } @@ -1020,12 +1077,14 @@ void GMainWindow::BootGame(const QString& filename) { &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); // Update the GUI + UpdateStatusButtons(); if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); game_list_placeholder->hide(); } status_bar_update_timer.start(2000); async_status_button->setDisabled(true); + multicore_status_button->setDisabled(true); renderer_status_button->setDisabled(true); if (UISettings::values.hide_mouse) { @@ -1034,20 +1093,20 @@ void GMainWindow::BootGame(const QString& filename) { ui.centralwidget->setMouseTracking(true); } - const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - std::string title_name; + std::string title_version; const auto res = Core::System::GetInstance().GetGameName(title_name); - if (res != Loader::ResultStatus::Success) { - const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata(); - if (metadata.first != nullptr) - title_name = metadata.first->GetApplicationName(); - if (title_name.empty()) - title_name = FileUtil::GetFilename(filename.toStdString()); + const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata(); + if (metadata.first != nullptr) { + title_version = metadata.first->GetVersionString(); + title_name = metadata.first->GetApplicationName(); + } + if (res != Loader::ResultStatus::Success || title_name.empty()) { + title_name = FileUtil::GetFilename(filename.toStdString()); } - LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name); - UpdateWindowTitle(QString::fromStdString(title_name)); + LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); + UpdateWindowTitle(title_name, title_version); loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); loading_screen->show(); @@ -1113,6 +1172,7 @@ void GMainWindow::ShutdownGame() { game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); async_status_button->setEnabled(true); + multicore_status_button->setEnabled(true); #ifdef HAS_VULKAN renderer_status_button->setEnabled(true); #endif @@ -1474,7 +1534,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { return; } - ConfigurePerGameGeneral dialog(this, title_id); + ConfigurePerGame dialog(this, title_id); dialog.LoadFromFile(v_file); auto result = dialog.exec(); if (result == QDialog::Accepted) { @@ -1485,7 +1545,14 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { game_list->PopulateAsync(UISettings::values.game_dirs); } - config->Save(); + // Do not cause the global config to write local settings into the config file + Settings::RestoreGlobalState(); + + if (!Core::System::GetInstance().IsPoweredOn()) { + config->Save(); + } + } else { + Settings::RestoreGlobalState(); } } @@ -1772,6 +1839,9 @@ void GMainWindow::OnStopGame() { } ShutdownGame(); + + Settings::RestoreGlobalState(); + UpdateStatusButtons(); } void GMainWindow::OnLoadComplete() { @@ -1797,6 +1867,26 @@ void GMainWindow::OnMenuReportCompatibility() { } } +void GMainWindow::OpenURL(const QUrl& url) { + const bool open = QDesktopServices::openUrl(url); + if (!open) { + QMessageBox::warning(this, tr("Error opening URL"), + tr("Unable to open the URL \"%1\".").arg(url.toString())); + } +} + +void GMainWindow::OnOpenModsPage() { + OpenURL(QUrl(QStringLiteral("https://github.com/yuzu-emu/yuzu/wiki/Switch-Mods"))); +} + +void GMainWindow::OnOpenQuickstartGuide() { + OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/help/quickstart/"))); +} + +void GMainWindow::OnOpenFAQ() { + OpenURL(QUrl(QStringLiteral("https://yuzu-emu.org/wiki/faq/"))); +} + void GMainWindow::ToggleFullscreen() { if (!emulation_running) { return; @@ -1859,7 +1949,7 @@ void GMainWindow::ToggleWindowMode() { void GMainWindow::ResetWindowSize() { const auto aspect_ratio = Layout::EmulationAspectRatio( - static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio), + static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); if (!ui.action_Single_Window_Mode->isChecked()) { render_window->resize(Layout::ScreenUndocked::Height / aspect_ratio, @@ -1907,12 +1997,7 @@ void GMainWindow::OnConfigure() { ui.centralwidget->setMouseTracking(false); } - dock_status_button->setChecked(Settings::values.use_docked_mode); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); -#ifdef HAS_VULKAN - renderer_status_button->setChecked(Settings::values.renderer_backend == - Settings::RendererBackend::Vulkan); -#endif + UpdateStatusButtons(); } void GMainWindow::OnLoadAmiibo() { @@ -1995,7 +2080,8 @@ void GMainWindow::OnCaptureScreenshot() { OnStartGame(); } -void GMainWindow::UpdateWindowTitle(const QString& title_name) { +void GMainWindow::UpdateWindowTitle(const std::string& title_name, + const std::string& title_version) { const auto full_name = std::string(Common::g_build_fullname); const auto branch_name = std::string(Common::g_scm_branch); const auto description = std::string(Common::g_scm_desc); @@ -2004,7 +2090,7 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) { const auto date = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString(); - if (title_name.isEmpty()) { + if (title_name.empty()) { const auto fmt = std::string(Common::g_title_bar_format_idle); setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt, full_name, branch_name, description, @@ -2012,8 +2098,8 @@ void GMainWindow::UpdateWindowTitle(const QString& title_name) { } else { const auto fmt = std::string(Common::g_title_bar_format_running); setWindowTitle(QString::fromStdString( - fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name, - description, title_name.toStdString(), date, build_id))); + fmt::format(fmt.empty() ? "yuzu {0}| {3} | {6} | {1}-{2}" : fmt, full_name, branch_name, + description, title_name, date, build_id, title_version))); } } @@ -2025,21 +2111,34 @@ void GMainWindow::UpdateStatusBar() { auto results = Core::System::GetInstance().GetAndResetPerfStats(); - if (Settings::values.use_frame_limit) { + if (Settings::values.use_frame_limit.GetValue()) { emu_speed_label->setText(tr("Speed: %1% / %2%") .arg(results.emulation_speed * 100.0, 0, 'f', 0) - .arg(Settings::values.frame_limit)); + .arg(Settings::values.frame_limit.GetValue())); } else { emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); } game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); - emu_speed_label->setVisible(true); + emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); game_fps_label->setVisible(true); emu_frametime_label->setVisible(true); } +void GMainWindow::UpdateStatusButtons() { + dock_status_button->setChecked(Settings::values.use_docked_mode); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue()); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); +#ifdef HAS_VULKAN + renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == + Settings::RendererBackend::Vulkan); +#endif +} + void GMainWindow::HideMouseCursor() { if (emu_thread == nullptr || UISettings::values.hide_mouse == false) { mouse_hide_timer.stop(); @@ -2123,6 +2222,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det if (answer == QMessageBox::Yes) { if (emu_thread) { ShutdownGame(); + + Settings::RestoreGlobalState(); + UpdateStatusButtons(); } } else { // Only show the message if the game is still running. @@ -2154,7 +2256,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { "title.keys_autogenerated"); } - Core::Crypto::KeyManager keys{}; + Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); if (keys.BaseDeriveNecessary()) { Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; @@ -2285,9 +2387,13 @@ void GMainWindow::closeEvent(QCloseEvent* event) { hotkey_registry.SaveHotkeys(); // Shutdown session if the emu thread is active... - if (emu_thread != nullptr) + if (emu_thread != nullptr) { ShutdownGame(); + Settings::RestoreGlobalState(); + UpdateStatusButtons(); + } + render_window->close(); QWidget::closeEvent(event); @@ -2467,8 +2573,6 @@ int main(int argc, char* argv[]) { QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, &GMainWindow::OnAppFocusStateChanged); - Settings::LogSettings(); - int result = app.exec(); detached_tasks.WaitForAllTasks(); return result; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4f4c8ddbe..8e3d39c38 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -181,6 +181,9 @@ private slots: void OnPauseGame(); void OnStopGame(); void OnMenuReportCompatibility(); + void OnOpenModsPage(); + void OnOpenQuickstartGuide(); + void OnOpenFAQ(); /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); @@ -215,10 +218,13 @@ private slots: private: std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); - void UpdateWindowTitle(const QString& title_name = {}); + void UpdateWindowTitle(const std::string& title_name = {}, + const std::string& title_version = {}); void UpdateStatusBar(); + void UpdateStatusButtons(); void HideMouseCursor(); void ShowMouseCursor(); + void OpenURL(const QUrl& url); Ui::MainWindow ui; @@ -234,6 +240,7 @@ private: QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; QPushButton* async_status_button = nullptr; + QPushButton* multicore_status_button = nullptr; QPushButton* renderer_status_button = nullptr; QPushButton* dock_status_button = nullptr; QTimer status_bar_update_timer; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 97c90f50b..bee6e107e 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -113,6 +113,9 @@ <string>&Help</string> </property> <addaction name="action_Report_Compatibility"/> + <addaction name="action_Open_Mods_Page"/> + <addaction name="action_Open_Quickstart_Guide"/> + <addaction name="action_Open_FAQ"/> <addaction name="separator"/> <addaction name="action_About"/> </widget> @@ -256,6 +259,21 @@ <bool>false</bool> </property> </action> + <action name="action_Open_Mods_Page"> + <property name="text"> + <string>Open Mods Page</string> + </property> + </action> + <action name="action_Open_Quickstart_Guide"> + <property name="text"> + <string>Open Quickstart Guide</string> + </property> + </action> + <action name="action_Open_FAQ"> + <property name="text"> + <string>FAQ</string> + </property> + </action> <action name="action_Open_yuzu_Folder"> <property name="text"> <string>Open yuzu Folder</string> diff --git a/src/yuzu/yuzu.rc b/src/yuzu/yuzu.rc index 1b253653f..4a3645a71 100644 --- a/src/yuzu/yuzu.rc +++ b/src/yuzu/yuzu.rc @@ -16,4 +16,4 @@ IDI_ICON1 ICON "../../dist/yuzu.ico" // RT_MANIFEST // -1 RT_MANIFEST "../../dist/yuzu.manifest" +0 RT_MANIFEST "../../dist/yuzu.manifest" |