From 47c86012229af1d64efd0fabe3178aa411e506fc Mon Sep 17 00:00:00 2001 From: crueter Date: Sun, 14 Dec 2025 01:55:53 -0500 Subject: [PATCH] fix config, initial gfx stuff Signed-off-by: crueter --- src/Eden/Config/GlobalConfigureDialog.qml | 44 ++- src/Eden/Config/fields/BaseField.qml | 9 +- src/Eden/Config/fields/ConfigCheckbox.qml | 3 +- src/Eden/Config/fields/FieldCheckbox.qml | 5 +- src/Eden/Config/pages/SettingsList.qml | 7 + .../Config/pages/audio/AudioGeneralPage.qml | 4 + src/Eden/Config/pages/cpu/CpuGeneralPage.qml | 4 + .../Config/pages/debug/DebugAdvancedPage.qml | 4 + src/Eden/Config/pages/debug/DebugCpuPage.qml | 4 + .../Config/pages/debug/DebugGeneralPage.qml | 5 + .../Config/pages/debug/DebugGraphicsPage.qml | 4 + .../Config/pages/general/UiGameListPage.qml | 4 + .../Config/pages/general/UiGeneralPage.qml | 8 +- .../pages/graphics/RendererAdvancedPage.qml | 4 + .../pages/graphics/RendererExtensionsPage.qml | 4 + .../Config/pages/graphics/RendererPage.qml | 10 +- src/Eden/Config/pages/system/AppletsPage.qml | 5 +- .../Config/pages/system/FileSystemPage.qml | 4 + .../Config/pages/system/SystemCorePage.qml | 4 + .../Config/pages/system/SystemGeneralPage.qml | 5 + src/Eden/Interface/CMakeLists.txt | 4 + src/Eden/Interface/MainWindowInterface.cpp | 330 +++++++++++++++++- src/Eden/Interface/MainWindowInterface.h | 41 ++- src/Eden/Interface/QMLConfig.h | 3 - src/Eden/Interface/QMLSetting.cpp | 6 +- src/Eden/Interface/RenderWindow.cpp | 230 ++++++++++++ src/Eden/Interface/RenderWindow.h | 55 +++ src/Eden/Interface/SettingsInterface.h | 1 - src/Eden/Main/Main.qml | 10 +- src/Eden/Native/EdenApplication.cpp | 16 +- src/frontend_common/config.cpp | 5 +- src/qt_common/CMakeLists.txt | 3 + src/qt_common/config/qt_config.cpp | 23 +- src/qt_common/config/qt_config.h | 1 + src/qt_common/qt_common.cpp | 1 + src/qt_common/qt_common.h | 15 +- src/qt_common/render/context.h | 104 ++++++ src/qt_common/render/emu_thread.cpp | 79 +++++ src/qt_common/render/emu_thread.h | 94 +++++ src/qt_common/util/content.cpp | 38 ++ src/qt_common/util/content.h | 3 + src/yuzu/bootmanager.cpp | 201 +---------- src/yuzu/bootmanager.h | 107 +----- src/yuzu/main_window.cpp | 123 +++---- src/yuzu/main_window.h | 11 +- 45 files changed, 1213 insertions(+), 432 deletions(-) create mode 100644 src/Eden/Interface/RenderWindow.cpp create mode 100644 src/Eden/Interface/RenderWindow.h create mode 100644 src/qt_common/render/context.h create mode 100644 src/qt_common/render/emu_thread.cpp create mode 100644 src/qt_common/render/emu_thread.h diff --git a/src/Eden/Config/GlobalConfigureDialog.qml b/src/Eden/Config/GlobalConfigureDialog.qml index 709cc9d651..74f3feb693 100644 --- a/src/Eden/Config/GlobalConfigureDialog.qml +++ b/src/Eden/Config/GlobalConfigureDialog.qml @@ -13,6 +13,7 @@ import Carboxyl.Contour NativeDialog { property list configs + property list settings width: Constants.width height: Constants.height @@ -20,16 +21,29 @@ NativeDialog { title: qsTr("Eden Configuration") standardButtons: Dialog.Ok | Dialog.Apply | Dialog.Cancel - Component.onCompleted: configs = Util.searchItem(swipe, "PageScrollView") + Component.onCompleted: { + configs = Util.searchItem(swipe, "PageScrollView") + settings = Util.searchItem(swipe, "BaseField") + + syncConfigs() + } function applyConfigs() { configs.forEach(config => { + // console.log(config) config.apply() }) QtConfig.save() } + function syncConfigs() { + configs.forEach(setting => { + console.log(setting) + setting.sync() + }) + } + MessageDialog { id: warn text: qsTr("To apply the new style, Eden will now close and re-open.") @@ -54,12 +68,8 @@ NativeDialog { onApplied: applyConfigs() - onRejected: { - - // TODO - // configs.forEach(config => config.sync()) - // QtConfig.reload() - } + onVisibilityChanged: if (visible) + syncConfigs() CarboxylTabBar { id: tabBar @@ -133,10 +143,20 @@ NativeDialog { GlobalGeneralPage { id: general } - GlobalSystemPage {} - GlobalCpuPage {} - GlobalGraphicsPage {} - GlobalAudioPage {} - GlobalDebugPage {} + GlobalSystemPage { + id: system + } + GlobalCpuPage { + id: cpu + } + GlobalGraphicsPage { + id: gfx + } + GlobalAudioPage { + id: audio + } + GlobalDebugPage { + id: debug + } } } diff --git a/src/Eden/Config/fields/BaseField.qml b/src/Eden/Config/fields/BaseField.qml index 1c86ca747c..c5ee218e7d 100644 --- a/src/Eden/Config/fields/BaseField.qml +++ b/src/Eden/Config/fields/BaseField.qml @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Layouts @@ -28,12 +27,17 @@ Item { function apply() { if (setting.value !== value) { + console.log("Changing value", setting.value, "of setting", + setting.label, "to", value) setting.value = value } } function sync() { if (value !== setting.value) { + + // console.log("Syncing setting", setting.label, "from", value, "to", + // setting.value) value = setting.value } } @@ -60,9 +64,10 @@ Item { FieldCheckbox { id: enable - setting: field.setting z: 2 + setting: field.setting force: field.forceCheckbox + field: parent height: 30 diff --git a/src/Eden/Config/fields/ConfigCheckbox.qml b/src/Eden/Config/fields/ConfigCheckbox.qml index 4b0daa6157..24c64cd286 100644 --- a/src/Eden/Config/fields/ConfigCheckbox.qml +++ b/src/Eden/Config/fields/ConfigCheckbox.qml @@ -1,11 +1,10 @@ + // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import QtQuick.Layouts - BaseField { forceCheckbox: true // // TODO: global/custom diff --git a/src/Eden/Config/fields/FieldCheckbox.qml b/src/Eden/Config/fields/FieldCheckbox.qml index 35bfdec4d8..5d55e8aab2 100644 --- a/src/Eden/Config/fields/FieldCheckbox.qml +++ b/src/Eden/Config/fields/FieldCheckbox.qml @@ -1,15 +1,14 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import QtQuick.Layouts - CheckBox { property bool force: false property var setting - property var other: setting.other === null ? setting : setting.other + property var field + property var other: setting.other === null ? field : setting.other indicator.implicitHeight: 25 indicator.implicitWidth: 25 diff --git a/src/Eden/Config/pages/SettingsList.qml b/src/Eden/Config/pages/SettingsList.qml index 4e45b9aaac..173153ee07 100644 --- a/src/Eden/Config/pages/SettingsList.qml +++ b/src/Eden/Config/pages/SettingsList.qml @@ -21,6 +21,13 @@ ListView { itm.apply() } } + function sync() { + for (var i = 0; i < count; ++i) { + var itm = itemAtIndex(i) + if (itm !== null) + itm.apply() + } + } clip: true boundsBehavior: Flickable.StopAtBounds diff --git a/src/Eden/Config/pages/audio/AudioGeneralPage.qml b/src/Eden/Config/pages/audio/AudioGeneralPage.qml index 1d5f10bec9..6d1a654e81 100644 --- a/src/Eden/Config/pages/audio/AudioGeneralPage.qml +++ b/src/Eden/Config/pages/audio/AudioGeneralPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { audio.apply() } + function sync() { + audio.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/cpu/CpuGeneralPage.qml b/src/Eden/Config/pages/cpu/CpuGeneralPage.qml index e31863e6d4..fcb61c8ef8 100644 --- a/src/Eden/Config/pages/cpu/CpuGeneralPage.qml +++ b/src/Eden/Config/pages/cpu/CpuGeneralPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { cpu.apply() } + function sync() { + cpu.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/debug/DebugAdvancedPage.qml b/src/Eden/Config/pages/debug/DebugAdvancedPage.qml index ac3f043414..6030ba0643 100644 --- a/src/Eden/Config/pages/debug/DebugAdvancedPage.qml +++ b/src/Eden/Config/pages/debug/DebugAdvancedPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { debug.apply() } + function sync() { + debug.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/debug/DebugCpuPage.qml b/src/Eden/Config/pages/debug/DebugCpuPage.qml index 0468e24fd1..3a0ccff40d 100644 --- a/src/Eden/Config/pages/debug/DebugCpuPage.qml +++ b/src/Eden/Config/pages/debug/DebugCpuPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { cpu.apply() } + function sync() { + cpu.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/debug/DebugGeneralPage.qml b/src/Eden/Config/pages/debug/DebugGeneralPage.qml index 884ee686a8..22e0ea8742 100644 --- a/src/Eden/Config/pages/debug/DebugGeneralPage.qml +++ b/src/Eden/Config/pages/debug/DebugGeneralPage.qml @@ -15,6 +15,11 @@ PageScrollView { debug.apply() misc.apply() } + function sync() { + debug.sync() + misc.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/debug/DebugGraphicsPage.qml b/src/Eden/Config/pages/debug/DebugGraphicsPage.qml index ba7445685a..aa171545e6 100644 --- a/src/Eden/Config/pages/debug/DebugGraphicsPage.qml +++ b/src/Eden/Config/pages/debug/DebugGraphicsPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { gfx.apply() } + function sync() { + gfx.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/general/UiGameListPage.qml b/src/Eden/Config/pages/general/UiGameListPage.qml index 1c3cbb98e4..a66c6cfff8 100644 --- a/src/Eden/Config/pages/general/UiGameListPage.qml +++ b/src/Eden/Config/pages/general/UiGameListPage.qml @@ -13,10 +13,14 @@ PageScrollView { function apply() { ui.apply() } + function sync() { + ui.sync() + } ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth + // TODO(qml): This is shown twice??????? SettingsList { id: ui category: SettingsCategories.UiGameList diff --git a/src/Eden/Config/pages/general/UiGeneralPage.qml b/src/Eden/Config/pages/general/UiGeneralPage.qml index 66b23f66a2..3743197f70 100644 --- a/src/Eden/Config/pages/general/UiGeneralPage.qml +++ b/src/Eden/Config/pages/general/UiGeneralPage.qml @@ -1,4 +1,3 @@ - // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later import QtQuick @@ -26,6 +25,13 @@ PageScrollView { Clover.theme = Clover.themes[theme.contentItem.currentIndex] } + function sync() { + ui.sync() + style.sync() + theme.sync() + accent.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/graphics/RendererAdvancedPage.qml b/src/Eden/Config/pages/graphics/RendererAdvancedPage.qml index 38f74f16fb..82f9f5aea2 100644 --- a/src/Eden/Config/pages/graphics/RendererAdvancedPage.qml +++ b/src/Eden/Config/pages/graphics/RendererAdvancedPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { gfx.apply() } + function sync() { + gfx.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/graphics/RendererExtensionsPage.qml b/src/Eden/Config/pages/graphics/RendererExtensionsPage.qml index d606cbed36..950751bca8 100644 --- a/src/Eden/Config/pages/graphics/RendererExtensionsPage.qml +++ b/src/Eden/Config/pages/graphics/RendererExtensionsPage.qml @@ -13,6 +13,10 @@ PageScrollView { function apply() { ext.apply() } + function sync() { + ext.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/graphics/RendererPage.qml b/src/Eden/Config/pages/graphics/RendererPage.qml index 6ad609e3a1..652929cf10 100644 --- a/src/Eden/Config/pages/graphics/RendererPage.qml +++ b/src/Eden/Config/pages/graphics/RendererPage.qml @@ -1,6 +1,6 @@ + // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -19,6 +19,14 @@ PageScrollView { vsync.apply() } + function sync() { + gfx.sync() + api.sync() + dev.sync() + shader.sync() + vsync.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/system/AppletsPage.qml b/src/Eden/Config/pages/system/AppletsPage.qml index 352a780cd7..b3bee5e830 100644 --- a/src/Eden/Config/pages/system/AppletsPage.qml +++ b/src/Eden/Config/pages/system/AppletsPage.qml @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -10,9 +9,13 @@ import Eden.Config PageScrollView { id: scroll + function apply() { app.apply() } + function sync() { + app.sync() + } ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/system/FileSystemPage.qml b/src/Eden/Config/pages/system/FileSystemPage.qml index 2fb03ff5d4..71bbf5123c 100644 --- a/src/Eden/Config/pages/system/FileSystemPage.qml +++ b/src/Eden/Config/pages/system/FileSystemPage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { fs.apply() } + function sync() { + fs.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/system/SystemCorePage.qml b/src/Eden/Config/pages/system/SystemCorePage.qml index 6715b6a865..12a6c2e374 100644 --- a/src/Eden/Config/pages/system/SystemCorePage.qml +++ b/src/Eden/Config/pages/system/SystemCorePage.qml @@ -14,6 +14,10 @@ PageScrollView { function apply() { core.apply() } + function sync() { + core.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Config/pages/system/SystemGeneralPage.qml b/src/Eden/Config/pages/system/SystemGeneralPage.qml index ff648e60a8..84aef10756 100644 --- a/src/Eden/Config/pages/system/SystemGeneralPage.qml +++ b/src/Eden/Config/pages/system/SystemGeneralPage.qml @@ -15,6 +15,11 @@ PageScrollView { net.apply() sys.apply() } + function sync() { + net.sync() + sys.sync() + } + ColumnLayout { width: scroll.width - scroll.effectiveScrollBarWidth diff --git a/src/Eden/Interface/CMakeLists.txt b/src/Eden/Interface/CMakeLists.txt index 861cbe8a39..6c63f592f4 100644 --- a/src/Eden/Interface/CMakeLists.txt +++ b/src/Eden/Interface/CMakeLists.txt @@ -5,6 +5,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt6 REQUIRED COMPONENTS Widgets) EdenModule( NAME Interface @@ -24,11 +25,14 @@ EdenModule( Vulkan::UtilityHeaders frontend_common qt_common + glad ) target_link_libraries(EdenInterface PRIVATE Qt6::Core) +target_link_libraries(EdenInterface PRIVATE Qt6::Widgets) target_sources(EdenInterface PRIVATE MainWindowInterface.h MainWindowInterface.cpp + RenderWindow.h RenderWindow.cpp ) diff --git a/src/Eden/Interface/MainWindowInterface.cpp b/src/Eden/Interface/MainWindowInterface.cpp index 6d87443549..fc55c3db90 100644 --- a/src/Eden/Interface/MainWindowInterface.cpp +++ b/src/Eden/Interface/MainWindowInterface.cpp @@ -3,18 +3,38 @@ #include "Eden/Models/GameListModel.h" #include "MainWindowInterface.h" +#include "QMLConfig.h" +#include "RenderWindow.h" +#include "common/string_util.h" +#include "core/hle/kernel/k_process.h" #include "frontend_common/content_manager.h" +#include "hid_core/hid_core.h" #include "qt_common/util/content.h" #include "qt_common/util/game.h" #include "qt_common/abstract/frontend.h" #include "qt_common/qt_constants.h" +// Applets // +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/error.h" +#include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/web_browser.h" +#include "video_core/gpu.h" +#include "video_core/renderer_base.h" + #include -MainWindowInterface::MainWindowInterface(GameListModel* model, QObject* parent) - : QObject{parent}, m_gameList(model) { +MainWindowInterface::MainWindowInterface(GameListModel* model, QMLConfig* config, + QQuickWindow* rootWindow, QObject* parent) + : QObject{parent}, m_gameList(model), m_config(config), + input_subsystem{std::make_shared()} { + m_renderWindow = new RenderWindow(rootWindow, input_subsystem); checkFirmwareDecryption(); } @@ -124,6 +144,312 @@ void MainWindowInterface::openFAQ() { openURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::helpPage))); } +void MainWindowInterface::openHomeMenu() { + auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); + + using namespace QtCommon::StringLookup; + + switch (result) { + case FirmwareManager::ErrorFirmwareMissing: + QtCommon::Frontend::Warning(this, tr("No firmware available"), + Lookup(FwCheckErrorFirmwareMissing)); + return; + case FirmwareManager::ErrorFirmwareCorrupted: + QtCommon::Frontend::Warning(this, tr("Firmware Corrupted"), + Lookup(FwCheckErrorFirmwareCorrupted)); + return; + default: + break; + } + + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); + + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + if (!qlaunch_applet_nca) { + QtCommon::Frontend::Warning(this, tr("Home Menu Applet"), + tr("Home Menu is not available. Please reinstall firmware.")); + return; + } + + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); + + const auto filename = QString::fromStdString((qlaunch_applet_nca->GetFullPath())); + UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); + bootGame(filename, libraryAppletParameters(QLaunchId, Service::AM::AppletId::QLaunch)); +} + +Service::AM::FrontendAppletParameters MainWindowInterface::applicationAppletParameters() { + return Service::AM::FrontendAppletParameters{ + .applet_id = Service::AM::AppletId::Application, + .applet_type = Service::AM::AppletType::Application, + }; +} + +Service::AM::FrontendAppletParameters MainWindowInterface::libraryAppletParameters( + u64 program_id, Service::AM::AppletId applet_id) { + return Service::AM::FrontendAppletParameters{ + .program_id = program_id, + .applet_id = applet_id, + .applet_type = Service::AM::AppletType::LibraryApplet, + }; +} + +void MainWindowInterface::executeProgram(std::size_t program_index) { + shutdownGame(); + + auto params = applicationAppletParameters(); + params.program_index = static_cast(program_index); + params.launch_type = Service::AM::LaunchType::ApplicationInitiated; + bootGame(last_filename_booted, params); +} + +void MainWindowInterface::bootGame(const QString& filename, + Service::AM::FrontendAppletParameters params, + StartGameType type) { + LOG_INFO(Frontend, "Eden starting..."); + + // if (params.program_id == 0 || + // params.program_id > static_cast(Service::AM::AppletProgramId::MaxProgramId)) { + // StoreRecentFile(filename); // Put the filename on top of the list + // } + + // Save configurations + // UpdateUISettings(); + m_config->save(); + + u64 title_id{0}; + + // last_filename_booted = filename; + + QtCommon::Content::configureFilesystemProvider(filename.toStdString()); + const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); + const auto loader = + Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); + + if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && + type == StartGameType::Normal) { + // Load per game settings + const auto file_path = + std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}; + const auto config_file_name = title_id == 0 + ? Common::FS::PathToUTF8String(file_path.filename()) + : fmt::format("{:016X}", title_id); + QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig); + QtCommon::system->HIDCore().ReloadInputDevices(); + QtCommon::system->ApplySettings(); + } + + Settings::LogSettings(); + + // if (UISettings::values.select_user_on_boot && !user_flag_cmd_line) { + // const Core::Frontend::ProfileSelectParameters parameters{ + // .mode = + // Service::AM::Frontend::UiMode::UserSelector, + // .invalid_uid_list = {}, + // .display_options = {}, + // .purpose = + // Service::AM::Frontend::UserSelectionPurpose::General, + // }; + // if (SelectAndSetCurrentUser(parameters) == false) { + // return; + // } + // } + + if (!loadROM(filename, params)) { + return; + } + + qDebug() << "Successfully loaded ROM from" << filename; + + QtCommon::system->SetShuttingDown(false); + + // Create and start the emulation thread + QtCommon::emu_thread = std::make_unique(); + // emit EmulationStarting(); + QtCommon::emu_thread->start(); + + // Register an ExecuteProgram callback such that Core can execute a sub-program + QtCommon::system->RegisterExecuteProgramCallback( + [this](std::size_t program_index_) { executeProgram(program_index_); }); + + QtCommon::system->RegisterExitCallback([this] { + QtCommon::emu_thread->ForceStop(); + shutdownGame(); + }); + + // connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame); + // connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity); + + connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, this, + [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + qDebug() << (int) stage << value << total; + }); + + // connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen, + // &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); + + // // Update the GUI + // UpdateStatusButtons(); + // if (ui->action_Single_Window_Mode->isChecked()) { + // game_list->hide(); + // game_list_placeholder->hide(); + // } + // status_bar_update_timer.start(500); + // renderer_status_button->setDisabled(true); + // refresh_button->setDisabled(true); + + // if (UISettings::values.hide_mouse || Settings::values.mouse_panning) { + // render_window->installEventFilter(render_window); + // render_window->setAttribute(Qt::WA_Hover, true); + // } + + // if (UISettings::values.hide_mouse) { + // mouse_hide_timer.start(); + // } + + // render_window->InitializeCamera(); + + std::string title_name; + std::string title_version; + const auto res = QtCommon::system->GetGameName(title_name); + + const auto metadata = [title_id] { + const FileSys::PatchManager pm(title_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()); + return pm.GetControlMetadata(); + }(); + if (metadata.first != nullptr) { + title_version = metadata.first->GetVersionString(); + title_name = metadata.first->GetApplicationName(); + } + if (res != Loader::ResultStatus::Success || title_name.empty()) { + title_name = Common::FS::PathToUTF8String( + std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} + .filename()); + } + const bool is_64bit = QtCommon::system->Kernel().ApplicationProcess()->Is64Bit(); + 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) + .toStdString(); + LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); + const auto gpu_vendor = QtCommon::system->GPU().Renderer().GetDeviceVendor(); + + // TODO + // UpdateWindowTitle(title_name, title_version, gpu_vendor); + + // loading_screen->Prepare(QtCommon::system->GetAppLoader()); + // loading_screen->show(); + + emulation_running = true; + // if (ui->action_Fullscreen->isChecked()) { + // ShowFullscreen(); + // } + // OnStartGame(); + QtCommon::emu_thread->SetRunning(true); +} + +bool MainWindowInterface::loadROM(const QString& filename, Service::AM::FrontendAppletParameters params) { + // Shutdown previous session if the emu thread is still active... + if (QtCommon::emu_thread != nullptr) { + shutdownGame(); + } + + if (!m_renderWindow->initRenderTarget()) { + return false; + } + + QtCommon::system->SetFilesystem(QtCommon::vfs); + + if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) { + QtCommon::system->GetUserChannel().clear(); + } + + QtCommon::system->SetFrontendAppletSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + nullptr, // Software Keyboard + nullptr, // Web Browser + nullptr, // Net Connect + }); + + /** firmware check */ + if (!QtCommon::Content::CheckGameFirmware(params.program_id, this)) { + return false; + } + + /** Exec */ + const Core::SystemResultStatus result{ + QtCommon::system->Load(*m_renderWindow, filename.toStdString(), params)}; + + if (result != Core::SystemResultStatus::Success) { + switch (result) { + case Core::SystemResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString()); + QtCommon::Frontend::Critical(tr("Error while loading ROM!"), + tr("The ROM format is not supported.")); + break; + case Core::SystemResultStatus::ErrorVideoCore: + QtCommon::Frontend::Critical( + tr("An error occurred initializing the video core."), + tr("Eden has encountered an error while running the video core. " + "This is usually caused by outdated GPU drivers, including integrated ones. " + "Please see the log for more details. " + "For more information on accessing the log, please see the following page: " + "" + "How to Upload the Log File. ")); + break; + default: + if (result > Core::SystemResultStatus::ErrorLoader) { + const u16 loader_id = static_cast(Core::SystemResultStatus::ErrorLoader); + const u16 error_id = static_cast(result) - loader_id; + const std::string error_code = fmt::format("({:04X}-{:04X})", loader_id, error_id); + LOG_CRITICAL(Frontend, "Failed to load ROM! {}", error_code); + + const auto title = + tr("Error while loading ROM! %1", "%1 signifies a numeric error code.") + .arg(QString::fromStdString(error_code)); + const auto description = + tr("%1
Please redump your files or ask on Discord/Revolt for help.", + "%1 signifies an error string.") + .arg(QString::fromStdString( + GetResultStatusString(static_cast(error_id)))); + + QtCommon::Frontend::Critical(title, description); + } else { + QtCommon::Frontend::Critical( + tr("Error while loading ROM!"), + tr("An unknown error occurred. Please see the log for more details.")); + } + break; + } + return false; + } + current_game_path = filename; + + return true; +} + +void MainWindowInterface::shutdownGame() { + if (!emulation_running) { + return; + } + + // STUBBED + + // TODO(crueter): make this common as well (frontend_common?) + // play_time_manager->Stop(); + // OnShutdownBegin(); + // OnEmulationStopTimeExpired(); + // OnEmulationStopped(); +} + /// PROPERTIES /// QString MainWindowInterface::firmwareDisplay() const { return m_firmwareDisplay; diff --git a/src/Eden/Interface/MainWindowInterface.h b/src/Eden/Interface/MainWindowInterface.h index b0c42d4464..473fa767db 100644 --- a/src/Eden/Interface/MainWindowInterface.h +++ b/src/Eden/Interface/MainWindowInterface.h @@ -4,8 +4,17 @@ #pragma once #include - +#include +#include "core/hle/service/am/applet_manager.h" +#include "qt_common/qt_common.h" + +namespace InputCommon { +class InputSubsystem; +} +class RenderWindow; class GameListModel; +class QMLConfig; + class MainWindowInterface : public QObject { Q_OBJECT Q_PROPERTY(bool firmwareGood READ firmwareGood WRITE setFirmwareGood NOTIFY firmwareGoodChanged FINAL) @@ -15,7 +24,8 @@ class MainWindowInterface : public QObject { firmwareDisplayChanged FINAL) public: - explicit MainWindowInterface(GameListModel* model, QObject* parent = nullptr); + explicit MainWindowInterface(GameListModel* model, QMLConfig* config, QQuickWindow* rootWindow, + QObject* parent = nullptr); Q_INVOKABLE void installFirmware(); Q_INVOKABLE void installFirmwareZip(); @@ -32,6 +42,12 @@ public: Q_INVOKABLE void openQuickstartGuide(); Q_INVOKABLE void openFAQ(); + Q_INVOKABLE void openHomeMenu(); + + Q_INVOKABLE void bootGame(const QString& filename, Service::AM::FrontendAppletParameters params, + StartGameType type = StartGameType::Normal); + Q_INVOKABLE bool loadROM(const QString& filename, Service::AM::FrontendAppletParameters params); + bool firmwareGood() const; void setFirmwareGood(bool newFirmwareGood); @@ -55,10 +71,29 @@ signals: private: GameListModel* m_gameList; - void setFirmwareVersion(); + QMLConfig* m_config; + RenderWindow* m_renderWindow; + std::shared_ptr input_subsystem; + + // Whether emulation is currently running in yuzu. + bool emulation_running = false; + // The path to the game currently running + QString current_game_path; + // Whether a user was set on the command line (skips UserSelector if it's forced to show up) + bool user_flag_cmd_line = false; + + // Last game booted, used for multi-process apps + QString last_filename_booted; bool m_firmwareGood = false; QString m_firmwareDisplay{}; QString m_firmwareTooltip{}; + + void shutdownGame(); + void setFirmwareVersion(); + void executeProgram(std::size_t program_index); + Service::AM::FrontendAppletParameters applicationAppletParameters(); + Service::AM::FrontendAppletParameters libraryAppletParameters(u64 program_id, + Service::AM::AppletId applet_id); }; diff --git a/src/Eden/Interface/QMLConfig.h b/src/Eden/Interface/QMLConfig.h index 866f5640b0..233c8015a3 100644 --- a/src/Eden/Interface/QMLConfig.h +++ b/src/Eden/Interface/QMLConfig.h @@ -18,9 +18,6 @@ public: : m_config{new QtConfig} {} - Q_INVOKABLE inline void reload() { - m_config->ReloadAllValues(); - } Q_INVOKABLE inline void save() { m_config->SaveAllValues(); } diff --git a/src/Eden/Interface/QMLSetting.cpp b/src/Eden/Interface/QMLSetting.cpp index d665283ad6..ea1ea880c0 100644 --- a/src/Eden/Interface/QMLSetting.cpp +++ b/src/Eden/Interface/QMLSetting.cpp @@ -144,11 +144,13 @@ QVariant QMLSetting::value() const return var; } -void QMLSetting::setValue(const QVariant &newValue) -{ +void QMLSetting::setValue(const QVariant& newValue) { + qDebug() << "changing value" << m_setting->ToString() << "to" << newValue << "for setting" + << m_setting->GetLabel(); QVariant var = newValue; var.convert(QMetaType(m_metaType)); + qDebug() << var.toString(); m_setting->LoadString(var.toString().toStdString()); emit valueChanged(); diff --git a/src/Eden/Interface/RenderWindow.cpp b/src/Eden/Interface/RenderWindow.cpp new file mode 100644 index 0000000000..25490fae25 --- /dev/null +++ b/src/Eden/Interface/RenderWindow.cpp @@ -0,0 +1,230 @@ +#include + +#include +#include "RenderWindow.h" +#include "common/scm_rev.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "input_common/main.h" +#include "qt_common/qt_common.h" +#include "qt_common/render/context.h" +#include "qt_common/abstract/frontend.h" + +struct OpenGLRenderItem : public QQuickItem { + explicit OpenGLRenderItem(RenderWindow* parent) : QQuickItem(parent) { + window()->setSurfaceType(QWindow::OpenGLSurface); + } + + void SetContext(std::unique_ptr&& context_) { + context = std::move(context_); + } + +private: + std::unique_ptr context; +}; + +struct VulkanRenderItem : public QQuickItem { + explicit VulkanRenderItem(RenderWindow* parent) : QQuickItem(parent) { + window()->setSurfaceType(QWindow::VulkanSurface); + } +}; + +struct NullRenderItem : public QQuickItem { + explicit NullRenderItem(RenderWindow* parent) : QQuickItem(parent) {} +}; + +bool RenderWindow::initializeOpenGL() { +#ifdef HAS_OPENGL + if (!QOpenGLContext::supportsThreadedOpenGL()) { + QtCommon::Frontend::Warning(tr("OpenGL not available!"), + tr("OpenGL shared contexts are not supported.")); + return false; + } + + // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, + // WA_DontShowOnScreen, WA_DeleteOnClose + auto child = new OpenGLRenderItem(this); + child_item = child; + child_item->window()->create(); + auto context = std::make_shared(child->window()); + main_context = context; + child->SetContext( + std::make_unique(context->GetShareContext(), child->window())); + + return true; +#else + QtCommon::Frontend::Warning(tr("OpenGL not available!"), + tr("Eden has not been compiled with OpenGL support.")); + return false; +#endif +} + +bool RenderWindow::initializeVulkan() { + qDebug() << "initializing Vulkan."; + auto child = new VulkanRenderItem(this); + child_item = child; + // child_item->window()->create(); + main_context = std::make_unique(); + + return true; +} + +void RenderWindow::initializeNull() { + child_item = new NullRenderItem(this); + main_context = std::make_unique(); +} + +RenderWindow::RenderWindow(QQuickWindow* window, + std::shared_ptr input_subsystem_) + : QQuickItem(window->contentItem()), input_subsystem{std::move(input_subsystem_)} { + // STUBBED + window->setTitle(QStringLiteral("Eden %1 | %2-%3") + .arg(QString::fromUtf8(Common::g_build_name), + QString::fromUtf8(Common::g_scm_branch), + QString::fromUtf8(Common::g_scm_desc))); + input_subsystem->Initialize(); + + strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland") || + QGuiApplication::platformName() == QStringLiteral("wayland-egl"); +} + +void RenderWindow::OnFrameDisplayed() { + input_subsystem->GetTas()->UpdateThread(); + const InputCommon::TasInput::TasState new_tas_state = + std::get<0>(input_subsystem->GetTas()->GetStatus()); + + if (!first_frame) { + last_tas_state = new_tas_state; + first_frame = true; + emit FirstFrameDisplayed(); + } + + if (new_tas_state != last_tas_state) { + last_tas_state = new_tas_state; + emit TasPlaybackStateChanged(); + } +} + +std::unique_ptr RenderWindow::CreateSharedContext() const { +#ifdef HAS_OPENGL + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { + auto c = static_cast(main_context.get()); + // Bind the shared contexts to the main surface in case the backend wants to take over + // presentation + return std::make_unique(c->GetShareContext(), child_item->window()); + } +#endif + return std::make_unique(); +} + +bool RenderWindow::IsShown() const { + // STUBBED + return true; +} + +bool RenderWindow::initRenderTarget() { + // STUBBED + main_context.reset(); + + { + // Create a dummy render widget so that Qt + // places the render window at the correct position. + const QQuickItem dummy_item{this}; + } + + first_frame = false; + + switch (Settings::values.renderer_backend.GetValue()) { + case Settings::RendererBackend::OpenGL: + if (!initializeOpenGL()) { + return false; + } + break; + case Settings::RendererBackend::Vulkan: + if (!initializeVulkan()) { + return false; + } + break; + case Settings::RendererBackend::Null: + initializeNull(); + break; + } + + // Update the Window System information with the new render target + window_info = QtCommon::GetWindowSystemInfo(window()); + + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + onFramebufferSizeChanged(); + // BackupGeometry(); + + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { + if (!loadOpenGL()) { + return false; + } + } + + return true; +} + +void RenderWindow::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { + window()->setMinimumSize(QSize(minimal_size.first, minimal_size.second)); +} + +bool RenderWindow::loadOpenGL() { + auto context = CreateSharedContext(); + auto scope = context->Acquire(); + if (!gladLoadGL()) { + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL!"), + tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver.")); + return false; + } + // Display various warnings (but not fatal errors) for missing OpenGL extensions or lack of + // OpenGL 4.6 support + const QString renderer = QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); + if (!GLAD_GL_VERSION_4_6) { + QtCommon::Frontend::Warning(tr("Error while initializing OpenGL 4.6!"), + tr("Your GPU may not support OpenGL 4.6, or you do not have the " + "latest graphics driver.

GL Renderer:
%1") + .arg(renderer)); + return false; + } + if (QStringList missing_ext = getUnsupportedGLExtensions(); !missing_ext.empty()) { + QtCommon::Frontend::Warning( + tr("Error while initializing OpenGL!"), + tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " + "have the latest graphics driver.

GL Renderer:
%1

Unsupported " + "extensions:
%2") + .arg(renderer, missing_ext.join(QStringLiteral("
")))); + // Non fatal + } + return true; +} + +QStringList RenderWindow::getUnsupportedGLExtensions() const { + QStringList missing_ext; + // Extensions required to support some texture formats. + if (!GLAD_GL_EXT_texture_compression_s3tc) + missing_ext.append(QStringLiteral("EXT_texture_compression_s3tc")); + if (!GLAD_GL_ARB_texture_compression_rgtc) + missing_ext.append(QStringLiteral("ARB_texture_compression_rgtc")); + if (!missing_ext.empty()) + LOG_ERROR(Frontend, "GPU does not support all required extensions"); + for (const QString& ext : missing_ext) + LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + return missing_ext; +} + +// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). +// +// Older versions get the window size (density independent pixels), +// and hence, do not support DPI scaling ("retina" displays). +// The result will be a viewport that is smaller than the extent of the window. +void RenderWindow::onFramebufferSizeChanged() { + // Screen changes potentially incur a change in screen DPI, hence we should update the + // framebuffer size + const qreal pixel_ratio = window()->devicePixelRatio(); + const u32 width = this->width() * pixel_ratio; + const u32 height = this->height() * pixel_ratio; + UpdateCurrentFramebufferLayout(width, height); +} diff --git a/src/Eden/Interface/RenderWindow.h b/src/Eden/Interface/RenderWindow.h new file mode 100644 index 0000000000..fb4a0345f0 --- /dev/null +++ b/src/Eden/Interface/RenderWindow.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "core/frontend/emu_window.h" +#include "input_common/drivers/tas_input.h" + +namespace InputCommon { +class InputSubsystem; +} +namespace Core { +class System; +} + +class EmuThread; +class RenderWindow : public QQuickItem, public Core::Frontend::EmuWindow { + Q_OBJECT +public: + RenderWindow(QQuickWindow* window, + std::shared_ptr input_subsystem_); + + // EmuWindow interface +public: + void OnFrameDisplayed(); + std::unique_ptr CreateSharedContext() const; + bool IsShown() const; + + bool initRenderTarget(); + +signals: + void FirstFrameDisplayed(); + void TasPlaybackStateChanged(); + +private: + void OnMinimalClientAreaChangeRequest(std::pair minimal_size); + + std::shared_ptr input_subsystem; + + // Main context that will be shared with all other contexts that are requested. + // If this is used in a shared context setting, then this should not be used directly, but + // should instead be shared from + std::shared_ptr main_context; + + bool first_frame = false; + InputCommon::TasInput::TasState last_tas_state; + + QQuickItem* child_item = nullptr; + + void initializeNull(); + bool initializeVulkan(); + bool initializeOpenGL(); + bool loadOpenGL(); + QStringList getUnsupportedGLExtensions() const; + void onFramebufferSizeChanged(); +}; diff --git a/src/Eden/Interface/SettingsInterface.h b/src/Eden/Interface/SettingsInterface.h index aca4184cc3..67fb99b3c9 100644 --- a/src/Eden/Interface/SettingsInterface.h +++ b/src/Eden/Interface/SettingsInterface.h @@ -48,7 +48,6 @@ enum class Category { Services = u32(Settings::Category::Services), Paths = u32(Settings::Category::Paths), LibraryApplet = u32(Settings::Category::LibraryApplet), - MaxEnum = u32(Settings::Category::MaxEnum), }; Q_ENUM_NS(Category) } diff --git a/src/Eden/Main/Main.qml b/src/Eden/Main/Main.qml index 3892bc1d8e..adab03fdea 100644 --- a/src/Eden/Main/Main.qml +++ b/src/Eden/Main/Main.qml @@ -234,6 +234,7 @@ ApplicationWindow { Action { text: qsTr("Open &Home Menu") + onTriggered: MainWindowInterface.openHomeMenu() } Action { @@ -329,10 +330,13 @@ ApplicationWindow { Label { id: firmware font.pixelSize: 14 - visible: MainWindowInterface.firmwareGood - text: MainWindowInterface.firmwareDisplay - ToolTip.text: MainWindowInterface.firmwareTooltip + visible: typeof MainWindowInterface !== 'undefined' + && MainWindowInterface.firmwareGood + text: typeof MainWindowInterface + !== 'undefined' ? MainWindowInterface.firmwareDisplay : "" + ToolTip.text: typeof MainWindowInterface + !== 'undefined' ? MainWindowInterface.firmwareTooltip : "" } } } diff --git a/src/Eden/Native/EdenApplication.cpp b/src/Eden/Native/EdenApplication.cpp index 83e353be15..32b620cc50 100644 --- a/src/Eden/Native/EdenApplication.cpp +++ b/src/Eden/Native/EdenApplication.cpp @@ -107,10 +107,6 @@ int EdenApplication::run() { TitleManager *title = new TitleManager(&engine); ctx->setContextProperty(QStringLiteral("TitleManager"), title); - // MainWindow interface - MainWindowInterface* mwint = new MainWindowInterface(gameListModel, &engine); - ctx->setContextProperty(QStringLiteral("MainWindowInterface"), mwint); - // :) ctx->setContextProperty(QStringLiteral("EdenApplication"), this); @@ -124,6 +120,18 @@ int EdenApplication::run() { engine.loadFromModule("Eden.Main", "Main"); + // MainWindow interface + QObject *root = engine.rootObjects()[0]; + QQuickWindow *window = qobject_cast(root); + + if (!window) { + qFatal("Error: Your root item has to be a window."); + return -1; + } + + MainWindowInterface* mwint = new MainWindowInterface(gameListModel, config, window, &engine); + ctx->setContextProperty(QStringLiteral("MainWindowInterface"), mwint); + ret = exec(); } while (ret == EXIT_RELOAD); diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 558335568c..626ef5d4c9 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "common/assert.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" @@ -881,7 +882,9 @@ const std::string& Config::GetConfigFilePath() const { void Config::ReadCategory(const Settings::Category category) { const auto& settings = FindRelevantList(category); - std::ranges::for_each(settings, [&](const auto& setting) { ReadSettingGeneric(setting); }); + std::ranges::for_each(settings, [&](const auto& setting) { + ReadSettingGeneric(setting); + }); } void Config::WriteCategory(const Settings::Category category) { diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index ffd7e3845e..c524a42ac5 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -39,6 +39,9 @@ add_library(qt_common STATIC util/vk_device_info.cpp util/vk_device_info.h qt_constants.h + render/emu_thread.h render/emu_thread.cpp + render/context.h + ) if (UNIX) diff --git a/src/qt_common/config/qt_config.cpp b/src/qt_common/config/qt_config.cpp index bf55fd169d..8ff85c4c24 100644 --- a/src/qt_common/config/qt_config.cpp +++ b/src/qt_common/config/qt_config.cpp @@ -562,12 +562,23 @@ void QtConfig::SaveMultiplayerValues() { } std::vector& QtConfig::FindRelevantList(Settings::Category category) { - // This solution sucks, but by_category is unreliable because the settings backend - // mangles category mapping for some reason. - static auto list = Settings::values.linkage.by_category[category]; - auto uilist = UISettings::values.linkage.by_category[category]; - list.insert(list.end(), uilist.begin(), uilist.end()); - return list; + qDebug() << "-- Category" << u32(category); + auto log_list = [](auto list, QString label) { + qDebug() << "-- !" << label; + for (auto s : list) { + qDebug() << "-- *" << s->GetLabel(); + } + }; + auto& list = Settings::values.linkage.by_category[category]; + log_list(list, QStringLiteral("Settings")); + + if (!list.empty()) + return list; + + auto& list2 = UISettings::values.linkage.by_category[category]; + log_list(list2, QStringLiteral("UISettings")); + + return list2; } void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) { diff --git a/src/qt_common/config/qt_config.h b/src/qt_common/config/qt_config.h index a8c80dd273..f73ff36e55 100644 --- a/src/qt_common/config/qt_config.h +++ b/src/qt_common/config/qt_config.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "frontend_common/config.h" diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index 3172e31174..4a6c99b735 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -57,6 +57,7 @@ QObject* rootObject = nullptr; std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; std::unique_ptr provider = nullptr; +std::unique_ptr emu_thread = nullptr; const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"), diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index 6d3f7a6ca2..715c58f9c9 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -4,13 +4,20 @@ #ifndef QT_COMMON_H #define QT_COMMON_H +#include #include + #include "core/core.h" +#include "core/frontend/emu_window.h" #include "core/file_sys/registered_cache.h" -#include -#include +#include "core/file_sys/vfs/vfs_real.h" -#include +#include "qt_common/render/emu_thread.h" + +enum class StartGameType { + Normal, // Can use custom configuration + Global, // Only uses global configuration +}; namespace QtCommon { @@ -19,6 +26,8 @@ extern QObject *rootObject; extern std::unique_ptr system; extern std::shared_ptr vfs; extern std::unique_ptr provider; +extern std::unique_ptr emu_thread; + extern const QStringList supported_file_extensions; typedef std::function QtProgressCallback; diff --git a/src/qt_common/render/context.h b/src/qt_common/render/context.h new file mode 100644 index 0000000000..68c0843c96 --- /dev/null +++ b/src/qt_common/render/context.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include "common/settings.h" +#include "core/frontend/graphics_context.h" +#include "common/logging/log.h" + +#ifdef HAS_OPENGL +#include +#include +#endif + +#ifdef HAS_OPENGL +class OpenGLSharedContext : public Core::Frontend::GraphicsContext { +public: + /// Create the original context that should be shared from + explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { + QSurfaceFormat format; + format.setVersion(4, 6); + format.setProfile(QSurfaceFormat::CompatibilityProfile); + format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); + if (Settings::values.renderer_debug) { + format.setOption(QSurfaceFormat::FormatOption::DebugContext); + } + // TODO: expose a setting for buffer value (ie default/single/double/triple) + format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + format.setSwapInterval(0); + + context = std::make_unique(); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create main openGL context"); + } + } + + /// Create the shared contexts for rendering and presentation + explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { + + // disable vsync for any shared contexts + auto format = share_context->format(); + 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(); + context->setShareContext(share_context); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create shared openGL context"); + } + + if (!main_surface) { + offscreen_surface = std::make_unique(nullptr); + offscreen_surface->setFormat(format); + offscreen_surface->create(); + surface = offscreen_surface.get(); + } else { + surface = main_surface; + } + } + + ~OpenGLSharedContext() { + DoneCurrent(); + } + + void SwapBuffers() override { + context->swapBuffers(surface); + } + + void MakeCurrent() override { + // We can't track the current state of the underlying context in this wrapper class because + // Qt may make the underlying context not current for one reason or another. In particular, + // the WebBrowser uses GL, so it seems to conflict if we aren't careful. + // Instead of always just making the context current (which does not have any caching to + // check if the underlying context is already current) we can check for the current context + // in the thread local data by calling `currentContext()` and checking if its ours. + if (QOpenGLContext::currentContext() != context.get()) { + context->makeCurrent(surface); + } + } + + void DoneCurrent() override { + context->doneCurrent(); + } + + QOpenGLContext* GetShareContext() { + return context.get(); + } + + const QOpenGLContext* GetShareContext() const { + return context.get(); + } + +private: + // Avoid using Qt parent system here since we might move the QObjects to new threads + // As a note, this means we should avoid using slots/signals with the objects too + std::unique_ptr context; + std::unique_ptr offscreen_surface{}; + QSurface* surface; +}; +#endif + +class DummyContext : public Core::Frontend::GraphicsContext {}; diff --git a/src/qt_common/render/emu_thread.cpp b/src/qt_common/render/emu_thread.cpp new file mode 100644 index 0000000000..b6fdc891fa --- /dev/null +++ b/src/qt_common/render/emu_thread.cpp @@ -0,0 +1,79 @@ +#include "core/core.h" +#include "core/cpu_manager.h" +#include "emu_thread.h" +#include "qt_common/qt_common.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" + +EmuThread::EmuThread() {} + +EmuThread::~EmuThread() = default; + +void EmuThread::run() { + Common::SetCurrentThreadName("EmuControlThread"); + + auto& gpu = QtCommon::system->GPU(); + auto stop_token = m_stop_source.get_token(); + + QtCommon::system->RegisterHostThread(); + + // Main process has been loaded. Make the context current to this thread and begin GPU and CPU + // execution. + gpu.ObtainContext(); + + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + if (Settings::values.use_disk_shader_cache.GetValue()) { + QtCommon::system->Renderer().ReadRasterizer()->LoadDiskResources( + QtCommon::system->GetApplicationProcessProgramID(), stop_token, + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + emit LoadProgress(stage, value, total); + }); + } + emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + + gpu.ReleaseContext(); + gpu.Start(); + + QtCommon::system->GetCpuManager().OnGpuReady(); + + if (QtCommon::system->DebuggerEnabled()) { + QtCommon::system->InitializeDebugger(); + } + + while (!stop_token.stop_requested()) { + std::unique_lock lk{m_should_run_mutex}; + if (m_should_run) { + QtCommon::system->Run(); + m_stopped.Reset(); + + m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); + } else { + QtCommon::system->Pause(); + m_stopped.Set(); + + EmulationPaused(lk); + m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); + EmulationResumed(lk); + } + } + + // Shutdown the main emulated process + QtCommon::system->DetachDebugger(); + QtCommon::system->ShutdownMainProcess(); +} + +// Unlock while emitting signals so that the main thread can +// continue pumping events. + +void EmuThread::EmulationPaused(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeEntered(); + lk.lock(); +} + +void EmuThread::EmulationResumed(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeLeft(); + lk.lock(); +} diff --git a/src/qt_common/render/emu_thread.h b/src/qt_common/render/emu_thread.h new file mode 100644 index 0000000000..32e3d64854 --- /dev/null +++ b/src/qt_common/render/emu_thread.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include "common/logging/log.h" +#include "common/thread.h" + +namespace Core { +class System; +} // namespace Core + +namespace VideoCore { +enum class LoadCallbackStage; +} // namespace VideoCore + +class EmuThread final : public QThread { + Q_OBJECT + +public: + explicit EmuThread(); + ~EmuThread() override; + + /** + * Start emulation (on new thread) + * @warning Only call when not running! + */ + void run() override; + + /** + * Sets whether the emulation thread should run or not + * @param should_run Boolean value, set the emulation thread to running if true + */ + void SetRunning(bool should_run) { + // TODO: Prevent other threads from modifying the state until we finish. + { + // Notify the running thread to change state. + std::unique_lock run_lk{m_should_run_mutex}; + m_should_run = should_run; + m_should_run_cv.notify_one(); + } + + // Wait until paused, if pausing. + if (!should_run) { + m_stopped.Wait(); + } + } + + /** + * Check if the emulation thread is running or not + * @return True if the emulation thread is running, otherwise false + */ + bool IsRunning() const { + return m_should_run; + } + + /** + * Requests for the emulation thread to immediately stop running + */ + void ForceStop() { + LOG_WARNING(Frontend, "Force stopping EmuThread"); + m_stop_source.request_stop(); + } + +private: + void EmulationPaused(std::unique_lock& lk); + void EmulationResumed(std::unique_lock& lk); + +private: + std::stop_source m_stop_source; + std::mutex m_should_run_mutex; + std::condition_variable_any m_should_run_cv; + Common::Event m_stopped; + bool m_should_run{true}; + +signals: + /** + * Emitted when the CPU has halted execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeEntered(); + + /** + * Emitted right before the CPU continues execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeLeft(); + + void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); +}; diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index 5a50f59dc8..79d518d974 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "core/file_sys/card_image.h" #include "qt_common/util/content.h" #include "qt_common/util/game.h" @@ -589,4 +590,41 @@ void InstallFirmwareZip() { } } +void configureFilesystemProvider(const std::string& filepath) { + // Ensure all NCAs are registered before launching the game + const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(*QtCommon::system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + QtCommon::provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, + file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + } // namespace QtCommon::Content diff --git a/src/qt_common/util/content.h b/src/qt_common/util/content.h index d09de6af65..2aca435580 100644 --- a/src/qt_common/util/content.h +++ b/src/qt_common/util/content.h @@ -59,6 +59,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, std::function callback = {}); void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function callback = {}); +// Loader // +void configureFilesystemProvider(const std::string& filepath); + // Profiles // void FixProfiles(); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index b6cd7d0985..f47fde9dc7 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -6,11 +6,10 @@ #include #include #include -#include -#include #include -#include +#include +#include #include "common/settings_enums.h" #include "qt_common/config/uisettings.h" #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA @@ -38,27 +37,21 @@ #include #endif -#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_window.h" #include "qt_common/qt_common.h" +#include "qt_common/render/context.h" class QObject; class QPaintEngine; @@ -66,171 +59,6 @@ class QSurface; constexpr int default_mouse_constrain_timeout = 10; -EmuThread::EmuThread(Core::System& system) : m_system{system} {} - -EmuThread::~EmuThread() = default; - -void EmuThread::run() { - Common::SetCurrentThreadName("EmuControlThread"); - - auto& gpu = m_system.GPU(); - auto stop_token = m_stop_source.get_token(); - - m_system.RegisterHostThread(); - - // Main process has been loaded. Make the context current to this thread and begin GPU and CPU - // execution. - gpu.ObtainContext(); - - emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - if (Settings::values.use_disk_shader_cache.GetValue()) { - m_system.Renderer().ReadRasterizer()->LoadDiskResources( - m_system.GetApplicationProcessProgramID(), stop_token, - [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { - emit LoadProgress(stage, value, total); - }); - } - emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); - - gpu.ReleaseContext(); - gpu.Start(); - - m_system.GetCpuManager().OnGpuReady(); - - if (m_system.DebuggerEnabled()) { - m_system.InitializeDebugger(); - } - - while (!stop_token.stop_requested()) { - std::unique_lock lk{m_should_run_mutex}; - if (m_should_run) { - m_system.Run(); - m_stopped.Reset(); - - m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); - } else { - m_system.Pause(); - m_stopped.Set(); - - EmulationPaused(lk); - m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); - EmulationResumed(lk); - } - } - - // Shutdown the main emulated process - m_system.DetachDebugger(); - m_system.ShutdownMainProcess(); -} - -// Unlock while emitting signals so that the main thread can -// continue pumping events. - -void EmuThread::EmulationPaused(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeEntered(); - lk.lock(); -} - -void EmuThread::EmulationResumed(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeLeft(); - lk.lock(); -} - -#ifdef HAS_OPENGL -class OpenGLSharedContext : public Core::Frontend::GraphicsContext { -public: - /// Create the original context that should be shared from - explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { - QSurfaceFormat format; - format.setVersion(4, 6); - format.setProfile(QSurfaceFormat::CompatibilityProfile); - format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); - if (Settings::values.renderer_debug) { - format.setOption(QSurfaceFormat::FormatOption::DebugContext); - } - // TODO: expose a setting for buffer value (ie default/single/double/triple) - format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); - format.setSwapInterval(0); - - context = std::make_unique(); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create main openGL context"); - } - } - - /// Create the shared contexts for rendering and presentation - explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { - - // disable vsync for any shared contexts - auto format = share_context->format(); - 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(); - context->setShareContext(share_context); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create shared openGL context"); - } - - if (!main_surface) { - offscreen_surface = std::make_unique(nullptr); - offscreen_surface->setFormat(format); - offscreen_surface->create(); - surface = offscreen_surface.get(); - } else { - surface = main_surface; - } - } - - ~OpenGLSharedContext() { - DoneCurrent(); - } - - void SwapBuffers() override { - context->swapBuffers(surface); - } - - void MakeCurrent() override { - // We can't track the current state of the underlying context in this wrapper class because - // Qt may make the underlying context not current for one reason or another. In particular, - // the WebBrowser uses GL, so it seems to conflict if we aren't careful. - // Instead of always just making the context current (which does not have any caching to - // check if the underlying context is already current) we can check for the current context - // in the thread local data by calling `currentContext()` and checking if its ours. - if (QOpenGLContext::currentContext() != context.get()) { - context->makeCurrent(surface); - } - } - - void DoneCurrent() override { - context->doneCurrent(); - } - - QOpenGLContext* GetShareContext() { - return context.get(); - } - - const QOpenGLContext* GetShareContext() const { - return context.get(); - } - -private: - // Avoid using Qt parent system here since we might move the QObjects to new threads - // As a note, this means we should avoid using slots/signals with the objects too - std::unique_ptr context; - std::unique_ptr offscreen_surface{}; - QSurface* surface; -}; -#endif - -class DummyContext : public Core::Frontend::GraphicsContext {}; - class RenderWidget : public QWidget { public: explicit RenderWidget(GRenderWindow* parent) : QWidget(parent) { @@ -271,11 +99,10 @@ struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; -GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_) +GRenderWindow::GRenderWindow(MainWindow* parent, + std::shared_ptr input_subsystem_) : QWidget(parent), - emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, system{system_} { + input_subsystem{std::move(input_subsystem_)} { setWindowTitle(QStringLiteral("Eden %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -694,7 +521,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { } void GRenderWindow::ConstrainMouse() { - if (emu_thread == nullptr || !Settings::values.mouse_panning) { + if (QtCommon::emu_thread == nullptr || !Settings::values.mouse_panning) { mouse_constrain_timer.stop(); return; } @@ -960,7 +787,7 @@ void GRenderWindow::ReleaseRenderTarget() { } void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { - auto& renderer = system.Renderer(); + auto& renderer = QtCommon::system->Renderer(); if (renderer.IsScreenshotPending()) { LOG_WARNING(Render, @@ -985,7 +812,11 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { screenshot_image.bits(), [=, this](bool invert_y) { const std::string std_screenshot_path = screenshot_path.toStdString(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + if (screenshot_image.flipped(invert_y ? Qt::Vertical : (Qt::Orientations) 0).save(screenshot_path)) { +#else if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { +#endif LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); } else { LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); @@ -1067,7 +898,7 @@ bool GRenderWindow::LoadOpenGL() { tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " "have the latest graphics driver.

GL Renderer:
%1

Unsupported " "extensions:
%2") - .arg(renderer).arg(missing_ext.join(QStringLiteral("
")))); + .arg(renderer, missing_ext.join(QStringLiteral("
")))); // Non fatal } return true; @@ -1087,12 +918,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { return missing_ext; } -void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) { - emu_thread = emu_thread_; -} - void GRenderWindow::OnEmulationStopping() { - emu_thread = nullptr; + QtCommon::emu_thread = nullptr; } void GRenderWindow::showEvent(QShowEvent* event) { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 83d364df4b..b79246b118 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -6,12 +6,9 @@ #pragma once -#include #include #include -#include #include -#include #include #include @@ -27,9 +24,6 @@ #include #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 MainWindow; @@ -45,10 +39,6 @@ class QShowEvent; class QTouchEvent; class QWheelEvent; -namespace Core { -class System; -} // namespace Core - namespace InputCommon { class InputSubsystem; enum class MouseButton; @@ -58,100 +48,13 @@ namespace InputCommon::TasInput { enum class TasState; } // namespace InputCommon::TasInput -namespace VideoCore { -enum class LoadCallbackStage; -} // namespace VideoCore - -class EmuThread final : public QThread { - Q_OBJECT - -public: - explicit EmuThread(Core::System& system); - ~EmuThread() override; - - /** - * Start emulation (on new thread) - * @warning Only call when not running! - */ - void run() override; - - /** - * Sets whether the emulation thread should run or not - * @param should_run Boolean value, set the emulation thread to running if true - */ - void SetRunning(bool should_run) { - // TODO: Prevent other threads from modifying the state until we finish. - { - // Notify the running thread to change state. - std::unique_lock run_lk{m_should_run_mutex}; - m_should_run = should_run; - m_should_run_cv.notify_one(); - } - - // Wait until paused, if pausing. - if (!should_run) { - m_stopped.Wait(); - } - } - - /** - * Check if the emulation thread is running or not - * @return True if the emulation thread is running, otherwise false - */ - bool IsRunning() const { - return m_should_run; - } - - /** - * Requests for the emulation thread to immediately stop running - */ - void ForceStop() { - LOG_WARNING(Frontend, "Force stopping EmuThread"); - m_stop_source.request_stop(); - } - -private: - void EmulationPaused(std::unique_lock& lk); - void EmulationResumed(std::unique_lock& lk); - -private: - Core::System& m_system; - - std::stop_source m_stop_source; - std::mutex m_should_run_mutex; - std::condition_variable_any m_should_run_cv; - Common::Event m_stopped; - bool m_should_run{true}; - -signals: - /** - * Emitted when the CPU has halted execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeEntered(); - - /** - * Emitted right before the CPU continues execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeLeft(); - - void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); -}; - +// TODO: This and the Quick render window will probably end up similar +// maybe qt_common those? class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: - explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_); + explicit GRenderWindow(MainWindow* parent, std::shared_ptr input_subsystem_); ~GRenderWindow() override; // EmuWindow implementation. @@ -216,7 +119,6 @@ public: void Exit(); public slots: - void OnEmulationStarting(EmuThread* emu_thread_); void OnEmulationStopping(); void OnFramebufferSizeChanged(); @@ -246,7 +148,6 @@ private: bool LoadOpenGL(); QStringList GetUnsupportedGLExtensions() const; - EmuThread* emu_thread; std::shared_ptr input_subsystem; // Main context that will be shared with all other contexts that are requested. @@ -275,8 +176,6 @@ private: QTimer mouse_constrain_timer; - Core::System& system; - protected: void showEvent(QShowEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override; diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 865a71efa1..8a04e462f3 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -3,6 +3,7 @@ // Qt on macOS doesn't define VMA shit #include "qt_common/qt_string_lookup.h" +#include "qt_common/render/emu_thread.h" #if defined(QT_STATICPLUGIN) && !defined(__APPLE__) #undef VMA_IMPLEMENTATION #endif @@ -104,16 +105,11 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual // Common // #include "common/fs/fs.h" -#include "common/logging/backend.h" -#include "common/memory_detect.h" +#include "common/logging/log.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_detect.h" -#endif - // Core // #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/mii_edit.h" @@ -924,7 +920,7 @@ void MainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui->action_Report_Compatibility->setVisible(true); #endif - render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); + render_window = new GRenderWindow(this, input_subsystem); render_window->hide(); game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, *QtCommon::system, this); @@ -1368,11 +1364,11 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { return; } if (UISettings::values.pause_when_in_background) { - if (emu_thread->IsRunning() && + if (QtCommon::emu_thread->IsRunning() && (state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) { auto_paused = true; OnPauseGame(); - } else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) { + } else if (!QtCommon::emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) { auto_paused = false; OnStartGame(); } @@ -1428,8 +1424,6 @@ void MainWindow::ConnectWidgetEvents() { connect(this, &MainWindow::UpdateInstallProgress, this, &MainWindow::IncrementInstallProgress); - connect(this, &MainWindow::EmulationStarting, render_window, - &GRenderWindow::OnEmulationStarting); connect(this, &MainWindow::EmulationStopping, render_window, &GRenderWindow::OnEmulationStopping); @@ -1538,7 +1532,7 @@ void MainWindow::ConnectMenuEvents() { } void MainWindow::UpdateMenuState() { - const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); + const bool is_paused = QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning(); const bool is_firmware_available = CheckFirmwarePresence(); const std::array running_actions{ @@ -1610,17 +1604,17 @@ void MainWindow::SetupPrepareForSleep() { } void MainWindow::OnPrepareForSleep(bool prepare_sleep) { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) { return; } if (prepare_sleep) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { auto_paused = true; OnPauseGame(); } } else { - if (!emu_thread->IsRunning() && auto_paused) { + if (!QtCommon::emu_thread->IsRunning() && auto_paused) { auto_paused = false; OnStartGame(); } @@ -1693,7 +1687,7 @@ void MainWindow::AllowOSSleep() { bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params) { // Shutdown previous session if the emu thread is still active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) { ShutdownGame(); } @@ -1812,43 +1806,6 @@ bool MainWindow::SelectAndSetCurrentUser( return true; } -void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { - // Ensure all NCAs are registered before launching the game - const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); - if (!file) { - return; - } - - auto loader = Loader::GetLoader(*QtCommon::system, file); - if (!loader) { - return; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return; - } - - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - QtCommon::provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, - file); - } else if (res2 == Loader::ResultStatus::Success && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - const auto nsp = file_type == Loader::FileType::NSP - ? std::make_shared(file) - : FileSys::XCI{file}.GetSecurePartitionNSP(); - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } - } -} - void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, StartGameType type) { LOG_INFO(Frontend, "Eden starting..."); @@ -1867,7 +1824,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa last_filename_booted = filename; - ConfigureFilesystemProvider(filename.toStdString()); + QtCommon::Content::configureFilesystemProvider(filename.toStdString()); const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); const auto loader = Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); @@ -1911,23 +1868,23 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa game_list->setDisabled(true); // Create and start the emulation thread - emu_thread = std::make_unique(*QtCommon::system); - emit EmulationStarting(emu_thread.get()); - emu_thread->start(); + QtCommon::emu_thread = std::make_unique(); + emit EmulationStarting(); + QtCommon::emu_thread->start(); // Register an ExecuteProgram callback such that Core can execute a sub-program QtCommon::system->RegisterExecuteProgramCallback( [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); QtCommon::system->RegisterExitCallback([this] { - emu_thread->ForceStop(); + QtCommon::emu_thread->ForceStop(); render_window->Exit(); }); connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame); connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity); - connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen, + connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen, &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); // Update the GUI @@ -2014,8 +1971,8 @@ bool MainWindow::OnShutdownBegin() { discord_rpc->Pause(); RequestGameExit(); - emu_thread->disconnect(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->SetRunning(true); emit EmulationStopping(); @@ -2030,7 +1987,7 @@ bool MainWindow::OnShutdownBegin() { shutdown_timer.setSingleShot(true); shutdown_timer.start(shutdown_time); connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired); - connect(emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); + connect(QtCommon::emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); // Disable everything to prevent anything from being triggered here ui->action_Pause->setEnabled(false); @@ -2047,17 +2004,17 @@ void MainWindow::OnShutdownBeginDialog() { } void MainWindow::OnEmulationStopTimeExpired() { - if (emu_thread) { - emu_thread->ForceStop(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->ForceStop(); } } void MainWindow::OnEmulationStopped() { shutdown_timer.stop(); - if (emu_thread) { - emu_thread->disconnect(); - emu_thread->wait(); - emu_thread.reset(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->wait(); + QtCommon::emu_thread.reset(); } if (shutdown_dialog) { @@ -2931,7 +2888,7 @@ void MainWindow::OnMenuRecentFile() { void MainWindow::OnStartGame() { PreventOSSleep(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->SetRunning(true); UpdateMenuState(); OnTasStateChanged(); @@ -2957,7 +2914,7 @@ void MainWindow::OnRestartGame() { } void MainWindow::OnPauseGame() { - emu_thread->SetRunning(false); + QtCommon::emu_thread->SetRunning(false); play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); @@ -2966,7 +2923,7 @@ void MainWindow::OnPauseGame() { void MainWindow::OnPauseContinueGame() { if (emulation_running) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { OnPauseGame(); } else { OnStartGame(); @@ -3552,7 +3509,7 @@ void MainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_ } void MainWindow::OnLoadAmiibo() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning()) { return; } if (is_amiibo_file_select_active) { @@ -3667,7 +3624,7 @@ void MainWindow::OnVerifyInstalledContents() { void MainWindow::OnInstallFirmware() { // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { + if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning()) { return; } @@ -3677,7 +3634,7 @@ void MainWindow::OnInstallFirmware() { void MainWindow::OnInstallFirmwareFromZIP() { // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { + if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning()) { return; } @@ -3687,7 +3644,7 @@ void MainWindow::OnInstallFirmwareFromZIP() { void MainWindow::OnInstallDecryptionKeys() { // Don't do this while emulation is running. - if (emu_thread != nullptr && emu_thread->IsRunning()) { + if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning()) { return; } @@ -3899,7 +3856,7 @@ void MainWindow::OnCreateHomeMenuApplicationMenuShortcut() { } void MainWindow::OnCaptureScreenshot() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning()) { return; } @@ -4035,7 +3992,7 @@ void MainWindow::OnTasStateChanged() { } void MainWindow::UpdateStatusBar() { - if (emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { status_bar_update_timer.stop(); return; } @@ -4172,7 +4129,7 @@ void MainWindow::UpdateInputDrivers() { } void MainWindow::HideMouseCursor() { - if (emu_thread == nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread == nullptr && UISettings::values.hide_mouse) { mouse_hide_timer.stop(); ShowMouseCursor(); return; @@ -4182,7 +4139,7 @@ void MainWindow::HideMouseCursor() { void MainWindow::ShowMouseCursor() { render_window->unsetCursor(); - if (emu_thread != nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread != nullptr && UISettings::values.hide_mouse) { mouse_hide_timer.start(); } } @@ -4341,7 +4298,7 @@ bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed } bool MainWindow::ConfirmClose() { - if (emu_thread == nullptr || + if (QtCommon::emu_thread == nullptr || UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) { return true; } @@ -4369,7 +4326,7 @@ void MainWindow::closeEvent(QCloseEvent* event) { game_list->UnloadController(); // Shutdown session if the emu thread is active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) { ShutdownGame(); } @@ -4425,7 +4382,7 @@ void MainWindow::dragMoveEvent(QDragMoveEvent* event) { } bool MainWindow::ConfirmChangeGame() { - if (emu_thread == nullptr) + if (QtCommon::emu_thread == nullptr) return true; // Use custom question to link controller navigation @@ -4436,7 +4393,7 @@ bool MainWindow::ConfirmChangeGame() { } bool MainWindow::ConfirmForceLockedExit() { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) { return true; } const auto text = tr("The currently running application has requested Eden to not exit.\n\n" diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index f7f860533a..c980827c1d 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -19,6 +19,7 @@ #include "common/common_types.h" #include "frontend_common/content_manager.h" #include "input_common/drivers/tas_input.h" +#include "qt_common/qt_common.h" #include "qt_common/config/qt_config.h" #include "qt_common/util/game.h" #include "yuzu/user_data_migration.h" @@ -62,11 +63,6 @@ class QtProfileSelectionDialog; class QtSoftwareKeyboardDialog; class QtNXWebEngineView; -enum class StartGameType { - Normal, // Can use custom configuration - Global, // Only uses global configuration -}; - namespace Core { enum class SystemResultStatus : u32; } // namespace Core @@ -180,7 +176,7 @@ signals: * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to * access/change emulation state). */ - void EmulationStarting(EmuThread* emu_thread); + void EmulationStarting(); /** * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core @@ -448,7 +444,7 @@ private: void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); bool CheckFirmwarePresence(); void SetFirmwareVersion(); - void ConfigureFilesystemProvider(const std::string& filepath); + /** * Open (or not) the right confirm dialog based on current setting and game exit lock * @returns true if the player confirmed or the settings do no require it @@ -517,7 +513,6 @@ private: // Whether emulation is currently running in yuzu. bool emulation_running = false; - std::unique_ptr emu_thread; // The path to the game currently running QString current_game_path; // Whether a user was set on the command line (skips UserSelector if it's forced to show up)