From 4f1fb2eafb7b98e68229ce0de8a0fb6460503c15 Mon Sep 17 00:00:00 2001 From: pineappleEA Date: Mon, 28 Aug 2023 03:24:22 +0200 Subject: [PATCH] early-access version 3841 --- README.md | 2 +- externals/ffmpeg/CMakeLists.txt | 4 +- src/common/settings.h | 1 + src/core/core.cpp | 7 + src/video_core/host1x/codecs/codec.cpp | 3 +- .../renderer_vulkan/vk_rasterizer.cpp | 28 ++- .../vulkan_common/vulkan_device.cpp | 7 + src/video_core/vulkan_common/vulkan_device.h | 14 ++ .../vulkan_common/vulkan_wrapper.cpp | 1 + src/video_core/vulkan_common/vulkan_wrapper.h | 13 ++ src/yuzu/CMakeLists.txt | 4 +- src/yuzu/game_list.cpp | 8 +- src/yuzu/game_list.h | 5 +- src/yuzu/game_list_p.h | 2 +- src/yuzu/game_list_worker.cpp | 25 ++- src/yuzu/game_list_worker.h | 6 +- src/yuzu/main.cpp | 11 +- src/yuzu/play_time_manager.cpp | 179 ++++++++++++++++++ src/yuzu/play_time_manager.h | 44 +++++ 19 files changed, 332 insertions(+), 32 deletions(-) create mode 100755 src/yuzu/play_time_manager.cpp create mode 100755 src/yuzu/play_time_manager.h diff --git a/README.md b/README.md index 6ee3c9cbf..b708bb2b1 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 3840. +This is the source code for early-access 3841. ## Legal Notice diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index cf82222c3..46da3b7ff 100755 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -164,7 +164,7 @@ if (NOT WIN32 AND NOT ANDROID) --enable-decoder=h264 --enable-decoder=vp8 --enable-decoder=vp9 - --enable-filter=yadif + --enable-filter=yadif,scale --cc="${FFmpeg_CC}" --cxx="${FFmpeg_CXX}" ${FFmpeg_HWACCEL_FLAGS} @@ -254,7 +254,7 @@ elseif(ANDROID) set(FFmpeg_INCLUDE_DIR "${FFmpeg_INCLUDE_DIR}" PARENT_SCOPE) elseif(WIN32) # Use yuzu FFmpeg binaries - set(FFmpeg_EXT_NAME "ffmpeg-5.1.3") + set(FFmpeg_EXT_NAME "ffmpeg-6.0") set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}") download_bundled_external("ffmpeg/" ${FFmpeg_EXT_NAME} "") set(FFmpeg_FOUND YES) diff --git a/src/common/settings.h b/src/common/settings.h index d4c1598c1..d67c4b0fa 100755 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -349,6 +349,7 @@ struct Values { Category::RendererDebug}; Setting disable_shader_loop_safety_checks{ linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; + bool graphics_AMD_workaround{}; // TODO: remove me once amd supports VK_EXT_depth_bias_control // System SwitchableSetting language_index{linkage, diff --git a/src/core/core.cpp b/src/core/core.cpp index 8eb119f65..48a00f5b3 100755 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -376,6 +376,10 @@ struct System::Impl { room_member->SendGameInfo(game_info); } + // Workarounds: + // Activate this in Super Smash Brothers, it only affects AMD cards using AMDVLK + Settings::values.graphics_AMD_workaround = program_id == 0x1006A800016E000ULL; + status = SystemResultStatus::Success; return status; } @@ -435,6 +439,9 @@ struct System::Impl { room_member->SendGameInfo(game_info); } + // Workarounds + Settings::values.graphics_AMD_workaround = false; + LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp index 591cbc987..350deff94 100755 --- a/src/video_core/host1x/codecs/codec.cpp +++ b/src/video_core/host1x/codecs/codec.cpp @@ -319,6 +319,7 @@ void Codec::Decode() { LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); return; } + bool is_interlaced = initial_frame->interlaced_frame != 0; if (av_codec_ctx->hw_device_ctx) { final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); @@ -334,7 +335,7 @@ void Codec::Decode() { UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); return; } - if (!final_frame->interlaced_frame) { + if (!is_interlaced) { av_frames.push(std::move(final_frame)); } else { if (!filters_initialized) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b25511237..1df994d37 100755 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1014,15 +1014,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) { regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM || regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM || regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM; - if (is_d24 && !device.SupportsD24DepthBuffer()) { + bool force_unorm = ([&] { + if (!is_d24 || device.SupportsD24DepthBuffer()) { + return false; + } + if (device.IsExtDepthBiasControlSupported()) { + return true; + } + if (!Settings::values.graphics_AMD_workaround) { + return false; + } // the base formulas can be obtained from here: // https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias const double rescale_factor = static_cast(1ULL << (32 - 24)) / (static_cast(0x1.ep+127)); units = static_cast(static_cast(units) * rescale_factor); - } + return false; + })(); scheduler.Record([constant = units, clamp = regs.depth_bias_clamp, - factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) { + factor = regs.slope_scale_depth_bias, force_unorm, + precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) { + if (force_unorm) { + VkDepthBiasRepresentationInfoEXT info{ + .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT, + .pNext = nullptr, + .depthBiasRepresentation = + VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT, + .depthBiasExact = precise ? VK_TRUE : VK_FALSE, + }; + cmdbuf.SetDepthBias(constant, clamp, factor, &info); + return; + } cmdbuf.SetDepthBias(constant, clamp, factor); }); } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 431605abe..c20c0cb02 100755 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1051,6 +1051,13 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + // VK_EXT_depth_bias_control + extensions.depth_bias_control = + features.depth_bias_control.depthBiasControl && + features.depth_bias_control.leastRepresentableValueForceUnormRepresentation; + RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control, + VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME); + // VK_EXT_depth_clip_control extensions.depth_clip_control = features.depth_clip_control.depthClipControl; RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 81c56aac3..e1aaa75b1 100755 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -41,6 +41,7 @@ VK_DEFINE_HANDLE(VmaAllocator) // Define all features which may be used by the implementation and require an extension here. #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ + FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ @@ -95,6 +96,7 @@ VK_DEFINE_HANDLE(VmaAllocator) #define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \ + EXTENSION_NAME(VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \ @@ -145,6 +147,9 @@ VK_DEFINE_HANDLE(VmaAllocator) // Define features where the absence of the feature may result in a degraded experience. #define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \ FEATURE_NAME(custom_border_color, customBorderColors) \ + FEATURE_NAME(depth_bias_control, depthBiasControl) \ + FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \ + FEATURE_NAME(depth_bias_control, depthBiasExact) \ FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ FEATURE_NAME(index_type_uint8, indexTypeUint8) \ FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \ @@ -451,6 +456,11 @@ public: return extensions.depth_clip_control; } + /// Returns true if the device supports VK_EXT_depth_bias_control. + bool IsExtDepthBiasControlSupported() const { + return extensions.depth_bias_control; + } + /// Returns true if the device supports VK_EXT_shader_viewport_index_layer. bool IsExtShaderViewportIndexLayerSupported() const { return extensions.shader_viewport_index_layer; @@ -606,6 +616,10 @@ public: return features.robustness2.nullDescriptor; } + bool HasExactDepthBiasControl() const { + return features.depth_bias_control.depthBiasExact; + } + u32 GetMaxVertexInputAttributes() const { return properties.properties.limits.maxVertexInputAttributes; } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 83b96ab7f..15ad6629c 100755 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -113,6 +113,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdPushDescriptorSetWithTemplateKHR); X(vkCmdSetBlendConstants); X(vkCmdSetDepthBias); + X(vkCmdSetDepthBias2EXT); X(vkCmdSetDepthBounds); X(vkCmdSetEvent); X(vkCmdSetScissor); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 606ffb359..cb2faa446 100755 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -226,6 +226,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; PFN_vkCmdSetDepthBias vkCmdSetDepthBias{}; + PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{}; PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{}; PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{}; PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{}; @@ -1333,6 +1334,18 @@ public: dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor); } + void SetDepthBias(float constant_factor, float clamp, float slope_factor, + VkDepthBiasRepresentationInfoEXT* extra) const noexcept { + VkDepthBiasInfoEXT info{ + .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT, + .pNext = extra, + .depthBiasConstantFactor = constant_factor, + .depthBiasClamp = clamp, + .depthBiasSlopeFactor = slope_factor, + }; + dld->vkCmdSetDepthBias2EXT(handle, &info); + } + void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept { dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds); } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 1e394fd83..a07d2a2c7 100755 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -195,8 +195,8 @@ add_executable(yuzu multiplayer/state.cpp multiplayer/state.h multiplayer/validation.h - play_time.cpp - play_time.h + play_time_manager.cpp + play_time_manager.h precompiled_headers.h qt_common.cpp qt_common.h diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 22b0ff2a4..45b626a6f 100755 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() { } GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, - Core::System& system_, GMainWindow* parent) - : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { + PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, + GMainWindow* parent) + : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, + play_time_manager{play_time_manager_}, system{system_} { watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -826,7 +828,7 @@ void GameList::PopulateAsync(QVector& game_dirs) { emit ShouldCancelWorker(); GameListWorker* worker = - new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); + new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 9356eeade..5c70a997a 100755 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -18,6 +18,7 @@ #include "core/core.h" #include "uisettings.h" #include "yuzu/compatibility_list.h" +#include "yuzu/play_time_manager.h" namespace Core { class System; @@ -79,7 +80,8 @@ public: }; explicit GameList(std::shared_ptr vfs_, - FileSys::ManualContentProvider* provider_, Core::System& system_, + FileSys::ManualContentProvider* provider_, + PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, GMainWindow* parent = nullptr); ~GameList() override; @@ -168,6 +170,7 @@ private: friend class GameListSearchField; + const PlayTime::PlayTimeManager& play_time_manager; Core::System& system; }; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index eea5aa96d..ec179980c 100755 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -18,7 +18,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "yuzu/play_time.h" +#include "yuzu/play_time_manager.h" #include "yuzu/uisettings.h" #include "yuzu/util/util.h" diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 892b3a3f5..51f4c0425 100755 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -27,7 +27,6 @@ #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" -#include "yuzu/play_time.h" #include "yuzu/uisettings.h" namespace { @@ -195,6 +194,7 @@ QList MakeGameListEntry(const std::string& path, const std::stri const std::size_t size, const std::vector& icon, Loader::AppLoader& loader, u64 program_id, const CompatibilityList& compatibility_list, + const PlayTime::PlayTimeManager& play_time_manager, const FileSys::PatchManager& patch) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -213,7 +213,7 @@ QList MakeGameListEntry(const std::string& path, const std::stri new GameListItemCompat(compatibility), new GameListItem(file_type_string), new GameListItemSize(size), - new GameListItemPlayTime(PlayTime::GetPlayTime(program_id)), + new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), }; const auto patch_versions = GetGameListCachedObject( @@ -229,9 +229,12 @@ QList MakeGameListEntry(const std::string& path, const std::stri GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, - const CompatibilityList& compatibility_list_, Core::System& system_) + const CompatibilityList& compatibility_list_, + const PlayTime::PlayTimeManager& play_time_manager_, + Core::System& system_) : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, system{system_} {} + compatibility_list{compatibility_list_}, + play_time_manager{play_time_manager_}, system{system_} {} GameListWorker::~GameListWorker() = default; @@ -282,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, patch), + program_id, compatibility_list, play_time_manager, patch), parent_dir); } } @@ -359,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa emit EntryReady(MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), icon, - *loader, id, compatibility_list, patch), + *loader, id, compatibility_list, + play_time_manager, patch), parent_dir); } } else { @@ -372,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; - emit EntryReady( - MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), - icon, *loader, program_id, compatibility_list, patch), - parent_dir); + emit EntryReady(MakeGameListEntry(physical_name, name, + Common::FS::GetSize(physical_name), icon, + *loader, program_id, compatibility_list, + play_time_manager, patch), + parent_dir); } } } else if (is_dir) { diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index a9d197731..cf6ef3643 100755 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -13,6 +13,7 @@ #include #include "yuzu/compatibility_list.h" +#include "yuzu/play_time_manager.h" namespace Core { class System; @@ -36,7 +37,9 @@ public: explicit GameListWorker(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, - const CompatibilityList& compatibility_list_, Core::System& system_); + const CompatibilityList& compatibility_list_, + const PlayTime::PlayTimeManager& play_time_manager_, + Core::System& system_); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -76,6 +79,7 @@ private: FileSys::ManualContentProvider* provider; QVector& game_dirs; const CompatibilityList& compatibility_list; + const PlayTime::PlayTimeManager& play_time_manager; QStringList watch_list; std::atomic_bool stop_processing; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e9637af3c..5db183038 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -146,7 +146,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" -#include "yuzu/play_time.h" +#include "yuzu/play_time_manager.h" #include "yuzu/startup_checks.h" #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" @@ -980,7 +980,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); render_window->hide(); - game_list = new GameList(vfs, provider.get(), *system, this); + game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); ui->horizontalLayout->addWidget(game_list); game_list_placeholder = new GameListPlaceholder(this); @@ -2477,11 +2477,8 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { QMessageBox::No) != QMessageBox::Yes) { return; } - if (!play_time_manager->ResetProgramPlayTime(program_id)) { - QMessageBox::warning(this, tr("Error Resetting Play Time Data"), - tr("Play time couldn't be cleared")); - return; - } + + play_time_manager->ResetProgramPlayTime(program_id); game_list->PopulateAsync(UISettings::values.game_dirs); } diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp new file mode 100755 index 000000000..155c36b7d --- /dev/null +++ b/src/yuzu/play_time_manager.cpp @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/thread.h" +#include "core/hle/service/acc/profile_manager.h" +#include "yuzu/play_time_manager.h" + +namespace PlayTime { + +namespace { + +struct PlayTimeElement { + ProgramId program_id; + PlayTime play_time; +}; + +std::optional GetCurrentUserPlayTimePath() { + const Service::Account::ProfileManager manager; + const auto uuid = manager.GetUser(static_cast(Settings::values.current_user)); + if (!uuid.has_value()) { + return std::nullopt; + } + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / + uuid->RawString().append(".bin"); +} + +[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) { + const auto filename = GetCurrentUserPlayTimePath(); + + if (!filename.has_value()) { + LOG_ERROR(Frontend, "Failed to get current user path"); + return false; + } + + out_play_time_db.clear(); + + if (Common::FS::Exists(filename.value())) { + Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + LOG_ERROR(Frontend, "Failed to open play time file: {}", + Common::FS::PathToUTF8String(filename.value())); + return false; + } + + const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement); + std::vector elements(num_elements); + + if (file.ReadSpan(elements) != num_elements) { + return false; + } + + for (const auto& [program_id, play_time] : elements) { + if (program_id != 0) { + out_play_time_db[program_id] = play_time; + } + } + } + + return true; +} + +[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) { + const auto filename = GetCurrentUserPlayTimePath(); + + if (!filename.has_value()) { + LOG_ERROR(Frontend, "Failed to get current user path"); + return false; + } + + Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + LOG_ERROR(Frontend, "Failed to open play time file: {}", + Common::FS::PathToUTF8String(filename.value())); + return false; + } + + std::vector elements; + elements.reserve(play_time_db.size()); + + for (auto& [program_id, play_time] : play_time_db) { + if (program_id != 0) { + elements.push_back(PlayTimeElement{program_id, play_time}); + } + } + + return file.WriteSpan(elements) == elements.size(); +} + +} // namespace + +PlayTimeManager::PlayTimeManager() { + if (!ReadPlayTimeFile(database)) { + LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default."); + } +} + +PlayTimeManager::~PlayTimeManager() { + Save(); +} + +void PlayTimeManager::SetProgramId(u64 program_id) { + running_program_id = program_id; +} + +void PlayTimeManager::Start() { + play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); }); +} + +void PlayTimeManager::Stop() { + play_time_thread = {}; +} + +void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { + Common::SetCurrentThreadName("PlayTimeReport"); + + using namespace std::literals::chrono_literals; + using std::chrono::seconds; + using std::chrono::steady_clock; + + auto timestamp = steady_clock::now(); + + const auto GetDuration = [&]() -> u64 { + const auto last_timestamp = std::exchange(timestamp, steady_clock::now()); + const auto duration = std::chrono::duration_cast(timestamp - last_timestamp); + return static_cast(duration.count()); + }; + + while (!stop_token.stop_requested()) { + Common::StoppableTimedWait(stop_token, 30s); + + database[running_program_id] += GetDuration(); + Save(); + } +} + +void PlayTimeManager::Save() { + if (!WritePlayTimeFile(database)) { + LOG_ERROR(Frontend, "Failed to update play time database!"); + } +} + +u64 PlayTimeManager::GetPlayTime(u64 program_id) const { + auto it = database.find(program_id); + if (it != database.end()) { + return it->second; + } else { + return 0; + } +} + +void PlayTimeManager::ResetProgramPlayTime(u64 program_id) { + database.erase(program_id); + Save(); +} + +QString ReadablePlayTime(qulonglong time_seconds) { + if (time_seconds == 0) { + return {}; + } + const auto time_minutes = std::max(static_cast(time_seconds) / 60, 1.0); + const auto time_hours = static_cast(time_seconds) / 3600; + const bool is_minutes = time_minutes < 60; + const char* unit = is_minutes ? "m" : "h"; + const auto value = is_minutes ? time_minutes : time_hours; + + return QStringLiteral("%L1 %2") + .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0) + .arg(QString::fromUtf8(unit)); +} + +} // namespace PlayTime diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h new file mode 100755 index 000000000..5f96f3447 --- /dev/null +++ b/src/yuzu/play_time_manager.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/polyfill_thread.h" + +namespace PlayTime { + +using ProgramId = u64; +using PlayTime = u64; +using PlayTimeDatabase = std::map; + +class PlayTimeManager { +public: + explicit PlayTimeManager(); + ~PlayTimeManager(); + + YUZU_NON_COPYABLE(PlayTimeManager); + YUZU_NON_MOVEABLE(PlayTimeManager); + + u64 GetPlayTime(u64 program_id) const; + void ResetProgramPlayTime(u64 program_id); + void SetProgramId(u64 program_id); + void Start(); + void Stop(); + +private: + PlayTimeDatabase database; + u64 running_program_id; + std::jthread play_time_thread; + void AutoTimestamp(std::stop_token stop_token); + void Save(); +}; + +QString ReadablePlayTime(qulonglong time_seconds); + +} // namespace PlayTime