mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-20 16:17:01 +02:00
[desktop] More qt_common reorganization (#3916)
Ported from QML branch. Main "big" change is that EmuThread is now a shared state in QtCommon, not individually managed/passed around by GRenderWindow and MainWindow. Signed-off-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3916 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
parent
300a646a34
commit
feb8c5f88e
44 changed files with 963 additions and 932 deletions
|
|
@ -582,8 +582,6 @@ if (ENABLE_QT)
|
|||
else()
|
||||
AddQt(6.9.3)
|
||||
endif()
|
||||
|
||||
set(YUZU_STATIC_BUILD ON)
|
||||
else()
|
||||
message(STATUS "Using system Qt")
|
||||
if (NOT Qt6_DIR)
|
||||
|
|
@ -592,23 +590,7 @@ if (ENABLE_QT)
|
|||
list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}")
|
||||
endif()
|
||||
|
||||
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
|
||||
|
||||
if (YUZU_USE_QT_MULTIMEDIA)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
|
||||
endif()
|
||||
|
||||
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
|
||||
# yes Qt, we get it
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
find_package(Qt6 REQUIRED COMPONENTS DBus OPTIONAL_COMPONENTS GuiPrivate)
|
||||
elseif (UNIX AND NOT APPLE)
|
||||
find_package(Qt6 REQUIRED COMPONENTS DBus Gui)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||
endif()
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
|
||||
if (NOT DEFINED QT_TARGET_PATH)
|
||||
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
|
||||
|
|
@ -631,21 +613,27 @@ if (ENABLE_QT)
|
|||
## Components ##
|
||||
|
||||
# Best practice is to ask for all components at once, so they are from the same version
|
||||
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent)
|
||||
if (PLATFORM_LINUX)
|
||||
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui)
|
||||
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
|
||||
list(APPEND YUZU_QT_COMPONENTS DBus)
|
||||
# yes Qt, we get it
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
list(APPEND YUZU_QT_OPTIONAL GuiPrivate)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_QT_MULTIMEDIA)
|
||||
list(APPEND YUZU_QT_COMPONENTS Multimedia)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
list(APPEND YUZU_QT_COMPONENTS LinguistTools)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS})
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS} OPTIONAL_COMPONENTS ${YUZU_QT_OPTIONAL})
|
||||
set(QT_MAJOR_VERSION 6)
|
||||
# Qt6 sets cxx_std_17 and we need to undo that
|
||||
set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "")
|
||||
|
|
|
|||
|
|
@ -715,8 +715,8 @@ function(AddCIPackage)
|
|||
"${${ARTIFACT_PACKAGE}_SOURCE_DIR}" PARENT_SCOPE)
|
||||
|
||||
if (PKG_ARGS_MODULE)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
list(PREPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}")
|
||||
Propagate(CMAKE_PREFIX_PATH)
|
||||
endif()
|
||||
else()
|
||||
find_package(${ARTIFACT_PACKAGE} ${ARTIFACT_MIN_VERSION} REQUIRED)
|
||||
|
|
@ -730,7 +730,7 @@ function(AddQt version)
|
|||
endif()
|
||||
|
||||
AddCIPackage(
|
||||
NAME Qt
|
||||
NAME qt
|
||||
PACKAGE Qt6
|
||||
VERSION ${version}
|
||||
MIN_VERSION 6
|
||||
|
|
@ -740,5 +740,8 @@ function(AddQt version)
|
|||
freebsd-amd64 solaris-amd64 openbsd-amd64
|
||||
MODULE)
|
||||
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
find_package(Qt6 REQUIRED PATHS ${Qt6_SOURCE_DIR} NO_DEFAULT_PATH)
|
||||
|
||||
Propagate(CMAKE_PREFIX_PATH)
|
||||
Propagate(Qt6_SOURCE_DIR)
|
||||
endfunction()
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@
|
|||
"options": [
|
||||
"QUAZIP_QT_MAJOR_VERSION 6",
|
||||
"QUAZIP_INSTALL OFF",
|
||||
"QUAZIP_ENABLE_QTEXTCODEC OFF"
|
||||
"QUAZIP_ENABLE_QTEXTCODEC OFF",
|
||||
"QUAZIP_BZIP2 OFF"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -435,4 +435,16 @@ void ToggleSlowMode() {
|
|||
SetSpeedMode(SpeedMode::Standard);
|
||||
}
|
||||
|
||||
bool IsOpenGL() {
|
||||
const auto backend = Settings::values.renderer_backend.GetValue();
|
||||
switch (backend) {
|
||||
case RendererBackend::OpenGL_GLSL:
|
||||
case RendererBackend::OpenGL_GLASM:
|
||||
case RendererBackend::OpenGL_SPIRV:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
|
|
|||
|
|
@ -863,6 +863,8 @@ bool IsFastmemEnabled();
|
|||
void SetNceEnabled(bool is_64bit);
|
||||
bool IsNceEnabled();
|
||||
|
||||
bool IsOpenGL();
|
||||
|
||||
bool IsDockedMode();
|
||||
|
||||
float Volume();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -16,6 +19,12 @@
|
|||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
|
||||
#undef CreateFile
|
||||
#undef CopyFile
|
||||
#undef MoveFile
|
||||
#undef DeleteFile
|
||||
#undef CreateDirectory
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// An enumeration representing what can be at the end of a path in a VfsFilesystem
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
add_library(yuzu-room STATIC EXCLUDE_FROM_ALL
|
||||
yuzu_room.cpp
|
||||
yuzu_room.h
|
||||
yuzu_room.rc
|
||||
)
|
||||
yuzu_room.rc)
|
||||
|
||||
target_link_libraries(yuzu-room PRIVATE common network)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ add_library(qt_common STATIC
|
|||
util/compress.h util/compress.cpp
|
||||
util/fs.h util/fs.cpp
|
||||
util/mod.h util/mod.cpp
|
||||
util/vk.h util/vk.cpp
|
||||
|
||||
abstract/frontend.h abstract/frontend.cpp
|
||||
abstract/progress.h abstract/progress.cpp
|
||||
|
|
@ -33,7 +34,9 @@ add_library(qt_common STATIC
|
|||
qt_string_lookup.h
|
||||
qt_compat.h
|
||||
|
||||
discord/discord.h)
|
||||
discord/discord.h
|
||||
render/context.h
|
||||
render/emu_thread.h render/emu_thread.cpp)
|
||||
|
||||
if (UNIX)
|
||||
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
|
||||
|
|
@ -56,9 +59,7 @@ if (USE_DISCORD_PRESENCE)
|
|||
endif()
|
||||
|
||||
# TODO(crueter)
|
||||
if (ENABLE_QT)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
endif()
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
|
||||
target_compile_definitions(qt_common PUBLIC
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
|
|
@ -80,7 +81,7 @@ target_compile_definitions(qt_common PUBLIC
|
|||
)
|
||||
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen-headers)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen-headers Vulkan::Headers)
|
||||
target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common)
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
|
|
|
|||
|
|
@ -165,8 +165,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||
INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"),
|
||||
tr("Allows saving shaders to storage for faster loading on following game "
|
||||
"boots.\nDisabling it is only intended for debugging."));
|
||||
INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
|
||||
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
|
||||
INSERT(
|
||||
Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
|
||||
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
|
||||
INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
|
||||
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
|
||||
"decoding, or perform no decoding at all (black screen on videos).\n"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,52 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "network/network.h"
|
||||
#include "qt_common.h"
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStringLiteral>
|
||||
#include "common/logging.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "qt_common/util/meta.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <thread>
|
||||
#include <JlCompress.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__APPLE__)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <objc/message.h>
|
||||
#include <objc/runtime.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <QApplication>
|
||||
#include <QSettings>
|
||||
#include <windows.h>
|
||||
#include "common/windows/timer_resolution.h"
|
||||
#include "core/core_timing.h"
|
||||
#endif
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
QWidget* rootObject = nullptr;
|
||||
|
|
@ -27,6 +54,11 @@ QWidget* rootObject = nullptr;
|
|||
std::unique_ptr<Core::System> system = nullptr;
|
||||
std::shared_ptr<FileSys::RealVfsFilesystem> vfs = nullptr;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> provider = nullptr;
|
||||
std::unique_ptr<EmuThread> emu_thread = nullptr;
|
||||
|
||||
const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"),
|
||||
QStringLiteral("nca"), QStringLiteral("xci"),
|
||||
QStringLiteral("nsp"), QStringLiteral("kip")};
|
||||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType() {
|
||||
// Determine WSI type based on Qt platform.
|
||||
|
|
@ -58,37 +90,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
// Our Win32 Qt external doesn't have the private API.
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
id layer = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
|
||||
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
|
||||
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
|
||||
// In Qt 6, the layer of the NSView might be a QContainerLayer.
|
||||
// MoltenVK needs a CAMetalLayer. We search for it in sublayers.
|
||||
Class metal_layer_class = objc_getClass("CAMetalLayer");
|
||||
id metal_layer = nullptr;
|
||||
|
||||
if (layer) {
|
||||
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
|
||||
layer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
|
||||
metal_layer = layer;
|
||||
} else {
|
||||
id sublayers = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
|
||||
layer, sel_registerName("sublayers"));
|
||||
if (sublayers) {
|
||||
unsigned long count = reinterpret_cast<unsigned long (*)(id, SEL)>(objc_msgSend)(
|
||||
sublayers, sel_registerName("count"));
|
||||
for (unsigned long i = 0; i < count; ++i) {
|
||||
id sublayer = reinterpret_cast<id (*)(id, SEL, unsigned long)>(objc_msgSend)(
|
||||
sublayers, sel_registerName("objectAtIndex:"), i);
|
||||
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
|
||||
sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
|
||||
metal_layer = sublayer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wsi.render_surface = reinterpret_cast<void*>(metal_layer ? metal_layer : layer);
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
|
|
@ -103,18 +106,148 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
}
|
||||
|
||||
const QString tr(const char* str) {
|
||||
return QGuiApplication::tr(str);
|
||||
return rootObject->tr(str);
|
||||
}
|
||||
|
||||
const QString tr(const std::string& str) {
|
||||
return QGuiApplication::tr(str.c_str());
|
||||
return rootObject->tr(str.c_str());
|
||||
}
|
||||
|
||||
static void LogRuntimes() {
|
||||
#ifdef _MSC_VER
|
||||
// It is possible that the name of the dll will change.
|
||||
// vcruntime140.dll is for 2015 and onwards
|
||||
static constexpr char runtime_dll_name[] = "vcruntime140.dll";
|
||||
UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
|
||||
bool runtime_version_inspection_worked = false;
|
||||
if (sz > 0) {
|
||||
std::vector<u8> buf(sz);
|
||||
if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
|
||||
VS_FIXEDFILEINFO* pvi;
|
||||
sz = sizeof(VS_FIXEDFILEINFO);
|
||||
if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
|
||||
if (pvi->dwSignature == VS_FFI_SIGNATURE) {
|
||||
runtime_version_inspection_worked = true;
|
||||
LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
|
||||
pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
|
||||
pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!runtime_version_inspection_worked) {
|
||||
LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion());
|
||||
}
|
||||
|
||||
static QString PrettyProductName() {
|
||||
#ifdef _WIN32
|
||||
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
|
||||
// With that notation change they changed the registry key used to denote the current version
|
||||
QSettings windows_registry(
|
||||
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
|
||||
QSettings::NativeFormat);
|
||||
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
|
||||
if (release_id == QStringLiteral("2009")) {
|
||||
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
|
||||
const QString display_version =
|
||||
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
|
||||
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
|
||||
u32 version = 10;
|
||||
if (current_build >= 22000) {
|
||||
version = 11;
|
||||
}
|
||||
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
|
||||
.arg(QString::number(version), display_version, QString::number(current_build),
|
||||
QString::number(ubr));
|
||||
}
|
||||
#endif
|
||||
return QSysInfo::prettyProductName();
|
||||
}
|
||||
|
||||
static void RemoveCachedContents() {
|
||||
const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
|
||||
const auto offline_fonts = cache_dir / "fonts";
|
||||
const auto offline_manual = cache_dir / "offline_web_applet_manual";
|
||||
const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
|
||||
const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
|
||||
|
||||
Common::FS::RemoveDirRecursively(offline_fonts);
|
||||
Common::FS::RemoveDirRecursively(offline_manual);
|
||||
Common::FS::RemoveDirRecursively(offline_legal_information);
|
||||
Common::FS::RemoveDirRecursively(offline_system_data);
|
||||
}
|
||||
|
||||
void Init(QWidget* root) {
|
||||
system = std::make_unique<Core::System>();
|
||||
rootObject = root;
|
||||
|
||||
system = std::make_unique<Core::System>();
|
||||
vfs = std::make_unique<FileSys::RealVfsFilesystem>();
|
||||
provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
|
||||
// initialization stuff
|
||||
Common::FS::CreateEdenPaths();
|
||||
|
||||
system->Initialize();
|
||||
|
||||
Common::Log::Initialize();
|
||||
Common::Log::Start();
|
||||
|
||||
Network::Init();
|
||||
|
||||
QtCommon::Meta::RegisterMetaTypes();
|
||||
|
||||
// build version
|
||||
const auto branch_name = std::string(Common::g_scm_branch);
|
||||
const auto description = std::string(Common::g_scm_desc);
|
||||
const auto build_id = std::string(Common::g_build_id);
|
||||
|
||||
const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description);
|
||||
const auto override_build =
|
||||
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
|
||||
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
|
||||
const auto processor_count = std::thread::hardware_concurrency();
|
||||
|
||||
// info logging
|
||||
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
|
||||
LogRuntimes();
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
const auto& caps = Common::GetCPUCaps();
|
||||
std::string cpu_string = caps.cpu_string;
|
||||
if (caps.avx || caps.avx2 || caps.avx512f) {
|
||||
cpu_string += " | AVX";
|
||||
if (caps.avx512f) {
|
||||
cpu_string += "512";
|
||||
} else if (caps.avx2) {
|
||||
cpu_string += '2';
|
||||
}
|
||||
if (caps.fma || caps.fma4) {
|
||||
cpu_string += " | FMA";
|
||||
}
|
||||
}
|
||||
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
|
||||
if (std::optional<int> processor_core = Common::GetProcessorCount()) {
|
||||
LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
|
||||
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
|
||||
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());
|
||||
QtCommon::system->CoreTiming().SetTimerResolutionNs(
|
||||
Common::Windows::GetCurrentTimerResolution());
|
||||
#endif
|
||||
|
||||
// Remove cached contents generated during the previous session
|
||||
RemoveCachedContents();
|
||||
}
|
||||
|
||||
std::filesystem::path GetEdenCommand() {
|
||||
|
|
@ -137,4 +270,15 @@ std::filesystem::path GetEdenCommand() {
|
|||
return command;
|
||||
}
|
||||
|
||||
void SetupContentProviders() {
|
||||
system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
provider.get());
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
}
|
||||
|
||||
void SetupHID() {
|
||||
system->HIDCore().ReloadInputDevices();
|
||||
}
|
||||
|
||||
} // namespace QtCommon
|
||||
|
|
|
|||
|
|
@ -6,19 +6,31 @@
|
|||
|
||||
#include <memory>
|
||||
#include <QWindow>
|
||||
#include <core/frontend/emu_window.h>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
#include <core/file_sys/vfs/vfs_real.h>
|
||||
#include "qt_common/render/emu_thread.h"
|
||||
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
|
||||
enum class StartGameType {
|
||||
Normal, // Can use custom configuration
|
||||
Global, // Only uses global configuration
|
||||
};
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
// TODO: Remove QWidget dependency
|
||||
extern QWidget* rootObject;
|
||||
|
||||
extern std::unique_ptr<Core::System> system;
|
||||
extern std::shared_ptr<FileSys::RealVfsFilesystem> vfs;
|
||||
extern std::unique_ptr<FileSys::ManualContentProvider> provider;
|
||||
extern std::unique_ptr<EmuThread> emu_thread;
|
||||
|
||||
extern const QStringList supported_file_extensions;
|
||||
|
||||
typedef std::function<bool(std::size_t, std::size_t)> QtProgressCallback;
|
||||
|
||||
|
|
@ -28,6 +40,9 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
|
||||
void Init(QWidget* root);
|
||||
|
||||
void SetupContentProviders();
|
||||
void SetupHID();
|
||||
|
||||
const QString tr(const char* str);
|
||||
const QString tr(const std::string& str);
|
||||
|
||||
|
|
|
|||
107
src/qt_common/render/context.h
Normal file
107
src/qt_common/render/context.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSurface>
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#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<QOpenGLContext>();
|
||||
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<QOpenGLContext>();
|
||||
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<QOffscreenSurface>(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<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||
QSurface* surface;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DummyContext : public Core::Frontend::GraphicsContext {};
|
||||
83
src/qt_common/render/emu_thread.cpp
Normal file
83
src/qt_common/render/emu_thread.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <qdebug.h>
|
||||
#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<std::mutex>& lk) {
|
||||
lk.unlock();
|
||||
emit DebugModeEntered();
|
||||
lk.lock();
|
||||
}
|
||||
|
||||
void EmuThread::EmulationResumed(std::unique_lock<std::mutex>& lk) {
|
||||
lk.unlock();
|
||||
emit DebugModeLeft();
|
||||
lk.lock();
|
||||
}
|
||||
97
src/qt_common/render/emu_thread.h
Normal file
97
src/qt_common/render/emu_thread.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
#include "common/logging.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<std::mutex>& lk);
|
||||
void EmulationResumed(std::unique_lock<std::mutex>& 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);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 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"
|
||||
|
||||
|
|
@ -494,5 +495,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str
|
|||
});
|
||||
}
|
||||
|
||||
// TODO(crueter): Port InstallFirmware et al. from QML Branch
|
||||
bool CheckKeys() {
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QtCommon::Frontend::Information(
|
||||
tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart Eden before attempting to install firmware."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallFirmware() {
|
||||
if (!CheckKeys())
|
||||
return;
|
||||
|
||||
const QString firmware_source_location =
|
||||
QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {});
|
||||
|
||||
if (!firmware_source_location.isEmpty())
|
||||
QtCommon::Content::InstallFirmware(firmware_source_location, false);
|
||||
}
|
||||
|
||||
void InstallFirmwareZip() {
|
||||
if (!CheckKeys())
|
||||
return;
|
||||
|
||||
const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName(
|
||||
tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (firmware_zip_location.isEmpty())
|
||||
return;
|
||||
|
||||
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
|
||||
|
||||
// In this case, it has to be done recursively, since sometimes people
|
||||
// will pack it into a subdirectory after dumping
|
||||
if (!qCacheDir.isEmpty()) {
|
||||
QtCommon::Content::InstallFirmware(qCacheDir, true);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware",
|
||||
ec);
|
||||
|
||||
if (ec) {
|
||||
QtCommon::Frontend::Warning(
|
||||
tr("Firmware cleanup failed"),
|
||||
tr("Failed to clean up extracted firmware cache.\n"
|
||||
"Check write permissions in the system temp directory and try "
|
||||
"again.\nOS reported error: %1")
|
||||
.arg(QString::fromStdString(ec.message())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<FileSys::NSP>(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
|
||||
|
|
|
|||
|
|
@ -37,9 +37,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult
|
|||
}
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive);
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location);
|
||||
|
||||
bool CheckKeys();
|
||||
void InstallFirmware();
|
||||
void InstallFirmwareZip();
|
||||
|
||||
// Keys //
|
||||
void InstallKeys();
|
||||
|
||||
|
|
@ -54,6 +57,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string&
|
|||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
|
||||
std::function<void()> callback = {});
|
||||
|
||||
// Loader //
|
||||
void configureFilesystemProvider(const std::string& filepath);
|
||||
|
||||
// Profiles //
|
||||
void FixProfiles();
|
||||
} // namespace QtCommon::Content
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include "qt_common/abstract/frontend.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QStandardPaths>
|
||||
|
|
@ -22,6 +21,7 @@
|
|||
#include <windows.h>
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/fs/file.h"
|
||||
#else
|
||||
#include <fstream>
|
||||
#include "fmt/ostream.h"
|
||||
|
|
@ -367,6 +367,7 @@ void ResetMetadata(bool show_message) {
|
|||
|
||||
// Uhhh //
|
||||
|
||||
// TODO(crueter): Make QtCommon::Shortcut
|
||||
// Messages in pre-defined message boxes for less code spaghetti
|
||||
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) {
|
||||
int result = 0;
|
||||
|
|
@ -533,4 +534,110 @@ void CreateHomeMenuShortcut(ShortcutTarget target) {
|
|||
CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
|
||||
}
|
||||
|
||||
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
|
||||
#if defined(WIN32)
|
||||
#pragma pack(push, 2)
|
||||
struct IconDir {
|
||||
WORD id_reserved;
|
||||
WORD id_type;
|
||||
WORD id_count;
|
||||
};
|
||||
|
||||
struct IconDirEntry {
|
||||
BYTE width;
|
||||
BYTE height;
|
||||
BYTE color_count;
|
||||
BYTE reserved;
|
||||
WORD planes;
|
||||
WORD bit_count;
|
||||
DWORD bytes_in_res;
|
||||
DWORD image_offset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
|
||||
constexpr int bytes_per_pixel = 4;
|
||||
|
||||
const IconDir icon_dir{
|
||||
.id_reserved = 0,
|
||||
.id_type = 1,
|
||||
.id_count = static_cast<WORD>(scale_sizes.size()),
|
||||
};
|
||||
|
||||
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile);
|
||||
if (!icon_file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon_file.Write(icon_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
|
||||
const IconDirEntry icon_entry{
|
||||
.width = static_cast<BYTE>(scale_sizes[i]),
|
||||
.height = static_cast<BYTE>(scale_sizes[i]),
|
||||
.color_count = 0,
|
||||
.reserved = 0,
|
||||
.planes = 1,
|
||||
.bit_count = bytes_per_pixel * 8,
|
||||
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
||||
.image_offset = static_cast<DWORD>(image_offset),
|
||||
};
|
||||
image_offset += icon_entry.bytes_in_res;
|
||||
if (!icon_file.Write(icon_entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const QImage scaled_image = source_image.scaled(
|
||||
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
const BITMAPINFOHEADER info_header{
|
||||
.biSize = sizeof(BITMAPINFOHEADER),
|
||||
.biWidth = scaled_image.width(),
|
||||
.biHeight = scaled_image.height() * 2,
|
||||
.biPlanes = 1,
|
||||
.biBitCount = bytes_per_pixel * 8,
|
||||
.biCompression = BI_RGB,
|
||||
.biSizeImage{},
|
||||
.biXPelsPerMeter{},
|
||||
.biYPelsPerMeter{},
|
||||
.biClrUsed{},
|
||||
.biClrImportant{},
|
||||
};
|
||||
|
||||
if (!icon_file.Write(info_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < scaled_image.height(); y++) {
|
||||
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
|
||||
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
|
||||
std::memcpy(line_data.data(), line, line_data.size());
|
||||
if (!icon_file.Write(line_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
icon_file.Close();
|
||||
|
||||
return true;
|
||||
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
|
||||
// Convert and write the icon as a PNG
|
||||
if (!image.save(QString::fromStdString(icon_path.string()))) {
|
||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||
} else {
|
||||
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ void CreateShortcut(const std::string& game_path, const u64 program_id,
|
|||
std::string GetShortcutPath(ShortcutTarget target);
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target);
|
||||
|
||||
/**
|
||||
* Saves a windows icon to a file
|
||||
* @param path The icons path
|
||||
* @param image The image to save
|
||||
* @return bool If the operation succeeded
|
||||
*/
|
||||
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
|
||||
#endif // QT_GAME_UTIL_H
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@
|
|||
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/logging.h"
|
||||
#include "qt_common/util/vk.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/vk_device_info.h"
|
||||
|
||||
class QWindow;
|
||||
|
||||
71
src/qt_common/util/vk.h
Normal file
71
src/qt_common/util/vk.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "vulkan/vulkan_core.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 inline 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;
|
||||
}
|
||||
}
|
||||
|
||||
static inline 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;
|
||||
}
|
||||
}
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace Settings {
|
||||
enum class VSyncMode : u32;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// TODO(crueter): Port some configure_graphics.cpp stuff
|
||||
void PopulateRecords(std::vector<Record>& records, QWindow* window);
|
||||
} // namespace VkDeviceInfo
|
||||
|
|
@ -217,8 +217,6 @@ 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
|
||||
user_data_migration.cpp
|
||||
|
|
@ -245,9 +243,7 @@ add_executable(yuzu
|
|||
|
||||
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
|
||||
libqt_common.h libqt_common.cpp
|
||||
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui
|
||||
|
||||
)
|
||||
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui)
|
||||
|
||||
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
#include <glad/glad.h>
|
||||
|
|
@ -45,16 +43,13 @@
|
|||
#include <QOpenGLContext>
|
||||
#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"
|
||||
|
|
@ -62,189 +57,19 @@
|
|||
#include "input_common/main.h"
|
||||
#include "qt_common/qt_common.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/render/context.h"
|
||||
#include "qt_common/render/emu_thread.h"
|
||||
|
||||
class QObject;
|
||||
class QPaintEngine;
|
||||
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<std::mutex>& lk) {
|
||||
lk.unlock();
|
||||
emit DebugModeEntered();
|
||||
lk.lock();
|
||||
}
|
||||
|
||||
void EmuThread::EmulationResumed(std::unique_lock<std::mutex>& 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<QOpenGLContext>();
|
||||
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<QOpenGLContext>();
|
||||
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<QOffscreenSurface>(nullptr);
|
||||
offscreen_surface->setFormat(format);
|
||||
offscreen_surface->create();
|
||||
surface = offscreen_surface.get();
|
||||
} else {
|
||||
surface = main_surface;
|
||||
}
|
||||
}
|
||||
|
||||
~OpenGLSharedContext() {
|
||||
DoneCurrent();
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
if (auto window = static_cast<QWindow*>(surface)) {
|
||||
if (!window->isExposed()) {
|
||||
LOG_DEBUG(Frontend, "SwapBuffers ignored: window not exposed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||
QSurface* surface;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DummyContext : public Core::Frontend::GraphicsContext {};
|
||||
|
||||
class RenderWidget : public QWidget {
|
||||
public:
|
||||
explicit RenderWidget(GRenderWindow* parent) : QWidget(parent) {
|
||||
|
|
@ -285,11 +110,9 @@ struct NullRenderWidget : public RenderWidget {
|
|||
explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {}
|
||||
};
|
||||
|
||||
GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
|
||||
Core::System& system_)
|
||||
: QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)},
|
||||
system{system_} {
|
||||
GRenderWindow::GRenderWindow(MainWindow* parent,
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
|
||||
: QWidget(parent), 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),
|
||||
|
|
@ -708,10 +531,11 @@ 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;
|
||||
}
|
||||
|
||||
if (!this->isActiveWindow()) {
|
||||
mouse_constrain_timer.stop();
|
||||
return;
|
||||
|
|
@ -964,7 +788,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,
|
||||
|
|
@ -989,7 +813,12 @@ 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);
|
||||
|
|
@ -1026,7 +855,8 @@ bool GRenderWindow::InitializeOpenGL() {
|
|||
|
||||
return true;
|
||||
#else
|
||||
QMessageBox::warning(this, tr("OpenGL not available!"), tr("Eden has not been compiled with OpenGL support."));
|
||||
QMessageBox::warning(this, tr("OpenGL not available!"),
|
||||
tr("Eden has not been compiled with OpenGL support."));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1072,8 +902,7 @@ bool GRenderWindow::LoadOpenGL() {
|
|||
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
|
||||
"have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
|
||||
"extensions:<br>%2")
|
||||
.arg(renderer)
|
||||
.arg(missing_ext.join(QStringLiteral("<br>"))));
|
||||
.arg(renderer, missing_ext.join(QStringLiteral("<br>"))));
|
||||
// Non fatal
|
||||
}
|
||||
return true;
|
||||
|
|
@ -1101,14 +930,6 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
|
|||
return missing_ext;
|
||||
}
|
||||
|
||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) {
|
||||
emu_thread = emu_thread_;
|
||||
}
|
||||
|
||||
void GRenderWindow::OnEmulationStopping() {
|
||||
emu_thread = nullptr;
|
||||
}
|
||||
|
||||
void GRenderWindow::showEvent(QShowEvent* event) {
|
||||
QWidget::showEvent(event);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QImage>
|
||||
|
|
@ -27,9 +24,6 @@
|
|||
#include <qobjectdefs.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
class MainWindow;
|
||||
|
|
@ -46,6 +40,7 @@ class QShowEvent;
|
|||
class QTouchEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
class EmuThread;
|
||||
namespace Core {
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
|
@ -63,96 +58,12 @@ 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<std::mutex>& lk);
|
||||
void EmulationResumed(std::unique_lock<std::mutex>& 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);
|
||||
};
|
||||
|
||||
class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
|
||||
Core::System& system_);
|
||||
explicit GRenderWindow(MainWindow* parent,
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
|
||||
~GRenderWindow() override;
|
||||
|
||||
// EmuWindow implementation.
|
||||
|
|
@ -217,8 +128,6 @@ public:
|
|||
void Exit();
|
||||
|
||||
public slots:
|
||||
void OnEmulationStarting(EmuThread* emu_thread_);
|
||||
void OnEmulationStopping();
|
||||
void OnFramebufferSizeChanged();
|
||||
|
||||
signals:
|
||||
|
|
@ -247,7 +156,6 @@ private:
|
|||
bool LoadOpenGL();
|
||||
QStringList GetUnsupportedGLExtensions() const;
|
||||
|
||||
EmuThread* emu_thread;
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
||||
|
||||
// Main context that will be shared with all other contexts that are requested.
|
||||
|
|
@ -277,8 +185,6 @@ private:
|
|||
|
||||
QTimer mouse_constrain_timer;
|
||||
|
||||
Core::System& system;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent)
|
|||
ui->treeView->expandAll();
|
||||
|
||||
int rows = item_model->rowCount();
|
||||
int height =
|
||||
4 + ui->treeView->contentsMargins().top() * 4 + ui->treeView->contentsMargins().bottom() * 4;
|
||||
int height = 4 + ui->treeView->contentsMargins().top() * 4 +
|
||||
ui->treeView->contentsMargins().bottom() * 4;
|
||||
int width = 0;
|
||||
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
|
|||
} else if (setting->Id() == Settings::values.cpu_backend.Id()) {
|
||||
backend_layout->addWidget(widget);
|
||||
backend_combobox = widget->combobox;
|
||||
} else if (setting->Id() == Settings::values.fast_cpu_time.Id() || setting->Id() == Settings::values.cpu_ticks.Id()) {
|
||||
} else if (setting->Id() == Settings::values.fast_cpu_time.Id() ||
|
||||
setting->Id() == Settings::values.cpu_ticks.Id()) {
|
||||
ui->general_layout->addWidget(widget);
|
||||
} else {
|
||||
// Presently, all other settings here are unsafe checkboxes
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ void ConfigureDebug::SetConfiguration() {
|
|||
ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
|
||||
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
|
||||
ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
|
||||
ui->serial_battery_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue())));
|
||||
ui->serial_board_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue())));
|
||||
ui->serial_battery_edit->setText(
|
||||
QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue())));
|
||||
ui->serial_board_edit->setText(
|
||||
QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue())));
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue());
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
#include "common/settings_enums.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "ui_configure.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "yuzu/configuration/configure_applets.h"
|
||||
#include "yuzu/configuration/configure_audio.h"
|
||||
#include "yuzu/configuration/configure_cpu.h"
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
#include <QDialog>
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/vk_device_info.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,7 @@
|
|||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <QBoxLayout>
|
||||
#include <QCheckBox>
|
||||
|
|
@ -34,52 +27,15 @@
|
|||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "ui_configure_graphics.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_graphics.h"
|
||||
#include "yuzu/configuration/shared_widget.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;
|
||||
}
|
||||
}
|
||||
|
||||
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_,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
|
||||
class QPushButton;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "ui_configure_per_game.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_applets.h"
|
||||
|
|
@ -44,7 +45,6 @@
|
|||
#include "yuzu/configuration/configure_per_game_addons.h"
|
||||
#include "yuzu/configuration/configure_system.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,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
#include "frontend_common/config.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
|
||||
namespace Core {
|
||||
|
|
|
|||
|
|
@ -63,27 +63,31 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir,
|
|||
|
||||
void DataWidget::clear() {
|
||||
std::optional<std::string> user_id = selectProfile();
|
||||
if (!user_id) return;
|
||||
if (!user_id)
|
||||
return;
|
||||
QtCommon::Content::ClearDataDir(m_dir, user_id.value());
|
||||
scan();
|
||||
}
|
||||
|
||||
void DataWidget::open() {
|
||||
std::optional<std::string> user_id = selectProfile();
|
||||
if (!user_id) return;
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(
|
||||
QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value()))));
|
||||
if (!user_id)
|
||||
return;
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(
|
||||
FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value()))));
|
||||
}
|
||||
|
||||
void DataWidget::upload() {
|
||||
std::optional<std::string> user_id = selectProfile();
|
||||
if (!user_id) return;
|
||||
if (!user_id)
|
||||
return;
|
||||
QtCommon::Content::ExportDataDir(m_dir, user_id.value(), m_exportName);
|
||||
}
|
||||
|
||||
void DataWidget::download() {
|
||||
std::optional<std::string> user_id = selectProfile();
|
||||
if (!user_id) return;
|
||||
if (!user_id)
|
||||
return;
|
||||
QtCommon::Content::ImportDataDir(m_dir, user_id.value(), std::bind(&DataWidget::scan, this));
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +111,8 @@ std::optional<std::string> DataWidget::selectProfile() {
|
|||
std::string user_id{};
|
||||
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
|
||||
user_id = GetProfileIDString();
|
||||
if (user_id.empty()) return std::nullopt;
|
||||
if (user_id.empty())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return user_id;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#endif
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option,
|
|||
const auto x_pos = option.rect.left() - (column * cell_width) + static_cast<int>(relative_x);
|
||||
|
||||
// also, add some additional padding here to prevent card overlap
|
||||
QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8, option.rect.height() - margins);
|
||||
QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8,
|
||||
option.rect.height() - margins);
|
||||
|
||||
// colors
|
||||
QPalette palette = option.palette;
|
||||
|
|
|
|||
|
|
@ -1113,10 +1113,6 @@ void GameList::LoadInterfaceLayout() {
|
|||
header->resizeSection(COLUMN_NAME, 840);
|
||||
}
|
||||
|
||||
const QStringList GameList::supported_file_extensions = {
|
||||
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
|
||||
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
// Reset the externals watcher whenever the game list is reloaded,
|
||||
// primarily ensures that new titles and external dirs are caught.
|
||||
|
|
|
|||
|
|
@ -95,8 +95,6 @@ public:
|
|||
/// Disables events from the emulated controller
|
||||
void UnloadController();
|
||||
|
||||
static const QStringList supported_file_extensions;
|
||||
|
||||
bool IsTreeMode();
|
||||
void ResetViewMode();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,17 +20,18 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game/game_list.h"
|
||||
#include "yuzu/game/game_list_p.h"
|
||||
|
|
@ -148,7 +149,7 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const
|
|||
|
||||
bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
return QtCommon::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool IsExtractedNCAMain(const std::string& file_name) {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Qt on macOS doesn't define VMA shit
|
||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||
#undef VMA_IMPLEMENTATION
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "qt_common/qt_string_lookup.h"
|
||||
#include "render/performance_overlay.h"
|
||||
#include "updater/update_dialog.h"
|
||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||
#undef VMA_IMPLEMENTATION
|
||||
#endif
|
||||
|
||||
#include "common/fs/ryujinx_compat.h"
|
||||
#include "main_window.h"
|
||||
|
|
@ -31,10 +31,10 @@
|
|||
|
||||
#include "bootmanager.h"
|
||||
#include "loading_screen.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "ryujinx_dialog.h"
|
||||
#include "set_play_time_dialog.h"
|
||||
#include "util/util.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "yuzu/game/game_list.h"
|
||||
|
||||
#include "applets/qt_amiibo_settings.h"
|
||||
|
|
@ -86,6 +86,7 @@
|
|||
#include "qt_common/abstract/frontend.h"
|
||||
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "qt_common/qt_string_lookup.h"
|
||||
|
||||
#include "qt_common/util/content.h"
|
||||
#include "qt_common/util/fs.h"
|
||||
|
|
@ -93,6 +94,8 @@
|
|||
#include "qt_common/util/mod.h"
|
||||
#include "qt_common/util/path.h"
|
||||
|
||||
#include "qt_common/render/emu_thread.h"
|
||||
|
||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
|
||||
// defines.
|
||||
static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path,
|
||||
|
|
@ -315,73 +318,6 @@ enum class CalloutFlag : uint32_t {
|
|||
|
||||
const int MainWindow::max_recent_files_item;
|
||||
|
||||
static void RemoveCachedContents() {
|
||||
const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
|
||||
const auto offline_fonts = cache_dir / "fonts";
|
||||
const auto offline_manual = cache_dir / "offline_web_applet_manual";
|
||||
const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
|
||||
const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
|
||||
|
||||
Common::FS::RemoveDirRecursively(offline_fonts);
|
||||
Common::FS::RemoveDirRecursively(offline_manual);
|
||||
Common::FS::RemoveDirRecursively(offline_legal_information);
|
||||
Common::FS::RemoveDirRecursively(offline_system_data);
|
||||
}
|
||||
|
||||
static void LogRuntimes() {
|
||||
#ifdef _MSC_VER
|
||||
// It is possible that the name of the dll will change.
|
||||
// vcruntime140.dll is for 2015 and onwards
|
||||
static constexpr char runtime_dll_name[] = "vcruntime140.dll";
|
||||
UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
|
||||
bool runtime_version_inspection_worked = false;
|
||||
if (sz > 0) {
|
||||
std::vector<u8> buf(sz);
|
||||
if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
|
||||
VS_FIXEDFILEINFO* pvi;
|
||||
sz = sizeof(VS_FIXEDFILEINFO);
|
||||
if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
|
||||
if (pvi->dwSignature == VS_FFI_SIGNATURE) {
|
||||
runtime_version_inspection_worked = true;
|
||||
LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
|
||||
pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
|
||||
pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!runtime_version_inspection_worked) {
|
||||
LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion());
|
||||
}
|
||||
|
||||
static QString PrettyProductName() {
|
||||
#ifdef _WIN32
|
||||
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
|
||||
// With that notation change they changed the registry key used to denote the current version
|
||||
QSettings windows_registry(
|
||||
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
|
||||
QSettings::NativeFormat);
|
||||
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
|
||||
if (release_id == QStringLiteral("2009")) {
|
||||
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
|
||||
const QString display_version =
|
||||
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
|
||||
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
|
||||
u32 version = 10;
|
||||
if (current_build >= 22000) {
|
||||
version = 11;
|
||||
}
|
||||
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
|
||||
.arg(QString::number(version), display_version, QString::number(current_build),
|
||||
QString::number(ubr));
|
||||
}
|
||||
#endif
|
||||
return QSysInfo::prettyProductName();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::pair<u32, const char*>, 5> default_game_icon_sizes{
|
||||
|
|
@ -447,11 +383,6 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||
|
||||
UISettings::RestoreWindowState(config);
|
||||
|
||||
QtCommon::system->Initialize();
|
||||
|
||||
Common::Log::Initialize();
|
||||
Common::Log::Start();
|
||||
|
||||
LoadTranslation();
|
||||
FrontendCommon::GenerateSettings();
|
||||
|
||||
|
|
@ -472,10 +403,6 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||
|
||||
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||
|
||||
Network::Init();
|
||||
|
||||
QtCommon::Meta::RegisterMetaTypes();
|
||||
|
||||
InitializeWidgets();
|
||||
InitializeDebugWidgets();
|
||||
InitializeRecentFileMenuActions();
|
||||
|
|
@ -487,53 +414,9 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||
ConnectMenuEvents();
|
||||
ConnectWidgetEvents();
|
||||
|
||||
QtCommon::system->HIDCore().ReloadInputDevices();
|
||||
QtCommon::SetupHID();
|
||||
controller_dialog->refreshConfiguration();
|
||||
|
||||
const auto branch_name = std::string(Common::g_scm_branch);
|
||||
const auto description = std::string(Common::g_scm_desc);
|
||||
const auto build_id = std::string(Common::g_build_id);
|
||||
|
||||
const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description);
|
||||
const auto override_build =
|
||||
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
|
||||
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
|
||||
const auto processor_count = std::thread::hardware_concurrency();
|
||||
|
||||
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
|
||||
LogRuntimes();
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
const auto& caps = Common::GetCPUCaps();
|
||||
std::string cpu_string = caps.cpu_string;
|
||||
if (caps.avx || caps.avx2 || caps.avx512f) {
|
||||
cpu_string += " | AVX";
|
||||
if (caps.avx512f) {
|
||||
cpu_string += "512";
|
||||
} else if (caps.avx2) {
|
||||
cpu_string += '2';
|
||||
}
|
||||
if (caps.fma || caps.fma4) {
|
||||
cpu_string += " | FMA";
|
||||
}
|
||||
}
|
||||
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
|
||||
if (std::optional<int> processor_core = Common::GetProcessorCount()) {
|
||||
LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
|
||||
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
|
||||
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());
|
||||
QtCommon::system->CoreTiming().SetTimerResolutionNs(
|
||||
Common::Windows::GetCurrentTimerResolution());
|
||||
#endif
|
||||
UpdateWindowTitle();
|
||||
|
||||
show();
|
||||
|
|
@ -548,13 +431,8 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||
}
|
||||
#endif
|
||||
|
||||
QtCommon::system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
QtCommon::provider.get());
|
||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
|
||||
// Remove cached contents generated during the previous session
|
||||
RemoveCachedContents();
|
||||
// Setup content providers.
|
||||
QtCommon::SetupContentProviders();
|
||||
|
||||
// Gen keys if necessary
|
||||
OnCheckFirmwareDecryption();
|
||||
|
|
@ -1079,7 +957,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,
|
||||
|
|
@ -1539,11 +1417,12 @@ 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();
|
||||
}
|
||||
|
|
@ -1595,11 +1474,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);
|
||||
|
||||
// Software Keyboard Applet
|
||||
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit);
|
||||
connect(this, &MainWindow::EmulationStopping, this, &MainWindow::SoftwareKeyboardExit);
|
||||
|
|
@ -1753,7 +1627,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{
|
||||
|
|
@ -1821,17 +1695,16 @@ 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();
|
||||
}
|
||||
|
|
@ -1904,19 +1777,16 @@ 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();
|
||||
}
|
||||
|
||||
if (!render_window->InitRenderTarget()) {
|
||||
if (!render_window->InitRenderTarget())
|
||||
return false;
|
||||
}
|
||||
|
||||
QtCommon::system->SetFilesystem(QtCommon::vfs);
|
||||
|
||||
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) {
|
||||
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated)
|
||||
QtCommon::system->GetUserChannel().clear();
|
||||
}
|
||||
|
||||
QtCommon::system->SetFrontendAppletSet({
|
||||
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
|
||||
|
|
@ -2023,36 +1893,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;
|
||||
}
|
||||
|
||||
if (QtCommon::provider->AddEntriesFromContainer(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);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params,
|
||||
StartGameType type) {
|
||||
LOG_INFO(Frontend, "Eden starting...");
|
||||
|
|
@ -2071,7 +1911,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);
|
||||
|
|
@ -2116,23 +1956,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<EmuThread>(*QtCommon::system);
|
||||
emit EmulationStarting(emu_thread.get());
|
||||
emu_thread->start();
|
||||
QtCommon::emu_thread = std::make_unique<EmuThread>();
|
||||
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
|
||||
|
|
@ -2227,9 +2067,10 @@ bool MainWindow::OnShutdownBegin() {
|
|||
discord_rpc->Pause();
|
||||
|
||||
RequestGameExit();
|
||||
emu_thread->disconnect();
|
||||
emu_thread->SetRunning(true);
|
||||
QtCommon::emu_thread->disconnect();
|
||||
QtCommon::emu_thread->SetRunning(true);
|
||||
|
||||
connect(QtCommon::emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped);
|
||||
emit EmulationStopping();
|
||||
|
||||
int shutdown_time = 1000;
|
||||
|
|
@ -2243,7 +2084,6 @@ 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);
|
||||
|
||||
// Disable everything to prevent anything from being triggered here
|
||||
ui->action_Pause->setEnabled(false);
|
||||
|
|
@ -2261,17 +2101,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) {
|
||||
|
|
@ -2912,7 +2752,7 @@ void MainWindow::OnMenuLoadFile() {
|
|||
is_load_file_select_active = true;
|
||||
const QString extensions =
|
||||
QStringLiteral("*.")
|
||||
.append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
|
||||
.append(QtCommon::supported_file_extensions.join(QStringLiteral(" *.")))
|
||||
.append(QStringLiteral(" main"));
|
||||
const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
|
||||
"%1 is an identifier for the Switch executable file extensions.")
|
||||
|
|
@ -3148,7 +2988,7 @@ void MainWindow::OnMenuRecentFile() {
|
|||
void MainWindow::OnStartGame() {
|
||||
PreventOSSleep();
|
||||
|
||||
emu_thread->SetRunning(true);
|
||||
QtCommon::emu_thread->SetRunning(true);
|
||||
|
||||
UpdateMenuState();
|
||||
OnTasStateChanged();
|
||||
|
|
@ -3174,7 +3014,7 @@ void MainWindow::OnRestartGame() {
|
|||
}
|
||||
|
||||
void MainWindow::OnPauseGame() {
|
||||
emu_thread->SetRunning(false);
|
||||
QtCommon::emu_thread->SetRunning(false);
|
||||
play_time_manager->Stop();
|
||||
UpdateMenuState();
|
||||
AllowOSSleep();
|
||||
|
|
@ -3183,7 +3023,7 @@ void MainWindow::OnPauseGame() {
|
|||
|
||||
void MainWindow::OnPauseContinueGame() {
|
||||
if (emulation_running) {
|
||||
if (emu_thread->IsRunning()) {
|
||||
if (QtCommon::emu_thread->IsRunning()) {
|
||||
OnPauseGame();
|
||||
} else {
|
||||
OnStartGame();
|
||||
|
|
@ -3863,12 +3703,9 @@ 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() ||
|
||||
is_amiibo_file_select_active)
|
||||
return;
|
||||
}
|
||||
if (is_amiibo_file_select_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
|
||||
|
||||
|
|
@ -3972,79 +3809,21 @@ void MainWindow::OnVerifyInstalledContents() {
|
|||
QtCommon::Content::VerifyInstalledContents();
|
||||
}
|
||||
|
||||
void MainWindow::InstallFirmware(const QString& location, bool recursive) {
|
||||
QtCommon::Content::InstallFirmware(location, recursive);
|
||||
void MainWindow::OnInstallFirmware() {
|
||||
QtCommon::Content::InstallFirmware();
|
||||
OnCheckFirmwareDecryption();
|
||||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for installed keys, error out, suggest restart?
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::information(
|
||||
this, tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart Eden before attempting to install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString firmware_source_location = QFileDialog::getExistingDirectory(
|
||||
this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly);
|
||||
if (firmware_source_location.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstallFirmware(firmware_source_location);
|
||||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for installed keys, error out, suggest restart?
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::information(
|
||||
this, tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart Eden before attempting to install firmware."));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString firmware_zip_location = QFileDialog::getOpenFileName(
|
||||
this, tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
|
||||
if (firmware_zip_location.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
|
||||
|
||||
// In this case, it has to be done recursively, since sometimes people
|
||||
// will pack it into a subdirectory after dumping
|
||||
if (!qCacheDir.isEmpty()) {
|
||||
InstallFirmware(qCacheDir, true);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware",
|
||||
ec);
|
||||
|
||||
if (ec) {
|
||||
QMessageBox::warning(this, tr("Firmware cleanup failed"),
|
||||
tr("Failed to clean up extracted firmware cache.\n"
|
||||
"Check write permissions in the system temp directory and try "
|
||||
"again.\nOS reported error: %1")
|
||||
.arg(QString::fromStdString(ec.message())));
|
||||
}
|
||||
}
|
||||
QtCommon::Content::InstallFirmwareZip();
|
||||
OnCheckFirmwareDecryption();
|
||||
}
|
||||
|
||||
// TODO(crueter): QtCommon this: game list populate can be a signal?
|
||||
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;
|
||||
}
|
||||
|
||||
QtCommon::Content::InstallKeys();
|
||||
|
||||
|
|
@ -4072,11 +3851,10 @@ void MainWindow::OnDataDialog() {
|
|||
|
||||
void MainWindow::OnToggleFilterBar() {
|
||||
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
|
||||
if (ui->action_Show_Filter_Bar->isChecked()) {
|
||||
if (ui->action_Show_Filter_Bar->isChecked())
|
||||
game_list->SetFilterFocus();
|
||||
} else {
|
||||
else
|
||||
game_list->ClearFilter();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnToggleStatusBar() {
|
||||
|
|
@ -4084,9 +3862,8 @@ void MainWindow::OnToggleStatusBar() {
|
|||
}
|
||||
|
||||
void MainWindow::OnTogglePerfOverlay() {
|
||||
if (perf_overlay) {
|
||||
if (perf_overlay)
|
||||
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnGameListRefresh() {
|
||||
|
|
@ -4191,9 +3968,8 @@ void MainWindow::OnCreateHomeMenuApplicationMenuShortcut() {
|
|||
}
|
||||
|
||||
void MainWindow::OnCaptureScreenshot() {
|
||||
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
|
||||
if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning())
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 title_id = QtCommon::system->GetApplicationProcessProgramID();
|
||||
const auto screenshot_path =
|
||||
|
|
@ -4205,9 +3981,8 @@ void MainWindow::OnCaptureScreenshot() {
|
|||
.arg(title_id, 16, 16, QLatin1Char{'0'})
|
||||
.arg(date);
|
||||
|
||||
if (!Common::FS::CreateDir(screenshot_path.toStdString())) {
|
||||
if (!Common::FS::CreateDir(screenshot_path.toStdString()))
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (UISettings::values.enable_screenshot_save_as) {
|
||||
|
|
@ -4313,16 +4088,15 @@ 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;
|
||||
}
|
||||
|
||||
if (Settings::values.tas_enable) {
|
||||
if (Settings::values.tas_enable)
|
||||
tas_label->setText(GetTasStateDescription());
|
||||
} else {
|
||||
else
|
||||
tas_label->clear();
|
||||
}
|
||||
|
||||
auto results = QtCommon::system->GetAndResetPerfStats();
|
||||
auto& shader_notify = QtCommon::system->GPU().ShaderNotify();
|
||||
|
|
@ -4444,14 +4218,13 @@ void MainWindow::UpdateUISettings() {
|
|||
}
|
||||
|
||||
void MainWindow::UpdateInputDrivers() {
|
||||
if (!input_subsystem) {
|
||||
if (!input_subsystem)
|
||||
return;
|
||||
}
|
||||
input_subsystem->PumpEvents();
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -4461,9 +4234,8 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnMouseActivity() {
|
||||
|
|
@ -4643,14 +4415,14 @@ bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed
|
|||
}
|
||||
|
||||
bool MainWindow::ConfirmClose() {
|
||||
if (emu_thread == nullptr ||
|
||||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) {
|
||||
if (QtCommon::emu_thread == nullptr ||
|
||||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!QtCommon::system->GetExitLocked() &&
|
||||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) {
|
||||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game)
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto text = tr("Are you sure you want to close Eden?");
|
||||
return question(this, tr("Eden"), text);
|
||||
}
|
||||
|
|
@ -4671,9 +4443,8 @@ 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();
|
||||
}
|
||||
|
||||
render_window->close();
|
||||
multiplayer_state->Close();
|
||||
|
|
@ -4736,7 +4507,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
|
||||
|
|
@ -4747,9 +4518,9 @@ 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"
|
||||
"Would you like to bypass this and exit anyway?");
|
||||
|
||||
|
|
@ -4757,9 +4528,8 @@ bool MainWindow::ConfirmForceLockedExit() {
|
|||
}
|
||||
|
||||
void MainWindow::RequestGameExit() {
|
||||
if (!QtCommon::system->IsPoweredOn()) {
|
||||
if (!QtCommon::system->IsPoweredOn())
|
||||
return;
|
||||
}
|
||||
|
||||
QtCommon::system->SetExitRequested(true);
|
||||
QtCommon::system->GetAppletManager().RequestExit();
|
||||
|
|
@ -4772,14 +4542,14 @@ void MainWindow::filterBarSetChecked(bool state) {
|
|||
|
||||
static void AdjustLinkColor() {
|
||||
QPalette new_pal(qApp->palette());
|
||||
if (UISettings::IsDarkTheme()) {
|
||||
|
||||
if (UISettings::IsDarkTheme())
|
||||
new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
|
||||
} else {
|
||||
else
|
||||
new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
|
||||
}
|
||||
if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) {
|
||||
|
||||
if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link))
|
||||
qApp->setPalette(new_pal);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::UpdateUITheme() {
|
||||
|
|
@ -4787,9 +4557,8 @@ void MainWindow::UpdateUITheme() {
|
|||
UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second);
|
||||
QString current_theme = QString::fromStdString(UISettings::values.theme);
|
||||
|
||||
if (current_theme.isEmpty()) {
|
||||
if (current_theme.isEmpty())
|
||||
current_theme = default_theme;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
QIcon::setThemeName(current_theme);
|
||||
|
|
@ -4850,17 +4619,15 @@ void MainWindow::LoadTranslation() {
|
|||
QStringLiteral(":/languages/"));
|
||||
}
|
||||
|
||||
if (loaded) {
|
||||
if (loaded)
|
||||
qApp->installTranslator(&translator);
|
||||
} else {
|
||||
else
|
||||
UISettings::values.language = std::string("en");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnLanguageChanged(const QString& locale) {
|
||||
if (UISettings::values.language.GetValue() != std::string("en")) {
|
||||
if (UISettings::values.language.GetValue() != std::string("en"))
|
||||
qApp->removeTranslator(&translator);
|
||||
}
|
||||
|
||||
QList<QAction*> actions = game_size_actions->actions();
|
||||
for (size_t i = 0; i < default_game_icon_sizes.size(); i++) {
|
||||
|
|
@ -4876,11 +4643,10 @@ void MainWindow::OnLanguageChanged(const QString& locale) {
|
|||
|
||||
void MainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
if (state) {
|
||||
if (state)
|
||||
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(*QtCommon::system);
|
||||
} else {
|
||||
else
|
||||
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
|
||||
}
|
||||
#else
|
||||
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@
|
|||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "frontend_common/update_checker.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "qt_common/util/game.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "common/net/net.h"
|
||||
#endif
|
||||
|
||||
class QtConfig;
|
||||
|
|
@ -66,11 +67,6 @@ class QtProfileSelectionDialog;
|
|||
class QtSoftwareKeyboardDialog;
|
||||
class QtNXWebEngineView;
|
||||
|
||||
enum class StartGameType {
|
||||
Normal, // Can use custom configuration
|
||||
Global, // Only uses global configuration
|
||||
};
|
||||
|
||||
namespace VideoCore {
|
||||
class ShaderNotify;
|
||||
}
|
||||
|
|
@ -184,11 +180,8 @@ signals:
|
|||
* Signal that is emitted when a new EmuThread has been created and an emulation session is
|
||||
* about to start. At this time, the core system emulation has been initialized, and all
|
||||
* emulation handles and memory should be valid.
|
||||
*
|
||||
* @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
|
||||
|
|
@ -465,7 +458,6 @@ private:
|
|||
bool CheckFirmwarePresence();
|
||||
void SetFirmwareVersion();
|
||||
void SetFPSSuffix();
|
||||
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
|
||||
|
|
@ -538,7 +530,6 @@ private:
|
|||
|
||||
// Whether emulation is currently running in yuzu.
|
||||
bool emulation_running = false;
|
||||
std::unique_ptr<EmuThread> 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)
|
||||
|
|
@ -599,8 +590,6 @@ private:
|
|||
const std::string& game_title, QtCommon::Game::ShortcutTarget target,
|
||||
std::string arguments, const bool needs_title);
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive = false);
|
||||
|
||||
protected:
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class UpdateDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateDialog(const Common::Net::Release &release, QWidget* parent = nullptr);
|
||||
explicit UpdateDialog(const Common::Net::Release& release, QWidget* parent = nullptr);
|
||||
~UpdateDialog();
|
||||
|
||||
private slots:
|
||||
|
|
@ -23,6 +23,6 @@ private slots:
|
|||
|
||||
private:
|
||||
Ui::UpdateDialog* ui;
|
||||
QList<QRadioButton *> m_buttons;
|
||||
QList<QRadioButton*> m_buttons;
|
||||
Common::Net::Asset m_asset;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,111 +43,6 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
|||
return circle_pixmap;
|
||||
}
|
||||
|
||||
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
|
||||
#if defined(WIN32)
|
||||
#pragma pack(push, 2)
|
||||
struct IconDir {
|
||||
WORD id_reserved;
|
||||
WORD id_type;
|
||||
WORD id_count;
|
||||
};
|
||||
|
||||
struct IconDirEntry {
|
||||
BYTE width;
|
||||
BYTE height;
|
||||
BYTE color_count;
|
||||
BYTE reserved;
|
||||
WORD planes;
|
||||
WORD bit_count;
|
||||
DWORD bytes_in_res;
|
||||
DWORD image_offset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
|
||||
constexpr int bytes_per_pixel = 4;
|
||||
|
||||
const IconDir icon_dir{
|
||||
.id_reserved = 0,
|
||||
.id_type = 1,
|
||||
.id_count = static_cast<WORD>(scale_sizes.size()),
|
||||
};
|
||||
|
||||
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile);
|
||||
if (!icon_file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon_file.Write(icon_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
|
||||
const IconDirEntry icon_entry{
|
||||
.width = static_cast<BYTE>(scale_sizes[i]),
|
||||
.height = static_cast<BYTE>(scale_sizes[i]),
|
||||
.color_count = 0,
|
||||
.reserved = 0,
|
||||
.planes = 1,
|
||||
.bit_count = bytes_per_pixel * 8,
|
||||
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
||||
.image_offset = static_cast<DWORD>(image_offset),
|
||||
};
|
||||
image_offset += icon_entry.bytes_in_res;
|
||||
if (!icon_file.Write(icon_entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const QImage scaled_image = source_image.scaled(
|
||||
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
const BITMAPINFOHEADER info_header{
|
||||
.biSize = sizeof(BITMAPINFOHEADER),
|
||||
.biWidth = scaled_image.width(),
|
||||
.biHeight = scaled_image.height() * 2,
|
||||
.biPlanes = 1,
|
||||
.biBitCount = bytes_per_pixel * 8,
|
||||
.biCompression = BI_RGB,
|
||||
.biSizeImage{},
|
||||
.biXPelsPerMeter{},
|
||||
.biYPelsPerMeter{},
|
||||
.biClrUsed{},
|
||||
.biClrImportant{},
|
||||
};
|
||||
|
||||
if (!icon_file.Write(info_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < scaled_image.height(); y++) {
|
||||
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
|
||||
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
|
||||
std::memcpy(line_data.data(), line, line_data.size());
|
||||
if (!icon_file.Write(line_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
icon_file.Close();
|
||||
|
||||
return true;
|
||||
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
|
||||
// Convert and write the icon as a PNG
|
||||
if (!image.save(QString::fromStdString(icon_path.string()))) {
|
||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||
} else {
|
||||
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
const std::optional<Common::UUID> GetProfileID() {
|
||||
// if there's only a single profile, the user probably wants to use that... right?
|
||||
const auto& profiles = QtCommon::system->GetProfileManager().FindExistingProfileUUIDs();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
#include "common/uuid.h"
|
||||
|
|
@ -24,14 +23,6 @@
|
|||
*/
|
||||
[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
||||
|
||||
/**
|
||||
* Saves a windows icon to a file
|
||||
* @param path The icons path
|
||||
* @param image The image to save
|
||||
* @return bool If the operation succeeded
|
||||
*/
|
||||
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
||||
|
||||
/**
|
||||
* Prompt the user for a profile ID. If there is only one valid profile, returns that profile.
|
||||
* @return The selected profile, or an std::nullopt if none were selected
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
// 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue