diff options
74 files changed, 1177 insertions, 250 deletions
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index feba3fd6e..155d8a5c8 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -18,19 +18,20 @@ cd .. mkdir package if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then - QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/' + QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins' else #fallback to qt - QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/' + QT_PLUGINS_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins' fi find build/ -name "yuzu*.exe" -exec cp {} 'package' \; # copy Qt plugins mkdir package/platforms -cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/ -cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/ -cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/ +cp -v "${QT_PLUGINS_PATH}/platforms/qwindows.dll" package/platforms/ +cp -rv "${QT_PLUGINS_PATH}/mediaservice/" package/ +cp -rv "${QT_PLUGINS_PATH}/imageformats/" package/ +cp -rv "${QT_PLUGINS_PATH}/styles/" package/ rm -f package/mediaservice/*d.dll for i in package/*.exe; do diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 0c12614d1a7a72d778609920dde96a4c63074ec +Subproject c6125082ea992c245edab26b6f86e7b904b86ee diff --git a/src/common/common_sizes.h b/src/common/common_sizes.h index 7e9fd968b..d07b7ee5a 100644 --- a/src/common/common_sizes.h +++ b/src/common/common_sizes.h @@ -24,6 +24,7 @@ enum : u64 { Size_128_MB = 128ULL * Size_1_MB, Size_448_MB = 448ULL * Size_1_MB, Size_507_MB = 507ULL * Size_1_MB, + Size_512_MB = 512ULL * Size_1_MB, Size_562_MB = 562ULL * Size_1_MB, Size_1554_MB = 1554ULL * Size_1_MB, Size_2048_MB = 2048ULL * Size_1_MB, diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp index f2b4939df..c1362631e 100644 --- a/src/common/detached_tasks.cpp +++ b/src/common/detached_tasks.cpp @@ -21,6 +21,8 @@ void DetachedTasks::WaitForAllTasks() { } DetachedTasks::~DetachedTasks() { + WaitForAllTasks(); + std::unique_lock lock{mutex}; ASSERT(count == 0); instance = nullptr; diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 710e88b39..077f34995 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -172,7 +172,7 @@ std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) size_t WriteStringToFile(const std::filesystem::path& path, FileType type, std::string_view string) { - if (!IsFile(path)) { + if (Exists(path) && !IsFile(path)) { return 0; } @@ -183,7 +183,7 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type, size_t AppendStringToFile(const std::filesystem::path& path, FileType type, std::string_view string) { - if (!IsFile(path)) { + if (Exists(path) && !IsFile(path)) { return 0; } diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 0f10b6003..588fe619d 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -49,7 +49,7 @@ void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::op /** * Reads an entire file at path and returns a string of the contents read from the file. - * If the filesystem object at path is not a file, this function returns an empty string. + * If the filesystem object at path is not a regular file, this function returns an empty string. * * @param path Filesystem path * @param type File type @@ -72,7 +72,8 @@ template <typename Path> /** * Writes a string to a file at path and returns the number of characters successfully written. * If a file already exists at path, its contents will be erased. - * If the filesystem object at path is not a file, this function returns 0. + * If a file does not exist at path, it creates and opens a new empty file for writing. + * If the filesystem object at path exists and is not a regular file, this function returns 0. * * @param path Filesystem path * @param type File type @@ -95,7 +96,8 @@ template <typename Path> /** * Appends a string to a file at path and returns the number of characters successfully written. - * If the filesystem object at path is not a file, this function returns 0. + * If a file does not exist at path, it creates and opens a new empty file for appending. + * If the filesystem object at path exists and is not a regular file, this function returns 0. * * @param path Filesystem path * @param type File type @@ -394,11 +396,11 @@ public: [[nodiscard]] size_t WriteString(std::span<const char> string) const; /** - * Flushes any unwritten buffered data into the file. + * Attempts to flush any unwritten buffered data into the file and flush the file into the disk. * * @returns True if the flush was successful, false otherwise. */ - [[nodiscard]] bool Flush() const; + bool Flush() const; /** * Resizes the file to a given size. diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index d3159e908..9089cad67 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -135,8 +135,9 @@ std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, File return nullptr; } - if (!IsFile(path)) { - LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + if (Exists(path) && !IsFile(path)) { + LOG_ERROR(Common_Filesystem, + "Filesystem object at path={} exists and is not a regular file", PathToUTF8String(path)); return nullptr; } diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h index f6f256349..183126de3 100644 --- a/src/common/fs/fs.h +++ b/src/common/fs/fs.h @@ -48,18 +48,18 @@ template <typename Path> * * Failures occur when: * - Input path is not valid - * - Filesystem object at path is not a file + * - Filesystem object at path is not a regular file * - Filesystem at path is read only * * @param path Filesystem path * * @returns True if file removal succeeds or file does not exist, false otherwise. */ -[[nodiscard]] bool RemoveFile(const std::filesystem::path& path); +bool RemoveFile(const std::filesystem::path& path); #ifdef _WIN32 template <typename Path> -[[nodiscard]] bool RemoveFile(const Path& path) { +bool RemoveFile(const Path& path) { if constexpr (IsChar<typename Path::value_type>) { return RemoveFile(ToU8String(path)); } else { @@ -74,7 +74,7 @@ template <typename Path> * Failures occur when: * - One or both input path(s) is not valid * - Filesystem object at old_path does not exist - * - Filesystem object at old_path is not a file + * - Filesystem object at old_path is not a regular file * - Filesystem object at new_path exists * - Filesystem at either path is read only * @@ -110,8 +110,8 @@ template <typename Path1, typename Path2> * * Failures occur when: * - Input path is not valid - * - Filesystem object at path is not a file - * - The file is not opened + * - Filesystem object at path exists and is not a regular file + * - The file is not open * * @param path Filesystem path * @param mode File access mode @@ -251,11 +251,11 @@ template <typename Path> * * @returns True if directory removal succeeds or directory does not exist, false otherwise. */ -[[nodiscard]] bool RemoveDir(const std::filesystem::path& path); +bool RemoveDir(const std::filesystem::path& path); #ifdef _WIN32 template <typename Path> -[[nodiscard]] bool RemoveDir(const Path& path) { +bool RemoveDir(const Path& path) { if constexpr (IsChar<typename Path::value_type>) { return RemoveDir(ToU8String(path)); } else { @@ -276,11 +276,11 @@ template <typename Path> * * @returns True if the directory and all of its contents are removed successfully, false otherwise. */ -[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); +bool RemoveDirRecursively(const std::filesystem::path& path); #ifdef _WIN32 template <typename Path> -[[nodiscard]] bool RemoveDirRecursively(const Path& path) { +bool RemoveDirRecursively(const Path& path) { if constexpr (IsChar<typename Path::value_type>) { return RemoveDirRecursively(ToU8String(path)); } else { @@ -301,11 +301,11 @@ template <typename Path> * * @returns True if all of the directory's contents are removed successfully, false otherwise. */ -[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); +bool RemoveDirContentsRecursively(const std::filesystem::path& path); #ifdef _WIN32 template <typename Path> -[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { +bool RemoveDirContentsRecursively(const Path& path) { if constexpr (IsChar<typename Path::value_type>) { return RemoveDirContentsRecursively(ToU8String(path)); } else { @@ -435,11 +435,13 @@ template <typename Path> #endif /** - * Returns whether a filesystem object at path is a file. + * Returns whether a filesystem object at path is a regular file. + * A regular file is a file that stores text or binary data. + * It is not a directory, symlink, FIFO, socket, block device, or character device. * * @param path Filesystem path * - * @returns True if a filesystem object at path is a file, false otherwise. + * @returns True if a filesystem object at path is a regular file, false otherwise. */ [[nodiscard]] bool IsFile(const std::filesystem::path& path); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d5cff400f..47ce06478 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -159,7 +159,7 @@ FileBackend::FileBackend(const std::filesystem::path& filename) { // Existence checks are done within the functions themselves. // We don't particularly care if these succeed or not. - void(FS::RemoveFile(old_filename)); + FS::RemoveFile(old_filename); void(FS::RenameFile(filename, old_filename)); file = @@ -186,7 +186,7 @@ void FileBackend::Write(const Entry& entry) { bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); if (entry.log_level >= Level::Error) { - void(file->Flush()); + file->Flush(); } } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 9ec71eced..e1bb4b7ff 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -59,6 +59,7 @@ void LogSettings() { log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); + log_setting("Renderer_UseGarbageCollection", values.use_caches_gc.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); log_setting("Audio_OutputEngine", values.sink_id); log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); @@ -122,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) { values.cpu_accuracy.SetGlobal(true); values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); + values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true); values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); values.cpuopt_unsafe_fastmem_check.SetGlobal(true); @@ -141,6 +143,7 @@ void RestoreGlobalState(bool is_powered_on) { values.use_assembly_shaders.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true); + values.use_caches_gc.SetGlobal(true); values.bg_red.SetGlobal(true); values.bg_green.SetGlobal(true); values.bg_blue.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 6198f2d9f..82ec18e27 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -129,6 +129,7 @@ struct Values { Setting<bool> cpuopt_unsafe_unfuse_fma; Setting<bool> cpuopt_unsafe_reduce_fp_error; + Setting<bool> cpuopt_unsafe_ignore_standard_fpcr; Setting<bool> cpuopt_unsafe_inaccurate_nan; Setting<bool> cpuopt_unsafe_fastmem_check; @@ -149,9 +150,11 @@ struct Values { Setting<bool> use_nvdec_emulation; Setting<bool> accelerate_astc; Setting<bool> use_vsync; + Setting<bool> disable_fps_limit; Setting<bool> use_assembly_shaders; Setting<bool> use_asynchronous_shaders; Setting<bool> use_fast_gpu_time; + Setting<bool> use_caches_gc; Setting<float> bg_red; Setting<float> bg_green; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index efb851f5a..83b5b7676 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -139,6 +139,7 @@ add_library(core STATIC frontend/input.h hardware_interrupt_manager.cpp hardware_interrupt_manager.h + hle/api_version.h hle/ipc.h hle/ipc_helpers.h hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -550,6 +551,8 @@ add_library(core STATIC hle/service/spl/module.h hle/service/spl/spl.cpp hle/service/spl/spl.h + hle/service/spl/spl_results.h + hle/service/spl/spl_types.h hle/service/ssl/ssl.cpp hle/service/ssl/ssl.h hle/service/time/clock_types.h diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c8f6dc765..f871f7bf4 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -186,6 +186,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } + if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; + } if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; } diff --git a/src/core/core.cpp b/src/core/core.cpp index c5004b7b4..e6f1aa0e7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <array> +#include <atomic> #include <memory> #include <utility> @@ -377,7 +378,7 @@ struct System::Impl { std::unique_ptr<Core::DeviceMemory> device_memory; Core::Memory::Memory memory; CpuManager cpu_manager; - bool is_powered_on = false; + std::atomic_bool is_powered_on{}; bool exit_lock = false; Reporter reporter; @@ -463,7 +464,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st } bool System::IsPoweredOn() const { - return impl->is_powered_on; + return impl->is_powered_on.load(std::memory_order::relaxed); } void System::PrepareReschedule() { diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index 54704105b..9b76d007e 100644 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -4,47 +4,29 @@ #include "core/file_sys/system_archive/system_version.h" #include "core/file_sys/vfs_vector.h" +#include "core/hle/api_version.h" namespace FileSys::SystemArchive { -namespace SystemVersionData { - -// This section should reflect the best system version to describe yuzu's HLE api. -// TODO(DarkLordZach): Update when HLE gets better. - -constexpr u8 VERSION_MAJOR = 11; -constexpr u8 VERSION_MINOR = 0; -constexpr u8 VERSION_MICRO = 1; - -constexpr u8 REVISION_MAJOR = 1; -constexpr u8 REVISION_MINOR = 0; - -constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf"; -constexpr char DISPLAY_VERSION[] = "11.0.1"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0"; - -} // namespace SystemVersionData - std::string GetLongDisplayVersion() { - return SystemVersionData::DISPLAY_TITLE; + return HLE::ApiVersion::DISPLAY_TITLE; } VirtualDir SystemVersion() { VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file"); - file->WriteObject(SystemVersionData::VERSION_MAJOR, 0); - file->WriteObject(SystemVersionData::VERSION_MINOR, 1); - file->WriteObject(SystemVersionData::VERSION_MICRO, 2); - file->WriteObject(SystemVersionData::REVISION_MAJOR, 4); - file->WriteObject(SystemVersionData::REVISION_MINOR, 5); - file->WriteArray(SystemVersionData::PLATFORM_STRING, - std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8); - file->WriteArray(SystemVersionData::VERSION_HASH, - std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28); - file->WriteArray(SystemVersionData::DISPLAY_VERSION, - std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68); - file->WriteArray(SystemVersionData::DISPLAY_TITLE, - std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80); + file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0); + file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1); + file->WriteObject(HLE::ApiVersion::HOS_VERSION_MICRO, 2); + file->WriteObject(HLE::ApiVersion::SDK_REVISION_MAJOR, 4); + file->WriteObject(HLE::ApiVersion::SDK_REVISION_MINOR, 5); + file->WriteArray(HLE::ApiVersion::PLATFORM_STRING, + std::min<u64>(sizeof(HLE::ApiVersion::PLATFORM_STRING), 0x20ULL), 0x8); + file->WriteArray(HLE::ApiVersion::VERSION_HASH, + std::min<u64>(sizeof(HLE::ApiVersion::VERSION_HASH), 0x40ULL), 0x28); + file->WriteArray(HLE::ApiVersion::DISPLAY_VERSION, + std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_VERSION), 0x18ULL), 0x68); + file->WriteArray(HLE::ApiVersion::DISPLAY_TITLE, + std::min<u64>(sizeof(HLE::ApiVersion::DISPLAY_TITLE), 0x80ULL), 0x80); return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file}, std::vector<VirtualDir>{}, "data"); } diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index d0b8fd046..3dad54f49 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -24,17 +24,12 @@ constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { case Mode::Read: return FS::FileAccessMode::Read; case Mode::Write: - return FS::FileAccessMode::Write; case Mode::ReadWrite: - return FS::FileAccessMode::ReadWrite; case Mode::Append: - return FS::FileAccessMode::Append; case Mode::ReadAppend: - return FS::FileAccessMode::ReadAppend; case Mode::WriteAppend: - return FS::FileAccessMode::Append; case Mode::All: - return FS::FileAccessMode::ReadAppend; + return FS::FileAccessMode::ReadWrite; default: return {}; } diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h new file mode 100644 index 000000000..811732179 --- /dev/null +++ b/src/core/hle/api_version.h @@ -0,0 +1,38 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" + +// This file contains yuzu's HLE API version constants. + +namespace HLE::ApiVersion { + +// Horizon OS version constants. + +constexpr u8 HOS_VERSION_MAJOR = 11; +constexpr u8 HOS_VERSION_MINOR = 0; +constexpr u8 HOS_VERSION_MICRO = 1; + +// NintendoSDK version constants. + +constexpr u8 SDK_REVISION_MAJOR = 1; +constexpr u8 SDK_REVISION_MINOR = 0; + +constexpr char PLATFORM_STRING[] = "NX"; +constexpr char VERSION_HASH[] = "69103fcb2004dace877094c2f8c29e6113be5dbf"; +constexpr char DISPLAY_VERSION[] = "11.0.1"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.1-1.0"; + +// Atmosphere version constants. + +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 4; + +constexpr u32 GetTargetFirmware() { + return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 | + u32{HOS_VERSION_MICRO} << 8 | 0U; +} + +} // namespace HLE::ApiVersion diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index da88f35bc..0c4bba66b 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -79,6 +79,7 @@ ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { R_UNLESS(current_values[index] <= value, ResultInvalidState); limit_values[index] = value; + peak_values[index] = current_values[index]; return ResultSuccess; } diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index a2844ea8c..dc15cf58b 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -313,7 +313,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - void(Common::FS::RemoveFile(zip_path)); + Common::FS::RemoveFile(zip_path); } HandleDownloadDisplayResult(applet_manager, res); @@ -445,7 +445,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - void(Common::FS::RemoveFile(bin_file_path)); + Common::FS::RemoveFile(bin_file_path); } HandleDownloadDisplayResult(applet_manager, res); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 7acad3798..1eb02aee2 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -314,6 +314,8 @@ void Controller_NPad::OnInit() { void Controller_NPad::OnLoadInputDevices() { const auto& players = Settings::values.players.GetValue(); + + std::lock_guard lock{mutex}; for (std::size_t i = 0; i < players.size(); ++i) { std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, @@ -348,6 +350,8 @@ void Controller_NPad::OnRelease() { } void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { + std::lock_guard lock{mutex}; + const auto controller_idx = NPadIdToIndex(npad_id); const auto controller_type = connected_controllers[controller_idx].type; if (!connected_controllers[controller_idx].is_connected) { diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index c050c9a44..1409d82a2 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -6,6 +6,8 @@ #include <array> #include <atomic> +#include <mutex> + #include "common/bit_field.h" #include "common/common_types.h" #include "common/quaternion.h" @@ -563,6 +565,8 @@ private: using MotionArray = std::array< std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>, 10>; + + std::mutex mutex; ButtonArray buttons; StickArray sticks; VibrationArray vibrations; diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index d1dbc659b..1d810562f 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -307,6 +307,9 @@ void NVFlinger::Compose() { } s64 NVFlinger::GetNextTicks() const { + if (Settings::values.disable_fps_limit.GetValue()) { + return 0; + } constexpr s64 max_hertz = 120LL; return (1000000000 * (1LL << swap_interval)) / max_hertz; } diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp index 1beca417c..9c7f89475 100644 --- a/src/core/hle/service/spl/csrng.cpp +++ b/src/core/hle/service/spl/csrng.cpp @@ -9,7 +9,7 @@ namespace Service::SPL { CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "csrng") { static const FunctionInfo functions[] = { - {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, + {0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index 0b5e2b7c3..ebb179aa8 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -10,6 +10,7 @@ #include <vector> #include "common/logging/log.h" #include "common/settings.h" +#include "core/hle/api_version.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/spl/csrng.h" #include "core/hle/service/spl/module.h" @@ -24,7 +25,46 @@ Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> modu Module::Interface::~Interface() = default; -void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { +void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto config_item = rp.PopEnum<ConfigItem>(); + + // This should call svcCallSecureMonitor with the appropriate args. + // Since we do not have it implemented yet, we will use this for now. + const auto smc_result = GetConfigImpl(config_item); + const auto result_code = smc_result.Code(); + + if (smc_result.Failed()) { + LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, + result_code.raw); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result_code); + } + + LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, + result_code.raw, *smc_result); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result_code); + rb.Push(*smc_result); +} + +void Module::Interface::ModularExponentiate(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("ModularExponentiate is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetConfig(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("SetConfig is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GenerateRandomBytes(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SPL, "called"); const std::size_t size = ctx.GetWriteBufferSize(); @@ -39,6 +79,88 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } +void Module::Interface::IsDevelopment(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("IsDevelopment is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::SetBootReason(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("SetBootReason is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +void Module::Interface::GetBootReason(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED_MSG("GetBootReason is not implemented!"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSecureMonitorNotImplemented); +} + +ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { + switch (config_item) { + case ConfigItem::DisableProgramVerification: + case ConfigItem::DramId: + case ConfigItem::SecurityEngineInterruptNumber: + case ConfigItem::FuseVersion: + case ConfigItem::HardwareType: + case ConfigItem::HardwareState: + case ConfigItem::IsRecoveryBoot: + case ConfigItem::DeviceId: + case ConfigItem::BootReason: + case ConfigItem::MemoryMode: + case ConfigItem::IsDevelopmentFunctionEnabled: + case ConfigItem::KernelConfiguration: + case ConfigItem::IsChargerHiZModeEnabled: + case ConfigItem::QuestState: + case ConfigItem::RegulatorType: + case ConfigItem::DeviceUniqueKeyGeneration: + case ConfigItem::Package2Hash: + return ResultSecureMonitorNotImplemented; + case ConfigItem::ExosphereApiVersion: + // Get information about the current exosphere version. + return MakeResult((u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | + (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()))); + case ConfigItem::ExosphereNeedsReboot: + // We are executing, so we aren't in the process of rebooting. + return MakeResult(u64{0}); + case ConfigItem::ExosphereNeedsShutdown: + // We are executing, so we aren't in the process of shutting down. + return MakeResult(u64{0}); + case ConfigItem::ExosphereGitCommitHash: + // Get information about the current exosphere git commit hash. + return MakeResult(u64{0}); + case ConfigItem::ExosphereHasRcmBugPatch: + // Get information about whether this unit has the RCM bug patched. + return MakeResult(u64{0}); + case ConfigItem::ExosphereBlankProdInfo: + // Get whether this unit should simulate a "blanked" PRODINFO. + return MakeResult(u64{0}); + case ConfigItem::ExosphereAllowCalWrites: + // Get whether this unit should allow writing to the calibration partition. + return MakeResult(u64{0}); + case ConfigItem::ExosphereEmummcType: + // Get what kind of emummc this unit has active. + return MakeResult(u64{0}); + case ConfigItem::ExospherePayloadAddress: + // Gets the physical address of the reboot payload buffer, if one exists. + return ResultSecureMonitorNotInitialized; + case ConfigItem::ExosphereLogConfiguration: + // Get the log configuration. + return MakeResult(u64{0}); + case ConfigItem::ExosphereForceEnableUsb30: + // Get whether usb 3.0 should be force-enabled. + return MakeResult(u64{0}); + default: + return ResultSecureMonitorInvalidArgument; + } +} + void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager); diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h index 71855c1bf..61630df80 100644 --- a/src/core/hle/service/spl/module.h +++ b/src/core/hle/service/spl/module.h @@ -6,6 +6,8 @@ #include <random> #include "core/hle/service/service.h" +#include "core/hle/service/spl/spl_results.h" +#include "core/hle/service/spl/spl_types.h" namespace Core { class System; @@ -21,12 +23,21 @@ public: const char* name); ~Interface() override; - void GetRandomBytes(Kernel::HLERequestContext& ctx); + // General + void GetConfig(Kernel::HLERequestContext& ctx); + void ModularExponentiate(Kernel::HLERequestContext& ctx); + void SetConfig(Kernel::HLERequestContext& ctx); + void GenerateRandomBytes(Kernel::HLERequestContext& ctx); + void IsDevelopment(Kernel::HLERequestContext& ctx); + void SetBootReason(Kernel::HLERequestContext& ctx); + void GetBootReason(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; private: + ResultVal<u64> GetConfigImpl(ConfigItem config_item) const; + std::mt19937 rng; }; }; diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index fff3f3c42..20384042f 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -10,13 +10,13 @@ SPL::SPL(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GetRandomBytes"}, - {11, nullptr, "IsDevelopment"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, }; // clang-format on @@ -27,22 +27,22 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:mig") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, {2, nullptr, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, {4, nullptr, "GenerateAesKey"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, - {11, nullptr, "IsDevelopment"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, {14, nullptr, "DecryptAesKey"}, {15, nullptr, "CryptAesCtr"}, {16, nullptr, "ComputeCmac"}, {21, nullptr, "AllocateAesKeyslot"}, {22, nullptr, "DeallocateAesKeySlot"}, {23, nullptr, "GetAesKeyslotAvailableEvent"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, }; // clang-format on @@ -53,16 +53,16 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:fs") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, {2, nullptr, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, {4, nullptr, "GenerateAesKey"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, {9, nullptr, "ImportLotusKey"}, {10, nullptr, "DecryptLotusMessage"}, - {11, nullptr, "IsDevelopment"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, {12, nullptr, "GenerateSpecificAesKey"}, {14, nullptr, "DecryptAesKey"}, {15, nullptr, "CryptAesCtr"}, @@ -71,8 +71,8 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr<Module> module_) {21, nullptr, "AllocateAesKeyslot"}, {22, nullptr, "DeallocateAesKeySlot"}, {23, nullptr, "GetAesKeyslotAvailableEvent"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, {31, nullptr, "GetPackage2Hash"}, }; // clang-format on @@ -84,14 +84,14 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:ssl") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, {2, nullptr, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, {4, nullptr, "GenerateAesKey"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GetRandomBytes"}, - {11, nullptr, "IsDevelopment"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, {13, nullptr, "DecryptDeviceUniqueData"}, {14, nullptr, "DecryptAesKey"}, {15, nullptr, "CryptAesCtr"}, @@ -99,8 +99,8 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr<Module> module_) {21, nullptr, "AllocateAesKeyslot"}, {22, nullptr, "DeallocateAesKeySlot"}, {23, nullptr, "GetAesKeyslotAvailableEvent"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, {26, nullptr, "DecryptAndStoreSslClientCertKey"}, {27, nullptr, "ModularExponentiateWithSslClientCertKey"}, }; @@ -113,14 +113,14 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:es") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, {2, nullptr, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, {4, nullptr, "GenerateAesKey"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GenerateRandomBytes"}, - {11, nullptr, "IsDevelopment"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, {13, nullptr, "DecryptDeviceUniqueData"}, {14, nullptr, "DecryptAesKey"}, {15, nullptr, "CryptAesCtr"}, @@ -131,8 +131,8 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr<Module> module_) {21, nullptr, "AllocateAesKeyslot"}, {22, nullptr, "DeallocateAesKeySlot"}, {23, nullptr, "GetAesKeyslotAvailableEvent"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, {28, nullptr, "DecryptAndStoreDrmDeviceCertKey"}, {29, nullptr, "ModularExponentiateWithDrmDeviceCertKey"}, {31, nullptr, "PrepareEsArchiveKey"}, @@ -147,14 +147,14 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_) : Interface(system_, std::move(module_), "spl:manu") { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GetConfig"}, - {1, nullptr, "ModularExponentiate"}, + {0, &SPL::GetConfig, "GetConfig"}, + {1, &SPL::ModularExponentiate, "ModularExponentiate"}, {2, nullptr, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, {4, nullptr, "GenerateAesKey"}, - {5, nullptr, "SetConfig"}, - {7, &SPL::GetRandomBytes, "GetRandomBytes"}, - {11, nullptr, "IsDevelopment"}, + {5, &SPL::SetConfig, "SetConfig"}, + {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, + {11, &SPL::IsDevelopment, "IsDevelopment"}, {13, nullptr, "DecryptDeviceUniqueData"}, {14, nullptr, "DecryptAesKey"}, {15, nullptr, "CryptAesCtr"}, @@ -162,8 +162,8 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr<Module> module_) {21, nullptr, "AllocateAesKeyslot"}, {22, nullptr, "DeallocateAesKeySlot"}, {23, nullptr, "GetAesKeyslotAvailableEvent"}, - {24, nullptr, "SetBootReason"}, - {25, nullptr, "GetBootReason"}, + {24, &SPL::SetBootReason, "SetBootReason"}, + {25, &SPL::GetBootReason, "GetBootReason"}, {30, nullptr, "ReencryptDeviceUniqueData"}, }; // clang-format on diff --git a/src/core/hle/service/spl/spl_results.h b/src/core/hle/service/spl/spl_results.h new file mode 100644 index 000000000..878fa91b7 --- /dev/null +++ b/src/core/hle/service/spl/spl_results.h @@ -0,0 +1,29 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/result.h" + +namespace Service::SPL { + +// Description 0 - 99 +constexpr ResultCode ResultSecureMonitorError{ErrorModule::SPL, 0}; +constexpr ResultCode ResultSecureMonitorNotImplemented{ErrorModule::SPL, 1}; +constexpr ResultCode ResultSecureMonitorInvalidArgument{ErrorModule::SPL, 2}; +constexpr ResultCode ResultSecureMonitorBusy{ErrorModule::SPL, 3}; +constexpr ResultCode ResultSecureMonitorNoAsyncOperation{ErrorModule::SPL, 4}; +constexpr ResultCode ResultSecureMonitorInvalidAsyncOperation{ErrorModule::SPL, 5}; +constexpr ResultCode ResultSecureMonitorNotPermitted{ErrorModule::SPL, 6}; +constexpr ResultCode ResultSecureMonitorNotInitialized{ErrorModule::SPL, 7}; + +constexpr ResultCode ResultInvalidSize{ErrorModule::SPL, 100}; +constexpr ResultCode ResultUnknownSecureMonitorError{ErrorModule::SPL, 101}; +constexpr ResultCode ResultDecryptionFailed{ErrorModule::SPL, 102}; + +constexpr ResultCode ResultOutOfKeySlots{ErrorModule::SPL, 104}; +constexpr ResultCode ResultInvalidKeySlot{ErrorModule::SPL, 105}; +constexpr ResultCode ResultBootReasonAlreadySet{ErrorModule::SPL, 106}; +constexpr ResultCode ResultBootReasonNotSet{ErrorModule::SPL, 107}; +constexpr ResultCode ResultInvalidArgument{ErrorModule::SPL, 108}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl_types.h b/src/core/hle/service/spl/spl_types.h new file mode 100644 index 000000000..23c39f7ed --- /dev/null +++ b/src/core/hle/service/spl/spl_types.h @@ -0,0 +1,230 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <span> + +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Service::SPL { + +constexpr size_t AES_128_KEY_SIZE = 0x10; + +namespace Smc { + +enum class FunctionId : u32 { + SetConfig = 0xC3000401, + GetConfig = 0xC3000002, + GetResult = 0xC3000003, + GetResultData = 0xC3000404, + ModularExponentiate = 0xC3000E05, + GenerateRandomBytes = 0xC3000006, + GenerateAesKek = 0xC3000007, + LoadAesKey = 0xC3000008, + ComputeAes = 0xC3000009, + GenerateSpecificAesKey = 0xC300000A, + ComputeCmac = 0xC300040B, + ReencryptDeviceUniqueData = 0xC300D60C, + DecryptDeviceUniqueData = 0xC300100D, + + ModularExponentiateWithStorageKey = 0xC300060F, + PrepareEsDeviceUniqueKey = 0xC3000610, + LoadPreparedAesKey = 0xC3000011, + PrepareCommonEsTitleKey = 0xC3000012, + + // Deprecated functions. + LoadEsDeviceKey = 0xC300100C, + DecryptAndStoreGcKey = 0xC300100E, + + // Atmosphere functions. + AtmosphereIramCopy = 0xF0000201, + AtmosphereReadWriteRegister = 0xF0000002, + + AtmosphereGetEmummcConfig = 0xF0000404, +}; + +enum class CipherMode { + CbcEncrypt = 0, + CbcDecrypt = 1, + Ctr = 2, +}; + +enum class DeviceUniqueDataMode { + DecryptDeviceUniqueData = 0, + DecryptAndStoreGcKey = 1, + DecryptAndStoreEsDeviceKey = 2, + DecryptAndStoreSslKey = 3, + DecryptAndStoreDrmDeviceCertKey = 4, +}; + +enum class ModularExponentiateWithStorageKeyMode { + Gc = 0, + Ssl = 1, + DrmDeviceCert = 2, +}; + +enum class EsCommonKeyType { + TitleKey = 0, + ArchiveKey = 1, +}; + +struct AsyncOperationKey { + u64 value; +}; + +} // namespace Smc + +enum class HardwareType { + Icosa = 0, + Copper = 1, + Hoag = 2, + Iowa = 3, + Calcio = 4, + Aula = 5, +}; + +enum class SocType { + Erista = 0, + Mariko = 1, +}; + +enum class HardwareState { + Development = 0, + Production = 1, +}; + +enum class MemoryArrangement { + Standard = 0, + StandardForAppletDev = 1, + StandardForSystemDev = 2, + Expanded = 3, + ExpandedForAppletDev = 4, + + // Note: Dynamic is not official. + // Atmosphere uses it to maintain compatibility with firmwares prior to 6.0.0, + // which removed the explicit retrieval of memory arrangement from PM. + Dynamic = 5, + Count, +}; + +enum class BootReason { + Unknown = 0, + AcOk = 1, + OnKey = 2, + RtcAlarm1 = 3, + RtcAlarm2 = 4, +}; + +struct BootReasonValue { + union { + u32 value{}; + + BitField<0, 8, u32> power_intr; + BitField<8, 8, u32> rtc_intr; + BitField<16, 8, u32> nv_erc; + BitField<24, 8, u32> boot_reason; + }; +}; +static_assert(sizeof(BootReasonValue) == sizeof(u32), "BootReasonValue definition!"); + +struct AesKey { + std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + + std::span<u8> AsBytes() { + return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; + } + + std::span<const u8> AsBytes() const { + return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; + } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AesKey definition!"); + +struct IvCtr { + std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + + std::span<u8> AsBytes() { + return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; + } + + std::span<const u8> AsBytes() const { + return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; + } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "IvCtr definition!"); + +struct Cmac { + std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + + std::span<u8> AsBytes() { + return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; + } + + std::span<const u8> AsBytes() const { + return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; + } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "Cmac definition!"); + +struct AccessKey { + std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + + std::span<u8> AsBytes() { + return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; + } + + std::span<const u8> AsBytes() const { + return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; + } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "AccessKey definition!"); + +struct KeySource { + std::array<u64, AES_128_KEY_SIZE / sizeof(u64)> data64{}; + + std::span<u8> AsBytes() { + return std::span{reinterpret_cast<u8*>(data64.data()), AES_128_KEY_SIZE}; + } + + std::span<const u8> AsBytes() const { + return std::span{reinterpret_cast<const u8*>(data64.data()), AES_128_KEY_SIZE}; + } +}; +static_assert(sizeof(AesKey) == AES_128_KEY_SIZE, "KeySource definition!"); + +enum class ConfigItem : u32 { + // Standard config items. + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineInterruptNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + QuestState = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + // Extension config items for exosphere. + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + ExosphereEmummcType = 65007, + ExospherePayloadAddress = 65008, + ExosphereLogConfiguration = 65009, + ExosphereForceEnableUsb30 = 65010, +}; + +} // namespace Service::SPL diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp index bf4402308..c634b6abd 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -125,7 +125,7 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati return ERROR_TIME_NOT_FOUND; } - vfs_file = zoneinfo_dir->GetFile(location_name); + vfs_file = zoneinfo_dir->GetFileRelative(location_name); if (!vfs_file) { LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.", time_zone_binary_titleid, location_name); diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp index a335e6da1..3b052ffb2 100644 --- a/src/input_common/mouse/mouse_input.cpp +++ b/src/input_common/mouse/mouse_input.cpp @@ -2,25 +2,23 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include <stop_token> +#include <thread> + #include "common/settings.h" #include "input_common/mouse/mouse_input.h" namespace MouseInput { Mouse::Mouse() { - update_thread = std::thread(&Mouse::UpdateThread, this); + update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); } -Mouse::~Mouse() { - update_thread_running = false; - if (update_thread.joinable()) { - update_thread.join(); - } -} +Mouse::~Mouse() = default; -void Mouse::UpdateThread() { +void Mouse::UpdateThread(std::stop_token stop_token) { constexpr int update_time = 10; - while (update_thread_running) { + while (!stop_token.stop_requested()) { for (MouseInfo& info : mouse_info) { const Common::Vec3f angular_direction{ -info.tilt_direction.y, diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h index 5a971ad67..c8bae99c1 100644 --- a/src/input_common/mouse/mouse_input.h +++ b/src/input_common/mouse/mouse_input.h @@ -6,6 +6,7 @@ #include <array> #include <mutex> +#include <stop_token> #include <thread> #include "common/common_types.h" @@ -85,7 +86,7 @@ public: [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; private: - void UpdateThread(); + void UpdateThread(std::stop_token stop_token); void UpdateYuzuSettings(); void StopPanning(); @@ -105,12 +106,11 @@ private: u16 buttons{}; u16 toggle_buttons{}; u16 lock_buttons{}; - std::thread update_thread; + std::jthread update_thread; MouseButton last_button{MouseButton::Undefined}; std::array<MouseInfo, 7> mouse_info; Common::SPSCQueue<MouseStatus> mouse_queue; bool configuring{false}; - bool update_thread_running{true}; int mouse_panning_timout{}; }; } // namespace MouseInput diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index a39505903..b121d36a3 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -256,6 +256,16 @@ public: stream_score += score; } + /// Sets the new frame tick + void SetFrameTick(u64 new_frame_tick) noexcept { + frame_tick = new_frame_tick; + } + + /// Returns the new frame tick + [[nodiscard]] u64 FrameTick() const noexcept { + return frame_tick; + } + /// Returns the likeliness of this being a stream buffer [[nodiscard]] int StreamScore() const noexcept { return stream_score; @@ -586,6 +596,7 @@ private: RasterizerInterface* rasterizer = nullptr; VAddr cpu_addr = 0; Words words; + u64 frame_tick = 0; BufferFlagBits flags{}; int stream_score = 0; }; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index d371b842f..6d04d00da 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -16,6 +16,7 @@ #include <boost/container/small_vector.hpp> +#include "common/common_sizes.h" #include "common/common_types.h" #include "common/div_ceil.h" #include "common/microprofile.h" @@ -65,6 +66,9 @@ class BufferCache { static constexpr BufferId NULL_BUFFER_ID{0}; + static constexpr u64 EXPECTED_MEMORY = Common::Size_512_MB; + static constexpr u64 CRITICAL_MEMORY = Common::Size_1_GB; + using Maxwell = Tegra::Engines::Maxwell3D::Regs; using Runtime = typename P::Runtime; @@ -102,6 +106,8 @@ public: void TickFrame(); + void RunGarbageCollector(); + void WriteMemory(VAddr cpu_addr, u64 size); void CachedWriteMemory(VAddr cpu_addr, u64 size); @@ -243,6 +249,8 @@ private: template <bool insert> void ChangeRegister(BufferId buffer_id); + void TouchBuffer(Buffer& buffer) const noexcept; + bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size); bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size); @@ -255,6 +263,10 @@ private: void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies); + void DownloadBufferMemory(Buffer& buffer_id); + + void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size); + void DeleteBuffer(BufferId buffer_id); void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id); @@ -319,6 +331,10 @@ private: size_t immediate_buffer_capacity = 0; std::unique_ptr<u8[]> immediate_buffer_alloc; + typename SlotVector<Buffer>::Iterator deletion_iterator; + u64 frame_tick = 0; + u64 total_used_memory = 0; + std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table; }; @@ -332,6 +348,28 @@ BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, runtime{runtime_} { // Ensure the first slot is used for the null buffer void(slot_buffers.insert(runtime, NullBufferParams{})); + deletion_iterator = slot_buffers.end(); +} + +template <class P> +void BufferCache<P>::RunGarbageCollector() { + const bool aggressive_gc = total_used_memory >= CRITICAL_MEMORY; + const u64 ticks_to_destroy = aggressive_gc ? 60 : 120; + int num_iterations = aggressive_gc ? 64 : 32; + for (; num_iterations > 0; --num_iterations) { + if (deletion_iterator == slot_buffers.end()) { + deletion_iterator = slot_buffers.begin(); + } + ++deletion_iterator; + if (deletion_iterator == slot_buffers.end()) { + break; + } + const auto [buffer_id, buffer] = *deletion_iterator; + if (buffer->FrameTick() + ticks_to_destroy < frame_tick) { + DownloadBufferMemory(*buffer); + DeleteBuffer(buffer_id); + } + } } template <class P> @@ -349,6 +387,10 @@ void BufferCache<P>::TickFrame() { const bool skip_preferred = hits * 256 < shots * 251; uniform_buffer_skip_cache_size = skip_preferred ? DEFAULT_SKIP_CACHE_SIZE : 0; + if (Settings::values.use_caches_gc.GetValue() && total_used_memory >= EXPECTED_MEMORY) { + RunGarbageCollector(); + } + ++frame_tick; delayed_destruction_ring.Tick(); } @@ -371,50 +413,8 @@ void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) { template <class P> void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) { - ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { - boost::container::small_vector<BufferCopy, 1> copies; - u64 total_size_bytes = 0; - u64 largest_copy = 0; - buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { - copies.push_back(BufferCopy{ - .src_offset = range_offset, - .dst_offset = total_size_bytes, - .size = range_size, - }); - total_size_bytes += range_size; - largest_copy = std::max(largest_copy, range_size); - }); - if (total_size_bytes == 0) { - return; - } - MICROPROFILE_SCOPE(GPU_DownloadMemory); - - if constexpr (USE_MEMORY_MAPS) { - auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); - const u8* const mapped_memory = download_staging.mapped_span.data(); - const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); - for (BufferCopy& copy : copies) { - // Modify copies to have the staging offset in mind - copy.dst_offset += download_staging.offset; - } - runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); - runtime.Finish(); - for (const BufferCopy& copy : copies) { - const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; - // Undo the modified offset - const u64 dst_offset = copy.dst_offset - download_staging.offset; - const u8* copy_mapped_memory = mapped_memory + dst_offset; - cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size); - } - } else { - const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); - for (const BufferCopy& copy : copies) { - buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); - const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; - cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size); - } - } - }); + ForEachBufferInRange(cpu_addr, size, + [&](BufferId, Buffer& buffer) { DownloadBufferMemory(buffer); }); } template <class P> @@ -640,6 +640,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { template <class P> void BufferCache<P>::BindHostIndexBuffer() { Buffer& buffer = slot_buffers[index_buffer.buffer_id]; + TouchBuffer(buffer); const u32 offset = buffer.Offset(index_buffer.cpu_addr); const u32 size = index_buffer.size; SynchronizeBuffer(buffer, index_buffer.cpu_addr, size); @@ -658,6 +659,7 @@ void BufferCache<P>::BindHostVertexBuffers() { for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { const Binding& binding = vertex_buffers[index]; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); SynchronizeBuffer(buffer, binding.cpu_addr, binding.size); if (!flags[Dirty::VertexBuffer0 + index]) { continue; @@ -693,6 +695,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 const VAddr cpu_addr = binding.cpu_addr; const u32 size = binding.size; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && size <= uniform_buffer_skip_cache_size && !buffer.IsRegionGpuModified(cpu_addr, size); @@ -744,6 +747,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) { const Binding& binding = storage_buffers[stage][index]; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -766,6 +770,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) { const Binding& binding = transform_feedback_buffers[index]; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -784,6 +789,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() { ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) { const Binding& binding = compute_uniform_buffers[index]; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -803,6 +809,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() { ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) { const Binding& binding = compute_storage_buffers[index]; Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer); const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); @@ -1101,6 +1108,7 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) { const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size); const u32 size = static_cast<u32>(overlap.end - overlap.begin); const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); + TouchBuffer(slot_buffers[new_buffer_id]); for (const BufferId overlap_id : overlap.ids) { JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); } @@ -1122,8 +1130,14 @@ template <class P> template <bool insert> void BufferCache<P>::ChangeRegister(BufferId buffer_id) { const Buffer& buffer = slot_buffers[buffer_id]; + const auto size = buffer.SizeBytes(); + if (insert) { + total_used_memory += Common::AlignUp(size, 1024); + } else { + total_used_memory -= Common::AlignUp(size, 1024); + } const VAddr cpu_addr_begin = buffer.CpuAddr(); - const VAddr cpu_addr_end = cpu_addr_begin + buffer.SizeBytes(); + const VAddr cpu_addr_end = cpu_addr_begin + size; const u64 page_begin = cpu_addr_begin / PAGE_SIZE; const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE); for (u64 page = page_begin; page != page_end; ++page) { @@ -1136,6 +1150,11 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) { } template <class P> +void BufferCache<P>::TouchBuffer(Buffer& buffer) const noexcept { + buffer.SetFrameTick(frame_tick); +} + +template <class P> bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { if (buffer.CpuAddr() == 0) { return true; @@ -1212,6 +1231,57 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, } template <class P> +void BufferCache<P>::DownloadBufferMemory(Buffer& buffer) { + DownloadBufferMemory(buffer, buffer.CpuAddr(), buffer.SizeBytes()); +} + +template <class P> +void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 size) { + boost::container::small_vector<BufferCopy, 1> copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { + copies.push_back(BufferCopy{ + .src_offset = range_offset, + .dst_offset = total_size_bytes, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }); + if (total_size_bytes == 0) { + return; + } + MICROPROFILE_SCOPE(GPU_DownloadMemory); + + if constexpr (USE_MEMORY_MAPS) { + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + const u8* const mapped_memory = download_staging.mapped_span.data(); + const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); + for (BufferCopy& copy : copies) { + // Modify copies to have the staging offset in mind + copy.dst_offset += download_staging.offset; + } + runtime.CopyBuffer(download_staging.buffer, buffer, copies_span); + runtime.Finish(); + for (const BufferCopy& copy : copies) { + const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; + // Undo the modified offset + const u64 dst_offset = copy.dst_offset - download_staging.offset; + const u8* copy_mapped_memory = mapped_memory + dst_offset; + cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size); + } + } else { + const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); + for (const BufferCopy& copy : copies) { + buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); + const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset; + cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size); + } + } +} + +template <class P> void BufferCache<P>::DeleteBuffer(BufferId buffer_id) { const auto scalar_replace = [buffer_id](Binding& binding) { if (binding.buffer_id == buffer_id) { @@ -1236,6 +1306,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id) { Unregister(buffer_id); delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); + slot_buffers.erase(buffer_id); NotifyBufferDeletion(); } diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index ffed42a29..335383955 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -242,6 +242,7 @@ public: return 4; default: UNREACHABLE(); + return 1; } } diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index f968b5b16..07939432f 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -4,10 +4,10 @@ #pragma once -#include <atomic> #include <functional> #include <optional> #include <span> +#include <stop_token> #include "common/common_types.h" #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" @@ -123,7 +123,7 @@ public: virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} /// Initialize disk cached resources for the game being emulated - virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, + virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading, const DiskResourceLoadCallback& callback) {} /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index f87bb269b..eb8bdaa85 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -351,7 +351,7 @@ void RasterizerOpenGL::SetupShaders(bool is_indexed) { } } -void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, +void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { shader_cache.LoadDiskCache(title_id, stop_loading, callback); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 76298517f..9995a563b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -94,7 +94,7 @@ public: const Tegra::Engines::Fermi2D::Config& copy_config) override; bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; - void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading, + void LoadDiskResources(u64 title_id, std::stop_token stop_loading, const VideoCore::DiskResourceLoadCallback& callback) override; /// Returns true when there are commands queued to the OpenGL server. diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 5cf7cd151..5a01c59ec 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -331,7 +331,7 @@ ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_, ShaderCacheOpenGL::~ShaderCacheOpenGL() = default; -void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, +void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, std::stop_token stop_loading, const VideoCore::DiskResourceLoadCallback& callback) { disk_cache.BindTitleID(title_id); const std::optional transferable = disk_cache.LoadTransferable(); @@ -372,7 +372,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop const auto scope = context->Acquire(); for (std::size_t i = begin; i < end; ++i) { - if (stop_loading) { + if (stop_loading.stop_requested()) { return; } const auto& entry = (*transferable)[i]; @@ -435,7 +435,7 @@ void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop precompiled_cache_altered = true; return; } - if (stop_loading) { + if (stop_loading.stop_requested()) { return; } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 2aed0697e..b30308b6f 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -127,7 +127,7 @@ public: ~ShaderCacheOpenGL() override; /// Loads disk cache for the current game - void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading, + void LoadDiskCache(u64 title_id, std::stop_token stop_loading, const VideoCore::DiskResourceLoadCallback& callback); /// Gets the current specified shader stage program diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 9b4038615..23948feed 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -737,6 +737,8 @@ Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_, } } +Image::~Image() = default; + void Image::UploadMemory(const ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer); diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index df8be12ff..25fe61566 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -143,6 +143,14 @@ public: explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); + ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + Image(Image&&) = default; + Image& operator=(Image&&) = default; + void UploadMemory(const ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); @@ -235,6 +243,7 @@ struct TextureCacheParams { static constexpr bool ENABLE_VALIDATION = true; static constexpr bool FRAMEBUFFER_BLITS = true; static constexpr bool HAS_EMULATED_COPIES = true; + static constexpr bool HAS_DEVICE_MEMORY_INFO = false; using Runtime = OpenGL::TextureCacheRuntime; using Image = OpenGL::Image; diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index db78ce3d9..6852c11b0 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -2,8 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <atomic> -#include <chrono> +#include <thread> #include "common/settings.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" @@ -12,8 +11,6 @@ namespace Vulkan { -using namespace std::chrono_literals; - MasterSemaphore::MasterSemaphore(const Device& device) { static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, @@ -34,9 +31,9 @@ MasterSemaphore::MasterSemaphore(const Device& device) { // Validation layers have a bug where they fail to track resource usage when using timeline // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have // a separate thread waiting for each timeline semaphore value. - debug_thread = std::thread([this] { + debug_thread = std::jthread([this](std::stop_token stop_token) { u64 counter = 0; - while (!shutdown) { + while (!stop_token.stop_requested()) { if (semaphore.Wait(counter, 10'000'000)) { ++counter; } @@ -44,13 +41,6 @@ MasterSemaphore::MasterSemaphore(const Device& device) { }); } -MasterSemaphore::~MasterSemaphore() { - shutdown = true; - - // This thread might not be started - if (debug_thread.joinable()) { - debug_thread.join(); - } -} +MasterSemaphore::~MasterSemaphore() = default; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h index 4b6d64daa..ee3cd35d0 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.h +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h @@ -65,11 +65,10 @@ public: } private: - vk::Semaphore semaphore; ///< Timeline semaphore. - std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. - std::atomic<u64> current_tick{1}; ///< Current logical tick. - std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed. - std::thread debug_thread; ///< Debug thread to workaround validation layer bugs. + vk::Semaphore semaphore; ///< Timeline semaphore. + std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. + std::atomic<u64> current_tick{1}; ///< Current logical tick. + std::jthread debug_thread; ///< Debug thread to workaround validation layer bugs. }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 52860b4cf..a2ab4d1ee 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -818,6 +818,10 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src, }); } +u64 TextureCacheRuntime::GetDeviceLocalMemory() const { + return device.GetDeviceLocalMemory(); +} + Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler}, @@ -876,6 +880,8 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_ } } +Image::~Image() = default; + void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) { // TODO: Move this to another API scheduler->RequestOutsideRenderPassOperationContext(); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 4a57d378b..172bcdf98 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -97,6 +97,8 @@ struct TextureCacheRuntime { // All known Vulkan drivers can natively handle BGR textures return true; } + + u64 GetDeviceLocalMemory() const; }; class Image : public VideoCommon::ImageBase { @@ -104,6 +106,14 @@ public: explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr); + ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + Image(Image&&) = default; + Image& operator=(Image&&) = default; + void UploadMemory(const StagingBufferRef& map, std::span<const VideoCommon::BufferImageCopy> copies); @@ -257,6 +267,7 @@ struct TextureCacheParams { static constexpr bool ENABLE_VALIDATION = true; static constexpr bool FRAMEBUFFER_BLITS = false; static constexpr bool HAS_EMULATED_COPIES = false; + static constexpr bool HAS_DEVICE_MEMORY_INFO = true; using Runtime = Vulkan::TextureCacheRuntime; using Image = Vulkan::Image; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 6308aef94..eb1746265 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -283,4 +283,11 @@ std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { return {DefaultBlockWidth(format), DefaultBlockHeight(format)}; } +u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format) { + constexpr u64 RGBA8_PIXEL_SIZE = 4; + const u64 base_block_size = static_cast<u64>(DefaultBlockWidth(format)) * + static_cast<u64>(DefaultBlockHeight(format)) * RGBA8_PIXEL_SIZE; + return (base_size * base_block_size) / BytesPerBlock(format); +} + } // namespace VideoCore::Surface diff --git a/src/video_core/surface.h b/src/video_core/surface.h index c40ab89d0..1503db81f 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -462,4 +462,6 @@ bool IsPixelFormatSRGB(PixelFormat format); std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); +u64 EstimatedDecompressedSize(u64 base_size, PixelFormat format); + } // namespace VideoCore::Surface diff --git a/src/video_core/texture_cache/image_base.cpp b/src/video_core/texture_cache/image_base.cpp index 9914926b3..ad69d32d1 100644 --- a/src/video_core/texture_cache/image_base.cpp +++ b/src/video_core/texture_cache/image_base.cpp @@ -113,6 +113,43 @@ void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_vie image_view_ids.push_back(image_view_id); } +bool ImageBase::IsSafeDownload() const noexcept { + // Skip images that were not modified from the GPU + if (False(flags & ImageFlagBits::GpuModified)) { + return false; + } + // Skip images that .are. modified from the CPU + // We don't want to write sensitive data from the guest + if (True(flags & ImageFlagBits::CpuModified)) { + return false; + } + if (info.num_samples > 1) { + LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); + return false; + } + return true; +} + +void ImageBase::CheckBadOverlapState() { + if (False(flags & ImageFlagBits::BadOverlap)) { + return; + } + if (!overlapping_images.empty()) { + return; + } + flags &= ~ImageFlagBits::BadOverlap; +} + +void ImageBase::CheckAliasState() { + if (False(flags & ImageFlagBits::Alias)) { + return; + } + if (!aliased_images.empty()) { + return; + } + flags &= ~ImageFlagBits::Alias; +} + void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) { static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format; ASSERT(lhs.info.type == rhs.info.type); diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h index b7f3b7e43..e326cab71 100644 --- a/src/video_core/texture_cache/image_base.h +++ b/src/video_core/texture_cache/image_base.h @@ -25,6 +25,12 @@ enum class ImageFlagBits : u32 { Strong = 1 << 5, ///< Exists in the image table, the dimensions are can be trusted Registered = 1 << 6, ///< True when the image is registered Picked = 1 << 7, ///< Temporary flag to mark the image as picked + + // Garbage Collection Flags + BadOverlap = 1 << 8, ///< This image overlaps other but doesn't fit, has higher + ///< garbage collection priority + Alias = 1 << 9, ///< This image has aliases and has priority on garbage + ///< collection }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -44,11 +50,16 @@ struct ImageBase { void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id); + [[nodiscard]] bool IsSafeDownload() const noexcept; + [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept { const VAddr overlap_end = overlap_cpu_addr + overlap_size; return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end; } + void CheckBadOverlapState(); + void CheckAliasState(); + ImageInfo info; u32 guest_size_bytes = 0; @@ -72,6 +83,7 @@ struct ImageBase { std::vector<SubresourceBase> slice_subresources; std::vector<AliasedImage> aliased_images; + std::vector<ImageId> overlapping_images; }; struct ImageAllocBase { diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h index eae3be6ea..6180b8c0e 100644 --- a/src/video_core/texture_cache/slot_vector.h +++ b/src/video_core/texture_cache/slot_vector.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <bit> #include <concepts> #include <numeric> #include <type_traits> @@ -32,6 +33,60 @@ template <class T> requires std::is_nothrow_move_assignable_v<T>&& std::is_nothrow_move_constructible_v<T> class SlotVector { public: + class Iterator { + friend SlotVector<T>; + + public: + constexpr Iterator() = default; + + Iterator& operator++() noexcept { + const u64* const bitset = slot_vector->stored_bitset.data(); + const u32 size = static_cast<u32>(slot_vector->stored_bitset.size()) * 64; + if (id.index < size) { + do { + ++id.index; + } while (id.index < size && !IsValid(bitset)); + if (id.index == size) { + id.index = SlotId::INVALID_INDEX; + } + } + return *this; + } + + Iterator operator++(int) noexcept { + const Iterator copy{*this}; + ++*this; + return copy; + } + + bool operator==(const Iterator& other) const noexcept { + return id.index == other.id.index; + } + + bool operator!=(const Iterator& other) const noexcept { + return id.index != other.id.index; + } + + std::pair<SlotId, T*> operator*() const noexcept { + return {id, std::addressof((*slot_vector)[id])}; + } + + T* operator->() const noexcept { + return std::addressof((*slot_vector)[id]); + } + + private: + Iterator(SlotVector<T>* slot_vector_, SlotId id_) noexcept + : slot_vector{slot_vector_}, id{id_} {} + + bool IsValid(const u64* bitset) const noexcept { + return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0; + } + + SlotVector<T>* slot_vector; + SlotId id; + }; + ~SlotVector() noexcept { size_t index = 0; for (u64 bits : stored_bitset) { @@ -70,6 +125,20 @@ public: ResetStorageBit(id.index); } + [[nodiscard]] Iterator begin() noexcept { + const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; }); + if (it == stored_bitset.end()) { + return end(); + } + const u32 word_index = static_cast<u32>(std::distance(it, stored_bitset.begin())); + const SlotId first_id{word_index * 64 + static_cast<u32>(std::countr_zero(*it))}; + return Iterator(this, first_id); + } + + [[nodiscard]] Iterator end() noexcept { + return Iterator(this, SlotId{SlotId::INVALID_INDEX}); + } + private: struct NonTrivialDummy { NonTrivialDummy() noexcept {} @@ -140,7 +209,6 @@ private: Entry* values = nullptr; size_t values_capacity = 0; - size_t values_size = 0; std::vector<u64> stored_bitset; std::vector<u32> free_list; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 59b7c678b..e7f8478b4 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -20,8 +20,10 @@ #include "common/alignment.h" #include "common/common_funcs.h" +#include "common/common_sizes.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/settings.h" #include "video_core/compatible_formats.h" #include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" @@ -69,12 +71,17 @@ class TextureCache { static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS; /// True when some copies have to be emulated static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES; + /// True when the API can provide info about the memory of the device. + static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO; /// Image view ID for null descriptors static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0}; /// Sampler ID for bugged sampler ids static constexpr SamplerId NULL_SAMPLER_ID{0}; + static constexpr u64 DEFAULT_EXPECTED_MEMORY = Common::Size_1_GB; + static constexpr u64 DEFAULT_CRITICAL_MEMORY = Common::Size_2_GB; + using Runtime = typename P::Runtime; using Image = typename P::Image; using ImageAlloc = typename P::ImageAlloc; @@ -103,6 +110,9 @@ public: /// Notify the cache that a new frame has been queued void TickFrame(); + /// Runs the Garbage Collector. + void RunGarbageCollector(); + /// Return a constant reference to the given image view id [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept; @@ -333,6 +343,10 @@ private: std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table; bool has_deleted_images = false; + u64 total_used_memory = 0; + u64 minimum_memory; + u64 expected_memory; + u64 critical_memory; SlotVector<Image> slot_images; SlotVector<ImageView> slot_image_views; @@ -353,6 +367,7 @@ private: u64 modification_tick = 0; u64 frame_tick = 0; + typename SlotVector<Image>::Iterator deletion_iterator; }; template <class P> @@ -373,11 +388,94 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& // This way the null resource becomes a compile time constant void(slot_image_views.insert(runtime, NullImageParams{})); void(slot_samplers.insert(runtime, sampler_descriptor)); + + deletion_iterator = slot_images.begin(); + + if constexpr (HAS_DEVICE_MEMORY_INFO) { + const auto device_memory = runtime.GetDeviceLocalMemory(); + const u64 possible_expected_memory = (device_memory * 3) / 10; + const u64 possible_critical_memory = (device_memory * 6) / 10; + expected_memory = std::max(possible_expected_memory, DEFAULT_EXPECTED_MEMORY); + critical_memory = std::max(possible_critical_memory, DEFAULT_CRITICAL_MEMORY); + minimum_memory = 0; + } else { + // on OGL we can be more conservatives as the driver takes care. + expected_memory = DEFAULT_EXPECTED_MEMORY + Common::Size_512_MB; + critical_memory = DEFAULT_CRITICAL_MEMORY + Common::Size_1_GB; + minimum_memory = expected_memory; + } +} + +template <class P> +void TextureCache<P>::RunGarbageCollector() { + const bool high_priority_mode = total_used_memory >= expected_memory; + const bool aggressive_mode = total_used_memory >= critical_memory; + const u64 ticks_to_destroy = high_priority_mode ? 60 : 100; + int num_iterations = aggressive_mode ? 256 : (high_priority_mode ? 128 : 64); + for (; num_iterations > 0; --num_iterations) { + if (deletion_iterator == slot_images.end()) { + deletion_iterator = slot_images.begin(); + if (deletion_iterator == slot_images.end()) { + break; + } + } + auto [image_id, image_tmp] = *deletion_iterator; + Image* image = image_tmp; // fix clang error. + const bool is_alias = True(image->flags & ImageFlagBits::Alias); + const bool is_bad_overlap = True(image->flags & ImageFlagBits::BadOverlap); + const bool must_download = image->IsSafeDownload(); + bool should_care = is_bad_overlap || is_alias || (high_priority_mode && !must_download); + const u64 ticks_needed = + is_bad_overlap + ? ticks_to_destroy >> 4 + : ((should_care && aggressive_mode) ? ticks_to_destroy >> 1 : ticks_to_destroy); + should_care |= aggressive_mode; + if (should_care && image->frame_tick + ticks_needed < frame_tick) { + if (is_bad_overlap) { + const bool overlap_check = std::ranges::all_of( + image->overlapping_images, [&, image](const ImageId& overlap_id) { + auto& overlap = slot_images[overlap_id]; + return overlap.frame_tick >= image->frame_tick; + }); + if (!overlap_check) { + ++deletion_iterator; + continue; + } + } + if (!is_bad_overlap && must_download) { + const bool alias_check = std::ranges::none_of( + image->aliased_images, [&, image](const AliasedImage& alias) { + auto& alias_image = slot_images[alias.id]; + return (alias_image.frame_tick < image->frame_tick) || + (alias_image.modification_tick < image->modification_tick); + }); + + if (alias_check) { + auto map = runtime.DownloadStagingBuffer(image->unswizzled_size_bytes); + const auto copies = FullDownloadCopies(image->info); + image->DownloadMemory(map, copies); + runtime.Finish(); + SwizzleImage(gpu_memory, image->gpu_addr, image->info, copies, map.mapped_span); + } + } + if (True(image->flags & ImageFlagBits::Tracked)) { + UntrackImage(*image); + } + UnregisterImage(image_id); + DeleteImage(image_id); + if (is_bad_overlap) { + ++num_iterations; + } + } + ++deletion_iterator; + } } template <class P> void TextureCache<P>::TickFrame() { - // Tick sentenced resources in this order to ensure they are destroyed in the right order + if (Settings::values.use_caches_gc.GetValue() && total_used_memory > minimum_memory) { + RunGarbageCollector(); + } sentenced_images.Tick(); sentenced_framebuffers.Tick(); sentenced_image_view.Tick(); @@ -568,17 +666,7 @@ template <class P> void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) { std::vector<ImageId> images; ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { - // Skip images that were not modified from the GPU - if (False(image.flags & ImageFlagBits::GpuModified)) { - return; - } - // Skip images that .are. modified from the CPU - // We don't want to write sensitive data from the guest - if (True(image.flags & ImageFlagBits::CpuModified)) { - return; - } - if (image.info.num_samples > 1) { - LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); + if (!image.IsSafeDownload()) { return; } image.flags &= ~ImageFlagBits::GpuModified; @@ -967,6 +1055,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA std::vector<ImageId> overlap_ids; std::vector<ImageId> left_aliased_ids; std::vector<ImageId> right_aliased_ids; + std::vector<ImageId> bad_overlap_ids; ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { if (info.type != overlap.info.type) { return; @@ -992,9 +1081,14 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA const ImageBase new_image_base(new_info, gpu_addr, cpu_addr); if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views, native_bgr)) { left_aliased_ids.push_back(overlap_id); + overlap.flags |= ImageFlagBits::Alias; } else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options, broken_views, native_bgr)) { right_aliased_ids.push_back(overlap_id); + overlap.flags |= ImageFlagBits::Alias; + } else { + bad_overlap_ids.push_back(overlap_id); + overlap.flags |= ImageFlagBits::BadOverlap; } }); const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr); @@ -1022,10 +1116,18 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA for (const ImageId aliased_id : right_aliased_ids) { ImageBase& aliased = slot_images[aliased_id]; AddImageAlias(new_image_base, aliased, new_image_id, aliased_id); + new_image.flags |= ImageFlagBits::Alias; } for (const ImageId aliased_id : left_aliased_ids) { ImageBase& aliased = slot_images[aliased_id]; AddImageAlias(aliased, new_image_base, aliased_id, new_image_id); + new_image.flags |= ImageFlagBits::Alias; + } + for (const ImageId aliased_id : bad_overlap_ids) { + ImageBase& aliased = slot_images[aliased_id]; + aliased.overlapping_images.push_back(new_image_id); + new_image.overlapping_images.push_back(aliased_id); + new_image.flags |= ImageFlagBits::BadOverlap; } RegisterImage(new_image_id); return new_image_id; @@ -1195,6 +1297,13 @@ void TextureCache<P>::RegisterImage(ImageId image_id) { image.flags |= ImageFlagBits::Registered; ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { page_table[page].push_back(image_id); }); + u64 tentative_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes); + if ((IsPixelFormatASTC(image.info.format) && + True(image.flags & ImageFlagBits::AcceleratedUpload)) || + True(image.flags & ImageFlagBits::Converted)) { + tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); + } + total_used_memory += Common::AlignUp(tentative_size, 1024); } template <class P> @@ -1203,6 +1312,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { ASSERT_MSG(True(image.flags & ImageFlagBits::Registered), "Trying to unregister an already registered image"); image.flags &= ~ImageFlagBits::Registered; + image.flags &= ~ImageFlagBits::BadOverlap; + u64 tentative_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes); + if ((IsPixelFormatASTC(image.info.format) && + True(image.flags & ImageFlagBits::AcceleratedUpload)) || + True(image.flags & ImageFlagBits::Converted)) { + tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); + } + total_used_memory -= Common::AlignUp(tentative_size, 1024); ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { @@ -1276,9 +1393,19 @@ void TextureCache<P>::DeleteImage(ImageId image_id) { std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) { return other_alias.id == image_id; }); + other_image.CheckAliasState(); ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}", num_removed_aliases); } + for (const ImageId overlap_id : image.overlapping_images) { + ImageBase& other_image = slot_images[overlap_id]; + [[maybe_unused]] const size_t num_removed_overlaps = std::erase_if( + other_image.overlapping_images, + [image_id](const ImageId other_overlap_id) { return other_overlap_id == image_id; }); + other_image.CheckBadOverlapState(); + ASSERT_MSG(num_removed_overlaps == 1, "Invalid number of removed overlapps: {}", + num_removed_overlaps); + } for (const ImageViewId image_view_id : image_view_ids) { sentenced_image_view.Push(std::move(slot_image_views[image_view_id])); slot_image_views.erase(image_view_id); diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 6835fd747..4efe042b6 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -581,6 +581,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr for (s32 layer = 0; layer < info.resources.layers; ++layer) { const std::span<const u8> src = input.subspan(host_offset); + gpu_memory.ReadBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes()); + SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height, num_tiles.depth, block.height, block.depth); diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp index f0ee76519..758c038ba 100644 --- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp @@ -50,7 +50,7 @@ NsightAftermathTracker::NsightAftermathTracker() { } dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash"; - void(Common::FS::RemoveDirRecursively(dump_dir)); + Common::FS::RemoveDirRecursively(dump_dir); if (!Common::FS::CreateDir(dump_dir)) { LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); return; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 64206b3d2..707a8b8fb 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -408,6 +408,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld); + CollectPhysicalMemoryInfo(); CollectTelemetryParameters(); CollectToolingInfo(); @@ -818,6 +819,17 @@ void Device::CollectTelemetryParameters() { } } +void Device::CollectPhysicalMemoryInfo() { + const auto mem_properties = physical.GetMemoryProperties(); + const std::size_t num_properties = mem_properties.memoryHeapCount; + device_access_memory = 0; + for (std::size_t element = 0; element < num_properties; element++) { + if ((mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { + device_access_memory += mem_properties.memoryHeaps[element].size; + } + } +} + void Device::CollectToolingInfo() { if (!ext_tooling_info) { return; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 67d70cd22..a1aba973b 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -225,6 +225,10 @@ public: return use_asynchronous_shaders; } + u64 GetDeviceLocalMemory() const { + return device_access_memory; + } + private: /// Checks if the physical device is suitable. void CheckSuitability(bool requires_swapchain) const; @@ -244,6 +248,9 @@ private: /// Collects information about attached tools. void CollectToolingInfo(); + /// Collects information about the device's local memory. + void CollectPhysicalMemoryInfo(); + /// Returns a list of queue initialization descriptors. std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const; @@ -302,6 +309,8 @@ private: /// Nsight Aftermath GPU crash tracker std::unique_ptr<NsightAftermathTracker> nsight_aftermath_tracker; + + u64 device_access_memory; }; } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 5edd06ebc..aa173d19e 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -69,10 +69,10 @@ constexpr VkExportMemoryAllocateInfo EXPORT_ALLOCATE_INFO{ class MemoryAllocation { public: - explicit MemoryAllocation(vk::DeviceMemory memory_, VkMemoryPropertyFlags properties, - u64 allocation_size_, u32 type) - : memory{std::move(memory_)}, allocation_size{allocation_size_}, property_flags{properties}, - shifted_memory_type{1U << type} {} + explicit MemoryAllocation(MemoryAllocator* const allocator_, vk::DeviceMemory memory_, + VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type) + : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_}, + property_flags{properties}, shifted_memory_type{1U << type} {} #if defined(_WIN32) || defined(__unix__) ~MemoryAllocation() { @@ -106,6 +106,10 @@ public: const auto it = std::ranges::find(commits, begin, &Range::begin); ASSERT_MSG(it != commits.end(), "Invalid commit"); commits.erase(it); + if (commits.empty()) { + // Do not call any code involving 'this' after this call, the object will be destroyed + allocator->ReleaseMemory(this); + } } [[nodiscard]] std::span<u8> Map() { @@ -171,6 +175,7 @@ private: return candidate; } + MemoryAllocator* const allocator; ///< Parent memory allocation. const vk::DeviceMemory memory; ///< Vulkan memory allocation handler. const u64 allocation_size; ///< Size of this allocation. const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags. @@ -275,10 +280,17 @@ bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, return false; } } - allocations.push_back(std::make_unique<MemoryAllocation>(std::move(memory), flags, size, type)); + allocations.push_back( + std::make_unique<MemoryAllocation>(this, std::move(memory), flags, size, type)); return true; } +void MemoryAllocator::ReleaseMemory(MemoryAllocation* alloc) { + const auto it = std::ranges::find(allocations, alloc, &std::unique_ptr<MemoryAllocation>::get); + ASSERT(it != allocations.end()); + allocations.erase(it); +} + std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements, VkMemoryPropertyFlags flags) { for (auto& allocation : allocations) { diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index db12d02f4..b61e931e0 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -69,6 +69,8 @@ private: /// Memory allocator container. /// Allocates and releases memory allocations on demand. class MemoryAllocator { + friend MemoryAllocation; + public: /** * Construct memory allocator @@ -104,6 +106,9 @@ private: /// Tries to allocate a chunk of memory. bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size); + /// Releases a chunk of memory. + void ReleaseMemory(MemoryAllocation* alloc); + /// Tries to allocate a memory commit. std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements, VkMemoryPropertyFlags flags); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 86495803e..7524e3c40 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -51,11 +51,11 @@ void EmuThread::run() { Common::SetCurrentThreadName(name.c_str()); auto& system = Core::System::GetInstance(); + auto& gpu = system.GPU(); + auto stop_token = stop_source.get_token(); system.RegisterHostThread(); - auto& gpu = system.GPU(); - // Main process has been loaded. Make the context current to this thread and begin GPU and CPU // execution. gpu.Start(); @@ -65,7 +65,7 @@ void EmuThread::run() { emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); system.Renderer().ReadRasterizer()->LoadDiskResources( - system.CurrentProcess()->GetTitleID(), stop_run, + system.CurrentProcess()->GetTitleID(), stop_token, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); }); @@ -78,7 +78,7 @@ void EmuThread::run() { // so that the DebugModeLeft signal can be emitted before the // next execution step bool was_active = false; - while (!stop_run) { + while (!stop_token.stop_requested()) { if (running) { if (was_active) { emit DebugModeLeft(); @@ -100,7 +100,7 @@ void EmuThread::run() { } running_guard = false; - if (!stop_run) { + if (!stop_token.stop_requested()) { was_active = true; emit DebugModeEntered(); } @@ -108,7 +108,7 @@ void EmuThread::run() { UNIMPLEMENTED(); } else { std::unique_lock lock{running_mutex}; - running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); + running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; }); } } diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index acfe2bc8c..402dd2ee1 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -89,16 +89,16 @@ public: * Requests for the emulation thread to stop running */ void RequestStop() { - stop_run = true; + stop_source.request_stop(); SetRunning(false); } private: bool exec_step = false; bool running = false; - std::atomic_bool stop_run{false}; + std::stop_source stop_source; std::mutex running_mutex; - std::condition_variable running_cv; + std::condition_variable_any running_cv; Common::Event running_wait{}; std::atomic_bool running_guard{false}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 916a22724..62bafc453 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ +const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, @@ -236,6 +236,7 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, + {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, @@ -756,6 +757,8 @@ void Config::ReadCpuValues() { QStringLiteral("cpuopt_unsafe_unfuse_fma"), true); ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error, QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); + ReadSettingGlobal(Settings::values.cpuopt_unsafe_ignore_standard_fpcr, + QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), true); ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check, @@ -811,12 +814,15 @@ void Config::ReadRendererValues() { true); ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true); ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); + ReadSettingGlobal(Settings::values.disable_fps_limit, QStringLiteral("disable_fps_limit"), + false); ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), false); ReadSettingGlobal(Settings::values.use_asynchronous_shaders, QStringLiteral("use_asynchronous_shaders"), false); ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), true); + ReadSettingGlobal(Settings::values.use_caches_gc, QStringLiteral("use_caches_gc"), false); ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0); ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0); ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); @@ -1339,6 +1345,8 @@ void Config::SaveCpuValues() { Settings::values.cpuopt_unsafe_unfuse_fma, true); WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), Settings::values.cpuopt_unsafe_reduce_fp_error, true); + WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_ignore_standard_fpcr"), + Settings::values.cpuopt_unsafe_ignore_standard_fpcr, true); WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), Settings::values.cpuopt_unsafe_inaccurate_nan, true); WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"), @@ -1395,12 +1403,15 @@ void Config::SaveRendererValues() { true); WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true); WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); + WriteSettingGlobal(QStringLiteral("disable_fps_limit"), Settings::values.disable_fps_limit, + false); WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, false); WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"), Settings::values.use_asynchronous_shaders, false); WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, true); + WriteSettingGlobal(QStringLiteral("use_caches_gc"), Settings::values.use_caches_gc, false); // Cast to double because Qt's written float values are not human-readable WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0); WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ce3355588..3c1de0ac9 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,7 +42,7 @@ public: default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; - static const std::array<UISettings::Shortcut, 17> default_hotkeys; + static const std::array<UISettings::Shortcut, 18> default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 22219cbad..13db2ba98 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -34,12 +34,15 @@ void ConfigureCpu::SetConfiguration() { ui->accuracy->setEnabled(runtime_lock); ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); + ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock); ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock); ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); ui->cpuopt_unsafe_reduce_fp_error->setChecked( Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); + ui->cpuopt_unsafe_ignore_standard_fpcr->setChecked( + Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()); ui->cpuopt_unsafe_inaccurate_nan->setChecked( Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); ui->cpuopt_unsafe_fastmem_check->setChecked( @@ -84,6 +87,9 @@ void ConfigureCpu::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error, ui->cpuopt_unsafe_reduce_fp_error, cpuopt_unsafe_reduce_fp_error); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_standard_fpcr, + ui->cpuopt_unsafe_ignore_standard_fpcr, + cpuopt_unsafe_ignore_standard_fpcr); ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, ui->cpuopt_unsafe_inaccurate_nan, cpuopt_unsafe_inaccurate_nan); @@ -137,6 +143,9 @@ void ConfigureCpu::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error, Settings::values.cpuopt_unsafe_reduce_fp_error, cpuopt_unsafe_reduce_fp_error); + ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_standard_fpcr, + Settings::values.cpuopt_unsafe_ignore_standard_fpcr, + cpuopt_unsafe_ignore_standard_fpcr); ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, Settings::values.cpuopt_unsafe_inaccurate_nan, cpuopt_unsafe_inaccurate_nan); diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index 57ff2772a..b2b5f1671 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h @@ -40,6 +40,7 @@ private: ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; + ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr; ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check; }; diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index 31ef9e3f5..0e296d4e5 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -112,6 +112,18 @@ </widget> </item> <item> + <widget class="QCheckBox" name="cpuopt_unsafe_ignore_standard_fpcr"> + <property name="toolTip"> + <string> + <div>This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.</div> + </string> + </property> + <property name="text"> + <string>Faster ASIMD instructions (32 bits only)</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> <property name="toolTip"> <string> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 35bf9c6be..8d13c9857 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,8 +28,10 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->disable_fps_limit->setChecked(Settings::values.disable_fps_limit.GetValue()); ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); + ui->use_caches_gc->setChecked(Settings::values.use_caches_gc.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); if (Settings::IsConfiguringGlobal()) { @@ -57,11 +59,15 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.disable_fps_limit, + ui->disable_fps_limit, disable_fps_limit); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, ui->use_assembly_shaders, use_assembly_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, ui->use_asynchronous_shaders, use_asynchronous_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_caches_gc, ui->use_caches_gc, + use_caches_gc); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); @@ -97,10 +103,12 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); + ui->disable_fps_limit->setEnabled(Settings::values.disable_fps_limit.UsingGlobal()); ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->use_caches_gc->setEnabled(Settings::values.use_caches_gc.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); @@ -108,6 +116,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { } ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); + ConfigurationShared::SetColoredTristate(ui->disable_fps_limit, + Settings::values.disable_fps_limit, disable_fps_limit); ConfigurationShared::SetColoredTristate( ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, @@ -115,6 +125,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::SetColoredTristate(ui->use_caches_gc, Settings::values.use_caches_gc, + use_caches_gc); ConfigurationShared::SetColoredComboBox( ui->gpu_accuracy, ui->label_gpu_accuracy, static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index e61b571c7..6ac5f20ec 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -35,7 +35,9 @@ private: std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; ConfigurationShared::CheckState use_vsync; + ConfigurationShared::CheckState disable_fps_limit; ConfigurationShared::CheckState use_assembly_shaders; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; + ConfigurationShared::CheckState use_caches_gc; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 846a30586..18c43629e 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -77,6 +77,24 @@ </widget> </item> <item> + <widget class="QCheckBox" name="disable_fps_limit"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string> + <html><head/><body> + <p>Presents guest frames as they become available, disabling the FPS limit in most titles.</p> + <p>NOTE: Will cause instabilities.</p> + </body></html> + </string> + </property> + <property name="text"> + <string>Disable framerate limit (experimental)</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="use_assembly_shaders"> <property name="toolTip"> <string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string> @@ -104,6 +122,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_caches_gc"> + <property name="toolTip"> + <string>Enables garbage collection for the GPU caches, this will try to keep VRAM within 3-4 GB by flushing the least used textures/buffers. May cause issues in a few games.</string> + </property> + <property name="text"> + <string>Enable GPU cache garbage collection (experimental)</string> + </property> + </widget> + </item> + <item> <widget class="QWidget" name="af_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_1"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index a1d434aca..8c00eec59 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -47,6 +47,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id, const std::str ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("Properties")); + // remove Help question mark button from the title bar + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->addonsTab->SetTitleId(title_id); diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index adf6d0b39..7da14146b 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/src/yuzu/configuration/configure_per_game.ui @@ -6,10 +6,15 @@ <rect> <x>0</x> <y>0</y> - <width>800</width> + <width>900</width> <height>600</height> </rect> </property> + <property name="minimumSize"> + <size> + <width>900</width> + </size> + </property> <property name="windowTitle"> <string>Dialog</string> </property> diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 9b709d405..ebb0f411c 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { std::sort(disabled_addons.begin(), disabled_addons.end()); std::sort(current.begin(), current.end()); if (disabled_addons != current) { - void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / - "game_list" / fmt::format("{:016X}.pv.txt", title_id))); + Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list" / fmt::format("{:016X}.pv.txt", title_id)); } Settings::values.disabled_addons[title_id] = disabled_addons; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index be8933c5c..75ab5ef44 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -194,10 +194,10 @@ static void RemoveCachedContents() { const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; - void(Common::FS::RemoveDirRecursively(offline_fonts)); - void(Common::FS::RemoveDirRecursively(offline_manual)); - void(Common::FS::RemoveDirRecursively(offline_legal_information)); - void(Common::FS::RemoveDirRecursively(offline_system_data)); + Common::FS::RemoveDirRecursively(offline_fonts); + Common::FS::RemoveDirRecursively(offline_manual); + Common::FS::RemoveDirRecursively(offline_legal_information); + Common::FS::RemoveDirRecursively(offline_system_data); } GMainWindow::GMainWindow() @@ -1025,7 +1025,11 @@ void GMainWindow::InitializeHotkeys() { connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this), &QShortcut::activated, this, [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); - + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Framerate Limit"), this), + &QShortcut::activated, this, [] { + Settings::values.disable_fps_limit.SetValue( + !Settings::values.disable_fps_limit.GetValue()); + }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this), &QShortcut::activated, this, [&] { Settings::values.mouse_panning = !Settings::values.mouse_panning; @@ -1739,8 +1743,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT RemoveAddOnContent(program_id, entry_type); break; } - void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / - "game_list")); + Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list"); game_list->PopulateAsync(UISettings::values.game_dirs); } @@ -2209,8 +2213,8 @@ void GMainWindow::OnMenuInstallToNAND() { : tr("%n file(s) failed to install\n", "", failed_files.size())); QMessageBox::information(this, tr("Install Results"), install_results); - void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / - "game_list")); + Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list"); game_list->PopulateAsync(UISettings::values.game_dirs); ui.action_Install_File_NAND->setEnabled(true); } @@ -2842,7 +2846,7 @@ void GMainWindow::MigrateConfigFiles() { LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); if (!Common::FS::RenameFile(origin, destination)) { // Delete the old config file if one already exists in the new location. - void(Common::FS::RemoveFile(origin)); + Common::FS::RemoveFile(origin); } } } @@ -3036,9 +3040,9 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); - void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated")); - void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated")); - void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated")); + Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated"); + Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated"); + Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated"); } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 621b31571..60bf66ec0 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -443,6 +443,8 @@ void Config::ReadValues() { sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true)); Settings::values.use_vsync.SetValue( static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); + Settings::values.disable_fps_limit.SetValue( + sdl2_config->GetBoolean("Renderer", "disable_fps_limit", false)); Settings::values.use_assembly_shaders.SetValue( sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true)); Settings::values.use_asynchronous_shaders.SetValue( diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 37d895ebd..cc9850aad 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -227,6 +227,10 @@ use_asynchronous_gpu_emulation = # 0: Off, 1 (default): On use_vsync = +# Whether to use garbage collection or not for GPU caches. +# 0 (default): Off, 1: On +use_caches_gc = + # The clear color for the renderer. What shows up on the sides of the bottom screen. # Must be in range of 0.0-1.0. Defaults to 1.0 for all. bg_red = @@ -362,6 +366,9 @@ use_debug_asserts = use_auto_stub = # Enables/Disables the macro JIT compiler disable_macro_jit=false +# Presents guest frames as they become available. Experimental. +# false: Disabled (default), true: Enabled +disable_fps_limit=false [WebService] # Whether or not to enable telemetry diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 584967f5c..50e388312 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -219,7 +219,7 @@ int main(int argc, char** argv) { system.GPU().Start(); system.Renderer().ReadRasterizer()->LoadDiskResources( - system.CurrentProcess()->GetTitleID(), false, + system.CurrentProcess()->GetTitleID(), std::stop_token{}, [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); void(system.Run()); |