diff options
Diffstat (limited to 'src/yuzu')
84 files changed, 2828 insertions, 968 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index dfc675cc8..fe98e3605 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -98,6 +98,9 @@ add_executable(yuzu configuration/configure_input_profile_dialog.cpp configuration/configure_input_profile_dialog.h configuration/configure_input_profile_dialog.ui + configuration/configure_mouse_panning.cpp + configuration/configure_mouse_panning.h + configuration/configure_mouse_panning.ui configuration/configure_motion_touch.cpp configuration/configure_motion_touch.h configuration/configure_motion_touch.ui @@ -189,6 +192,8 @@ add_executable(yuzu multiplayer/state.h multiplayer/validation.h precompiled_headers.h + qt_common.cpp + qt_common.h startup_checks.cpp startup_checks.h uisettings.cpp @@ -208,6 +213,8 @@ add_executable(yuzu util/url_request_interceptor.h util/util.cpp util/util.h + vk_device_info.cpp + vk_device_info.h compatdb.cpp compatdb.h yuzu.qrc @@ -314,7 +321,7 @@ endif() create_target_directory_groups(yuzu) target_link_libraries(yuzu PRIVATE common core input_common network video_core) -target_link_libraries(yuzu PRIVATE Boost::boost glad Qt${QT_MAJOR_VERSION}::Widgets) +target_link_libraries(yuzu PRIVATE Boost::headers glad Qt${QT_MAJOR_VERSION}::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_link_libraries(yuzu PRIVATE Vulkan::Headers) @@ -353,7 +360,7 @@ if (USE_DISCORD_PRESENCE) discord_impl.cpp discord_impl.h ) - target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc) + target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) endif() @@ -376,11 +383,7 @@ if(UNIX AND NOT APPLE) endif() if (WIN32 AND QT_VERSION VERSION_GREATER_EQUAL 6) - if (MSVC AND NOT ${CMAKE_GENERATOR} STREQUAL "Ninja") - set(YUZU_EXE_DIR "${CMAKE_BINARY_DIR}/bin/$<CONFIG>") - else() - set(YUZU_EXE_DIR "${CMAKE_BINARY_DIR}/bin") - endif() + set(YUZU_EXE_DIR "$<TARGET_FILE_DIR:yuzu>") add_custom_command(TARGET yuzu POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${YUZU_EXE_DIR}/yuzu.exe" --dir "${YUZU_EXE_DIR}" --libdir "${YUZU_EXE_DIR}" --plugindir "${YUZU_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0) endif() diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp index 93ad4b4f9..4988fcc83 100644 --- a/src/yuzu/applets/qt_amiibo_settings.cpp +++ b/src/yuzu/applets/qt_amiibo_settings.cpp @@ -8,7 +8,7 @@ #include "common/assert.h" #include "common/string_util.h" -#include "core/hle/service/nfp/nfp_device.h" +#include "core/hle/service/nfc/common/device.h" #include "core/hle/service/nfp/nfp_result.h" #include "input_common/drivers/virtual_amiibo.h" #include "input_common/main.h" @@ -22,7 +22,7 @@ QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_, InputCommon::InputSubsystem* input_subsystem_, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device_) + std::shared_ptr<Service::NFC::NfcDevice> nfp_device_) : QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()), input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)}, parameters(std::move(parameters_)) { @@ -52,11 +52,11 @@ void QtAmiiboSettingsDialog::LoadInfo() { return; } - if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound && - nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) { + if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound && + nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) { return; } - nfp_device->Mount(Service::NFP::MountTarget::All); + nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All); LoadAmiiboInfo(); LoadAmiiboData(); @@ -245,20 +245,29 @@ void QtAmiiboSettingsDialog::SetSettingsDescription() { QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) { connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent, &GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection); + connect(this, &QtAmiiboSettings::MainWindowRequestExit, &parent, + &GMainWindow::AmiiboSettingsRequestExit, Qt::QueuedConnection); connect(&parent, &GMainWindow::AmiiboSettingsFinished, this, &QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection); } QtAmiiboSettings::~QtAmiiboSettings() = default; +void QtAmiiboSettings::Close() const { + callback = {}; + emit MainWindowRequestExit(); +} + void QtAmiiboSettings::ShowCabinetApplet( const Core::Frontend::CabinetCallback& callback_, const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const { + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const { callback = std::move(callback_); emit MainWindowShowAmiiboSettings(parameters, nfp_device); } void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) { - callback(is_success, name); + if (callback) { + callback(is_success, name); + } } diff --git a/src/yuzu/applets/qt_amiibo_settings.h b/src/yuzu/applets/qt_amiibo_settings.h index 930c96739..ee66a0255 100644 --- a/src/yuzu/applets/qt_amiibo_settings.h +++ b/src/yuzu/applets/qt_amiibo_settings.h @@ -23,9 +23,9 @@ namespace Ui { class QtAmiiboSettingsDialog; } -namespace Service::NFP { -class NfpDevice; -} // namespace Service::NFP +namespace Service::NFC { +class NfcDevice; +} // namespace Service::NFC class QtAmiiboSettingsDialog final : public QDialog { Q_OBJECT @@ -33,7 +33,7 @@ class QtAmiiboSettingsDialog final : public QDialog { public: explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_, InputCommon::InputSubsystem* input_subsystem_, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device_); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device_); ~QtAmiiboSettingsDialog() override; int exec() override; @@ -52,7 +52,7 @@ private: std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui; InputCommon::InputSubsystem* input_subsystem; - std::shared_ptr<Service::NFP::NfpDevice> nfp_device; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device; // Parameters sent in from the backend HLE applet. Core::Frontend::CabinetParameters parameters; @@ -68,13 +68,15 @@ public: explicit QtAmiiboSettings(GMainWindow& parent); ~QtAmiiboSettings() override; + void Close() const override; void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_, const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override; signals: void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const; + void MainWindowRequestExit() const; private: void MainWindowFinished(bool is_success, const std::string& name); diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index c30b54499..00aafb8f8 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -300,7 +300,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() { if (num_connected_players < min_supported_players || num_connected_players > max_supported_players) { parameters_met = false; - ui->buttonBox->setEnabled(parameters_met); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met); return parameters_met; } @@ -327,7 +327,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() { }(); parameters_met = all_controllers_compatible; - ui->buttonBox->setEnabled(parameters_met); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met); return parameters_met; } @@ -678,18 +678,27 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() { QtControllerSelector::QtControllerSelector(GMainWindow& parent) { connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent, &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection); + connect(this, &QtControllerSelector::MainWindowRequestExit, &parent, + &GMainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection); connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this, &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection); } QtControllerSelector::~QtControllerSelector() = default; +void QtControllerSelector::Close() const { + callback = {}; + emit MainWindowRequestExit(); +} + void QtControllerSelector::ReconfigureControllers( ReconfigureCallback callback_, const Core::Frontend::ControllerParameters& parameters) const { callback = std::move(callback_); emit MainWindowReconfigureControllers(parameters); } -void QtControllerSelector::MainWindowReconfigureFinished() { - callback(); +void QtControllerSelector::MainWindowReconfigureFinished(bool is_success) { + if (callback) { + callback(is_success); + } } diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 16e99f507..2fdc35857 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -156,6 +156,7 @@ public: explicit QtControllerSelector(GMainWindow& parent); ~QtControllerSelector() override; + void Close() const override; void ReconfigureControllers( ReconfigureCallback callback_, const Core::Frontend::ControllerParameters& parameters) const override; @@ -163,9 +164,10 @@ public: signals: void MainWindowReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) const; + void MainWindowRequestExit() const; private: - void MainWindowReconfigureFinished(); + void MainWindowReconfigureFinished(bool is_success); mutable ReconfigureCallback callback; }; diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui index f5eccba70..729e921ee 100644 --- a/src/yuzu/applets/qt_controller.ui +++ b/src/yuzu/applets/qt_controller.ui @@ -2629,7 +2629,7 @@ <bool>true</bool> </property> <property name="standardButtons"> - <set>QDialogButtonBox::Ok</set> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item> @@ -2649,5 +2649,11 @@ <receiver>QtControllerSelectorDialog</receiver> <slot>accept()</slot> </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>QtControllerSelectorDialog</receiver> + <slot>reject()</slot> + </connection> </connections> </ui> diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp index e0190a979..1dc4f0383 100644 --- a/src/yuzu/applets/qt_error.cpp +++ b/src/yuzu/applets/qt_error.cpp @@ -8,12 +8,19 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); + connect(this, &QtErrorDisplay::MainWindowRequestExit, &parent, + &GMainWindow::ErrorDisplayRequestExit, Qt::QueuedConnection); connect(&parent, &GMainWindow::ErrorDisplayFinished, this, &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); } QtErrorDisplay::~QtErrorDisplay() = default; +void QtErrorDisplay::Close() const { + callback = {}; + emit MainWindowRequestExit(); +} + void QtErrorDisplay::ShowError(Result error, FinishedCallback finished) const { callback = std::move(finished); emit MainWindowDisplayError( @@ -55,5 +62,7 @@ void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text, } void QtErrorDisplay::MainWindowFinishedError() { - callback(); + if (callback) { + callback(); + } } diff --git a/src/yuzu/applets/qt_error.h b/src/yuzu/applets/qt_error.h index e4e174721..957f170ad 100644 --- a/src/yuzu/applets/qt_error.h +++ b/src/yuzu/applets/qt_error.h @@ -16,6 +16,7 @@ public: explicit QtErrorDisplay(GMainWindow& parent); ~QtErrorDisplay() override; + void Close() const override; void ShowError(Result error, FinishedCallback finished) const override; void ShowErrorWithTimestamp(Result error, std::chrono::seconds time, FinishedCallback finished) const override; @@ -24,6 +25,7 @@ public: signals: void MainWindowDisplayError(QString error_code, QString error_text) const; + void MainWindowRequestExit() const; private: void MainWindowFinishedError(); diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp index 4145c5299..1f3f23038 100644 --- a/src/yuzu/applets/qt_profile_select.cpp +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -46,11 +46,13 @@ QPixmap GetIcon(Common::UUID uuid) { } } // Anonymous namespace -QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent) +QtProfileSelectionDialog::QtProfileSelectionDialog( + Core::HID::HIDCore& hid_core, QWidget* parent, + const Core::Frontend::ProfileSelectParameters& parameters) : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) { outer_layout = new QVBoxLayout; - instruction_label = new QLabel(tr("Select a user:")); + instruction_label = new QLabel(); scroll_area = new QScrollArea; @@ -93,6 +95,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, scroll_area->setLayout(layout); connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + connect(tree_view, &QTreeView::doubleClicked, this, &QtProfileSelectionDialog::accept); connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, [this](Qt::Key key) { if (!this->isActiveWindow()) { @@ -120,7 +123,8 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, item_model->appendRow(item); setLayout(outer_layout); - setWindowTitle(tr("Profile Selector")); + SetWindowTitle(parameters); + SetDialogPurpose(parameters); resize(550, 400); } @@ -154,20 +158,101 @@ void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { user_index = index.row(); } +void QtProfileSelectionDialog::SetWindowTitle( + const Core::Frontend::ProfileSelectParameters& parameters) { + using Service::AM::Applets::UiMode; + switch (parameters.mode) { + case UiMode::UserCreator: + case UiMode::UserCreatorForStarter: + setWindowTitle(tr("Profile Creator")); + return; + case UiMode::EnsureNetworkServiceAccountAvailable: + setWindowTitle(tr("Profile Selector")); + return; + case UiMode::UserIconEditor: + setWindowTitle(tr("Profile Icon Editor")); + return; + case UiMode::UserNicknameEditor: + setWindowTitle(tr("Profile Nickname Editor")); + return; + case UiMode::NintendoAccountAuthorizationRequestContext: + case UiMode::IntroduceExternalNetworkServiceAccount: + case UiMode::IntroduceExternalNetworkServiceAccountForRegistration: + case UiMode::NintendoAccountNnidLinker: + case UiMode::LicenseRequirementsForNetworkService: + case UiMode::LicenseRequirementsForNetworkServiceWithUserContextImpl: + case UiMode::UserCreatorForImmediateNaLoginTest: + case UiMode::UserQualificationPromoter: + case UiMode::UserSelector: + default: + setWindowTitle(tr("Profile Selector")); + } +} + +void QtProfileSelectionDialog::SetDialogPurpose( + const Core::Frontend::ProfileSelectParameters& parameters) { + using Service::AM::Applets::UserSelectionPurpose; + + switch (parameters.purpose) { + case UserSelectionPurpose::GameCardRegistration: + instruction_label->setText(tr("Who will receive the points?")); + return; + case UserSelectionPurpose::EShopLaunch: + instruction_label->setText(tr("Who is using Nintendo eShop?")); + return; + case UserSelectionPurpose::EShopItemShow: + instruction_label->setText(tr("Who is making this purchase?")); + return; + case UserSelectionPurpose::PicturePost: + instruction_label->setText(tr("Who is posting?")); + return; + case UserSelectionPurpose::NintendoAccountLinkage: + instruction_label->setText(tr("Select a user to link to a Nintendo Account.")); + return; + case UserSelectionPurpose::SettingsUpdate: + instruction_label->setText(tr("Change settings for which user?")); + return; + case UserSelectionPurpose::SaveDataDeletion: + instruction_label->setText(tr("Format data for which user?")); + return; + case UserSelectionPurpose::UserMigration: + instruction_label->setText(tr("Which user will be transferred to another console?")); + return; + case UserSelectionPurpose::SaveDataTransfer: + instruction_label->setText(tr("Send save data for which user?")); + return; + case UserSelectionPurpose::General: + default: + instruction_label->setText(tr("Select a user:")); + return; + } +} + QtProfileSelector::QtProfileSelector(GMainWindow& parent) { connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); + connect(this, &QtProfileSelector::MainWindowRequestExit, &parent, + &GMainWindow::ProfileSelectorRequestExit, Qt::QueuedConnection); connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); } QtProfileSelector::~QtProfileSelector() = default; -void QtProfileSelector::SelectProfile(SelectProfileCallback callback_) const { +void QtProfileSelector::Close() const { + callback = {}; + emit MainWindowRequestExit(); +} + +void QtProfileSelector::SelectProfile( + SelectProfileCallback callback_, + const Core::Frontend::ProfileSelectParameters& parameters) const { callback = std::move(callback_); - emit MainWindowSelectProfile(); + emit MainWindowSelectProfile(parameters); } void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) { - callback(uuid); + if (callback) { + callback(uuid); + } } diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h index 637a3bda2..99056e274 100644 --- a/src/yuzu/applets/qt_profile_select.h +++ b/src/yuzu/applets/qt_profile_select.h @@ -28,7 +28,8 @@ class QtProfileSelectionDialog final : public QDialog { Q_OBJECT public: - explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent); + explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent, + const Core::Frontend::ProfileSelectParameters& parameters); ~QtProfileSelectionDialog() override; int exec() override; @@ -40,6 +41,9 @@ public: private: void SelectUser(const QModelIndex& index); + void SetWindowTitle(const Core::Frontend::ProfileSelectParameters& parameters); + void SetDialogPurpose(const Core::Frontend::ProfileSelectParameters& parameters); + int user_index = 0; QVBoxLayout* layout; @@ -65,10 +69,13 @@ public: explicit QtProfileSelector(GMainWindow& parent); ~QtProfileSelector() override; - void SelectProfile(SelectProfileCallback callback_) const override; + void Close() const override; + void SelectProfile(SelectProfileCallback callback_, + const Core::Frontend::ProfileSelectParameters& parameters) const override; signals: - void MainWindowSelectProfile() const; + void MainWindowSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters) const; + void MainWindowRequestExit() const; private: void MainWindowFinishedSelection(std::optional<Common::UUID> uuid); diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index 734b0ea40..4ae49506d 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -575,7 +575,7 @@ void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { QDialog::resize(size); // High DPI - const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f; RescaleKeyboardElements(size.width(), size.height(), dpi_scale); } diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h index 30ac8ecf6..ac23ce047 100644 --- a/src/yuzu/applets/qt_software_keyboard.h +++ b/src/yuzu/applets/qt_software_keyboard.h @@ -233,6 +233,10 @@ public: explicit QtSoftwareKeyboard(GMainWindow& parent); ~QtSoftwareKeyboard() override; + void Close() const override { + ExitKeyboard(); + } + void InitializeKeyboard(bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, SubmitNormalCallback submit_normal_callback_, diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 0a5912326..28acc0ff8 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -393,6 +393,8 @@ void QtNXWebEngineView::FocusFirstLinkElement() { QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); + connect(this, &QtWebBrowser::MainWindowRequestExit, &main_window, + &GMainWindow::WebBrowserRequestExit, Qt::QueuedConnection); connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); connect(&main_window, &GMainWindow::WebBrowserClosed, this, @@ -401,6 +403,11 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { QtWebBrowser::~QtWebBrowser() = default; +void QtWebBrowser::Close() const { + callback = {}; + emit MainWindowRequestExit(); +} + void QtWebBrowser::OpenLocalWebPage(const std::string& local_url, ExtractROMFSCallback extract_romfs_callback_, OpenWebPageCallback callback_) const { @@ -436,5 +443,7 @@ void QtWebBrowser::MainWindowExtractOfflineRomFS() { void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url) { - callback(exit_reason, last_url); + if (callback) { + callback(exit_reason, last_url); + } } diff --git a/src/yuzu/applets/qt_web_browser.h b/src/yuzu/applets/qt_web_browser.h index e8fe511ed..1234108ae 100644 --- a/src/yuzu/applets/qt_web_browser.h +++ b/src/yuzu/applets/qt_web_browser.h @@ -110,7 +110,7 @@ private: /** * Handles button presses to execute functions assigned in yuzu_key_callbacks. * yuzu_key_callbacks contains specialized functions for the buttons in the window footer - * that can be overriden by games to achieve desired functionality. + * that can be overridden by games to achieve desired functionality. * * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks */ @@ -196,6 +196,7 @@ public: explicit QtWebBrowser(GMainWindow& parent); ~QtWebBrowser() override; + void Close() const override; void OpenLocalWebPage(const std::string& local_url, ExtractROMFSCallback extract_romfs_callback_, OpenWebPageCallback callback_) const override; @@ -206,6 +207,7 @@ public: signals: void MainWindowOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local) const; + void MainWindowRequestExit() const; private: void MainWindowExtractOfflineRomFS(); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index d65991734..bdd1497b5 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,50 +1,68 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> +#include <array> +#include <cmath> +#include <cstring> +#include <string> +#include <tuple> +#include <type_traits> #include <glad/glad.h> -#include <QApplication> +#include <QtCore/qglobal.h> #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#include <QCamera> #include <QCameraImageCapture> #include <QCameraInfo> #endif +#include <QCursor> +#include <QEvent> +#include <QGuiApplication> #include <QHBoxLayout> +#include <QKeyEvent> +#include <QLayout> +#include <QList> #include <QMessageBox> -#include <QPainter> #include <QScreen> -#include <QString> -#include <QStringList> +#include <QSize> +#include <QStringLiteral> +#include <QSurfaceFormat> +#include <QTimer> #include <QWindow> +#include <QtCore/qobjectdefs.h> #ifdef HAS_OPENGL #include <QOffscreenSurface> #include <QOpenGLContext> #endif -#if !defined(WIN32) -#include <qpa/qplatformnativeinterface.h> -#endif - -#include <fmt/format.h> - -#include "common/assert.h" #include "common/microprofile.h" +#include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/settings.h" +#include "common/settings_input.h" +#include "common/thread.h" #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" +#include "core/frontend/graphics_context.h" #include "input_common/drivers/camera.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/drivers/tas_input.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main.h" +#include "yuzu/qt_common.h" -static Core::Frontend::WindowSystemType GetWindowSystemType(); +class QObject; +class QPaintEngine; +class QSurface; EmuThread::EmuThread(Core::System& system) : m_system{system} {} @@ -67,7 +85,7 @@ void EmuThread::run() { emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); if (Settings::values.use_disk_shader_cache.GetValue()) { m_system.Renderer().ReadRasterizer()->LoadDiskResources( - m_system.GetCurrentProcessProgramID(), stop_token, + m_system.GetApplicationProcessProgramID(), stop_token, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); }); @@ -87,14 +105,12 @@ void EmuThread::run() { std::unique_lock lk{m_should_run_mutex}; if (m_should_run) { m_system.Run(); - m_is_running.store(true); - m_is_running.notify_all(); + m_stopped.Reset(); Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; }); } else { m_system.Pause(); - m_is_running.store(false); - m_is_running.notify_all(); + m_stopped.Set(); EmulationPaused(lk); Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return m_should_run; }); @@ -154,7 +170,10 @@ public: // disable vsync for any shared contexts auto format = share_context->format(); - format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0); + const int swap_interval = + Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; + + format.setSwapInterval(main_surface ? swap_interval : 0); context = std::make_unique<QOpenGLContext>(); context->setShareContext(share_context); @@ -221,7 +240,7 @@ public: explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) { setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_PaintOnScreen); - if (GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) { + if (QtCommon::GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) { setAttribute(Qt::WA_DontCreateNativeAncestors); } } @@ -259,46 +278,6 @@ struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; -static Core::Frontend::WindowSystemType GetWindowSystemType() { - // Determine WSI type based on Qt platform. - QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("windows")) - return Core::Frontend::WindowSystemType::Windows; - else if (platform_name == QStringLiteral("xcb")) - return Core::Frontend::WindowSystemType::X11; - else if (platform_name == QStringLiteral("wayland")) - return Core::Frontend::WindowSystemType::Wayland; - else if (platform_name == QStringLiteral("wayland-egl")) - return Core::Frontend::WindowSystemType::Wayland; - else if (platform_name == QStringLiteral("cocoa")) - return Core::Frontend::WindowSystemType::Cocoa; - else if (platform_name == QStringLiteral("android")) - return Core::Frontend::WindowSystemType::Android; - - LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString()); - return Core::Frontend::WindowSystemType::Windows; -} - -static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { - Core::Frontend::EmuWindow::WindowSystemInfo wsi; - wsi.type = GetWindowSystemType(); - - // Our Win32 Qt external doesn't have the private API. -#if defined(WIN32) || defined(__APPLE__) - wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; -#else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - wsi.display_connection = pni->nativeResourceForWindow("display", window); - if (wsi.type == Core::Frontend::WindowSystemType::Wayland) - wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr; - else - wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; -#endif - wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; - - return wsi; -} - GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_, Core::System& system_) @@ -652,7 +631,10 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) { const auto [x, y] = ScaleTouch(pos); const auto [touch_x, touch_y] = MapToTouchScreen(x, y); const auto button = QtButtonToMouseButton(event->button()); - input_subsystem->GetMouse()->PressButton(x, y, touch_x, touch_y, button); + + input_subsystem->GetMouse()->PressMouseButton(button); + input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), button); + input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, button); emit MouseActivity(); } @@ -669,7 +651,10 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { const auto [touch_x, touch_y] = MapToTouchScreen(x, y); const int center_x = width() / 2; const int center_y = height() / 2; - input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, center_x, center_y); + + input_subsystem->GetMouse()->MouseMove(touch_x, touch_y); + input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); + input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y); if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); @@ -898,7 +883,7 @@ bool GRenderWindow::InitRenderTarget() { } // Update the Window System information with the new render target - window_info = GetWindowSystemInfo(child_widget->windowHandle()); + window_info = QtCommon::GetWindowSystemInfo(child_widget->windowHandle()); child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); layout()->addWidget(child_widget); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 092c6206f..87b23df12 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -3,29 +3,46 @@ #pragma once -#include <atomic> #include <condition_variable> +#include <cstddef> #include <memory> #include <mutex> +#include <utility> +#include <vector> +#include <QByteArray> #include <QImage> +#include <QObject> +#include <QPoint> +#include <QString> #include <QStringList> #include <QThread> -#include <QTouchEvent> #include <QWidget> +#include <qglobal.h> +#include <qnamespace.h> +#include <qobjectdefs.h> +#include "common/common_types.h" +#include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/thread.h" #include "core/frontend/emu_window.h" -class GRenderWindow; class GMainWindow; class QCamera; class QCameraImageCapture; +class QCloseEvent; +class QFocusEvent; class QKeyEvent; +class QMouseEvent; +class QObject; +class QResizeEvent; +class QShowEvent; +class QTimer; +class QTouchEvent; +class QWheelEvent; namespace Core { -enum class SystemResultStatus : u32; class System; } // namespace Core @@ -40,7 +57,6 @@ enum class TasState; namespace VideoCore { enum class LoadCallbackStage; -class RendererBase; } // namespace VideoCore class EmuThread final : public QThread { @@ -71,7 +87,7 @@ public: // Wait until paused, if pausing. if (!should_run) { - m_is_running.wait(true); + m_stopped.Wait(); } } @@ -80,7 +96,7 @@ public: * @return True if the emulation thread is running, otherwise false */ bool IsRunning() const { - return m_is_running.load() || m_should_run; + return m_should_run; } /** @@ -101,7 +117,7 @@ private: std::stop_source m_stop_source; std::mutex m_should_run_mutex; std::condition_variable_any m_should_run_cv; - std::atomic<bool> m_is_running{false}; + Common::Event m_stopped; bool m_should_run{true}; signals: @@ -147,6 +163,8 @@ public: qreal windowPixelRatio() const; + std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; + void closeEvent(QCloseEvent* event) override; void resizeEvent(QResizeEvent* event) override; @@ -184,8 +202,6 @@ public: void CaptureScreenshot(const QString& screenshot_path); - std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; - /** * Instructs the window to re-launch the application using the specified program_index. * @param program_index Specifies the index within the application of the program to launch. diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 05f49c0d2..a57a96a38 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -76,7 +76,7 @@ void CompatDB::Submit() { compatibility_Graphical->addButton(ui->radioButton_Audio_Minor, 1); compatibility_Audio->addButton(ui->radioButton_Audio_No, 2); - const int compatiblity = static_cast<int>(CalculateCompatibility()); + const int compatibility = static_cast<int>(CalculateCompatibility()); switch ((static_cast<CompatDBPage>(currentId()))) { case CompatDBPage::Intro: @@ -113,9 +113,9 @@ void CompatDB::Submit() { break; case CompatDBPage::Final: back(); - LOG_INFO(Frontend, "Compatibility Rating: {}", compatiblity); + LOG_INFO(Frontend, "Compatibility Rating: {}", compatibility); telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", - compatiblity); + compatibility); button(NextButton)->setEnabled(false); button(NextButton)->setText(tr("Submitting")); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1f1ef658c..29467d380 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -6,6 +6,7 @@ #include <QSettings> #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" @@ -64,6 +65,48 @@ const std::array<int, 2> Config::default_ringcon_analogs{{ Qt::Key_D, }}; +const std::map<Settings::AntiAliasing, QString> Config::anti_aliasing_texts_map = { + {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))}, + {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))}, + {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))}, +}; + +const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_map = { + {Settings::ScalingFilter::NearestNeighbor, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))}, + {Settings::ScalingFilter::Bilinear, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))}, + {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))}, + {Settings::ScalingFilter::Gaussian, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))}, + {Settings::ScalingFilter::ScaleForce, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))}, + {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, +}; + +const std::map<bool, QString> Config::use_docked_mode_texts_map = { + {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))}, + {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))}, +}; + +const std::map<Settings::GPUAccuracy, QString> Config::gpu_accuracy_texts_map = { + {Settings::GPUAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))}, + {Settings::GPUAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))}, + {Settings::GPUAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))}, +}; + +const std::map<Settings::RendererBackend, QString> Config::renderer_backend_texts_map = { + {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))}, + {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))}, + {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))}, +}; + +const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = { + {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))}, + {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))}, + {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))}, +}; + // This shouldn't have anything except static initializers (no functions). So // QKeySequence(...).toString() is NOT ALLOWED HERE. // This must be in alphabetical order according to action name as it must have the same order as @@ -308,6 +351,10 @@ void Config::ReadPlayerValue(std::size_t player_index) { player_motions = default_param; } } + + if (player_index == 0) { + ReadMousePanningValues(); + } } void Config::ReadDebugValues() { @@ -428,6 +475,7 @@ void Config::ReadControlValues() { ReadKeyboardValues(); ReadMouseValues(); ReadTouchscreenValues(); + ReadMousePanningValues(); ReadMotionTouchValues(); ReadHidbusValues(); ReadIrCameraValues(); @@ -438,8 +486,9 @@ void Config::ReadControlValues() { Settings::values.enable_raw_input = false; #endif ReadBasicSetting(Settings::values.emulate_analog_keyboard); - Settings::values.mouse_panning = false; - ReadBasicSetting(Settings::values.mouse_panning_sensitivity); + ReadBasicSetting(Settings::values.enable_joycon_driver); + ReadBasicSetting(Settings::values.enable_procon_driver); + ReadBasicSetting(Settings::values.random_amiibo_id); ReadBasicSetting(Settings::values.tas_enable); ReadBasicSetting(Settings::values.tas_loop); @@ -450,6 +499,16 @@ void Config::ReadControlValues() { qt_config->endGroup(); } +void Config::ReadMousePanningValues() { + ReadBasicSetting(Settings::values.mouse_panning); + ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity); + ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity); + ReadBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight); + ReadBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight); + ReadBasicSetting(Settings::values.mouse_panning_decay_strength); + ReadBasicSetting(Settings::values.mouse_panning_min_decay); +} + void Config::ReadMotionTouchValues() { int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -495,7 +554,7 @@ void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); ReadGlobalSetting(Settings::values.use_multi_core); - ReadGlobalSetting(Settings::values.use_extended_memory_layout); + ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout); qt_config->endGroup(); } @@ -690,6 +749,7 @@ void Config::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); ReadGlobalSetting(Settings::values.renderer_backend); + ReadGlobalSetting(Settings::values.async_presentation); ReadGlobalSetting(Settings::values.renderer_force_max_clock); ReadGlobalSetting(Settings::values.vulkan_device); ReadGlobalSetting(Settings::values.fullscreen_mode); @@ -705,17 +765,25 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation); ReadGlobalSetting(Settings::values.nvdec_emulation); ReadGlobalSetting(Settings::values.accelerate_astc); - ReadGlobalSetting(Settings::values.use_vsync); + ReadGlobalSetting(Settings::values.async_astc); + ReadGlobalSetting(Settings::values.astc_recompression); + ReadGlobalSetting(Settings::values.use_reactive_flushing); ReadGlobalSetting(Settings::values.shader_backend); ReadGlobalSetting(Settings::values.use_asynchronous_shaders); ReadGlobalSetting(Settings::values.use_fast_gpu_time); - ReadGlobalSetting(Settings::values.use_pessimistic_flushes); ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); + ReadGlobalSetting(Settings::values.enable_compute_pipelines); + ReadGlobalSetting(Settings::values.use_video_framerate); + ReadGlobalSetting(Settings::values.barrier_feedback_loops); ReadGlobalSetting(Settings::values.bg_red); ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); if (global) { + Settings::values.vsync_mode.SetValue(static_cast<Settings::VSyncMode>( + ReadSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()), + static_cast<u32>(Settings::values.vsync_mode.GetDefault())) + .value<u32>())); ReadBasicSetting(Settings::values.renderer_debug); ReadBasicSetting(Settings::values.renderer_shader_feedback); ReadBasicSetting(Settings::values.enable_nsight_aftermath); @@ -1010,6 +1078,10 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.motions[i]), QString::fromStdString(default_param)); } + + if (player_index == 0) { + SaveMousePanningValues(); + } } void Config::SaveDebugValues() { @@ -1046,6 +1118,16 @@ void Config::SaveTouchscreenValues() { WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); } +void Config::SaveMousePanningValues() { + // Don't overwrite values.mouse_panning + WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity); + WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity); + WriteBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight); + WriteBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight); + WriteBasicSetting(Settings::values.mouse_panning_decay_strength); + WriteBasicSetting(Settings::values.mouse_panning_min_decay); +} + void Config::SaveMotionTouchValues() { WriteBasicSetting(Settings::values.touch_device); WriteBasicSetting(Settings::values.touch_from_button_map_index); @@ -1102,6 +1184,7 @@ void Config::SaveValues() { SaveRendererValues(); SaveAudioValues(); SaveSystemValues(); + qt_config->sync(); } void Config::SaveAudioValues() { @@ -1131,6 +1214,7 @@ void Config::SaveControlValues() { SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); + SaveMousePanningValues(); SaveMotionTouchValues(); SaveHidbusValues(); SaveIrCameraValues(); @@ -1140,9 +1224,11 @@ void Config::SaveControlValues() { WriteGlobalSetting(Settings::values.enable_accurate_vibrations); WriteGlobalSetting(Settings::values.motion_enabled); WriteBasicSetting(Settings::values.enable_raw_input); + WriteBasicSetting(Settings::values.enable_joycon_driver); + WriteBasicSetting(Settings::values.enable_procon_driver); + WriteBasicSetting(Settings::values.random_amiibo_id); WriteBasicSetting(Settings::values.keyboard_enabled); WriteBasicSetting(Settings::values.emulate_analog_keyboard); - WriteBasicSetting(Settings::values.mouse_panning_sensitivity); WriteBasicSetting(Settings::values.controller_navigation); WriteBasicSetting(Settings::values.tas_enable); @@ -1156,7 +1242,7 @@ void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); WriteGlobalSetting(Settings::values.use_multi_core); - WriteGlobalSetting(Settings::values.use_extended_memory_layout); + WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout); qt_config->endGroup(); } @@ -1308,9 +1394,8 @@ void Config::SaveRendererValues() { static_cast<u32>(Settings::values.renderer_backend.GetValue(global)), static_cast<u32>(Settings::values.renderer_backend.GetDefault()), Settings::values.renderer_backend.UsingGlobal()); - WriteSetting(QString::fromStdString(Settings::values.renderer_force_max_clock.GetLabel()), - static_cast<u32>(Settings::values.renderer_force_max_clock.GetValue(global)), - static_cast<u32>(Settings::values.renderer_force_max_clock.GetDefault())); + WriteGlobalSetting(Settings::values.async_presentation); + WriteGlobalSetting(Settings::values.renderer_force_max_clock); WriteGlobalSetting(Settings::values.vulkan_device); WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()), static_cast<u32>(Settings::values.fullscreen_mode.GetValue(global)), @@ -1346,20 +1431,30 @@ void Config::SaveRendererValues() { static_cast<u32>(Settings::values.nvdec_emulation.GetDefault()), Settings::values.nvdec_emulation.UsingGlobal()); WriteGlobalSetting(Settings::values.accelerate_astc); - WriteGlobalSetting(Settings::values.use_vsync); + WriteGlobalSetting(Settings::values.async_astc); + WriteSetting(QString::fromStdString(Settings::values.astc_recompression.GetLabel()), + static_cast<u32>(Settings::values.astc_recompression.GetValue(global)), + static_cast<u32>(Settings::values.astc_recompression.GetDefault()), + Settings::values.astc_recompression.UsingGlobal()); + WriteGlobalSetting(Settings::values.use_reactive_flushing); WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()), static_cast<u32>(Settings::values.shader_backend.GetValue(global)), static_cast<u32>(Settings::values.shader_backend.GetDefault()), Settings::values.shader_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_shaders); WriteGlobalSetting(Settings::values.use_fast_gpu_time); - WriteGlobalSetting(Settings::values.use_pessimistic_flushes); WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); + WriteGlobalSetting(Settings::values.enable_compute_pipelines); + WriteGlobalSetting(Settings::values.use_video_framerate); + WriteGlobalSetting(Settings::values.barrier_feedback_loops); WriteGlobalSetting(Settings::values.bg_red); WriteGlobalSetting(Settings::values.bg_green); WriteGlobalSetting(Settings::values.bg_blue); if (global) { + WriteSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()), + static_cast<u32>(Settings::values.vsync_mode.GetValue()), + static_cast<u32>(Settings::values.vsync_mode.GetDefault())); WriteBasicSetting(Settings::values.renderer_debug); WriteBasicSetting(Settings::values.renderer_shader_feedback); WriteBasicSetting(Settings::values.enable_nsight_aftermath); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 7d26e9ab6..1211389d2 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -49,6 +49,13 @@ public: static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; static const std::array<UISettings::Shortcut, 22> default_hotkeys; + static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; + static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; + static const std::map<bool, QString> use_docked_mode_texts_map; + static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map; + static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; + static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map; + static constexpr UISettings::Theme default_theme{ #ifdef _WIN32 UISettings::Theme::DarkColorful @@ -67,6 +74,7 @@ private: void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); + void ReadMousePanningValues(); void ReadMotionTouchValues(); void ReadHidbusValues(); void ReadIrCameraValues(); @@ -97,6 +105,7 @@ private: void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); + void SaveMousePanningValues(); void SaveMotionTouchValues(); void SaveHidbusValues(); void SaveIrCameraValues(); @@ -208,3 +217,4 @@ Q_DECLARE_METATYPE(Settings::ScalingFilter); Q_DECLARE_METATYPE(Settings::AntiAliasing); Q_DECLARE_METATYPE(Settings::RendererBackend); Q_DECLARE_METATYPE(Settings::ShaderBackend); +Q_DECLARE_METATYPE(Settings::AstcRecompression); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 70cc6f84b..fcd6d61a0 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -10,6 +10,7 @@ #include "ui_configure_audio.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_audio.h" +#include "yuzu/uisettings.h" ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} { @@ -47,6 +48,7 @@ void ConfigureAudio::SetConfiguration() { const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); ui->volume_slider->setValue(volume_value); + ui->toggle_background_mute->setChecked(UISettings::values.mute_when_in_background.GetValue()); if (!Settings::IsConfiguringGlobal()) { if (Settings::values.volume.UsingGlobal()) { @@ -56,8 +58,13 @@ void ConfigureAudio::SetConfiguration() { ui->volume_combo_box->setCurrentIndex(1); ui->volume_slider->setEnabled(true); } + ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); + ConfigurationShared::SetHighlight(ui->mode_label, + !Settings::values.sound_index.UsingGlobal()); ConfigurationShared::SetHighlight(ui->volume_layout, !Settings::values.volume.UsingGlobal()); + } else { + ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue()); } SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } @@ -109,6 +116,8 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); + if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString(); @@ -116,6 +125,7 @@ void ConfigureAudio::ApplyConfiguration() { ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString()); Settings::values.audio_input_device_id.SetValue( ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString()); + UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked(); // Guard if during game and set to game-specific value if (Settings::values.volume.UsingGlobal()) { @@ -173,11 +183,14 @@ void ConfigureAudio::RetranslateUI() { void ConfigureAudio::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { + ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); - return; } + ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->mode_label, + Settings::values.sound_index.GetValue(true)); + connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) { ui->volume_slider->setEnabled(index == 1); ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index 6034d8581..4128c83ad 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -39,7 +39,7 @@ <item> <widget class="QLabel" name="output_label"> <property name="text"> - <string>Output Device</string> + <string>Output Device:</string> </property> </widget> </item> @@ -53,7 +53,7 @@ <item> <widget class="QLabel" name="input_label"> <property name="text"> - <string>Input Device</string> + <string>Input Device:</string> </property> </widget> </item> @@ -62,6 +62,36 @@ </item> </layout> </item> + <item> + <layout class="QHBoxLayout" name="mode_layout"> + <item> + <widget class="QLabel" name="mode_label"> + <property name="text"> + <string>Sound Output Mode:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_sound"> + <item> + <property name="text"> + <string>Mono</string> + </property> + </item> + <item> + <property name="text"> + <string>Stereo</string> + </property> + </item> + <item> + <property name="text"> + <string>Surround</string> + </property> + </item> + </widget> + </item> + </layout> + </item> <item> <widget class="QWidget" name="volume_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_2"> @@ -149,6 +179,17 @@ </layout> </widget> </item> + <item> + <layout class="QHBoxLayout" name="mute_layout"> + <item> + <widget class="QCheckBox" name="toggle_background_mute"> + <property name="text"> + <string>Mute audio when in background</string> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 4301313cf..bdf83ebfe 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -6,6 +6,7 @@ #include "common/settings.h" #include "core/core.h" #include "ui_configure.h" +#include "vk_device_info.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_audio.h" #include "yuzu/configuration/configure_cpu.h" @@ -28,6 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, + std::vector<VkDeviceInfo::Record>& vk_device_records, Core::System& system_, bool enable_web_config) : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, @@ -36,8 +38,10 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)}, filesystem_tab{std::make_unique<ConfigureFilesystem>(this)}, general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, - graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)}, graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, + graphics_tab{std::make_unique<ConfigureGraphics>( + system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, + this)}, hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, input_tab{std::make_unique<ConfigureInput>(system_, this)}, network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, @@ -66,7 +70,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, web_tab->SetWebServiceConfigEnabled(enable_web_config); hotkeys_tab->Populate(registry); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); input_tab->Initialize(input_subsystem); diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 1f724834a..2a08b7fee 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -4,7 +4,9 @@ #pragma once #include <memory> +#include <vector> #include <QDialog> +#include "yuzu/vk_device_info.h" namespace Core { class System; @@ -40,8 +42,9 @@ class ConfigureDialog : public QDialog { public: explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, - InputCommon::InputSubsystem* input_subsystem, Core::System& system_, - bool enable_web_config = true); + InputCommon::InputSubsystem* input_subsystem, + std::vector<VkDeviceInfo::Record>& vk_device_records, + Core::System& system_, bool enable_web_config = true); ~ConfigureDialog() override; void ApplyConfiguration(); @@ -72,8 +75,8 @@ private: std::unique_ptr<ConfigureDebugTab> debug_tab_tab; std::unique_ptr<ConfigureFilesystem> filesystem_tab; std::unique_ptr<ConfigureGeneral> general_tab; - std::unique_ptr<ConfigureGraphics> graphics_tab; std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; + std::unique_ptr<ConfigureGraphics> graphics_tab; std::unique_ptr<ConfigureHotkeys> hotkeys_tab; std::unique_ptr<ConfigureInput> input_tab; std::unique_ptr<ConfigureNetwork> network_tab; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 7783f362a..d74e663d4 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -35,14 +35,10 @@ void ConfigureGeneral::SetConfiguration() { ui->use_multi_core->setEnabled(runtime_lock); ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); - ui->use_extended_memory_layout->setEnabled(runtime_lock); - ui->use_extended_memory_layout->setChecked( - Settings::values.use_extended_memory_layout.GetValue()); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue()); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue()); ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background.GetValue()); - ui->toggle_background_mute->setChecked(UISettings::values.mute_when_in_background.GetValue()); ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue()); ui->toggle_controller_applet_disabled->setEnabled(runtime_lock); ui->toggle_controller_applet_disabled->setChecked(UISettings::values.controller_applet_disabled.GetValue()); @@ -82,15 +78,11 @@ void ConfigureGeneral::ResetDefaults() { void ConfigureGeneral::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, use_multi_core); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout, - ui->use_extended_memory_layout, - use_extended_memory_layout); if (Settings::IsConfiguringGlobal()) { 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.mute_when_in_background = ui->toggle_background_mute->isChecked(); UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); UISettings::values.controller_applet_disabled = ui->toggle_controller_applet_disabled->isChecked(); @@ -147,9 +139,6 @@ void ConfigureGeneral::SetupPerGameUI() { Settings::values.use_speed_limit, use_speed_limit); ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, use_multi_core); - ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout, - Settings::values.use_extended_memory_layout, - use_extended_memory_layout); connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() { ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() && diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index a090c1a3f..7ff63f425 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -47,7 +47,6 @@ private: ConfigurationShared::CheckState use_speed_limit; ConfigurationShared::CheckState use_multi_core; - ConfigurationShared::CheckState use_extended_memory_layout; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 2fa8324fb..fe757d011 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -62,13 +62,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_extended_memory_layout"> - <property name="text"> - <string>Extended memory layout (6GB DRAM)</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="toggle_check_exit"> <property name="text"> <string>Confirm exit while emulation is running</string> @@ -90,13 +83,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="toggle_background_mute"> - <property name="text"> - <string>Mute audio when in background</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="toggle_hide_mouse"> <property name="text"> <string>Hide mouse on inactivity</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index e9388daad..a4965524a 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -1,25 +1,81 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// Include this early to include Vulkan headers how we want to -#include "video_core/vulkan_common/vulkan_wrapper.h" - +#include <algorithm> +#include <functional> +#include <iosfwd> +#include <iterator> +#include <string> +#include <tuple> +#include <utility> +#include <vector> +#include <QBoxLayout> +#include <QCheckBox> #include <QColorDialog> -#include <QVulkanInstance> +#include <QComboBox> +#include <QIcon> +#include <QLabel> +#include <QPixmap> +#include <QPushButton> +#include <QSlider> +#include <QStringLiteral> +#include <QtCore/qobjectdefs.h> +#include <qcoreevent.h> +#include <qglobal.h> +#include <vulkan/vulkan_core.h> #include "common/common_types.h" +#include "common/dynamic_library.h" #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_graphics.h" -#include "video_core/vulkan_common/vulkan_instance.h" -#include "video_core/vulkan_common/vulkan_library.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" +#include "yuzu/qt_common.h" #include "yuzu/uisettings.h" +#include "yuzu/vk_device_info.h" + +static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR}; + +// Converts a setting to a present mode (or vice versa) +static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { + switch (mode) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::FIFO: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FIFORelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; + } +} -ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent) - : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} { +static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return Settings::VSyncMode::Immediate; + case VK_PRESENT_MODE_MAILBOX_KHR: + return Settings::VSyncMode::Mailbox; + case VK_PRESENT_MODE_FIFO_KHR: + return Settings::VSyncMode::FIFO; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return Settings::VSyncMode::FIFORelaxed; + default: + return Settings::VSyncMode::FIFO; + } +} + +ConfigureGraphics::ConfigureGraphics(const Core::System& system_, + std::vector<VkDeviceInfo::Record>& records_, + const std::function<void()>& expose_compute_option_, + QWidget* parent) + : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, records{records_}, + expose_compute_option{expose_compute_option_}, system{system_} { vulkan_device = Settings::values.vulkan_device.GetValue(); RetrieveVulkanDevices(); @@ -39,13 +95,16 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateAPILayout(); + PopulateVSyncModeSelection(); if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight( ui->api_widget, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); } }); - connect(ui->device, qOverload<int>(&QComboBox::activated), this, - [this](int device) { UpdateDeviceSelection(device); }); + connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) { + UpdateDeviceSelection(device); + PopulateVSyncModeSelection(); + }); connect(ui->backend, qOverload<int>(&QComboBox::activated), this, [this](int backend) { UpdateShaderBackendSelection(backend); }); @@ -70,6 +129,43 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal()); } +void ConfigureGraphics::PopulateVSyncModeSelection() { + const Settings::RendererBackend backend{GetCurrentGraphicsBackend()}; + if (backend == Settings::RendererBackend::Null) { + ui->vsync_mode_combobox->setEnabled(false); + return; + } + ui->vsync_mode_combobox->setEnabled(true); + + const int current_index = //< current selected vsync mode from combobox + ui->vsync_mode_combobox->currentIndex(); + const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR + current_index == -1 ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue()) + : vsync_mode_combobox_enum_map[current_index]; + int index{}; + const int device{ui->device->currentIndex()}; //< current selected Vulkan device + const auto& present_modes = //< relevant vector of present modes for the selected device or API + backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] + : default_present_modes; + + ui->vsync_mode_combobox->clear(); + vsync_mode_combobox_enum_map.clear(); + vsync_mode_combobox_enum_map.reserve(present_modes.size()); + for (const auto present_mode : present_modes) { + const auto mode_name = TranslateVSyncMode(present_mode, backend); + if (mode_name.isEmpty()) { + continue; + } + + ui->vsync_mode_combobox->insertItem(index, mode_name); + vsync_mode_combobox_enum_map.push_back(present_mode); + if (present_mode == current_mode) { + ui->vsync_mode_combobox->setCurrentIndex(index); + } + index++; + } +} + void ConfigureGraphics::UpdateDeviceSelection(int device) { if (device == -1) { return; @@ -99,6 +195,9 @@ void ConfigureGraphics::SetConfiguration() { ui->nvdec_emulation_widget->setEnabled(runtime_lock); ui->resolution_combobox->setEnabled(runtime_lock); ui->accelerate_astc->setEnabled(runtime_lock); + ui->vsync_mode_layout->setEnabled(runtime_lock || + Settings::values.renderer_backend.GetValue() == + Settings::RendererBackend::Vulkan); 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()); @@ -170,7 +269,24 @@ void ConfigureGraphics::SetConfiguration() { Settings::values.bg_green.GetValue(), Settings::values.bg_blue.GetValue())); UpdateAPILayout(); + PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition()); + + // VSync setting needs to be determined after populating the VSync combobox + if (Settings::IsConfiguringGlobal()) { + const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue(); + const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting); + int index{}; + for (const auto mode : vsync_mode_combobox_enum_map) { + if (mode == vsync_mode) { + break; + } + index++; + } + if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) { + ui->vsync_mode_combobox->setCurrentIndex(index); + } + } } void ConfigureGraphics::SetFSRIndicatorText(int percentage) { @@ -178,6 +294,27 @@ void ConfigureGraphics::SetFSRIndicatorText(int percentage) { tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2))); } +const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode, + Settings::RendererBackend backend) const { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return backend == Settings::RendererBackend::OpenGL + ? tr("Off") + : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); + case VK_PRESENT_MODE_MAILBOX_KHR: + return QStringLiteral("Mailbox (%1)").arg(tr("Recommended")); + case VK_PRESENT_MODE_FIFO_KHR: + return backend == Settings::RendererBackend::OpenGL + ? tr("On") + : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return QStringLiteral("FIFO Relaxed"); + default: + return {}; + break; + } +} + void ConfigureGraphics::ApplyConfiguration() { const auto resolution_setup = static_cast<Settings::ResolutionSetup>( ui->resolution_combobox->currentIndex() - @@ -232,6 +369,10 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.anti_aliasing.SetValue(anti_aliasing); } Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value()); + + const auto mode = vsync_mode_combobox_enum_map[ui->vsync_mode_combobox->currentIndex()]; + const auto vsync_mode = PresentModeToSetting(mode); + Settings::values.vsync_mode.SetValue(vsync_mode); } else { if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.resolution_setup.SetGlobal(true); @@ -345,7 +486,9 @@ void ConfigureGraphics::UpdateAPILayout() { ui->backend_widget->setVisible(true); break; case Settings::RendererBackend::Vulkan: - ui->device->setCurrentIndex(vulkan_device); + if (static_cast<int>(vulkan_device) < ui->device->count()) { + ui->device->setCurrentIndex(vulkan_device); + } ui->device_widget->setVisible(true); ui->backend_widget->setVisible(false); break; @@ -356,26 +499,19 @@ void ConfigureGraphics::UpdateAPILayout() { } } -void ConfigureGraphics::RetrieveVulkanDevices() try { - if (UISettings::values.has_broken_vulkan) { - return; - } - - using namespace Vulkan; - - vk::InstanceDispatch dld; - const Common::DynamicLibrary library = OpenLibrary(); - const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1); - const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); - +void ConfigureGraphics::RetrieveVulkanDevices() { vulkan_devices.clear(); - vulkan_devices.reserve(physical_devices.size()); - for (const VkPhysicalDevice device : physical_devices) { - const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName; - vulkan_devices.push_back(QString::fromStdString(name)); + vulkan_devices.reserve(records.size()); + device_present_modes.clear(); + device_present_modes.reserve(records.size()); + for (const auto& record : records) { + vulkan_devices.push_back(QString::fromStdString(record.name)); + device_present_modes.push_back(record.vsync_support); + + if (record.has_broken_compute) { + expose_compute_option(); + } } -} catch (const Vulkan::vk::Exception& exception) { - LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { @@ -465,4 +601,6 @@ void ConfigureGraphics::SetupPerGameUI() { ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); + + ui->vsync_mode_layout->setVisible(false); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index d98d6624e..be9310b74 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -3,11 +3,25 @@ #pragma once +#include <functional> #include <memory> #include <vector> +#include <QColor> #include <QString> #include <QWidget> -#include "common/settings.h" +#include <qobjectdefs.h> +#include <vulkan/vulkan_core.h> +#include "common/common_types.h" +#include "vk_device_info.h" + +class QEvent; +class QObject; + +namespace Settings { +enum class NvdecEmulation : u32; +enum class RendererBackend : u32; +enum class ShaderBackend : u32; +} // namespace Settings namespace Core { class System; @@ -25,7 +39,10 @@ class ConfigureGraphics : public QWidget { Q_OBJECT public: - explicit ConfigureGraphics(const Core::System& system_, QWidget* parent = nullptr); + explicit ConfigureGraphics(const Core::System& system_, + std::vector<VkDeviceInfo::Record>& records, + const std::function<void()>& expose_compute_option_, + QWidget* parent = nullptr); ~ConfigureGraphics() override; void ApplyConfiguration(); @@ -35,6 +52,7 @@ private: void changeEvent(QEvent* event) override; void RetranslateUI(); + void PopulateVSyncModeSelection(); void UpdateBackgroundColorButton(QColor color); void UpdateAPILayout(); void UpdateDeviceSelection(int device); @@ -43,6 +61,10 @@ private: void RetrieveVulkanDevices(); void SetFSRIndicatorText(int percentage); + /* Turns a Vulkan present mode into a textual string for a UI + * (and eventually for a human to read) */ + const QString TranslateVSyncMode(VkPresentModeKHR mode, + Settings::RendererBackend backend) const; void SetupPerGameUI(); @@ -57,9 +79,15 @@ private: ConfigurationShared::CheckState use_disk_shader_cache; ConfigurationShared::CheckState use_asynchronous_gpu_emulation; + std::vector<VkDeviceInfo::Record>& records; std::vector<QString> vulkan_devices; + std::vector<std::vector<VkPresentModeKHR>> device_present_modes; + std::vector<VkPresentModeKHR> + vsync_mode_combobox_enum_map; //< Keeps track of which present mode corresponds to which + // selection in the combobox u32 vulkan_device{}; Settings::ShaderBackend shader_backend{}; + const std::function<void()>& expose_compute_option; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index bb9910a53..39f70e406 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -189,6 +189,44 @@ </widget> </item> <item> + <widget class="QWidget" name="vsync_mode_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="vsync_mode_label"> + <property name="text"> + <string>VSync Mode:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="vsync_mode_combobox"> + <property name="toolTip"> + <string>FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. +FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. +Mailbox can have lower latency than FIFO and does not tear but may drop frames. +Immediate (no synchronization) just presents whatever is available and can exhibit tearing.</string> + </property> + <property name="currentText"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QWidget" name="nvdec_emulation_widget" native="true"> <layout class="QHBoxLayout" name="nvdec_emulation_layout"> <property name="leftMargin"> @@ -366,7 +404,7 @@ </item> <item> <property name="text"> - <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string> + <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string> </property> </item> <item> @@ -460,7 +498,7 @@ </item> <item> <property name="text"> - <string>AMD FidelityFX™️ Super Resolution (Vulkan Only)</string> + <string>AMD FidelityFX™️ Super Resolution</string> </property> </item> </widget> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index cc0155a2c..c0a044767 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -15,61 +15,90 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(const Core::System& system_ SetupPerGameUI(); SetConfiguration(); + + ui->enable_compute_pipelines_checkbox->setVisible(false); } ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default; void ConfigureGraphicsAdvanced::SetConfiguration() { const bool runtime_lock = !system.IsPoweredOn(); - ui->use_vsync->setEnabled(runtime_lock); + ui->use_reactive_flushing->setEnabled(runtime_lock); + ui->async_present->setEnabled(runtime_lock); ui->renderer_force_max_clock->setEnabled(runtime_lock); + ui->async_astc->setEnabled(runtime_lock); + ui->astc_recompression_combobox->setEnabled(runtime_lock); ui->use_asynchronous_shaders->setEnabled(runtime_lock); ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); + ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock); + ui->async_present->setChecked(Settings::values.async_presentation.GetValue()); ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue()); - ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->use_reactive_flushing->setChecked(Settings::values.use_reactive_flushing.GetValue()); + ui->async_astc->setChecked(Settings::values.async_astc.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); - ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); ui->use_vulkan_driver_pipeline_cache->setChecked( Settings::values.use_vulkan_driver_pipeline_cache.GetValue()); + ui->enable_compute_pipelines_checkbox->setChecked( + Settings::values.enable_compute_pipelines.GetValue()); + ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue()); + ui->barrier_feedback_loops_checkbox->setChecked( + Settings::values.barrier_feedback_loops.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( static_cast<int>(Settings::values.gpu_accuracy.GetValue())); ui->anisotropic_filtering_combobox->setCurrentIndex( Settings::values.max_anisotropy.GetValue()); + ui->astc_recompression_combobox->setCurrentIndex( + static_cast<int>(Settings::values.astc_recompression.GetValue())); } else { ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, &Settings::values.max_anisotropy); + ConfigurationShared::SetPerGameSetting(ui->astc_recompression_combobox, + &Settings::values.astc_recompression); ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, !Settings::values.gpu_accuracy.UsingGlobal()); - ConfigurationShared::SetHighlight(ui->renderer_force_max_clock, - !Settings::values.renderer_force_max_clock.UsingGlobal()); ConfigurationShared::SetHighlight(ui->af_label, !Settings::values.max_anisotropy.UsingGlobal()); + ConfigurationShared::SetHighlight(ui->label_astc_recompression, + !Settings::values.astc_recompression.UsingGlobal()); } } void ConfigureGraphicsAdvanced::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation, + ui->async_present, async_present); ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock, ui->renderer_force_max_clock, renderer_force_max_clock); ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_reactive_flushing, + ui->use_reactive_flushing, use_reactive_flushing); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc, + async_astc); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.astc_recompression, + ui->astc_recompression_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, ui->use_asynchronous_shaders, use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, - ui->use_pessimistic_flushes, use_pessimistic_flushes); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vulkan_driver_pipeline_cache, ui->use_vulkan_driver_pipeline_cache, use_vulkan_driver_pipeline_cache); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines, + ui->enable_compute_pipelines_checkbox, + enable_compute_pipelines); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate, + ui->use_video_framerate_checkbox, use_video_framerate); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.barrier_feedback_loops, + ui->barrier_feedback_loops_checkbox, + barrier_feedback_loops); } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -88,41 +117,67 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { // Disable if not global (only happens during game) if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); + ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); ui->renderer_force_max_clock->setEnabled( Settings::values.renderer_force_max_clock.UsingGlobal()); - ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); + ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal()); + ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal()); + ui->astc_recompression_combobox->setEnabled( + Settings::values.astc_recompression.UsingGlobal()); ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); - ui->use_pessimistic_flushes->setEnabled( - Settings::values.use_pessimistic_flushes.UsingGlobal()); ui->use_vulkan_driver_pipeline_cache->setEnabled( Settings::values.use_vulkan_driver_pipeline_cache.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); + ui->enable_compute_pipelines_checkbox->setEnabled( + Settings::values.enable_compute_pipelines.UsingGlobal()); + ui->use_video_framerate_checkbox->setEnabled( + Settings::values.use_video_framerate.UsingGlobal()); + ui->barrier_feedback_loops_checkbox->setEnabled( + Settings::values.barrier_feedback_loops.UsingGlobal()); return; } + ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation, + async_present); ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock, Settings::values.renderer_force_max_clock, renderer_force_max_clock); - ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); + ConfigurationShared::SetColoredTristate( + ui->use_reactive_flushing, Settings::values.use_reactive_flushing, use_reactive_flushing); + ConfigurationShared::SetColoredTristate(ui->async_astc, Settings::values.async_astc, + async_astc); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, Settings::values.use_asynchronous_shaders, use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); - ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, - Settings::values.use_pessimistic_flushes, - use_pessimistic_flushes); ConfigurationShared::SetColoredTristate(ui->use_vulkan_driver_pipeline_cache, Settings::values.use_vulkan_driver_pipeline_cache, use_vulkan_driver_pipeline_cache); + ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox, + Settings::values.enable_compute_pipelines, + enable_compute_pipelines); + ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox, + Settings::values.use_video_framerate, + use_video_framerate); + ConfigurationShared::SetColoredTristate(ui->barrier_feedback_loops_checkbox, + Settings::values.barrier_feedback_loops, + barrier_feedback_loops); ConfigurationShared::SetColoredComboBox( ui->gpu_accuracy, ui->label_gpu_accuracy, static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); ConfigurationShared::SetColoredComboBox( ui->anisotropic_filtering_combobox, ui->af_label, static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); + ConfigurationShared::SetColoredComboBox( + ui->astc_recompression_combobox, ui->label_astc_recompression, + static_cast<int>(Settings::values.astc_recompression.GetValue(true))); +} + +void ConfigureGraphicsAdvanced::ExposeComputeOption() { + ui->enable_compute_pipelines_checkbox->setVisible(true); } diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index df557d585..369a7c83e 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -28,6 +28,8 @@ public: void ApplyConfiguration(); void SetConfiguration(); + void ExposeComputeOption(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); @@ -36,12 +38,17 @@ private: std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; + ConfigurationShared::CheckState async_present; ConfigurationShared::CheckState renderer_force_max_clock; ConfigurationShared::CheckState use_vsync; + ConfigurationShared::CheckState async_astc; + ConfigurationShared::CheckState use_reactive_flushing; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; - ConfigurationShared::CheckState use_pessimistic_flushes; ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache; + ConfigurationShared::CheckState enable_compute_pipelines; + ConfigurationShared::CheckState use_video_framerate; + ConfigurationShared::CheckState barrier_feedback_loops; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 061885e30..d527a6f38 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>404</width> - <height>321</height> + <height>376</height> </rect> </property> <property name="windowTitle"> @@ -70,6 +70,57 @@ </widget> </item> <item> + <widget class="QWidget" name="astc_recompression_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_astc_recompression"> + <property name="text"> + <string>ASTC recompression:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="astc_recompression_combobox"> + <item> + <property name="text"> + <string>Uncompressed (Best quality)</string> + </property> + </item> + <item> + <property name="text"> + <string>BC1 (Low quality)</string> + </property> + </item> + <item> + <property name="text"> + <string>BC3 (Medium quality)</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QCheckBox" name="async_present"> + <property name="text"> + <string>Enable asynchronous presentation (Vulkan only)</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="renderer_force_max_clock"> <property name="toolTip"> <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string> @@ -80,12 +131,22 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_vsync"> + <widget class="QCheckBox" name="async_astc"> + <property name="toolTip"> + <string>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</string> + </property> + <property name="text"> + <string>Decode ASTC textures asynchronously (Hack)</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="use_reactive_flushing"> <property name="toolTip"> - <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> + <string>Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.</string> </property> <property name="text"> - <string>Use VSync</string> + <string>Enable Reactive Flushing</string> </property> </widget> </item> @@ -102,7 +163,7 @@ <item> <widget class="QCheckBox" name="use_fast_gpu_time"> <property name="toolTip"> - <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> + <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> </property> <property name="text"> <string>Use Fast GPU Time (Hack)</string> @@ -110,22 +171,43 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_pessimistic_flushes"> + <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache"> <property name="toolTip"> - <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> + <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string> </property> <property name="text"> - <string>Use pessimistic buffer flushes (Hack)</string> + <string>Use Vulkan pipeline cache</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache"> + <widget class="QCheckBox" name="enable_compute_pipelines_checkbox"> <property name="toolTip"> - <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string> + <string>Enable compute pipelines, required by some games. This setting only exists for Intel proprietary drivers, and may crash if enabled. +Compute pipelines are always enabled on all other drivers.</string> </property> <property name="text"> - <string>Use Vulkan pipeline cache</string> + <string>Enable Compute Pipelines (Intel Vulkan only)</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="use_video_framerate_checkbox"> + <property name="toolTip"> + <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string> + </property> + <property name="text"> + <string>Sync to framerate of video playback</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="barrier_feedback_loops_checkbox"> + <property name="toolTip"> + <string>Improves rendering of transparency effects in specific games.</string> + </property> + <property name="text"> + <string>Barrier feedback loops</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index daa77a8f8..0b2a965f8 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -48,7 +48,9 @@ ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent connect(poll_timer.get(), &QTimer::timeout, [this] { const auto buttons = controller->GetNpadButtons(); - if (buttons.raw != Core::HID::NpadButton::None) { + const auto home_pressed = controller->GetHomeButtons().home != 0; + const auto capture_pressed = controller->GetCaptureButtons().capture != 0; + if (home_pressed || capture_pressed) { SetPollingResult(buttons.raw, false); return; } @@ -154,8 +156,10 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) { model->setData(index, previous_key); return; } - - const QString button_string = tr("Home+%1").arg(GetButtonName(button)); + const auto home_pressed = this->controller->GetHomeButtons().home != 0; + const auto capture_pressed = this->controller->GetCaptureButtons().capture != 0; + const QString button_string = + GetButtonCombinationName(button, home_pressed, capture_pressed); const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string); @@ -174,72 +178,83 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) { poll_timer->start(200); // Check for new inputs every 200ms // We need to disable configuration to be able to read npad buttons controller->DisableConfiguration(); - controller->DisableSystemButtons(); } void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) { timeout_timer->stop(); poll_timer->stop(); + (*input_setter)(button, cancel); // Re-Enable configuration controller->EnableConfiguration(); - controller->EnableSystemButtons(); - - (*input_setter)(button, cancel); input_setter = std::nullopt; } -QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const { +QString ConfigureHotkeys::GetButtonCombinationName(Core::HID::NpadButton button, + const bool home = false, + const bool capture = false) const { Core::HID::NpadButtonState state{button}; + QString button_combination; + if (home) { + button_combination.append(QStringLiteral("Home+")); + } + if (capture) { + button_combination.append(QStringLiteral("Screenshot+")); + } if (state.a) { - return QStringLiteral("A"); + button_combination.append(QStringLiteral("A+")); } if (state.b) { - return QStringLiteral("B"); + button_combination.append(QStringLiteral("B+")); } if (state.x) { - return QStringLiteral("X"); + button_combination.append(QStringLiteral("X+")); } if (state.y) { - return QStringLiteral("Y"); + button_combination.append(QStringLiteral("Y+")); } if (state.l || state.right_sl || state.left_sl) { - return QStringLiteral("L"); + button_combination.append(QStringLiteral("L+")); } if (state.r || state.right_sr || state.left_sr) { - return QStringLiteral("R"); + button_combination.append(QStringLiteral("R+")); } if (state.zl) { - return QStringLiteral("ZL"); + button_combination.append(QStringLiteral("ZL+")); } if (state.zr) { - return QStringLiteral("ZR"); + button_combination.append(QStringLiteral("ZR+")); } if (state.left) { - return QStringLiteral("Dpad_Left"); + button_combination.append(QStringLiteral("Dpad_Left+")); } if (state.right) { - return QStringLiteral("Dpad_Right"); + button_combination.append(QStringLiteral("Dpad_Right+")); } if (state.up) { - return QStringLiteral("Dpad_Up"); + button_combination.append(QStringLiteral("Dpad_Up+")); } if (state.down) { - return QStringLiteral("Dpad_Down"); + button_combination.append(QStringLiteral("Dpad_Down+")); } if (state.stick_l) { - return QStringLiteral("Left_Stick"); + button_combination.append(QStringLiteral("Left_Stick+")); } if (state.stick_r) { - return QStringLiteral("Right_Stick"); + button_combination.append(QStringLiteral("Right_Stick+")); } if (state.minus) { - return QStringLiteral("Minus"); + button_combination.append(QStringLiteral("Minus+")); } if (state.plus) { - return QStringLiteral("Plus"); + button_combination.append(QStringLiteral("Plus+")); + } + if (button_combination.isEmpty()) { + return tr("Invalid"); + } else { + button_combination.chop(1); + return button_combination; } - return tr("Invalid"); } std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h index b45ecb185..5fd1bcbfe 100644 --- a/src/yuzu/configuration/configure_hotkeys.h +++ b/src/yuzu/configuration/configure_hotkeys.h @@ -34,7 +34,7 @@ public: /** * Populates the hotkey list widget using data from the provided registry. - * Called everytime the Configure dialog is opened. + * Called every time the Configure dialog is opened. * @param registry The HotkeyRegistry whose data is used to populate the list. */ void Populate(const HotkeyRegistry& registry); @@ -59,7 +59,7 @@ private: QStandardItemModel* model; void SetPollingResult(Core::HID::NpadButton button, bool cancel); - QString GetButtonName(Core::HID::NpadButton button) const; + QString GetButtonCombinationName(Core::HID::NpadButton button, bool home, bool capture) const; Core::HID::EmulatedController* controller; std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> poll_timer; diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 1db374d4a..7fce85bca 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -189,6 +189,8 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const { } void ConfigureInput::ApplyConfiguration() { + const bool was_global = Settings::values.players.UsingGlobal(); + Settings::values.players.SetGlobal(true); for (auto* controller : player_controllers) { controller->ApplyConfiguration(); } @@ -201,6 +203,7 @@ void ConfigureInput::ApplyConfiguration() { Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); + Settings::values.players.SetGlobal(was_global); } void ConfigureInput::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 235b813d9..3cfd5d439 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -129,15 +129,15 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); - Settings::values.mouse_panning = ui->mouse_panning->isChecked(); - Settings::values.mouse_panning_sensitivity = - static_cast<float>(ui->mouse_panning_sensitivity->value()); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); + Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked(); + Settings::values.enable_procon_driver = ui->enable_procon_driver->isChecked(); + Settings::values.random_amiibo_id = ui->random_amiibo_id->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -164,14 +164,15 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue()); ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue()); ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue()); - ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue()); - ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue()); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); + ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue()); + ui->enable_procon_driver->setChecked(Settings::values.enable_procon_driver.GetValue()); + ui->random_amiibo_id->setChecked(Settings::values.random_amiibo_id.GetValue()); UpdateUIEnabled(); } @@ -191,8 +192,6 @@ void ConfigureInputAdvanced::RetranslateUI() { void ConfigureInputAdvanced::UpdateUIEnabled() { ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); - ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); - ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA) ui->enable_ir_sensor->setEnabled(false); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index fac8cf827..2994d0ab4 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2696,7 +2696,10 @@ </widget> </item> <item row="5" column="0"> - <widget class="QCheckBox" name="mouse_panning"> + <widget class="QCheckBox" name="enable_joycon_driver"> + <property name="toolTip"> + <string>Requires restarting yuzu</string> + </property> <property name="minimumSize"> <size> <width>0</width> @@ -2704,40 +2707,50 @@ </size> </property> <property name="text"> - <string>Enable mouse panning</string> + <string>Enable direct JoyCon driver</string> </property> </widget> </item> - <item row="5" column="2"> - <widget class="QSpinBox" name="mouse_panning_sensitivity"> + <item row="6" column="0"> + <widget class="QCheckBox" name="enable_procon_driver"> <property name="toolTip"> - <string>Mouse sensitivity</string> + <string>Requires restarting yuzu</string> </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> </property> - <property name="suffix"> - <string>%</string> + <property name="text"> + <string>Enable direct Pro Controller driver [EXPERIMENTAL]</string> </property> - <property name="minimum"> - <number>1</number> + </widget> + </item> + <item row="7" column="0"> + <widget class="QCheckBox" name="random_amiibo_id"> + <property name="toolTip"> + <string>Allows unlimited uses of the same Amiibo in games that would otherwise limit you to one use.</string> </property> - <property name="maximum"> - <number>100</number> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> </property> - <property name="value"> - <number>100</number> + <property name="text"> + <string>Use random Amiibo ID</string> </property> </widget> </item> - <item row="6" column="0"> + <item row="8" column="0"> <widget class="QLabel" name="motion_touch"> <property name="text"> <string>Motion / Touch</string> </property> </widget> </item> - <item row="6" column="2"> + <item row="8" column="2"> <widget class="QPushButton" name="buttonMotionTouch"> <property name="text"> <string>Configure</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index c40d980c9..576f5b571 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -8,6 +8,7 @@ #include <QInputDialog> #include <QMenu> #include <QMessageBox> +#include <QMouseEvent> #include <QTimer> #include "common/assert.h" #include "common/param_package.h" @@ -22,6 +23,7 @@ #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/configuration/configure_mouse_panning.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/util/limitable_input_dialog.h" @@ -66,6 +68,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) { return QObject::tr("R"); case Common::Input::ButtonNames::TriggerL: return QObject::tr("L"); + case Common::Input::ButtonNames::TriggerZR: + return QObject::tr("ZR"); + case Common::Input::ButtonNames::TriggerZL: + return QObject::tr("ZL"); + case Common::Input::ButtonNames::TriggerSR: + return QObject::tr("SR"); + case Common::Input::ButtonNames::TriggerSL: + return QObject::tr("SL"); + case Common::Input::ButtonNames::ButtonStickL: + return QObject::tr("Stick L"); + case Common::Input::ButtonNames::ButtonStickR: + return QObject::tr("Stick R"); case Common::Input::ButtonNames::ButtonA: return QObject::tr("A"); case Common::Input::ButtonNames::ButtonB: @@ -76,6 +90,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) { return QObject::tr("Y"); case Common::Input::ButtonNames::ButtonStart: return QObject::tr("Start"); + case Common::Input::ButtonNames::ButtonPlus: + return QObject::tr("Plus"); + case Common::Input::ButtonNames::ButtonMinus: + return QObject::tr("Minus"); + case Common::Input::ButtonNames::ButtonHome: + return QObject::tr("Home"); + case Common::Input::ButtonNames::ButtonCapture: + return QObject::tr("Capture"); case Common::Input::ButtonNames::L1: return QObject::tr("L1"); case Common::Input::ButtonNames::L2: @@ -162,12 +184,13 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); + const QString turbo = QString::fromStdString(param.Get("turbo", false) ? "$" : ""); const auto common_button_name = input_subsystem->GetButtonName(param); // Retrieve the names from Qt if (param.Get("engine", "") == "keyboard") { const QString button_str = GetKeyName(param.Get("code", 0)); - return QObject::tr("%1%2%3").arg(toggle, inverted, button_str); + return QObject::tr("%1%2%3%4").arg(turbo, toggle, inverted, button_str); } if (common_button_name == Common::Input::ButtonNames::Invalid) { @@ -181,11 +204,11 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { if (common_button_name == Common::Input::ButtonNames::Value) { if (param.Has("hat")) { const QString hat = GetDirectionName(param.Get("direction", "")); - return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); + return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, hat); } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); - return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); + return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, axis); } if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); @@ -199,22 +222,22 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("button")) { const QString button = QString::fromStdString(param.Get("button", "")); - return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); + return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button); } } QString button_name = GetButtonName(common_button_name); if (param.Has("hat")) { - return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name); } if (param.Has("axis")) { - return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, button_name); } if (param.Has("motion")) { return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); } if (param.Has("button")) { - return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button_name); } return QObject::tr("[unknown]"); @@ -375,6 +398,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Turbo button"), [&] { + const bool turbo_value = !param.Get("turbo", false); + param.Set("turbo", turbo_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } if (param.Has("axis")) { context_menu.addAction(tr("Invert axis"), [&] { @@ -383,6 +412,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Invert button"), [&] { + const bool invert_value = !param.Get("inverted", false); + param.Set("inverted", invert_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); context_menu.addAction(tr("Set threshold"), [&] { const int button_threshold = static_cast<int>(param.Get("threshold", 0.5f) * 100.0f); @@ -445,6 +480,9 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i param.Set("threshold", new_threshold / 1000.0f); emulated_controller->SetMotionParam(motion_id, param); }); + context_menu.addAction(tr("Calibrate sensor"), [&] { + emulated_controller->StartMotionCalibration(); + }); } context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); }); @@ -674,6 +712,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } + if (player_index_ == 0) { + connect(ui->mousePanningButton, &QPushButton::clicked, [this, input_subsystem_] { + const auto right_stick_param = + emulated_controller->GetStickParam(Settings::NativeAnalog::RStick); + ConfigureMousePanning dialog(this, input_subsystem_, + right_stick_param.Get("deadzone", 0.0f), + right_stick_param.Get("range", 1.0f)); + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } + }); + } else { + ui->mousePanningWidget->hide(); + } + // Player Connected checkbox connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { emit Connected(checked); }); @@ -1463,7 +1516,7 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { } const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); - input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); + input_subsystem->GetMouse()->PressButton(0, 0, button); } void ConfigureInputPlayer::wheelEvent(QWheelEvent* event) { diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 99a9c875d..d4df43d73 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -224,7 +224,7 @@ private: /// Bottom row is where console wide settings are held, and its "owned" by the parent /// ConfigureInput widget. On show, add this widget to the main layout. This will change the - /// parent of the widget to this widget (but thats fine). + /// parent of the widget to this widget (but that's fine). QWidget* bottom_row; Core::HID::HIDCore& hid_core; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index a9567c6ee..43f6c7b50 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -3048,6 +3048,102 @@ </item> </layout> </item> + <item> + <widget class="QWidget" name="mousePanningWidget" native="true"> + <layout class="QHBoxLayout" name="mousePanningHorizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <spacer name="mousePanningHorizontalSpacerLeft"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="mousePanningGroup"> + <property name="title"> + <string>Mouse panning</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="mousePanningVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="mousePanningButton"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="mousePanningHorizontalSpacerRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 11390fec0..a188eef92 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -81,7 +81,6 @@ void PlayerControlPreview::UpdateColors() { colors.outline = QColor(0, 0, 0); colors.primary = QColor(225, 225, 225); colors.button = QColor(109, 111, 114); - colors.button2 = QColor(109, 111, 114); colors.button2 = QColor(77, 80, 84); colors.slider_arrow = QColor(65, 68, 73); colors.font2 = QColor(0, 0, 0); @@ -100,12 +99,17 @@ void PlayerControlPreview::UpdateColors() { colors.led_off = QColor(170, 238, 255); colors.indicator2 = QColor(59, 165, 93); colors.charging = QColor(250, 168, 26); + colors.button_turbo = QColor(217, 158, 4); colors.left = colors.primary; colors.right = colors.primary; - // Possible alternative to set colors from settings - // colors.left = QColor(controller->GetColors().left.body); - // colors.right = QColor(controller->GetColors().right.body); + + const auto color_left = controller->GetColorsValues()[0].body; + const auto color_right = controller->GetColorsValues()[1].body; + if (color_left != 0 && color_right != 0) { + colors.left = QColor(color_left); + colors.right = QColor(color_right); + } } void PlayerControlPreview::ResetInputs() { @@ -176,6 +180,10 @@ void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType typ battery_values = controller->GetBatteryValues(); needs_redraw = true; break; + case Core::HID::ControllerTriggerType::Motion: + motion_values = controller->GetMotions(); + needs_redraw = true; + break; default: break; } @@ -309,6 +317,15 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-140, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + } + using namespace Settings::NativeButton; // D-pad constants @@ -431,6 +448,15 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(140, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -551,6 +577,17 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-180, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + Draw3dCube(p, center + QPointF(180, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -643,6 +680,15 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -115), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -746,6 +792,15 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.button); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -100), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -2465,7 +2520,6 @@ void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, float width, float height, Direction direction, float radius) { - p.setBrush(button_color); if (pressed.value) { switch (direction) { case Direction::Left: @@ -2483,16 +2537,16 @@ void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, case Direction::None: break; } - p.setBrush(colors.highlight); } QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f}; + p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo)); p.drawRoundedRect(rect, radius, radius); } void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, const Common::Input::ButtonStatus& pressed, int button_size) { p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawRectangle(p, center, button_size, button_size / 3.0f); } void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, @@ -2500,7 +2554,7 @@ void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, int button_size) { // Draw outer line p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawRectangle(p, center, button_size, button_size / 3.0f); DrawRectangle(p, center, button_size / 3.0f, button_size); @@ -2522,7 +2576,7 @@ void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } @@ -2535,7 +2589,7 @@ void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } @@ -2549,17 +2603,15 @@ void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button2); + p.setBrush(GetButtonColor(colors.button2, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, const Common::Input::ButtonStatus& pressed, float button_size) { - p.setBrush(button_color); - if (pressed.value) { - p.setBrush(colors.highlight); - } + + p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo)); p.drawEllipse(center, button_size, button_size); } @@ -2616,7 +2668,7 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, // Draw arrow button p.setPen(pressed.value ? colors.highlight : colors.button); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, arrow_button); switch (direction) { @@ -2668,10 +2720,20 @@ void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center, // Draw arrow button p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, qtrigger_button); } +QColor PlayerControlPreview::GetButtonColor(QColor default_color, bool is_pressed, bool turbo) { + if (is_pressed && turbo) { + return colors.button_turbo; + } + if (is_pressed) { + return colors.highlight; + } + return default_color; +} + void PlayerControlPreview::DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery) { if (battery == Common::Input::BatteryLevel::None) { @@ -2860,6 +2922,46 @@ void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Di DrawPolygon(p, arrow_symbol); } +// Draw motion functions +void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, + float size) { + std::array<Common::Vec3f, 8> cube{ + Common::Vec3f{-0.7f, -1, -0.5f}, + {-0.7f, 1, -0.5f}, + {0.7f, 1, -0.5f}, + {0.7f, -1, -0.5f}, + {-0.7f, -1, 0.5f}, + {-0.7f, 1, 0.5f}, + {0.7f, 1, 0.5f}, + {0.7f, -1, 0.5f}, + }; + + for (Common::Vec3f& point : cube) { + point.RotateFromOrigin(euler.x, euler.y, euler.z); + point *= size; + } + + const std::array<QPointF, 4> front_face{ + center + QPointF{cube[0].x, cube[0].y}, + center + QPointF{cube[1].x, cube[1].y}, + center + QPointF{cube[2].x, cube[2].y}, + center + QPointF{cube[3].x, cube[3].y}, + }; + const std::array<QPointF, 4> back_face{ + center + QPointF{cube[4].x, cube[4].y}, + center + QPointF{cube[5].x, cube[5].y}, + center + QPointF{cube[6].x, cube[6].y}, + center + QPointF{cube[7].x, cube[7].y}, + }; + + DrawPolygon(p, front_face); + DrawPolygon(p, back_face); + p.drawLine(center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[4].x, cube[4].y}); + p.drawLine(center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[5].x, cube[5].y}); + p.drawLine(center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[6].x, cube[6].y}); + p.drawLine(center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[7].x, cube[7].y}); +} + template <size_t N> void PlayerControlPreview::DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon) { p.drawPolygon(polygon.data(), static_cast<int>(polygon.size())); diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index b258c6d77..a16943c3c 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -9,6 +9,7 @@ #include "common/input.h" #include "common/settings_input.h" +#include "common/vector_math.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_types.h" @@ -43,7 +44,7 @@ public: // Handles emulated controller events void ControllerUpdate(Core::HID::ControllerTriggerType type); - // Updates input on sheduled interval + // Updates input on scheduled interval void UpdateInput(); protected: @@ -81,6 +82,7 @@ private: QColor right{}; QColor button{}; QColor button2{}; + QColor button_turbo{}; QColor font{}; QColor font2{}; QColor highlight{}; @@ -183,6 +185,7 @@ private: const Common::Input::ButtonStatus& pressed, float size = 1.0f); void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, const Common::Input::ButtonStatus& pressed); + QColor GetButtonColor(QColor default_color, bool is_pressed, bool turbo); // Draw battery functions void DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery); @@ -191,6 +194,9 @@ private: void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size); void DrawArrow(QPainter& p, QPointF center, Direction direction, float size); + // Draw motion functions + void Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, float size); + // Draw primitive types template <size_t N> void DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon); @@ -220,4 +226,5 @@ private: Core::HID::SticksValues stick_values{}; Core::HID::TriggerValues trigger_values{}; Core::HID::BatteryValues battery_values{}; + Core::HID::MotionState motion_values{}; }; diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index d1b870c72..fb1292f07 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -89,7 +89,6 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); SetConfiguration(); UpdateUiDisplay(); ConnectEvents(); diff --git a/src/yuzu/configuration/configure_mouse_panning.cpp b/src/yuzu/configuration/configure_mouse_panning.cpp new file mode 100644 index 000000000..f183d2740 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QCloseEvent> + +#include "common/settings.h" +#include "ui_configure_mouse_panning.h" +#include "yuzu/configuration/configure_mouse_panning.h" + +ConfigureMousePanning::ConfigureMousePanning(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_, + float right_stick_deadzone, float right_stick_range) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique<Ui::ConfigureMousePanning>()) { + ui->setupUi(this); + SetConfiguration(right_stick_deadzone, right_stick_range); + ConnectEvents(); +} + +ConfigureMousePanning::~ConfigureMousePanning() = default; + +void ConfigureMousePanning::closeEvent(QCloseEvent* event) { + event->accept(); +} + +void ConfigureMousePanning::SetConfiguration(float right_stick_deadzone, float right_stick_range) { + ui->enable->setChecked(Settings::values.mouse_panning.GetValue()); + ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetValue()); + ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetValue()); + ui->deadzone_x_counterweight->setValue( + Settings::values.mouse_panning_deadzone_x_counterweight.GetValue()); + ui->deadzone_y_counterweight->setValue( + Settings::values.mouse_panning_deadzone_y_counterweight.GetValue()); + ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetValue()); + ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetValue()); + + if (right_stick_deadzone > 0.0f || right_stick_range != 1.0f) { + ui->warning_label->setText(QString::fromStdString( + "Mouse panning works better with a deadzone of 0% and a range of 100%.\n" + "Current values are " + + std::to_string(static_cast<int>(right_stick_deadzone * 100.0f)) + "% and " + + std::to_string(static_cast<int>(right_stick_range * 100.0f)) + "% respectively.")); + } else { + ui->warning_label->hide(); + } +} + +void ConfigureMousePanning::SetDefaultConfiguration() { + ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetDefault()); + ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetDefault()); + ui->deadzone_x_counterweight->setValue( + Settings::values.mouse_panning_deadzone_x_counterweight.GetDefault()); + ui->deadzone_y_counterweight->setValue( + Settings::values.mouse_panning_deadzone_y_counterweight.GetDefault()); + ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetDefault()); + ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetDefault()); +} + +void ConfigureMousePanning::ConnectEvents() { + connect(ui->default_button, &QPushButton::clicked, this, + &ConfigureMousePanning::SetDefaultConfiguration); + connect(ui->button_box, &QDialogButtonBox::accepted, this, + &ConfigureMousePanning::ApplyConfiguration); + connect(ui->button_box, &QDialogButtonBox::rejected, this, [this] { reject(); }); +} + +void ConfigureMousePanning::ApplyConfiguration() { + Settings::values.mouse_panning = ui->enable->isChecked(); + Settings::values.mouse_panning_x_sensitivity = static_cast<float>(ui->x_sensitivity->value()); + Settings::values.mouse_panning_y_sensitivity = static_cast<float>(ui->y_sensitivity->value()); + Settings::values.mouse_panning_deadzone_x_counterweight = + static_cast<float>(ui->deadzone_x_counterweight->value()); + Settings::values.mouse_panning_deadzone_y_counterweight = + static_cast<float>(ui->deadzone_y_counterweight->value()); + Settings::values.mouse_panning_decay_strength = static_cast<float>(ui->decay_strength->value()); + Settings::values.mouse_panning_min_decay = static_cast<float>(ui->min_decay->value()); + + accept(); +} diff --git a/src/yuzu/configuration/configure_mouse_panning.h b/src/yuzu/configuration/configure_mouse_panning.h new file mode 100644 index 000000000..08c6e1f62 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> +#include <QDialog> + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureMousePanning; +} + +class ConfigureMousePanning : public QDialog { + Q_OBJECT +public: + explicit ConfigureMousePanning(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_, + float right_stick_deadzone, float right_stick_range); + ~ConfigureMousePanning() override; + +public slots: + void ApplyConfiguration(); + +private: + void closeEvent(QCloseEvent* event) override; + void SetConfiguration(float right_stick_deadzone, float right_stick_range); + void SetDefaultConfiguration(); + void ConnectEvents(); + + InputCommon::InputSubsystem* input_subsystem; + std::unique_ptr<Ui::ConfigureMousePanning> ui; +}; diff --git a/src/yuzu/configuration/configure_mouse_panning.ui b/src/yuzu/configuration/configure_mouse_panning.ui new file mode 100644 index 000000000..75795b727 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.ui @@ -0,0 +1,238 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMousePanning</class> + <widget class="QDialog" name="configure_mouse_panning"> + <property name="windowTitle"> + <string>Configure mouse panning</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QCheckBox" name="enable"> + <property name="text"> + <string>Enable</string> + </property> + <property name="toolTip"> + <string>Can be toggled via a hotkey</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QGroupBox" name="sensitivity_box"> + <property name="title"> + <string>Sensitivity</string> + </property> + <layout class="QGridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="x_sensitivity_label"> + <property name="text"> + <string>Horizontal</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="x_sensitivity"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>50</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="y_sensitivity_label"> + <property name="text"> + <string>Vertical</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="y_sensitivity"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>50</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="deadzone_counterweight_box"> + <property name="title"> + <string>Deadzone counterweight</string> + </property> + <property name="toolTip"> + <string>Counteracts a game's built-in deadzone</string> + </property> + <layout class="QGridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="deadzone_x_counterweight_label"> + <property name="text"> + <string>Horizontal</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="deadzone_x_counterweight"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="deadzone_y_counterweight_label"> + <property name="text"> + <string>Vertical</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="deadzone_y_counterweight"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="decay_box"> + <property name="title"> + <string>Stick decay</string> + </property> + <layout class="QGridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="decay_strength_label"> + <property name="text"> + <string>Strength</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="decay_strength"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>22</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="min_decay_label"> + <property name="text"> + <string>Minimum</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="min_decay"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>5</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="warning_label"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QPushButton" name="default_button"> + <property name="text"> + <string>Default</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="button_box"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 93db47cfd..eb96e6068 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -6,6 +6,7 @@ #include <memory> #include <string> #include <utility> +#include <vector> #include <fmt/format.h> @@ -34,8 +35,10 @@ #include "yuzu/configuration/configure_system.h" #include "yuzu/uisettings.h" #include "yuzu/util/util.h" +#include "yuzu/vk_device_info.h" ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, + std::vector<VkDeviceInfo::Record>& vk_device_records, Core::System& system_) : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} { @@ -48,8 +51,9 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st audio_tab = std::make_unique<ConfigureAudio>(system_, this); cpu_tab = std::make_unique<ConfigureCpu>(system_, this); general_tab = std::make_unique<ConfigureGeneral>(system_, this); - graphics_tab = std::make_unique<ConfigureGraphics>(system_, this); graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this); + graphics_tab = std::make_unique<ConfigureGraphics>( + system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this); input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); system_tab = std::make_unique<ConfigureSystem>(system_, this); @@ -66,8 +70,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("Properties")); - // remove Help question mark button from the title bar - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); addons_tab->SetTitleId(title_id); diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index 4ecc43541..7ec1ded06 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -5,11 +5,13 @@ #include <memory> #include <string> +#include <vector> #include <QDialog> #include <QList> #include "core/file_sys/vfs_types.h" +#include "vk_device_info.h" #include "yuzu/configuration/config.h" namespace Core { @@ -45,6 +47,7 @@ class ConfigurePerGame : public QDialog { public: // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, + std::vector<VkDeviceInfo::Record>& vk_device_records, Core::System& system_); ~ConfigurePerGame() override; @@ -75,8 +78,8 @@ private: std::unique_ptr<ConfigureAudio> audio_tab; std::unique_ptr<ConfigureCpu> cpu_tab; std::unique_ptr<ConfigureGeneral> general_tab; - std::unique_ptr<ConfigureGraphics> graphics_tab; std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; + std::unique_ptr<ConfigureGraphics> graphics_tab; std::unique_ptr<ConfigureInputPerGame> input_tab; std::unique_ptr<ConfigureSystem> system_tab; }; diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index 688c2dd38..71afbc423 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -4,9 +4,11 @@ #include <memory> #include <QKeyEvent> #include <QMenu> +#include <QMessageBox> #include <QTimer> +#include <fmt/format.h> -#include "core/hid/emulated_devices.h" +#include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" @@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, ui->buttonRingAnalogPush, }; - emulated_device = hid_core_.GetEmulatedDevices(); - emulated_device->SaveCurrentConfig(); - emulated_device->EnableConfiguration(); + emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, + .is_npad_service = false, + }; + callback_key = emulated_controller->SetCallback(engine_callback); + is_controller_set = true; LoadConfiguration(); @@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, HandleClick( analog_map_buttons[sub_button_id], [=, this](const Common::ParamPackage& params) { - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); }, InputCommon::Polling::InputType::Stick); }); @@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, connect(analog_button, &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) { QMenu context_menu; - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); context_menu.addAction(tr("Clear"), [&] { - emulated_device->SetRingParam({}); + emulated_controller->SetRingParam(param); analog_map_buttons[sub_button_id]->setText(tr("[not set]")); }); context_menu.addAction(tr("Invert axis"), [&] { const bool invert_value = param.Get("invert_x", "+") == "-"; const std::string invert_str = invert_value ? "+" : "-"; param.Set("invert_x", invert_str); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id2) { analog_map_buttons[sub_button_id2]->setText( @@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, } connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); const auto slider_value = ui->sliderRingAnalogDeadzone->value(); ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value)); param.Set("deadzone", slider_value / 100.0f); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); }); connect(ui->restore_defaults_button, &QPushButton::clicked, this, &ConfigureRingController::RestoreDefaults); + connect(ui->enable_ring_controller_button, &QPushButton::clicked, this, + &ConfigureRingController::EnableRingController); + timeout_timer->setSingleShot(true); connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); @@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, } ConfigureRingController::~ConfigureRingController() { - emulated_device->DisableConfiguration(); + emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); + emulated_controller->DisableConfiguration(); + + if (is_controller_set) { + emulated_controller->DeleteCallback(callback_key); + is_controller_set = false; + } }; void ConfigureRingController::changeEvent(QEvent* event) { @@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() { void ConfigureRingController::UpdateUI() { RetranslateUI(); - const Common::ParamPackage param = emulated_device->GetRingParam(); + const Common::ParamPackage param = emulated_controller->GetRingParam(); for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { auto* const analog_button = analog_map_buttons[sub_button_id]; @@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() { } void ConfigureRingController::ApplyConfiguration() { - emulated_device->DisableConfiguration(); - emulated_device->SaveCurrentConfig(); - emulated_device->EnableConfiguration(); + emulated_controller->DisableConfiguration(); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); } void ConfigureRingController::LoadConfiguration() { @@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() { void ConfigureRingController::RestoreDefaults() { const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys( 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); - emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); + emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string)); UpdateUI(); } +void ConfigureRingController::EnableRingController() { + const auto dialog_title = tr("Error enabling ring input"); + + is_ring_enabled = false; + ui->ring_controller_sensor_value->setText(tr("Not connected")); + + if (!Settings::values.enable_joycon_driver) { + QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled")); + return; + } + + ui->enable_ring_controller_button->setEnabled(false); + ui->enable_ring_controller_button->setText(tr("Configuring")); + // SetPollingMode is blocking. Allow to update the button status before calling the command + repaint(); + + const auto result = emulated_controller->SetPollingMode( + Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring); + switch (result) { + case Common::Input::DriverResult::Success: + is_ring_enabled = true; + break; + case Common::Input::DriverResult::NotSupported: + QMessageBox::warning(this, dialog_title, + tr("The current mapped device doesn't support the ring controller")); + break; + case Common::Input::DriverResult::NoDeviceDetected: + QMessageBox::warning(this, dialog_title, + tr("The current mapped device doesn't have a ring attached")); + break; + default: + QMessageBox::warning(this, dialog_title, + tr("Unexpected driver result %1").arg(static_cast<int>(result))); + break; + } + ui->enable_ring_controller_button->setEnabled(true); + ui->enable_ring_controller_button->setText(tr("Enable")); +} + +void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) { + if (!is_ring_enabled) { + return; + } + if (type != Core::HID::ControllerTriggerType::RingController) { + return; + } + + const auto value = emulated_controller->GetRingSensorValues(); + const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value)); + ui->ring_controller_sensor_value->setText(tex_value); +} + void ConfigureRingController::HandleClick( QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, InputCommon::Polling::InputType type) { @@ -300,7 +371,7 @@ void ConfigureRingController::mousePressEvent(QMouseEvent* event) { } const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); - input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); + input_subsystem->GetMouse()->PressButton(0, 0, button); } void ConfigureRingController::keyPressEvent(QKeyEvent* event) { diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h index 38a9cb716..b23c27906 100644 --- a/src/yuzu/configuration/configure_ringcon.h +++ b/src/yuzu/configuration/configure_ringcon.h @@ -13,7 +13,7 @@ class InputSubsystem; namespace Core::HID { class HIDCore; -class EmulatedDevices; +class EmulatedController; } // namespace Core::HID namespace Ui { @@ -42,6 +42,12 @@ private: /// Restore all buttons to their default values. void RestoreDefaults(); + /// Sets current polling mode to ring input + void EnableRingController(); + + // Handles emulated controller events + void ControllerUpdate(Core::HID::ControllerTriggerType type); + /// Called when the button was pressed. void HandleClick(QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, @@ -78,7 +84,11 @@ private: std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; InputCommon::InputSubsystem* input_subsystem; - Core::HID::EmulatedDevices* emulated_device; + Core::HID::EmulatedController* emulated_controller; + + bool is_ring_enabled{}; + bool is_controller_set{}; + int callback_key; std::unique_ptr<Ui::ConfigureRingController> ui; }; diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui index 9ec634dd4..38ecccc3d 100644 --- a/src/yuzu/configuration/configure_ringcon.ui +++ b/src/yuzu/configuration/configure_ringcon.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>298</width> - <height>339</height> + <width>315</width> + <height>400</height> </rect> </property> <property name="windowTitle"> @@ -23,7 +23,7 @@ </size> </property> <property name="text"> - <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string> + <string>To use Ring-Con, configure player 1 as right Joy-Con (both physical and emulated), and player 2 as left Joy-Con (left physical and dual emulated) before starting the game.</string> </property> <property name="wordWrap"> <bool>true</bool> @@ -46,187 +46,283 @@ </property> </spacer> </item> - <item> - <widget class="QGroupBox" name="RingAnalog"> - <property name="title"> - <string>Ring Sensor Parameters</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="spacing"> - <number>0</number> - </property> - <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <item> + <widget class="QGroupBox" name="RingAnalog"> + <property name="title"> + <string>Virtual Ring Sensor Parameters</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_1"> <property name="spacing"> - <number>3</number> + <number>0</number> </property> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> - <property name="title"> - <string>Pull</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <property name="spacing"> + <number>3</number> </property> - <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> - <property name="spacing"> - <number>3</number> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> + <property name="title"> + <string>Pull</string> </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> + <property name="alignment"> + <set>Qt::AlignCenter</set> </property> - <property name="rightMargin"> - <number>3</number> + <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPull"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Pull</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> + <property name="title"> + <string>Push</string> </property> - <property name="bottomMargin"> - <number>3</number> + <property name="alignment"> + <set>Qt::AlignCenter</set> </property> - <item> - <widget class="QPushButton" name="buttonRingAnalogPull"> - <property name="minimumSize"> - <size> - <width>68</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> - <property name="text"> - <string>Pull</string> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPush"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Push</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> </item> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> - <property name="title"> - <string>Push</string> + <item> + <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + <property name="spacing"> + <number>3</number> </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> </property> - <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> - <property name="spacing"> - <number>3</number> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>3</number> - </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> <item> - <widget class="QPushButton" name="buttonRingAnalogPush"> - <property name="minimumSize"> - <size> - <width>68</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> + <widget class="QLabel" name="labelRingAnalogDeadzone"> <property name="text"> - <string>Push</string> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> </property> - </widget> + </widget> </item> - </layout> - </widget> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRingAnalogDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="RingDriver"> + <property name="title"> + <string>Direct Joycon Driver</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> <property name="spacing"> - <number>3</number> + <number>0</number> </property> <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> + <enum>QLayout::SetDefaultConstraint</enum> </property> <property name="leftMargin"> - <number>0</number> + <number>3</number> </property> <property name="topMargin"> - <number>10</number> + <number>6</number> </property> <property name="rightMargin"> - <number>0</number> + <number>3</number> </property> <property name="bottomMargin"> - <number>3</number> + <number>10</number> </property> <item> - <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> - <item> - <widget class="QLabel" name="labelRingAnalogDeadzone"> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>10</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>76</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="enable_ring_controller_label"> + <property name="text"> + <string>Enable Ring Input</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="enable_ring_controller_button"> <property name="text"> - <string>Deadzone: 0%</string> + <string>Enable</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="ring_controller_sensor_label"> + <property name="text"> + <string>Ring Sensor Value</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="ring_controller_sensor_value"> + <property name="text"> + <string>Not connected</string> </property> <property name="alignment"> - <set>Qt::AlignHCenter</set> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> - </widget> + </widget> </item> - </layout> - </item> - <item> - <widget class="QSlider" name="sliderRingAnalogDeadzone"> - <property name="maximum"> - <number>100</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> + </layout> </item> - </layout> - </item> - </layout> - </widget> - </item> + </layout> + </widget> + </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> @@ -273,6 +369,6 @@ <signal>rejected()</signal> <receiver>ConfigureRingController</receiver> <slot>reject()</slot> - </connection> + </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 9ea4c02da..f1ae312c6 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -40,8 +40,6 @@ static bool IsValidLocale(u32 region_index, u32 language_index) { ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) : QWidget(parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} { ui->setupUi(this); - connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, - &ConfigureSystem::RefreshConsoleID); connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) { ui->rng_seed_edit->setEnabled(state == Qt::Checked); @@ -76,9 +74,6 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) locale_check); connect(ui->combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check); - ui->label_console_id->setVisible(Settings::IsConfiguringGlobal()); - ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal()); - SetupPerGameUI(); SetConfiguration(); @@ -116,19 +111,20 @@ void ConfigureSystem::SetConfiguration() { ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time)); ui->device_name_edit->setText( QString::fromUtf8(Settings::values.device_name.GetValue().c_str())); + ui->use_unsafe_extended_memory_layout->setEnabled(enabled); + ui->use_unsafe_extended_memory_layout->setChecked( + Settings::values.use_unsafe_extended_memory_layout.GetValue()); if (Settings::IsConfiguringGlobal()) { 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()); } 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); ConfigurationShared::SetHighlight(ui->label_language, !Settings::values.language_index.UsingGlobal()); @@ -136,8 +132,6 @@ void ConfigureSystem::SetConfiguration() { !Settings::values.region_index.UsingGlobal()); ConfigurationShared::SetHighlight(ui->label_timezone, !Settings::values.time_zone_index.UsingGlobal()); - ConfigurationShared::SetHighlight(ui->label_sound, - !Settings::values.sound_index.UsingGlobal()); } } @@ -150,8 +144,7 @@ void ConfigureSystem::ApplyConfiguration() { if (ui->custom_rtc_checkbox->isChecked()) { Settings::values.custom_rtc = ui->custom_rtc_edit->dateTime().toSecsSinceEpoch(); if (system.IsPoweredOn()) { - const s64 posix_time{*Settings::values.custom_rtc + - Service::Time::TimeManager::GetExternalTimeZoneOffset()}; + const s64 posix_time{*Settings::values.custom_rtc}; system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); } } else { @@ -169,7 +162,9 @@ void ConfigureSystem::ApplyConfiguration() { 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); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout, + ui->use_unsafe_extended_memory_layout, + use_unsafe_extended_memory_layout); if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value @@ -202,29 +197,11 @@ void ConfigureSystem::ApplyConfiguration() { } } -void ConfigureSystem::RefreshConsoleID() { - QMessageBox::StandardButton reply; - QString warning_text = tr("This will replace your current virtual Switch with a new one. " - "Your current virtual Switch will not be recoverable. " - "This might have unexpected effects in games. This might fail, " - "if you use an outdated config savegame. Continue?"); - reply = QMessageBox::critical(this, tr("Warning"), warning_text, - QMessageBox::No | QMessageBox::Yes); - if (reply == QMessageBox::No) { - return; - } - - u64 console_id{}; - ui->label_console_id->setText( - tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); -} - void ConfigureSystem::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { 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()); @@ -237,14 +214,16 @@ void ConfigureSystem::SetupPerGameUI() { Settings::values.region_index.GetValue(true)); ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone, Settings::values.time_zone_index.GetValue(true)); - ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound, - Settings::values.sound_index.GetValue(true)); ConfigurationShared::SetColoredTristate( ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), Settings::values.rng_seed.GetValue().has_value(), Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); + ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout, + Settings::values.use_unsafe_extended_memory_layout, + use_unsafe_extended_memory_layout); + ui->custom_rtc_checkbox->setVisible(false); ui->custom_rtc_edit->setVisible(false); } diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index a7f086258..ce1a91601 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -35,14 +35,13 @@ private: void ReadSystemSettings(); - void RefreshConsoleID(); - void SetupPerGameUI(); std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; ConfigurationShared::CheckState use_rng_seed; + ConfigurationShared::CheckState use_unsafe_extended_memory_layout; Core::System& system; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 0459cd924..e0caecd5e 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -411,7 +411,7 @@ </item> </widget> </item> - <item row="5" column="0"> + <item row="4" column="0"> <widget class="QCheckBox" name="custom_rtc_checkbox"> <property name="text"> <string>Custom RTC</string> @@ -425,54 +425,21 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="5" column="0"> <widget class="QCheckBox" name="rng_seed_checkbox"> <property name="text"> <string>RNG Seed</string> </property> </widget> </item> - <item row="7" column="0"> + <item row="6" column="0"> <widget class="QLabel" name="device_name_label"> <property name="text"> <string>Device Name</string> </property> </widget> </item> - <item row="3" column="1"> - <widget class="QComboBox" name="combo_sound"> - <item> - <property name="text"> - <string>Mono</string> - </property> - </item> - <item> - <property name="text"> - <string>Stereo</string> - </property> - </item> - <item> - <property name="text"> - <string>Surround</string> - </property> - </item> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_console_id"> - <property name="text"> - <string>Console ID:</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_sound"> - <property name="text"> - <string>Sound output mode</string> - </property> - </widget> - </item> - <item row="5" column="1"> + <item row="4" column="1"> <widget class="QDateTimeEdit" name="custom_rtc_edit"> <property name="minimumDate"> <date> @@ -483,14 +450,14 @@ </property> </widget> </item> - <item row="7" column="1"> + <item row="6" column="1"> <widget class="QLineEdit" name="device_name_edit"> <property name="maxLength"> <number>128</number> </property> </widget> </item> - <item row="6" column="1"> + <item row="5" column="1"> <widget class="QLineEdit" name="rng_seed_edit"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> @@ -511,19 +478,10 @@ </property> </widget> </item> - <item row="4" column="1"> - <widget class="QPushButton" name="button_regenerate_console_id"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="layoutDirection"> - <enum>Qt::RightToLeft</enum> - </property> + <item row="7" column="0"> + <widget class="QCheckBox" name="use_unsafe_extended_memory_layout"> <property name="text"> - <string>Regenerate</string> + <string>Unsafe extended memory layout (8GB DRAM)</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 1edc5f1f3..5a545aa70 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -17,7 +17,6 @@ ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("TAS Configuration")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); connect(ui->tas_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 9bb69cab1..41ef4250a 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -58,13 +58,16 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() { std::vector<std::string> profile_names; profile_names.reserve(map_profiles.size()); - for (const auto& [profile_name, config] : map_profiles) { + auto it = map_profiles.cbegin(); + while (it != map_profiles.cend()) { + const auto& [profile_name, config] = *it; if (!ProfileExistsInFilesystem(profile_name)) { - DeleteProfile(profile_name); + it = map_profiles.erase(it); continue; } profile_names.push_back(profile_name); + ++it; } std::stable_sort(profile_names.begin(), profile_names.end()); diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 19f3775a3..e2f55ebae 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -20,9 +20,8 @@ ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_, setWindowTitle(tr("Controller P1")); resize(500, 350); setMinimumSize(500, 350); - // Remove the "?" button from the titlebar and enable the maximize button - setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | - Qt::WindowMaximizeButtonHint); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); widget = new PlayerControlPreview(this); refreshConfiguration(); diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index d3e2d3c12..493ee0b17 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -49,9 +49,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Di setObjectName(QStringLiteral("MicroProfile")); setWindowTitle(tr("&MicroProfile")); resize(1000, 600); - // Remove the "?" button from the titlebar and enable the maximize button - setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | - Qt::WindowMaximizeButtonHint); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); #if MICROPROFILE_ENABLED diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 7f7c5fc42..0783a2430 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -112,33 +112,6 @@ QString WaitTreeText::GetText() const { return text; } -WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table, - Core::System& system_) - : mutex_address{mutex_address_}, system{system_} { - mutex_value = system.Memory().Read32(mutex_address); - owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Svc::HandleWaitMask); - owner = handle_table.GetObject<Kernel::KThread>(owner_handle).GetPointerUnsafe(); -} - -WaitTreeMutexInfo::~WaitTreeMutexInfo() = default; - -QString WaitTreeMutexInfo::GetText() const { - return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char{'0'}); -} - -std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const { - const bool has_waiters = (mutex_value & Kernel::Svc::HandleWaitMask) != 0; - - std::vector<std::unique_ptr<WaitTreeItem>> list; - list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters))); - list.push_back(std::make_unique<WaitTreeText>( - tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char{'0'}))); - if (owner != nullptr) { - list.push_back(std::make_unique<WaitTreeThread>(*owner, system)); - } - return list; -} - WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_) : thread{thread_}, system{system_} {} WaitTreeCallstack::~WaitTreeCallstack() = default; @@ -182,10 +155,9 @@ bool WaitTreeExpandableItem::IsExpandable() const { } QString WaitTreeSynchronizationObject::GetText() const { - return tr("[%1] %2 %3") + return tr("[%1] %2") .arg(object.GetId()) - .arg(QString::fromStdString(object.GetTypeObj().GetName()), - QString::fromStdString(object.GetName())); + .arg(QString::fromStdString(object.GetTypeObj().GetName())); } std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make( @@ -217,26 +189,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChi return list; } -WaitTreeObjectList::WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, - bool w_all, Core::System& system_) - : object_list(list), wait_all(w_all), system{system_} {} - -WaitTreeObjectList::~WaitTreeObjectList() = default; - -QString WaitTreeObjectList::GetText() const { - if (wait_all) - return tr("waiting for all objects"); - return tr("waiting for one of the following objects"); -} - -std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() const { - std::vector<std::unique_ptr<WaitTreeItem>> list(object_list.size()); - std::transform(object_list.begin(), object_list.end(), list.begin(), [this](const auto& t) { - return WaitTreeSynchronizationObject::make(*t, system); - }); - return list; -} - WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread, Core::System& system_) : WaitTreeSynchronizationObject(thread, system_), system{system_} {} WaitTreeThread::~WaitTreeThread() = default; @@ -348,32 +300,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); list.push_back(std::make_unique<WaitTreeText>( - tr("ideal core = %1").arg(thread.GetIdealCoreForDebugging()))); - list.push_back(std::make_unique<WaitTreeText>( tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask()))); - list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID()))); + list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") .arg(thread.GetPriority()) .arg(thread.GetBasePriority()))); list.push_back(std::make_unique<WaitTreeText>( tr("last running ticks = %1").arg(thread.GetLastScheduledTick()))); - const VAddr mutex_wait_address = thread.GetMutexWaitAddressForDebugging(); - if (mutex_wait_address != 0) { - const auto& handle_table = thread.GetOwnerProcess()->GetHandleTable(); - list.push_back( - std::make_unique<WaitTreeMutexInfo>(mutex_wait_address, handle_table, system)); - } else { - list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); - } - - if (thread.GetState() == Kernel::ThreadState::Waiting && - thread.GetWaitReasonForDebugging() == - Kernel::ThreadWaitReasonForDebugging::Synchronization) { - list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjectsForDebugging(), - thread.IsCancellable(), system)); - } - list.push_back(std::make_unique<WaitTreeCallstack>(thread, system)); return list; diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 7e528b592..23c329fbe 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -74,25 +74,6 @@ public: bool IsExpandable() const override; }; -class WaitTreeMutexInfo : public WaitTreeExpandableItem { - Q_OBJECT -public: - explicit WaitTreeMutexInfo(VAddr mutex_address_, const Kernel::KHandleTable& handle_table, - Core::System& system_); - ~WaitTreeMutexInfo() override; - - QString GetText() const override; - std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; - -private: - VAddr mutex_address{}; - u32 mutex_value{}; - Kernel::Handle owner_handle{}; - Kernel::KThread* owner{}; - - Core::System& system; -}; - class WaitTreeCallstack : public WaitTreeExpandableItem { Q_OBJECT public: @@ -127,23 +108,6 @@ private: Core::System& system; }; -class WaitTreeObjectList : public WaitTreeExpandableItem { - Q_OBJECT -public: - WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, bool wait_all, - Core::System& system_); - ~WaitTreeObjectList() override; - - QString GetText() const override; - std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; - -private: - const std::vector<Kernel::KSynchronizationObject*>& object_list; - bool wait_all; - - Core::System& system; -}; - class WaitTreeThread : public WaitTreeSynchronizationObject { Q_OBJECT public: diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp index c351e9b83..ac2fc1bcb 100644 --- a/src/yuzu/discord_impl.cpp +++ b/src/yuzu/discord_impl.cpp @@ -4,7 +4,10 @@ #include <chrono> #include <string> #include <discord_rpc.h> +#include <fmt/format.h> +#include <httplib.h> #include "common/common_types.h" +#include "common/string_util.h" #include "core/core.h" #include "core/loader/loader.h" #include "yuzu/discord_impl.h" @@ -14,7 +17,6 @@ namespace DiscordRPC { DiscordImpl::DiscordImpl(Core::System& system_) : system{system_} { DiscordEventHandlers handlers{}; - // The number is the client ID for yuzu, it's used for images and the // application name Discord_Initialize("712465656758665259", &handlers, 1, nullptr); @@ -29,23 +31,76 @@ void DiscordImpl::Pause() { Discord_ClearPresence(); } +static std::string GetGameString(const std::string& title) { + // Convert to lowercase + std::string icon_name = Common::ToLower(title); + + // Replace spaces with dashes + std::replace(icon_name.begin(), icon_name.end(), ' ', '-'); + + // Remove non-alphanumeric characters but keep dashes + std::erase_if(icon_name, [](char c) { return !std::isalnum(c) && c != '-'; }); + + // Remove dashes from the start and end of the string + icon_name.erase(icon_name.begin(), std::find_if(icon_name.begin(), icon_name.end(), + [](int ch) { return ch != '-'; })); + icon_name.erase( + std::find_if(icon_name.rbegin(), icon_name.rend(), [](int ch) { return ch != '-'; }).base(), + icon_name.end()); + + // Remove double dashes + icon_name.erase(std::unique(icon_name.begin(), icon_name.end(), + [](char a, char b) { return a == '-' && b == '-'; }), + icon_name.end()); + + return icon_name; +} + void DiscordImpl::Update() { s64 start_time = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()) .count(); + const std::string default_text = "yuzu is an emulator for the Nintendo Switch"; + const std::string default_image = "yuzu_logo"; + std::string game_cover_url = "https://yuzu-emu.org"; std::string title; - if (system.IsPoweredOn()) { - system.GetAppLoader().ReadTitle(title); - } + DiscordRichPresence presence{}; - presence.largeImageKey = "yuzu_logo"; - presence.largeImageText = "yuzu is an emulator for the Nintendo Switch"; + if (system.IsPoweredOn()) { + system.GetAppLoader().ReadTitle(title); + + // Used to format Icon URL for yuzu website game compatibility page + std::string icon_name = GetGameString(title); + + // New Check for game cover + httplib::Client cli(game_cover_url); + cli.set_connection_timeout(std::chrono::seconds(3)); + cli.set_read_timeout(std::chrono::seconds(3)); + + if (auto res = cli.Head(fmt::format("/images/game/boxart/{}.png", icon_name))) { + if (res->status == 200) { + game_cover_url += fmt::format("/images/game/boxart/{}.png", icon_name); + } else { + game_cover_url = "yuzu_logo"; + } + } else { + game_cover_url = "yuzu_logo"; + } + + presence.largeImageKey = game_cover_url.c_str(); + presence.largeImageText = title.c_str(); + + presence.smallImageKey = default_image.c_str(); + presence.smallImageText = default_text.c_str(); presence.state = title.c_str(); presence.details = "Currently in game"; } else { - presence.details = "Not in game"; + presence.largeImageKey = default_image.c_str(); + presence.largeImageText = default_text.c_str(); + presence.details = "Currently not in game"; } + presence.startTimestamp = start_time; Discord_UpdatePresence(&presence); } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 22aa19c56..465084fea 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -544,6 +544,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); + QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); remove_menu->addSeparator(); @@ -614,6 +615,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); }); + connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { + emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); + }); connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); }); @@ -870,6 +874,7 @@ void GameList::ToggleFavorite(u64 program_id) { tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); } } + SaveConfig(); } void GameList::AddFavorite(u64 program_id) { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index f7ff93ed9..6c2f75e53 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -45,6 +45,7 @@ enum class GameListRemoveTarget { VkShaderCache, AllShaderCache, CustomConfiguration, + CacheStorage, }; enum class DumpRomFSTarget { @@ -122,6 +123,7 @@ signals: void AddDirectory(); void ShowList(bool show); void PopulatingCompleted(); + void SaveConfig(); private slots: void OnItemExpanded(const QModelIndex& item); diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 84ec4fe13..673bbaa83 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -46,7 +46,6 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo vbox_layout->addLayout(hbox_layout); setLayout(vbox_layout); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Install Files to NAND")); } diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index e263a07a7..b081fff6b 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -153,7 +153,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size } QString estimate; - // If theres a drastic slowdown in the rate, then display an estimate + // If there's a drastic slowdown in the rate, then display an estimate if (now - previous_time > milliseconds{50} || slow_shader_compile_start) { if (!slow_shader_compile_start) { slow_shader_start = steady_clock::now(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e57e02652..24e59f646 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -27,6 +27,7 @@ #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" #include "configuration/configure_tas.h" +#include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/cabinet.h" @@ -91,6 +92,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/microprofile.h" #include "common/scm_rev.h" #include "common/scope_exit.h" +#ifdef _WIN32 +#include "common/windows/timer_resolution.h" +#endif #ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" #endif @@ -143,6 +147,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/startup_checks.h" #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" +#include "yuzu/vk_device_info.h" #ifdef YUZU_DBGHELP #include "yuzu/mini_dump.h" @@ -219,7 +224,7 @@ static void LogRuntimes() { #ifdef _MSC_VER // It is possible that the name of the dll will change. // vcruntime140.dll is for 2015 and onwards - constexpr char runtime_dll_name[] = "vcruntime140.dll"; + static constexpr char runtime_dll_name[] = "vcruntime140.dll"; UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); bool runtime_version_inspection_worked = false; if (sz > 0) { @@ -271,7 +276,7 @@ static QString PrettyProductName() { #ifdef _WIN32 static void OverrideWindowsFont() { - // Qt5 chooses these fonts on Windows and they have fairly ugly alphanumeric/cyrllic characters + // Qt5 chooses these fonts on Windows and they have fairly ugly alphanumeric/cyrillic characters // Asking to use "MS Shell Dlg 2" gives better other chars while leaving the Chinese Characters. const QString startup_font = QApplication::font().family(); const QStringList ugly_fonts = {QStringLiteral("SimSun"), QStringLiteral("PMingLiU")}; @@ -304,6 +309,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan system->Initialize(); Common::Log::Initialize(); + Common::Log::Start(); + LoadTranslation(); setAcceptDrops(true); @@ -377,6 +384,12 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); +#ifdef _WIN32 + LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", + std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>( + Common::Windows::SetCurrentTimerResolutionToMaximum()) + .count()); +#endif UpdateWindowTitle(); show(); @@ -428,10 +441,20 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan renderer_status_button->setDisabled(true); renderer_status_button->setChecked(false); + } else { + VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle()); } #if defined(HAVE_SDL2) && !defined(_WIN32) SDL_InitSubSystem(SDL_INIT_VIDEO); + + // Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to + // the user through their desktop environment. + //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the + //: computer from sleeping + QByteArray wakelock_reason = tr("Running a game").toLatin1(); + SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data()); + // SDL disables the screen saver by default, and setting the hint // SDL_HINT_VIDEO_ALLOW_SCREENSAVER doesn't seem to work, so we just enable the screen saver // for now. @@ -440,8 +463,6 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan SetupPrepareForSleep(); - Common::Log::Start(); - QStringList args = QApplication::arguments(); if (args.size() < 2) { @@ -561,12 +582,16 @@ void GMainWindow::RegisterMetaTypes() { // Cabinet Applet qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters"); - qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>( - "std::shared_ptr<Service::NFP::NfpDevice>"); + qRegisterMetaType<std::shared_ptr<Service::NFC::NfcDevice>>( + "std::shared_ptr<Service::NFC::NfcDevice>"); // Controller Applet qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); + // Profile Select Applet + qRegisterMetaType<Core::Frontend::ProfileSelectParameters>( + "Core::Frontend::ProfileSelectParameters"); + // Software Keyboard Applet qRegisterMetaType<Core::Frontend::KeyboardInitializeParameters>( "Core::Frontend::KeyboardInitializeParameters"); @@ -586,51 +611,82 @@ void GMainWindow::RegisterMetaTypes() { } void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) { - QtAmiiboSettingsDialog dialog(this, parameters, input_subsystem.get(), nfp_device); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) { + cabinet_applet = + new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device); + SCOPE_EXIT({ + cabinet_applet->deleteLater(); + cabinet_applet = nullptr; + }); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) { + cabinet_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + cabinet_applet->setWindowModality(Qt::WindowModal); + + if (cabinet_applet->exec() == QDialog::Rejected) { emit AmiiboSettingsFinished(false, {}); return; } - emit AmiiboSettingsFinished(true, dialog.GetName()); + emit AmiiboSettingsFinished(true, cabinet_applet->GetName()); +} + +void GMainWindow::AmiiboSettingsRequestExit() { + if (cabinet_applet) { + cabinet_applet->reject(); + } } void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { - QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - dialog.exec(); + controller_applet = + new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system); + SCOPE_EXIT({ + controller_applet->deleteLater(); + controller_applet = nullptr; + }); - emit ControllerSelectorReconfigureFinished(); + controller_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | + Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + controller_applet->setWindowModality(Qt::WindowModal); + bool is_success = controller_applet->exec() != QDialog::Rejected; // Don't forget to apply settings. + system->HIDCore().DisableAllControllerConfiguration(); system->ApplySettings(); config->Save(); UpdateStatusButtons(); + + emit ControllerSelectorReconfigureFinished(is_success); } -void GMainWindow::ProfileSelectorSelectProfile() { - QtProfileSelectionDialog dialog(system->HIDCore(), this); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) { +void GMainWindow::ControllerSelectorRequestExit() { + if (controller_applet) { + controller_applet->reject(); + } +} + +void GMainWindow::ProfileSelectorSelectProfile( + const Core::Frontend::ProfileSelectParameters& parameters) { + profile_select_applet = new QtProfileSelectionDialog(system->HIDCore(), this, parameters); + SCOPE_EXIT({ + profile_select_applet->deleteLater(); + profile_select_applet = nullptr; + }); + + profile_select_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | + Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + profile_select_applet->setWindowModality(Qt::WindowModal); + if (profile_select_applet->exec() == QDialog::Rejected) { emit ProfileSelectorFinishedSelection(std::nullopt); return; } const Service::Account::ProfileManager manager; - const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex())); + const auto uuid = manager.GetUser(static_cast<std::size_t>(profile_select_applet->GetIndex())); if (!uuid.has_value()) { emit ProfileSelectorFinishedSelection(std::nullopt); return; @@ -639,6 +695,12 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } +void GMainWindow::ProfileSelectorRequestExit() { + if (profile_select_applet) { + profile_select_applet->reject(); + } +} + void GMainWindow::SoftwareKeyboardInitialize( bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) { if (software_keyboard) { @@ -680,8 +742,10 @@ void GMainWindow::SoftwareKeyboardShowNormal() { const auto y = layout.screen.top; const auto w = layout.screen.GetWidth(); const auto h = layout.screen.GetHeight(); + const auto scale_ratio = devicePixelRatioF(); - software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); + software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y) / scale_ratio), + QSize(w, h) / scale_ratio); } void GMainWindow::SoftwareKeyboardShowTextCheck( @@ -714,9 +778,11 @@ void GMainWindow::SoftwareKeyboardShowInline( (1.0f - appear_parameters.key_top_scale_y)))); const auto w = static_cast<int>(layout.screen.GetWidth() * appear_parameters.key_top_scale_x); const auto h = static_cast<int>(layout.screen.GetHeight() * appear_parameters.key_top_scale_y); + const auto scale_ratio = devicePixelRatioF(); software_keyboard->ShowInlineKeyboard(std::move(appear_parameters), - render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); + render_window->mapToGlobal(QPoint(x, y) / scale_ratio), + QSize(w, h) / scale_ratio); } void GMainWindow::SoftwareKeyboardHideInline() { @@ -759,7 +825,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, return; } - QtNXWebEngineView web_browser_view(this, *system, input_subsystem.get()); + web_applet = new QtNXWebEngineView(this, *system, input_subsystem.get()); ui->action_Pause->setEnabled(false); ui->action_Restart->setEnabled(false); @@ -786,9 +852,9 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, loading_progress.setValue(1); if (is_local) { - web_browser_view.LoadLocalWebPage(main_url, additional_args); + web_applet->LoadLocalWebPage(main_url, additional_args); } else { - web_browser_view.LoadExternalWebPage(main_url, additional_args); + web_applet->LoadExternalWebPage(main_url, additional_args); } if (render_window->IsLoadingComplete()) { @@ -796,13 +862,16 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, } const auto& layout = render_window->GetFramebufferLayout(); - web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); - web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); - web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / - static_cast<qreal>(Layout::ScreenUndocked::Width)); + const auto scale_ratio = devicePixelRatioF(); + web_applet->resize(layout.screen.GetWidth() / scale_ratio, + layout.screen.GetHeight() / scale_ratio); + web_applet->move(layout.screen.left / scale_ratio, + (layout.screen.top / scale_ratio) + menuBar()->height()); + web_applet->setZoomFactor(static_cast<qreal>(layout.screen.GetWidth() / scale_ratio) / + static_cast<qreal>(Layout::ScreenUndocked::Width)); - web_browser_view.setFocus(); - web_browser_view.show(); + web_applet->setFocus(); + web_applet->show(); loading_progress.setValue(2); @@ -815,7 +884,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, // TODO (Morph): Remove this QAction* exit_action = new QAction(tr("Disable Web Applet"), this); - connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { + connect(exit_action, &QAction::triggered, this, [this] { const auto result = QMessageBox::warning( this, tr("Disable Web Applet"), tr("Disabling the web applet can lead to undefined behavior and should only be used " @@ -824,21 +893,21 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) { UISettings::values.disable_web_applet = true; - web_browser_view.SetFinished(true); + web_applet->SetFinished(true); } }); ui->menubar->addAction(exit_action); - while (!web_browser_view.IsFinished()) { + while (!web_applet->IsFinished()) { QCoreApplication::processEvents(); if (!exit_check) { - web_browser_view.page()->runJavaScript( + web_applet->page()->runJavaScript( QStringLiteral("end_applet;"), [&](const QVariant& variant) { exit_check = false; if (variant.toBool()) { - web_browser_view.SetFinished(true); - web_browser_view.SetExitReason( + web_applet->SetFinished(true); + web_applet->SetExitReason( Service::AM::Applets::WebExitReason::EndButtonPressed); } }); @@ -846,22 +915,22 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, exit_check = true; } - if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { - if (!web_browser_view.IsFinished()) { - web_browser_view.SetFinished(true); - web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); + if (web_applet->GetCurrentURL().contains(QStringLiteral("localhost"))) { + if (!web_applet->IsFinished()) { + web_applet->SetFinished(true); + web_applet->SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); } - web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); + web_applet->SetLastURL(web_applet->GetCurrentURL().toStdString()); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - const auto exit_reason = web_browser_view.GetExitReason(); - const auto last_url = web_browser_view.GetLastURL(); + const auto exit_reason = web_applet->GetExitReason(); + const auto last_url = web_applet->GetLastURL(); - web_browser_view.hide(); + web_applet->hide(); render_window->setFocus(); @@ -887,6 +956,15 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, #endif } +void GMainWindow::WebBrowserRequestExit() { +#ifdef YUZU_USE_QT_WEB_ENGINE + if (web_applet) { + web_applet->SetExitReason(Service::AM::Applets::WebExitReason::ExitRequested); + web_applet->SetFinished(true); + } +#endif +} + void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui->action_Report_Compatibility->setVisible(true); @@ -957,6 +1035,56 @@ void GMainWindow::InitializeWidgets() { tas_label->setFocusPolicy(Qt::NoFocus); statusBar()->insertPermanentWidget(0, tas_label); + volume_popup = new QWidget(this); + volume_popup->setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Popup); + volume_popup->setLayout(new QVBoxLayout()); + volume_popup->setMinimumWidth(200); + + volume_slider = new QSlider(Qt::Horizontal); + volume_slider->setObjectName(QStringLiteral("volume_slider")); + volume_slider->setMaximum(200); + volume_slider->setPageStep(5); + connect(volume_slider, &QSlider::valueChanged, this, [this](int percentage) { + Settings::values.audio_muted = false; + const auto volume = static_cast<u8>(percentage); + Settings::values.volume.SetValue(volume); + UpdateVolumeUI(); + }); + volume_popup->layout()->addWidget(volume_slider); + + volume_button = new QPushButton(); + volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); + volume_button->setFocusPolicy(Qt::NoFocus); + volume_button->setCheckable(true); + UpdateVolumeUI(); + connect(volume_button, &QPushButton::clicked, this, [&] { + UpdateVolumeUI(); + volume_popup->setVisible(!volume_popup->isVisible()); + QRect rect = volume_button->geometry(); + QPoint bottomLeft = statusBar()->mapToGlobal(rect.topLeft()); + bottomLeft.setY(bottomLeft.y() - volume_popup->geometry().height()); + volume_popup->setGeometry(QRect(bottomLeft, QSize(rect.width(), rect.height()))); + }); + volume_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(volume_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction( + Settings::values.audio_muted ? tr("Unmute") : tr("Mute"), [this] { + Settings::values.audio_muted = !Settings::values.audio_muted; + UpdateVolumeUI(); + }); + + context_menu.addAction(tr("Reset Volume"), [this] { + Settings::values.volume.SetValue(100); + UpdateVolumeUI(); + }); + + context_menu.exec(volume_button->mapToGlobal(menu_location)); + volume_button->repaint(); + }); + statusBar()->insertPermanentWidget(0, volume_button); + // setup AA button aa_status_button = new QPushButton(); aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -975,6 +1103,19 @@ void GMainWindow::InitializeWidgets() { UpdateAAText(); aa_status_button->setCheckable(true); aa_status_button->setChecked(true); + aa_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(aa_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + for (auto const& aa_text_pair : Config::anti_aliasing_texts_map) { + context_menu.addAction(aa_text_pair.second, [this, aa_text_pair] { + Settings::values.anti_aliasing.SetValue(aa_text_pair.first); + UpdateAAText(); + }); + } + context_menu.exec(aa_status_button->mapToGlobal(menu_location)); + aa_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, aa_status_button); // Setup Filter button @@ -983,14 +1124,22 @@ void GMainWindow::InitializeWidgets() { filter_status_button->setFocusPolicy(Qt::NoFocus); connect(filter_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleAdaptingFilter); - auto filter = Settings::values.scaling_filter.GetValue(); - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor); - } UpdateFilterText(); filter_status_button->setCheckable(true); filter_status_button->setChecked(true); + filter_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(filter_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + for (auto const& filter_text_pair : Config::scaling_filter_texts_map) { + context_menu.addAction(filter_text_pair.second, [this, filter_text_pair] { + Settings::values.scaling_filter.SetValue(filter_text_pair.first); + UpdateFilterText(); + }); + } + context_menu.exec(filter_status_button->mapToGlobal(menu_location)); + filter_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, filter_status_button); // Setup Dock button @@ -1000,14 +1149,47 @@ void GMainWindow::InitializeWidgets() { connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode); dock_status_button->setCheckable(true); UpdateDockedButton(); + dock_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(dock_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) { + context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] { + if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) { + OnToggleDockedMode(); + } + }); + } + context_menu.exec(dock_status_button->mapToGlobal(menu_location)); + dock_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, dock_status_button); + // Setup GPU Accuracy button gpu_accuracy_button = new QPushButton(); gpu_accuracy_button->setObjectName(QStringLiteral("GPUStatusBarButton")); gpu_accuracy_button->setCheckable(true); gpu_accuracy_button->setFocusPolicy(Qt::NoFocus); connect(gpu_accuracy_button, &QPushButton::clicked, this, &GMainWindow::OnToggleGpuAccuracy); UpdateGPUAccuracyButton(); + gpu_accuracy_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(gpu_accuracy_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) { + if (gpu_accuracy_pair.first == Settings::GPUAccuracy::Extreme) { + continue; + } + context_menu.addAction(gpu_accuracy_pair.second, [this, gpu_accuracy_pair] { + Settings::values.gpu_accuracy.SetValue(gpu_accuracy_pair.first); + UpdateGPUAccuracyButton(); + }); + } + context_menu.exec(gpu_accuracy_button->mapToGlobal(menu_location)); + gpu_accuracy_button->repaint(); + }); statusBar()->insertPermanentWidget(0, gpu_accuracy_button); // Setup Renderer API button @@ -1020,6 +1202,24 @@ void GMainWindow::InitializeWidgets() { renderer_status_button->setCheckable(true); renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); + renderer_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(renderer_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& renderer_backend_pair : Config::renderer_backend_texts_map) { + if (renderer_backend_pair.first == Settings::RendererBackend::Null) { + continue; + } + context_menu.addAction( + renderer_backend_pair.second, [this, renderer_backend_pair] { + Settings::values.renderer_backend.SetValue(renderer_backend_pair.first); + UpdateAPIText(); + }); + } + context_menu.exec(renderer_status_button->mapToGlobal(menu_location)); + renderer_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->setVisible(true); @@ -1070,7 +1270,8 @@ void GMainWindow::InitializeRecentFileMenuActions() { UpdateRecentFiles(); } -void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name) { +void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name, + const bool tas_allowed) { static const QString main_window = QStringLiteral("Main Window"); action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name)); action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); @@ -1082,7 +1283,14 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(main_window, action_name, controller); connect( - controller_hotkey, &ControllerShortcut::Activated, this, [action] { action->trigger(); }, + controller_hotkey, &ControllerShortcut::Activated, this, + [action, tas_allowed, this] { + auto [tas_status, current_tas_frame, total_tas_frames] = + input_subsystem->GetTas()->GetStatus(); + if (tas_allowed || tas_status == InputCommon::TasInput::TasState::Stopped) { + action->trigger(); + } + }, Qt::QueuedConnection); } @@ -1099,9 +1307,9 @@ void GMainWindow::InitializeHotkeys() { LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); - LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop")); - LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record")); - LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset")); + LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); + LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true); + LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true); static const QString main_window = QStringLiteral("Main Window"); const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { @@ -1124,34 +1332,21 @@ void GMainWindow::InitializeHotkeys() { &GMainWindow::OnToggleAdaptingFilter); connect_shortcut(QStringLiteral("Change Docked Mode"), &GMainWindow::OnToggleDockedMode); connect_shortcut(QStringLiteral("Change GPU Accuracy"), &GMainWindow::OnToggleGpuAccuracy); - connect_shortcut(QStringLiteral("Audio Mute/Unmute"), - [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); - connect_shortcut(QStringLiteral("Audio Volume Down"), [] { - const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); - int step = 5; - if (current_volume <= 30) { - step = 2; - } - if (current_volume <= 6) { - step = 1; - } - Settings::values.volume.SetValue(std::max(current_volume - step, 0)); - }); - connect_shortcut(QStringLiteral("Audio Volume Up"), [] { - const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); - int step = 5; - if (current_volume < 30) { - step = 2; - } - if (current_volume < 6) { - step = 1; - } - Settings::values.volume.SetValue(current_volume + step); - }); + connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &GMainWindow::OnMute); + connect_shortcut(QStringLiteral("Audio Volume Down"), &GMainWindow::OnDecreaseVolume); + connect_shortcut(QStringLiteral("Audio Volume Up"), &GMainWindow::OnIncreaseVolume); connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); }); connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { + if (Settings::values.mouse_enabled) { + Settings::values.mouse_panning = false; + QMessageBox::warning( + this, tr("Emulated mouse is enabled"), + tr("Real mouse input and mouse panning are incompatible. Please disable the " + "emulated mouse in input advanced settings to allow mouse panning.")); + return; + } Settings::values.mouse_panning = !Settings::values.mouse_panning; if (Settings::values.mouse_panning) { render_window->installEventFilter(render_window); @@ -1258,6 +1453,7 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); connect(game_list, &GameList::PopulatingCompleted, [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); }); + connect(game_list, &GameList::SaveConfig, this, &GMainWindow::OnSaveConfig); connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); @@ -1435,45 +1631,6 @@ void GMainWindow::OnPrepareForSleep(bool prepare_sleep) { } #ifdef __unix__ -static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) { - if (!QDBusConnection::sessionBus().isConnected()) { - return {}; - } - // reference: https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Inhibit - QDBusInterface xdp(QString::fromLatin1("org.freedesktop.portal.Desktop"), - QString::fromLatin1("/org/freedesktop/portal/desktop"), - QString::fromLatin1("org.freedesktop.portal.Inhibit")); - if (!xdp.isValid()) { - LOG_WARNING(Frontend, "Couldn't connect to XDP D-Bus endpoint"); - return {}; - } - QVariantMap options = {}; - //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the - //: computer from sleeping - options.insert(QString::fromLatin1("reason"), - QCoreApplication::translate("GMainWindow", "yuzu is running a game")); - // 0x4: Suspend lock; 0x8: Idle lock - QDBusReply<QDBusObjectPath> reply = - xdp.call(QString::fromLatin1("Inhibit"), - QString::fromLatin1("x11:") + QString::number(window_id, 16), 12U, options); - - if (reply.isValid()) { - return reply.value(); - } - LOG_WARNING(Frontend, "Couldn't read Inhibit reply from XDP: {}", - reply.error().message().toStdString()); - return {}; -} - -static void ReleaseWakeLockLinux(QDBusObjectPath lock) { - if (!QDBusConnection::sessionBus().isConnected()) { - return; - } - QDBusInterface unlocker(QString::fromLatin1("org.freedesktop.portal.Desktop"), lock.path(), - QString::fromLatin1("org.freedesktop.portal.Request")); - unlocker.call(QString::fromLatin1("Close")); -} - std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0}; void GMainWindow::SetupSigInterrupts() { @@ -1526,12 +1683,6 @@ void GMainWindow::PreventOSSleep() { SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); #elif defined(HAVE_SDL2) SDL_DisableScreenSaver(); -#ifdef __unix__ - auto reply = HoldWakeLockLinux(winId()); - if (reply) { - wake_lock = std::move(reply.value()); - } -#endif #endif } @@ -1540,11 +1691,6 @@ void GMainWindow::AllowOSSleep() { SetThreadExecutionState(ES_CONTINUOUS); #elif defined(HAVE_SDL2) SDL_EnableScreenSaver(); -#ifdef __unix__ - if (!wake_lock.path().isEmpty()) { - ReleaseWakeLockLinux(wake_lock); - } -#endif #endif } @@ -1645,8 +1791,9 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p return true; } -bool GMainWindow::SelectAndSetCurrentUser() { - QtProfileSelectionDialog dialog(system->HIDCore(), this); +bool GMainWindow::SelectAndSetCurrentUser( + const Core::Frontend::ProfileSelectParameters& parameters) { + QtProfileSelectionDialog dialog(system->HIDCore(), this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -1692,7 +1839,13 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { - if (SelectAndSetCurrentUser() == false) { + const Core::Frontend::ProfileSelectParameters parameters{ + .mode = Service::AM::Applets::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Applets::UserSelectionPurpose::General, + }; + if (SelectAndSetCurrentUser(parameters) == false) { return; } } @@ -1702,6 +1855,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t } system->SetShuttingDown(false); + game_list->setDisabled(true); // Create and start the emulation thread emu_thread = std::make_unique<EmuThread>(*system); @@ -1767,7 +1921,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} .filename()); } - const bool is_64bit = system->Kernel().CurrentProcess()->Is64BitProcess(); + const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") .arg(QString::fromStdString(title_name), instruction_set_suffix) @@ -1897,6 +2051,9 @@ void GMainWindow::OnEmulationStopped() { // When closing the game, destroy the GLWindow to clear the context after the game is closed render_window->ReleaseRenderTarget(); + // Enable game list + game_list->setEnabled(true); + Settings::RestoreGlobalState(system->IsPoweredOn()); system->HIDCore().ReloadInputDevices(); UpdateStatusButtons(); @@ -1984,7 +2141,13 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target if (has_user_save) { // User save data const auto select_profile = [this] { - QtProfileSelectionDialog dialog(system->HIDCore(), this); + const Core::Frontend::ProfileSelectParameters parameters{ + .mode = Service::AM::Applets::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Applets::UserSelectionPurpose::General, + }; + QtProfileSelectionDialog dialog(system->HIDCore(), this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -2221,6 +2384,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ return tr("Delete All Transferable Shader Caches?"); case GameListRemoveTarget::CustomConfiguration: return tr("Remove Custom Game Configuration?"); + case GameListRemoveTarget::CacheStorage: + return tr("Remove Cache Storage?"); default: return QString{}; } @@ -2244,6 +2409,9 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ case GameListRemoveTarget::CustomConfiguration: RemoveCustomConfiguration(program_id, game_path); break; + case GameListRemoveTarget::CacheStorage: + RemoveCacheStorage(program_id); + break; } } @@ -2333,6 +2501,21 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g } } +void GMainWindow::RemoveCacheStorage(u64 program_id) { + const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); + auto vfs_nand_dir = + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); + + const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath( + *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, + FileSys::SaveDataType::CacheStorage, 0 /* program_id */, {}, 0); + + const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path); + + // Not an error if it wasn't cleared. + Common::FS::RemoveDirRecursively(path); +} + void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target) { const auto failed = [this] { @@ -2642,6 +2825,8 @@ void GMainWindow::OnGameListAddDirectory() { } else { LOG_WARNING(Frontend, "Selected directory is already in the game list"); } + + OnSaveConfig(); } void GMainWindow::OnGameListShowList(bool show) { @@ -2759,8 +2944,7 @@ void GMainWindow::OnMenuInstallToNAND() { ui->action_Install_File_NAND->setEnabled(false); install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); - install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & - ~Qt::WindowMaximizeButtonHint); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); install_progress->setAttribute(Qt::WA_DeleteOnClose, true); install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); install_progress->show(); @@ -2845,7 +3029,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { return false; } - std::array<u8, 0x1000> buffer{}; + std::vector<u8> buffer(1_MiB); for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { if (install_progress->wasCanceled()) { @@ -3004,8 +3188,10 @@ void GMainWindow::OnRestartGame() { if (!system->IsPoweredOn()) { return; } - // Make a copy since BootGame edits game_path - BootGame(QString(current_game_path)); + // Make a copy since ShutdownGame edits game_path + const auto current_game = QString(current_game_path); + ShutdownGame(); + BootGame(current_game); } void GMainWindow::OnPauseGame() { @@ -3056,13 +3242,23 @@ void GMainWindow::OnSaveConfig() { } void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { - OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), - Qt::AlignLeft | Qt::AlignVCenter); - dialog.exec(); + error_applet = new OverlayDialog(render_window, *system, error_code, error_text, QString{}, + tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); + SCOPE_EXIT({ + error_applet->deleteLater(); + error_applet = nullptr; + }); + error_applet->exec(); emit ErrorDisplayFinished(); } +void GMainWindow::ErrorDisplayRequestExit() { + if (error_applet) { + error_applet->reject(); + } +} + void GMainWindow::OnMenuReportCompatibility() { #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__) const auto& caps = Common::GetCPUCaps(); @@ -3257,9 +3453,11 @@ void GMainWindow::ResetWindowSize1080() { void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); + const auto old_language_index = Settings::values.language_index.GetValue(); Settings::SetConfiguringGlobal(true); - ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, + ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), + vk_device_records, *system, !multiplayer_state->IsHostingPublicRoom()); connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, &GMainWindow::OnLanguageChanged); @@ -3325,7 +3523,7 @@ void GMainWindow::OnConfigure() { emit UpdateThemedIcons(); const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); - if (reload) { + if (reload || Settings::values.language_index.GetValue() != old_language_index) { game_list->PopulateAsync(UISettings::values.game_dirs); } @@ -3369,6 +3567,7 @@ void GMainWindow::OnConfigureTas() { return; } else if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); + OnSaveConfig(); } } @@ -3463,6 +3662,39 @@ void GMainWindow::OnToggleGpuAccuracy() { UpdateGPUAccuracyButton(); } +void GMainWindow::OnMute() { + Settings::values.audio_muted = !Settings::values.audio_muted; + UpdateVolumeUI(); +} + +void GMainWindow::OnDecreaseVolume() { + Settings::values.audio_muted = false; + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); + int step = 5; + if (current_volume <= 30) { + step = 2; + } + if (current_volume <= 6) { + step = 1; + } + Settings::values.volume.SetValue(std::max(current_volume - step, 0)); + UpdateVolumeUI(); +} + +void GMainWindow::OnIncreaseVolume() { + Settings::values.audio_muted = false; + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); + int step = 5; + if (current_volume < 30) { + step = 2; + } + if (current_volume < 6) { + step = 1; + } + Settings::values.volume.SetValue(current_volume + step); + UpdateVolumeUI(); +} + void GMainWindow::OnToggleAdaptingFilter() { auto filter = Settings::values.scaling_filter.GetValue(); if (filter == Settings::ScalingFilter::LastFilter) { @@ -3470,10 +3702,6 @@ void GMainWindow::OnToggleAdaptingFilter() { } else { filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1); } - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - filter = Settings::ScalingFilter::NearestNeighbor; - } Settings::values.scaling_filter.SetValue(filter); filter_status_button->setChecked(true); UpdateFilterText(); @@ -3492,7 +3720,7 @@ void GMainWindow::OnToggleGraphicsAPI() { } void GMainWindow::OnConfigurePerGame() { - const u64 title_id = system->GetCurrentProcessProgramID(); + const u64 title_id = system->GetApplicationProcessProgramID(); OpenPerGameConfiguration(title_id, current_game_path.toStdString()); } @@ -3500,7 +3728,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file const auto v_file = Core::GetGameFileFromPath(vfs, file_name); Settings::SetConfiguringGlobal(false); - ConfigurePerGame dialog(this, title_id, file_name, *system); + ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *system); dialog.LoadFromFile(v_file); const auto result = dialog.exec(); @@ -3533,7 +3761,7 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st const std::string& command, const std::string& arguments, const std::string& categories, const std::string& keywords) { #if defined(__linux__) || defined(__FreeBSD__) - // This desktop file template was writting referencing + // This desktop file template was writing referencing // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html std::string shortcut_contents{}; shortcut_contents.append("[Desktop Entry]\n"); @@ -3571,7 +3799,7 @@ void GMainWindow::OnLoadAmiibo() { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); // Remove amiibo if one is connected - if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; @@ -3599,7 +3827,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); const QString title = tr("Error loading Amiibo data"); // Remove amiibo if one is connected - if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; @@ -3651,7 +3879,7 @@ void GMainWindow::OnCaptureScreenshot() { return; } - const u64 title_id = system->GetCurrentProcessProgramID(); + const u64 title_id = system->GetApplicationProcessProgramID(); const auto screenshot_path = QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)); const auto date = @@ -3835,93 +4063,55 @@ void GMainWindow::UpdateStatusBar() { } void GMainWindow::UpdateGPUAccuracyButton() { - switch (Settings::values.gpu_accuracy.GetValue()) { - case Settings::GPUAccuracy::Normal: { - gpu_accuracy_button->setText(tr("GPU NORMAL")); - gpu_accuracy_button->setChecked(false); - break; - } - case Settings::GPUAccuracy::High: { - gpu_accuracy_button->setText(tr("GPU HIGH")); - gpu_accuracy_button->setChecked(true); - break; - } - case Settings::GPUAccuracy::Extreme: { - gpu_accuracy_button->setText(tr("GPU EXTREME")); - gpu_accuracy_button->setChecked(true); - break; - } - default: { - gpu_accuracy_button->setText(tr("GPU ERROR")); - gpu_accuracy_button->setChecked(true); - break; - } - } + const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue(); + const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second; + gpu_accuracy_button->setText(gpu_accuracy_text.toUpper()); + gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GPUAccuracy::Normal); } void GMainWindow::UpdateDockedButton() { const bool is_docked = Settings::values.use_docked_mode.GetValue(); dock_status_button->setChecked(is_docked); - dock_status_button->setText(is_docked ? tr("DOCKED") : tr("HANDHELD")); + dock_status_button->setText( + Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper()); } void GMainWindow::UpdateAPIText() { const auto api = Settings::values.renderer_backend.GetValue(); - switch (api) { - case Settings::RendererBackend::OpenGL: - renderer_status_button->setText(tr("OPENGL")); - break; - case Settings::RendererBackend::Vulkan: - renderer_status_button->setText(tr("VULKAN")); - break; - case Settings::RendererBackend::Null: - renderer_status_button->setText(tr("NULL")); - break; - } + const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second; + renderer_status_button->setText( + api == Settings::RendererBackend::OpenGL + ? tr("%1 %2").arg( + renderer_status_text.toUpper(), + Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue()) + ->second) + : renderer_status_text.toUpper()); } void GMainWindow::UpdateFilterText() { const auto filter = Settings::values.scaling_filter.GetValue(); - switch (filter) { - case Settings::ScalingFilter::NearestNeighbor: - filter_status_button->setText(tr("NEAREST")); - break; - case Settings::ScalingFilter::Bilinear: - filter_status_button->setText(tr("BILINEAR")); - break; - case Settings::ScalingFilter::Bicubic: - filter_status_button->setText(tr("BICUBIC")); - break; - case Settings::ScalingFilter::Gaussian: - filter_status_button->setText(tr("GAUSSIAN")); - break; - case Settings::ScalingFilter::ScaleForce: - filter_status_button->setText(tr("SCALEFORCE")); - break; - case Settings::ScalingFilter::Fsr: - filter_status_button->setText(tr("FSR")); - break; - default: - filter_status_button->setText(tr("BILINEAR")); - break; - } + const auto filter_text = Config::scaling_filter_texts_map.find(filter)->second; + filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR") + : filter_text.toUpper()); } void GMainWindow::UpdateAAText() { const auto aa_mode = Settings::values.anti_aliasing.GetValue(); - switch (aa_mode) { - case Settings::AntiAliasing::None: - aa_status_button->setText(tr("NO AA")); - break; - case Settings::AntiAliasing::Fxaa: - aa_status_button->setText(tr("FXAA")); - break; - case Settings::AntiAliasing::Smaa: - aa_status_button->setText(tr("SMAA")); - break; - default: - aa_status_button->setText(tr("NO AA")); - break; + const auto aa_text = Config::anti_aliasing_texts_map.find(aa_mode)->second; + aa_status_button->setText(aa_mode == Settings::AntiAliasing::None + ? QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "NO AA")) + : aa_text.toUpper()); +} + +void GMainWindow::UpdateVolumeUI() { + const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); + volume_slider->setValue(volume_value); + if (Settings::values.audio_muted) { + volume_button->setChecked(false); + volume_button->setText(tr("VOLUME: MUTE")); + } else { + volume_button->setChecked(true); + volume_button->setText(tr("VOLUME: %1%", "Volume percentage (e.g. 50%)").arg(volume_value)); } } @@ -3933,6 +4123,7 @@ void GMainWindow::UpdateStatusButtons() { UpdateDockedButton(); UpdateFilterText(); UpdateAAText(); + UpdateVolumeUI(); } void GMainWindow::UpdateUISettings() { @@ -4022,6 +4213,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + bool all_keys_present{true}; + if (keys.BaseDeriveNecessary()) { Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; @@ -4046,6 +4239,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors += tr(" - Missing PRODINFO"); } if (!errors.isEmpty()) { + all_keys_present = false; QMessageBox::warning( this, tr("Derivation Components Missing"), tr("Encryption keys are missing. " @@ -4073,11 +4267,40 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { system->GetFileSystemController().CreateFactories(*vfs); + if (all_keys_present && !this->CheckSystemArchiveDecryption()) { + LOG_WARNING(Frontend, "Mii model decryption failed"); + QMessageBox::warning( + this, tr("System Archive Decryption Failed"), + tr("Encryption keys failed to decrypt firmware. " + "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu " + "quickstart guide</a> to get all your keys, firmware and " + "games.")); + } + if (behavior == ReinitializeKeyBehavior::Warning) { game_list->PopulateAsync(UISettings::values.game_dirs); } } +bool GMainWindow::CheckSystemArchiveDecryption() { + constexpr u64 MiiModelId = 0x0100000000000802; + + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + // Not having system BIS files is not an error. + return true; + } + + auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data); + if (!mii_nca) { + // Not having the Mii model is not an error. + return true; + } + + // Return whether we are able to decrypt the RomFS of the Mii model. + return mii_nca->GetRomFS().get() != nullptr; +} + std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id) { const auto dlc_entries = @@ -4402,6 +4625,55 @@ void GMainWindow::changeEvent(QEvent* event) { #undef main #endif +static void SetHighDPIAttributes() { +#ifdef _WIN32 + // For Windows, we want to avoid scaling artifacts on fractional scaling ratios. + // This is done by setting the optimal scaling policy for the primary screen. + + // Create a temporary QApplication. + int temp_argc = 0; + char** temp_argv = nullptr; + QApplication temp{temp_argc, temp_argv}; + + // Get the current screen geometry. + const QScreen* primary_screen = QGuiApplication::primaryScreen(); + if (primary_screen == nullptr) { + return; + } + + const QRect screen_rect = primary_screen->geometry(); + const int real_width = screen_rect.width(); + const int real_height = screen_rect.height(); + const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f; + + // Recommended minimum width and height for proper window fit. + // Any screen with a lower resolution than this will still have a scale of 1. + constexpr float minimum_width = 1350.0f; + constexpr float minimum_height = 900.0f; + + const float width_ratio = std::max(1.0f, real_width / minimum_width); + const float height_ratio = std::max(1.0f, real_height / minimum_height); + + // Get the lower of the 2 ratios and truncate, this is the maximum integer scale. + const float max_ratio = std::trunc(std::min(width_ratio, height_ratio)); + + if (max_ratio > real_ratio) { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Round); + } else { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Floor); + } +#else + // Other OSes should be better than Windows at fractional scaling. + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +} + int main(int argc, char* argv[]) { std::unique_ptr<Config> config = std::make_unique<Config>(); bool has_broken_vulkan = false; @@ -4457,8 +4729,16 @@ int main(int argc, char* argv[]) { } #endif + SetHighDPIAttributes(); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // Disables the "?" button on all dialogs. Disabled by default on Qt6. + QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif + // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + QApplication app(argc, argv); #ifdef _WIN32 diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0f61abc7a..2cfb96257 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -37,6 +37,8 @@ class QLabel; class MultiplayerState; class QPushButton; class QProgressDialog; +class QSlider; +class QHBoxLayout; class WaitTreeWidget; enum class GameListOpenTarget; enum class GameListRemoveTarget; @@ -45,7 +47,11 @@ enum class DumpRomFSTarget; enum class InstalledEntryType; class GameListPlaceholder; +class QtAmiiboSettingsDialog; +class QtControllerSelectorDialog; +class QtProfileSelectionDialog; class QtSoftwareKeyboardDialog; +class QtNXWebEngineView; enum class StartGameType { Normal, // Can use custom configuration @@ -63,6 +69,7 @@ struct ControllerParameters; struct InlineAppearParameters; struct InlineTextParameters; struct KeyboardInitializeParameters; +struct ProfileSelectParameters; } // namespace Core::Frontend namespace DiscordRPC { @@ -86,9 +93,9 @@ enum class SwkbdReplyType : u32; enum class WebExitReason : u32; } // namespace Service::AM::Applets -namespace Service::NFP { -class NfpDevice; -} // namespace Service::NFP +namespace Service::NFC { +class NfcDevice; +} // namespace Service::NFC namespace Ui { class MainWindow; @@ -111,6 +118,10 @@ enum class ReinitializeKeyBehavior { Warning, }; +namespace VkDeviceInfo { +class Record; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -159,7 +170,7 @@ signals: void AmiiboSettingsFinished(bool is_success, const std::string& name); - void ControllerSelectorReconfigureFinished(); + void ControllerSelectorReconfigureFinished(bool is_success); void ErrorDisplayFinished(); @@ -181,9 +192,11 @@ public slots: void OnExit(); void OnSaveConfig(); void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device); + void AmiiboSettingsRequestExit(); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); + void ControllerSelectorRequestExit(); void SoftwareKeyboardInitialize( bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); void SoftwareKeyboardShowNormal(); @@ -194,15 +207,19 @@ public slots: void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); void SoftwareKeyboardExit(); void ErrorDisplayDisplayError(QString error_code, QString error_text); - void ProfileSelectorSelectProfile(); + void ErrorDisplayRequestExit(); + void ProfileSelectorSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters); + void ProfileSelectorRequestExit(); void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local); + void WebBrowserRequestExit(); void OnAppFocusStateChanged(Qt::ApplicationState state); void OnTasStateChanged(); private: /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. - void LinkActionShortcut(QAction* action, const QString& action_name); + void LinkActionShortcut(QAction* action, const QString& action_name, + const bool tas_allowed = false); void RegisterMetaTypes(); @@ -231,7 +248,7 @@ private: void SetDiscordEnabled(bool state); void LoadAmiibo(const QString& filename); - bool SelectAndSetCurrentUser(); + bool SelectAndSetCurrentUser(const Core::Frontend::ProfileSelectParameters& parameters); /** * Stores the filename in the recently loaded files list. @@ -312,6 +329,9 @@ private slots: void OnMenuRecentFile(); void OnConfigure(); void OnConfigureTas(); + void OnDecreaseVolume(); + void OnIncreaseVolume(); + void OnMute(); void OnTasStartStop(); void OnTasRecord(); void OnTasReset(); @@ -354,6 +374,7 @@ private: void RemoveVulkanDriverPipelineCache(u64 program_id); void RemoveAllTransferableShaderCaches(u64 program_id); void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); + void RemoveCacheStorage(u64 program_id); std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); InstallResult InstallNSPXCI(const QString& filename); InstallResult InstallNCA(const QString& filename); @@ -364,6 +385,7 @@ private: void UpdateAPIText(); void UpdateFilterText(); void UpdateAAText(); + void UpdateVolumeUI(); void UpdateStatusBar(); void UpdateGPUAccuracyButton(); void UpdateStatusButtons(); @@ -376,6 +398,7 @@ private: void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); bool CheckDarkMode(); + bool CheckSystemArchiveDecryption(); QString GetTasStateDescription() const; bool CreateShortcut(const std::string& shortcut_path, const std::string& title, @@ -399,6 +422,8 @@ private: GameListPlaceholder* game_list_placeholder; + std::vector<VkDeviceInfo::Record> vk_device_records; + // Status bar elements QLabel* message_label = nullptr; QLabel* shader_building_label = nullptr; @@ -412,6 +437,9 @@ private: QPushButton* dock_status_button = nullptr; QPushButton* filter_status_button = nullptr; QPushButton* aa_status_button = nullptr; + QPushButton* volume_button = nullptr; + QWidget* volume_popup = nullptr; + QSlider* volume_slider = nullptr; QTimer status_bar_update_timer; std::unique_ptr<Config> config; @@ -457,7 +485,12 @@ private: QString last_filename_booted; // Applets + QtAmiiboSettingsDialog* cabinet_applet = nullptr; + QtControllerSelectorDialog* controller_applet = nullptr; + QtProfileSelectionDialog* profile_select_applet = nullptr; + QDialog* error_applet = nullptr; QtSoftwareKeyboardDialog* software_keyboard = nullptr; + QtNXWebEngineView* web_applet = nullptr; // True if amiibo file select is visible bool is_amiibo_file_select_active{}; @@ -471,8 +504,6 @@ private: #ifdef __unix__ QSocketNotifier* sig_interrupt_notifier; static std::array<int, 3> sig_interrupt_fds; - - QDBusObjectPath wake_lock{}; #endif protected: diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index cbd52da85..d71cc23a7 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -81,20 +81,13 @@ void DirectConnectWindow::Connect() { } } } - switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) { - case ConnectionType::TraversalServer: - break; - case ConnectionType::IP: - if (!ui->ip->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); - return; - } - if (!ui->port->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); - return; - } - break; + if (!ui->ip->hasAcceptableInput()) { + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); + return; + } + if (!ui->port->hasAcceptableInput()) { + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); + return; } // Store settings diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui index 57d6ec25a..0dd4e6829 100644 --- a/src/yuzu/multiplayer/direct_connect.ui +++ b/src/yuzu/multiplayer/direct_connect.ui @@ -27,19 +27,10 @@ <number>0</number> </property> <item> - <widget class="QComboBox" name="connection_type"> - <item> - <property name="text"> - <string>IP Address</string> - </property> - </item> - </widget> - </item> - <item> <widget class="QWidget" name="ip_container" native="true"> <layout class="QHBoxLayout" name="ip_layout"> <property name="leftMargin"> - <number>5</number> + <number>0</number> </property> <property name="topMargin"> <number>0</number> @@ -53,17 +44,17 @@ <item> <widget class="QLabel" name="label_2"> <property name="text"> - <string>IP</string> + <string>Server Address</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="ip"> <property name="toolTip"> - <string><html><head/><body><p>IPv4 address of the host</p></body></html></string> + <string><html><head/><body><p>Server address of the host</p></body></html></string> </property> <property name="maxLength"> - <number>16</number> + <number>253</number> </property> </widget> </item> @@ -85,6 +76,12 @@ <property name="placeholderText"> <string notr="true" extracomment="placeholder string that tells user default port">24872</string> </property> + <property name="maximumSize"> + <size> + <width>65</width> + <height>50</height> + </size> + </property> </widget> </item> </layout> diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 08c275696..387f6f7c9 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -77,6 +77,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, // UI Buttons connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); + connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); @@ -277,7 +278,7 @@ void Lobby::OnRefreshLobby() { } } - // Reenable the refresh button and resize the columns + // Re-enable the refresh button and resize the columns ui->refresh_list->setEnabled(true); ui->refresh_list->setText(tr("Refresh List")); ui->room_list->header()->stretchLastSection(); @@ -329,6 +330,16 @@ bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s return true; } + // filter by empty rooms + if (filter_empty) { + QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); + int player_count = + sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size(); + if (player_count == 0) { + return false; + } + } + // filter by filled rooms if (filter_full) { QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); @@ -399,6 +410,11 @@ void LobbyFilterProxyModel::SetFilterOwned(bool filter) { invalidate(); } +void LobbyFilterProxyModel::SetFilterEmpty(bool filter) { + filter_empty = filter; + invalidate(); +} + void LobbyFilterProxyModel::SetFilterFull(bool filter) { filter_full = filter; invalidate(); diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 300dad13e..2674ae7c3 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -130,12 +130,14 @@ public: public slots: void SetFilterOwned(bool); + void SetFilterEmpty(bool); void SetFilterFull(bool); void SetFilterSearch(const QString&); private: QStandardItemModel* game_list; bool filter_owned = false; + bool filter_empty = false; bool filter_full = false; QString filter_search; }; diff --git a/src/yuzu/multiplayer/lobby.ui b/src/yuzu/multiplayer/lobby.ui index 4c9901c9a..0ef0ef762 100644 --- a/src/yuzu/multiplayer/lobby.ui +++ b/src/yuzu/multiplayer/lobby.ui @@ -78,6 +78,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="hide_empty"> + <property name="text"> + <string>Hide Empty Rooms</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="hide_full"> <property name="text"> <string>Hide Full Rooms</string> diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index 285bb150d..d82ca9aee 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -112,7 +112,7 @@ void MultiplayerState::SetNotificationStatus(NotificationStatus status) { void MultiplayerState::UpdateNotificationStatus() { switch (notification_status) { - case NotificationStatus::Unitialized: + case NotificationStatus::Uninitialized: status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); status_text->setText(tr("Not Connected. Click here to find a room!")); leave_room->setEnabled(false); diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index 5d681c5c6..d6149838f 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -23,7 +23,7 @@ class MultiplayerState : public QWidget { public: enum class NotificationStatus { - Unitialized, + Uninitialized, Disconnected, Connected, Notification, @@ -98,7 +98,7 @@ private: QAction* show_room; std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; - NotificationStatus notification_status = NotificationStatus::Unitialized; + NotificationStatus notification_status = NotificationStatus::Uninitialized; bool has_mod_perms = false; Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h index dd25af280..cbbe6757b 100644 --- a/src/yuzu/multiplayer/validation.h +++ b/src/yuzu/multiplayer/validation.h @@ -38,11 +38,28 @@ private: QRegularExpression(QStringLiteral("^[a-zA-Z0-9._ -]{4,20}")); QRegularExpressionValidator nickname; - /// ipv4 address only - // TODO remove this when we support hostnames in direct connect + /// ipv4 / ipv6 / hostnames QRegularExpression ip_regex = QRegularExpression(QStringLiteral( - "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|" - "2[0-4][0-9]|25[0-5])")); + // IPv4 regex + "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|" + // IPv6 regex + "^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|" + "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-" + "5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)" + "(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]" + "\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?" + "\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|" + // Hostname regex + "^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$")); QRegularExpressionValidator ip; /// port must be between 0 and 65535 diff --git a/src/yuzu/qt_common.cpp b/src/yuzu/qt_common.cpp new file mode 100644 index 000000000..5d0fd7674 --- /dev/null +++ b/src/yuzu/qt_common.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QGuiApplication> +#include <QStringLiteral> +#include <QWindow> +#include "common/logging/log.h" +#include "core/frontend/emu_window.h" +#include "yuzu/qt_common.h" + +#if !defined(WIN32) && !defined(__APPLE__) +#include <qpa/qplatformnativeinterface.h> +#endif + +namespace QtCommon { +Core::Frontend::WindowSystemType GetWindowSystemType() { + // Determine WSI type based on Qt platform. + QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("windows")) + return Core::Frontend::WindowSystemType::Windows; + else if (platform_name == QStringLiteral("xcb")) + return Core::Frontend::WindowSystemType::X11; + else if (platform_name == QStringLiteral("wayland")) + return Core::Frontend::WindowSystemType::Wayland; + else if (platform_name == QStringLiteral("wayland-egl")) + return Core::Frontend::WindowSystemType::Wayland; + else if (platform_name == QStringLiteral("cocoa")) + return Core::Frontend::WindowSystemType::Cocoa; + else if (platform_name == QStringLiteral("android")) + return Core::Frontend::WindowSystemType::Android; + + LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString()); + return Core::Frontend::WindowSystemType::Windows; +} // namespace Core::Frontend::WindowSystemType + +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { + Core::Frontend::EmuWindow::WindowSystemInfo wsi; + wsi.type = GetWindowSystemType(); + + // Our Win32 Qt external doesn't have the private API. +#if defined(WIN32) || defined(__APPLE__) + wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; +#else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + wsi.display_connection = pni->nativeResourceForWindow("display", window); + if (wsi.type == Core::Frontend::WindowSystemType::Wayland) + wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr; + else + wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; +#endif + wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; + + return wsi; +} +} // namespace QtCommon diff --git a/src/yuzu/qt_common.h b/src/yuzu/qt_common.h new file mode 100644 index 000000000..9c63f08f3 --- /dev/null +++ b/src/yuzu/qt_common.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <QWindow> +#include "core/frontend/emu_window.h" + +namespace QtCommon { + +Core::Frontend::WindowSystemType GetWindowSystemType(); + +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window); + +} // namespace QtCommon diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 9f702fe95..6eefc94ed 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -25,9 +25,9 @@ void CheckVulkan() { // Just start the Vulkan loader, this will crash if something is wrong try { Vulkan::vk::InstanceDispatch dld; - const Common::DynamicLibrary library = Vulkan::OpenLibrary(); + const auto library = Vulkan::OpenLibrary(); const Vulkan::vk::Instance instance = - Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1); + Vulkan::CreateInstance(*library, dld, VK_API_VERSION_1_1); } catch (const Vulkan::vk::Exception& exception) { fmt::print(stderr, "Failed to initialize Vulkan: {}\n", exception.what()); @@ -86,7 +86,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulka return false; } - // Wait until the processs exits and get exit code from it + // Wait until the process exits and get exit code from it WaitForSingleObject(process_info.hProcess, INFINITE); DWORD exit_code = STILL_ACTIVE; const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp index bbb370595..5f6a9c193 100644 --- a/src/yuzu/util/limitable_input_dialog.cpp +++ b/src/yuzu/util/limitable_input_dialog.cpp @@ -16,8 +16,6 @@ LimitableInputDialog::LimitableInputDialog(QWidget* parent) : QDialog{parent} { LimitableInputDialog::~LimitableInputDialog() = default; void LimitableInputDialog::CreateUI() { - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - text_label = new QLabel(this); text_entry = new QLineEdit(this); text_label_invalid = new QLabel(this); diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp index 796f5bf41..ee35a3e15 100644 --- a/src/yuzu/util/overlay_dialog.cpp +++ b/src/yuzu/util/overlay_dialog.cpp @@ -163,7 +163,7 @@ void OverlayDialog::MoveAndResizeWindow() { const auto height = static_cast<float>(parentWidget()->height()); // High DPI - const float dpi_scale = parentWidget()->windowHandle()->screen()->logicalDotsPerInch() / 96.0f; + const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f; const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; const auto body_text_font_size = diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h index 872283d61..62f9da311 100644 --- a/src/yuzu/util/overlay_dialog.h +++ b/src/yuzu/util/overlay_dialog.h @@ -71,7 +71,7 @@ private: const QString& left_button_text, const QString& right_button_text, Qt::Alignment alignment); - /// Moves and resizes the dialog to be fully overlayed on top of the parent window. + /// Moves and resizes the dialog to be fully overlaid on top of the parent window. void MoveAndResizeWindow(); /** diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp index 4b10fa517..1670aa596 100644 --- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp @@ -8,7 +8,6 @@ SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Enter a hotkey")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); key_sequence = new QKeySequenceEdit; diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp new file mode 100644 index 000000000..7c26a3dc7 --- /dev/null +++ b/src/yuzu/vk_device_info.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <utility> +#include <vector> +#include "common/dynamic_library.h" +#include "common/logging/log.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_library.h" +#include "video_core/vulkan_common/vulkan_surface.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" +#include "vulkan/vulkan_core.h" +#include "yuzu/qt_common.h" +#include "yuzu/vk_device_info.h" + +class QWindow; + +namespace VkDeviceInfo { +Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_, + bool has_broken_compute_) + : name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {} + +Record::~Record() = default; + +void PopulateRecords(std::vector<Record>& records, QWindow* window) try { + using namespace Vulkan; + + auto wsi = QtCommon::GetWindowSystemInfo(window); + + vk::InstanceDispatch dld; + const auto library = OpenLibrary(); + const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type); + const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); + vk::SurfaceKHR surface = CreateSurface(instance, wsi); + + records.clear(); + records.reserve(physical_devices.size()); + for (const VkPhysicalDevice device : physical_devices) { + const auto physical_device = vk::PhysicalDevice(device, dld); + const std::string name = physical_device.GetProperties().deviceName; + const std::vector<VkPresentModeKHR> present_modes = + physical_device.GetSurfacePresentModesKHR(*surface); + + VkPhysicalDeviceDriverProperties driver_properties{}; + driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + driver_properties.pNext = nullptr; + VkPhysicalDeviceProperties2 properties{}; + properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; + properties.pNext = &driver_properties; + dld.vkGetPhysicalDeviceProperties2(physical_device, &properties); + + bool has_broken_compute{Vulkan::Device::CheckBrokenCompute( + driver_properties.driverID, properties.properties.driverVersion)}; + + records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute)); + } +} catch (const Vulkan::vk::Exception& exception) { + LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); +} +} // namespace VkDeviceInfo diff --git a/src/yuzu/vk_device_info.h b/src/yuzu/vk_device_info.h new file mode 100644 index 000000000..bda8262f4 --- /dev/null +++ b/src/yuzu/vk_device_info.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <algorithm> +#include <iterator> +#include <memory> +#include <string> +#include <string_view> +#include <vector> +#include "common/common_types.h" +#include "vulkan/vulkan_core.h" + +class QWindow; + +namespace Settings { +enum class VSyncMode : u32; +} +// #include "common/settings.h" + +namespace VkDeviceInfo { +// Short class to record Vulkan driver information for configuration purposes +class Record { +public: + explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes, + bool has_broken_compute); + ~Record(); + + const std::string name; + const std::vector<VkPresentModeKHR> vsync_support; + const bool has_broken_compute; +}; + +void PopulateRecords(std::vector<Record>& records, QWindow* window); +} // namespace VkDeviceInfo |
