diff options
50 files changed, 574 insertions, 101 deletions
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 9cf71680c..598f4e8bf 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -218,7 +218,6 @@ public: | |||
| 218 | return; | 218 | return; |
| 219 | } | 219 | } |
| 220 | m_window->OnSurfaceChanged(m_native_window); | 220 | m_window->OnSurfaceChanged(m_native_window); |
| 221 | m_system.Renderer().NotifySurfaceChanged(); | ||
| 222 | } | 221 | } |
| 223 | 222 | ||
| 224 | void ConfigureFilesystemProvider(const std::string& filepath) { | 223 | void ConfigureFilesystemProvider(const std::string& filepath) { |
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index c54ce7654..441c8af97 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #define LOAD_DIR "load" | 18 | #define LOAD_DIR "load" |
| 19 | #define LOG_DIR "log" | 19 | #define LOG_DIR "log" |
| 20 | #define NAND_DIR "nand" | 20 | #define NAND_DIR "nand" |
| 21 | #define PLAY_TIME_DIR "play_time" | ||
| 21 | #define SCREENSHOTS_DIR "screenshots" | 22 | #define SCREENSHOTS_DIR "screenshots" |
| 22 | #define SDMC_DIR "sdmc" | 23 | #define SDMC_DIR "sdmc" |
| 23 | #define SHADER_DIR "shader" | 24 | #define SHADER_DIR "shader" |
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 461c170f7..0abd81a45 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -124,6 +124,7 @@ public: | |||
| 124 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); | 124 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); |
| 125 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); | 125 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); |
| 126 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); | 126 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); |
| 127 | GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR); | ||
| 127 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); | 128 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); |
| 128 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | 129 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); |
| 129 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | 130 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); |
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 61593bdf7..63801c924 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h | |||
| @@ -20,6 +20,7 @@ enum class YuzuPath { | |||
| 20 | LoadDir, // Where cheat/mod files are stored. | 20 | LoadDir, // Where cheat/mod files are stored. |
| 21 | LogDir, // Where log files are stored. | 21 | LogDir, // Where log files are stored. |
| 22 | NANDDir, // Where the emulated NAND is stored. | 22 | NANDDir, // Where the emulated NAND is stored. |
| 23 | PlayTimeDir, // Where play time data is stored. | ||
| 23 | ScreenshotsDir, // Where yuzu screenshots are stored. | 24 | ScreenshotsDir, // Where yuzu screenshots are stored. |
| 24 | SDMCDir, // Where the emulated SDMC is stored. | 25 | SDMCDir, // Where the emulated SDMC is stored. |
| 25 | ShaderDir, // Where shaders are stored. | 26 | ShaderDir, // Where shaders are stored. |
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index feab1653d..4c7aba3f5 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp | |||
| @@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) { | |||
| 135 | return convert.from_bytes(input.data(), input.data() + input.size()); | 135 | return convert.from_bytes(input.data(), input.data() + input.size()); |
| 136 | } | 136 | } |
| 137 | 137 | ||
| 138 | std::u32string UTF8ToUTF32(std::string_view input) { | ||
| 139 | std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert; | ||
| 140 | return convert.from_bytes(input.data(), input.data() + input.size()); | ||
| 141 | } | ||
| 142 | |||
| 138 | #ifdef _WIN32 | 143 | #ifdef _WIN32 |
| 139 | static std::wstring CPToUTF16(u32 code_page, std::string_view input) { | 144 | static std::wstring CPToUTF16(u32 code_page, std::string_view input) { |
| 140 | const auto size = | 145 | const auto size = |
diff --git a/src/common/string_util.h b/src/common/string_util.h index c351f1a0c..9da1ca4e9 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h | |||
| @@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ | |||
| 38 | 38 | ||
| 39 | [[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); | 39 | [[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); |
| 40 | [[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); | 40 | [[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); |
| 41 | [[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input); | ||
| 41 | 42 | ||
| 42 | #ifdef _WIN32 | 43 | #ifdef _WIN32 |
| 43 | [[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); | 44 | [[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 08cbb8978..0ab2e3b76 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -1078,6 +1078,10 @@ void System::ApplySettings() { | |||
| 1078 | impl->RefreshTime(); | 1078 | impl->RefreshTime(); |
| 1079 | 1079 | ||
| 1080 | if (IsPoweredOn()) { | 1080 | if (IsPoweredOn()) { |
| 1081 | if (Settings::values.custom_rtc_enabled) { | ||
| 1082 | const s64 posix_time{Settings::values.custom_rtc.GetValue()}; | ||
| 1083 | GetTimeManager().UpdateLocalSystemClockTime(posix_time); | ||
| 1084 | } | ||
| 1081 | Renderer().RefreshBaseSettings(); | 1085 | Renderer().RefreshBaseSettings(); |
| 1082 | } | 1086 | } |
| 1083 | } | 1087 | } |
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index e55831f27..82964f0a1 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -2,6 +2,8 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <atomic> | 4 | #include <atomic> |
| 5 | #include <codecvt> | ||
| 6 | #include <locale> | ||
| 5 | #include <numeric> | 7 | #include <numeric> |
| 6 | #include <optional> | 8 | #include <optional> |
| 7 | #include <thread> | 9 | #include <thread> |
| @@ -12,6 +14,7 @@ | |||
| 12 | #include "common/logging/log.h" | 14 | #include "common/logging/log.h" |
| 13 | #include "common/scope_exit.h" | 15 | #include "common/scope_exit.h" |
| 14 | #include "common/settings.h" | 16 | #include "common/settings.h" |
| 17 | #include "common/string_util.h" | ||
| 15 | #include "core/arm/arm_interface.h" | 18 | #include "core/arm/arm_interface.h" |
| 16 | #include "core/core.h" | 19 | #include "core/core.h" |
| 17 | #include "core/debugger/gdbstub.h" | 20 | #include "core/debugger/gdbstub.h" |
| @@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) { | |||
| 68 | } | 71 | } |
| 69 | 72 | ||
| 70 | static std::string EscapeXML(std::string_view data) { | 73 | static std::string EscapeXML(std::string_view data) { |
| 74 | std::u32string converted = U"[Encoding error]"; | ||
| 75 | try { | ||
| 76 | converted = Common::UTF8ToUTF32(data); | ||
| 77 | } catch (std::range_error&) { | ||
| 78 | } | ||
| 79 | |||
| 71 | std::string escaped; | 80 | std::string escaped; |
| 72 | escaped.reserve(data.size()); | 81 | escaped.reserve(data.size()); |
| 73 | 82 | ||
| 74 | for (char c : data) { | 83 | for (char32_t c : converted) { |
| 75 | switch (c) { | 84 | switch (c) { |
| 76 | case '&': | 85 | case '&': |
| 77 | escaped += "&"; | 86 | escaped += "&"; |
| @@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) { | |||
| 86 | escaped += ">"; | 95 | escaped += ">"; |
| 87 | break; | 96 | break; |
| 88 | default: | 97 | default: |
| 89 | escaped += c; | 98 | if (c > 0x7f) { |
| 99 | escaped += fmt::format("&#{};", static_cast<u32>(c)); | ||
| 100 | } else { | ||
| 101 | escaped += static_cast<char>(c); | ||
| 102 | } | ||
| 90 | break; | 103 | break; |
| 91 | } | 104 | } |
| 92 | } | 105 | } |
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index f00479bd3..8e291ff67 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include <vector> | 5 | #include <vector> |
| 6 | 6 | ||
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | #include "common/scope_exit.h" | ||
| 8 | #include "core/file_sys/program_metadata.h" | 9 | #include "core/file_sys/program_metadata.h" |
| 9 | #include "core/file_sys/vfs.h" | 10 | #include "core/file_sys/vfs.h" |
| 10 | #include "core/loader/loader.h" | 11 | #include "core/loader/loader.h" |
| @@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { | |||
| 95 | return Loader::ResultStatus::Success; | 96 | return Loader::ResultStatus::Success; |
| 96 | } | 97 | } |
| 97 | 98 | ||
| 99 | Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) { | ||
| 100 | const u64 original_program_id = aci_header.title_id; | ||
| 101 | SCOPE_EXIT({ aci_header.title_id = original_program_id; }); | ||
| 102 | |||
| 103 | return this->Load(file); | ||
| 104 | } | ||
| 105 | |||
| 98 | /*static*/ ProgramMetadata ProgramMetadata::GetDefault() { | 106 | /*static*/ ProgramMetadata ProgramMetadata::GetDefault() { |
| 99 | // Allow use of cores 0~3 and thread priorities 1~63. | 107 | // Allow use of cores 0~3 and thread priorities 1~63. |
| 100 | constexpr u32 default_thread_info_capability = 0x30007F7; | 108 | constexpr u32 default_thread_info_capability = 0x30007F7; |
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 2e8960b07..9f8e74b13 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h | |||
| @@ -56,6 +56,7 @@ public: | |||
| 56 | static ProgramMetadata GetDefault(); | 56 | static ProgramMetadata GetDefault(); |
| 57 | 57 | ||
| 58 | Loader::ResultStatus Load(VirtualFile file); | 58 | Loader::ResultStatus Load(VirtualFile file); |
| 59 | Loader::ResultStatus Reload(VirtualFile file); | ||
| 59 | 60 | ||
| 60 | /// Load from parameters instead of NPDM file, used for KIP | 61 | /// Load from parameters instead of NPDM file, used for KIP |
| 61 | void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio, | 62 | void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio, |
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 5a42dea48..5c36b71e5 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp | |||
| @@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect | |||
| 118 | return {ResultStatus::ErrorMissingNPDM, {}}; | 118 | return {ResultStatus::ErrorMissingNPDM, {}}; |
| 119 | } | 119 | } |
| 120 | 120 | ||
| 121 | const ResultStatus result2 = metadata.Load(npdm); | 121 | const ResultStatus result2 = metadata.Reload(npdm); |
| 122 | if (result2 != ResultStatus::Success) { | 122 | if (result2 != ResultStatus::Success) { |
| 123 | return {result2, {}}; | 123 | return {result2, {}}; |
| 124 | } | 124 | } |
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 6b912027f..8bb429578 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt | |||
| @@ -19,6 +19,7 @@ set(SHADER_FILES | |||
| 19 | block_linear_unswizzle_2d.comp | 19 | block_linear_unswizzle_2d.comp |
| 20 | block_linear_unswizzle_3d.comp | 20 | block_linear_unswizzle_3d.comp |
| 21 | convert_abgr8_to_d24s8.frag | 21 | convert_abgr8_to_d24s8.frag |
| 22 | convert_d32f_to_abgr8.frag | ||
| 22 | convert_d24s8_to_abgr8.frag | 23 | convert_d24s8_to_abgr8.frag |
| 23 | convert_depth_to_float.frag | 24 | convert_depth_to_float.frag |
| 24 | convert_float_to_depth.frag | 25 | convert_float_to_depth.frag |
diff --git a/src/video_core/host_shaders/convert_d32f_to_abgr8.frag b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag new file mode 100644 index 000000000..04cfef8b5 --- /dev/null +++ b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #version 450 | ||
| 5 | |||
| 6 | layout(binding = 0) uniform sampler2D depth_tex; | ||
| 7 | |||
| 8 | layout(location = 0) out vec4 color; | ||
| 9 | |||
| 10 | void main() { | ||
| 11 | ivec2 coord = ivec2(gl_FragCoord.xy); | ||
| 12 | float depth = textureLod(depth_tex, coord, 0).r; | ||
| 13 | color = vec4(depth, depth, depth, 1.0); | ||
| 14 | } | ||
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 3e12a8813..78ea5208b 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h | |||
| @@ -89,9 +89,6 @@ public: | |||
| 89 | void RequestScreenshot(void* data, std::function<void(bool)> callback, | 89 | void RequestScreenshot(void* data, std::function<void(bool)> callback, |
| 90 | const Layout::FramebufferLayout& layout); | 90 | const Layout::FramebufferLayout& layout); |
| 91 | 91 | ||
| 92 | /// This is called to notify the rendering backend of a surface change | ||
| 93 | virtual void NotifySurfaceChanged() {} | ||
| 94 | |||
| 95 | protected: | 92 | protected: |
| 96 | Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. | 93 | Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. |
| 97 | std::unique_ptr<Core::Frontend::GraphicsContext> context; | 94 | std::unique_ptr<Core::Frontend::GraphicsContext> context; |
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index c7dc7e0a1..5ea9e2378 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h | |||
| @@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB | |||
| 116 | {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT | 116 | {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT |
| 117 | {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT | 117 | {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT |
| 118 | {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM | 118 | {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM |
| 119 | {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM | ||
| 119 | {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT | 120 | {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT |
| 120 | {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT | 121 | {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT |
| 121 | {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM | 122 | {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM |
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 1032c9d12..f01d2394e 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "video_core/host_shaders/blit_color_float_frag_spv.h" | 9 | #include "video_core/host_shaders/blit_color_float_frag_spv.h" |
| 10 | #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" | 10 | #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" |
| 11 | #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" | 11 | #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" |
| 12 | #include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h" | ||
| 12 | #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" | 13 | #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" |
| 13 | #include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" | 14 | #include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" |
| 14 | #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" | 15 | #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" |
| @@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, | |||
| 433 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), | 434 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), |
| 434 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), | 435 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), |
| 435 | convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), | 436 | convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), |
| 437 | convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), | ||
| 436 | convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), | 438 | convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), |
| 437 | convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), | 439 | convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), |
| 438 | linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), | 440 | linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), |
| @@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, | |||
| 557 | Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); | 559 | Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); |
| 558 | } | 560 | } |
| 559 | 561 | ||
| 562 | void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, | ||
| 563 | ImageView& src_image_view) { | ||
| 564 | ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(), | ||
| 565 | convert_d32f_to_abgr8_frag); | ||
| 566 | ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view); | ||
| 567 | } | ||
| 568 | |||
| 560 | void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, | 569 | void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, |
| 561 | ImageView& src_image_view) { | 570 | ImageView& src_image_view) { |
| 562 | ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), | 571 | ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), |
| @@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool | |||
| 609 | const VkPipelineLayout layout = *clear_color_pipeline_layout; | 618 | const VkPipelineLayout layout = *clear_color_pipeline_layout; |
| 610 | scheduler.RequestRenderpass(dst_framebuffer); | 619 | scheduler.RequestRenderpass(dst_framebuffer); |
| 611 | scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { | 620 | scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { |
| 621 | constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f}; | ||
| 622 | cmdbuf.SetBlendConstants(blend_constants.data()); | ||
| 612 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | 623 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); |
| 613 | BindBlitState(cmdbuf, dst_region); | 624 | BindBlitState(cmdbuf, dst_region); |
| 614 | cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); | 625 | cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); |
| @@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( | |||
| 865 | .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | 876 | .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, |
| 866 | .pNext = nullptr, | 877 | .pNext = nullptr, |
| 867 | .flags = 0, | 878 | .flags = 0, |
| 868 | .depthTestEnable = VK_FALSE, | 879 | .depthTestEnable = key.depth_clear, |
| 869 | .depthWriteEnable = key.depth_clear, | 880 | .depthWriteEnable = key.depth_clear, |
| 870 | .depthCompareOp = VK_COMPARE_OP_ALWAYS, | 881 | .depthCompareOp = VK_COMPARE_OP_ALWAYS, |
| 871 | .depthBoundsTestEnable = VK_FALSE, | 882 | .depthBoundsTestEnable = VK_FALSE, |
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index dcfe217aa..a032c71fb 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h | |||
| @@ -67,6 +67,8 @@ public: | |||
| 67 | 67 | ||
| 68 | void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); | 68 | void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); |
| 69 | 69 | ||
| 70 | void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); | ||
| 71 | |||
| 70 | void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); | 72 | void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); |
| 71 | 73 | ||
| 72 | void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); | 74 | void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); |
| @@ -128,6 +130,7 @@ private: | |||
| 128 | vk::ShaderModule convert_depth_to_float_frag; | 130 | vk::ShaderModule convert_depth_to_float_frag; |
| 129 | vk::ShaderModule convert_float_to_depth_frag; | 131 | vk::ShaderModule convert_float_to_depth_frag; |
| 130 | vk::ShaderModule convert_abgr8_to_d24s8_frag; | 132 | vk::ShaderModule convert_abgr8_to_d24s8_frag; |
| 133 | vk::ShaderModule convert_d32f_to_abgr8_frag; | ||
| 131 | vk::ShaderModule convert_d24s8_to_abgr8_frag; | 134 | vk::ShaderModule convert_d24s8_to_abgr8_frag; |
| 132 | vk::ShaderModule convert_s8d24_to_abgr8_frag; | 135 | vk::ShaderModule convert_s8d24_to_abgr8_frag; |
| 133 | vk::Sampler linear_sampler; | 136 | vk::Sampler linear_sampler; |
| @@ -146,6 +149,7 @@ private: | |||
| 146 | vk::Pipeline convert_d16_to_r16_pipeline; | 149 | vk::Pipeline convert_d16_to_r16_pipeline; |
| 147 | vk::Pipeline convert_r16_to_d16_pipeline; | 150 | vk::Pipeline convert_r16_to_d16_pipeline; |
| 148 | vk::Pipeline convert_abgr8_to_d24s8_pipeline; | 151 | vk::Pipeline convert_abgr8_to_d24s8_pipeline; |
| 152 | vk::Pipeline convert_d32f_to_abgr8_pipeline; | ||
| 149 | vk::Pipeline convert_d24s8_to_abgr8_pipeline; | 153 | vk::Pipeline convert_d24s8_to_abgr8_pipeline; |
| 150 | vk::Pipeline convert_s8d24_to_abgr8_pipeline; | 154 | vk::Pipeline convert_s8d24_to_abgr8_pipeline; |
| 151 | }; | 155 | }; |
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 208e88533..a08f2f67f 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp | |||
| @@ -214,8 +214,9 @@ struct FormatTuple { | |||
| 214 | {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT | 214 | {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT |
| 215 | 215 | ||
| 216 | // Depth formats | 216 | // Depth formats |
| 217 | {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT | 217 | {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT |
| 218 | {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM | 218 | {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM |
| 219 | {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM | ||
| 219 | 220 | ||
| 220 | // Stencil formats | 221 | // Stencil formats |
| 221 | {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT | 222 | {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 590bc1c64..14e257cf7 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h | |||
| @@ -56,10 +56,6 @@ public: | |||
| 56 | return device.GetDriverName(); | 56 | return device.GetDriverName(); |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | void NotifySurfaceChanged() override { | ||
| 60 | present_manager.NotifySurfaceChanged(); | ||
| 61 | } | ||
| 62 | |||
| 63 | private: | 59 | private: |
| 64 | void Report() const; | 60 | void Report() const; |
| 65 | 61 | ||
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 31928bb94..52fc142d1 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp | |||
| @@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { | |||
| 96 | VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { | 96 | VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { |
| 97 | switch (framebuffer.pixel_format) { | 97 | switch (framebuffer.pixel_format) { |
| 98 | case Service::android::PixelFormat::Rgba8888: | 98 | case Service::android::PixelFormat::Rgba8888: |
| 99 | case Service::android::PixelFormat::Rgbx8888: | ||
| 99 | return VK_FORMAT_A8B8G8R8_UNORM_PACK32; | 100 | return VK_FORMAT_A8B8G8R8_UNORM_PACK32; |
| 100 | case Service::android::PixelFormat::Rgb565: | 101 | case Service::android::PixelFormat::Rgb565: |
| 101 | return VK_FORMAT_R5G6B5_UNORM_PACK16; | 102 | return VK_FORMAT_R5G6B5_UNORM_PACK16; |
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index d681bd22a..2ef36583b 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp | |||
| @@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, | |||
| 103 | surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), | 103 | surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), |
| 104 | swapchain.GetImageViewFormat())}, | 104 | swapchain.GetImageViewFormat())}, |
| 105 | use_present_thread{Settings::values.async_presentation.GetValue()}, | 105 | use_present_thread{Settings::values.async_presentation.GetValue()}, |
| 106 | image_count{swapchain.GetImageCount()}, last_render_surface{ | 106 | image_count{swapchain.GetImageCount()} { |
| 107 | render_window_.GetWindowInfo().render_surface} { | ||
| 108 | 107 | ||
| 109 | auto& dld = device.GetLogical(); | 108 | auto& dld = device.GetLogical(); |
| 110 | cmdpool = dld.CreateCommandPool({ | 109 | cmdpool = dld.CreateCommandPool({ |
| @@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) { | |||
| 289 | } | 288 | } |
| 290 | } | 289 | } |
| 291 | 290 | ||
| 292 | void PresentManager::NotifySurfaceChanged() { | 291 | void PresentManager::RecreateSwapchain(Frame* frame) { |
| 293 | #ifdef ANDROID | 292 | swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb); |
| 294 | std::scoped_lock lock{recreate_surface_mutex}; | 293 | image_count = swapchain.GetImageCount(); |
| 295 | recreate_surface_cv.notify_one(); | ||
| 296 | #endif | ||
| 297 | } | 294 | } |
| 298 | 295 | ||
| 299 | void PresentManager::CopyToSwapchain(Frame* frame) { | 296 | void PresentManager::CopyToSwapchain(Frame* frame) { |
| 300 | MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); | 297 | bool requires_recreation = false; |
| 301 | 298 | ||
| 302 | const auto recreate_swapchain = [&] { | 299 | while (true) { |
| 303 | swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb); | 300 | try { |
| 304 | image_count = swapchain.GetImageCount(); | 301 | // Recreate surface and swapchain if needed. |
| 305 | }; | 302 | if (requires_recreation) { |
| 306 | 303 | surface = CreateSurface(instance, render_window.GetWindowInfo()); | |
| 307 | #ifdef ANDROID | 304 | RecreateSwapchain(frame); |
| 308 | std::unique_lock lock{recreate_surface_mutex}; | 305 | } |
| 309 | 306 | ||
| 310 | const auto needs_recreation = [&] { | 307 | // Draw to swapchain. |
| 311 | if (last_render_surface != render_window.GetWindowInfo().render_surface) { | 308 | return CopyToSwapchainImpl(frame); |
| 312 | return true; | 309 | } catch (const vk::Exception& except) { |
| 313 | } | 310 | if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) { |
| 314 | if (swapchain.NeedsRecreation(frame->is_srgb)) { | 311 | throw; |
| 315 | return true; | 312 | } |
| 313 | |||
| 314 | requires_recreation = true; | ||
| 316 | } | 315 | } |
| 317 | return false; | ||
| 318 | }; | ||
| 319 | |||
| 320 | recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400), | ||
| 321 | [&]() { return !needs_recreation(); }); | ||
| 322 | |||
| 323 | // If the frontend recreated the surface, recreate the renderer surface and swapchain. | ||
| 324 | if (last_render_surface != render_window.GetWindowInfo().render_surface) { | ||
| 325 | last_render_surface = render_window.GetWindowInfo().render_surface; | ||
| 326 | surface = CreateSurface(instance, render_window.GetWindowInfo()); | ||
| 327 | recreate_swapchain(); | ||
| 328 | } | 316 | } |
| 329 | #endif | 317 | } |
| 318 | |||
| 319 | void PresentManager::CopyToSwapchainImpl(Frame* frame) { | ||
| 320 | MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); | ||
| 330 | 321 | ||
| 331 | // If the size or colorspace of the incoming frames has changed, recreate the swapchain | 322 | // If the size or colorspace of the incoming frames has changed, recreate the swapchain |
| 332 | // to account for that. | 323 | // to account for that. |
| @@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) { | |||
| 334 | const bool size_changed = | 325 | const bool size_changed = |
| 335 | swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; | 326 | swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; |
| 336 | if (srgb_changed || size_changed) { | 327 | if (srgb_changed || size_changed) { |
| 337 | recreate_swapchain(); | 328 | RecreateSwapchain(frame); |
| 338 | } | 329 | } |
| 339 | 330 | ||
| 340 | while (swapchain.AcquireNextImage()) { | 331 | while (swapchain.AcquireNextImage()) { |
| 341 | recreate_swapchain(); | 332 | RecreateSwapchain(frame); |
| 342 | } | 333 | } |
| 343 | 334 | ||
| 344 | const vk::CommandBuffer cmdbuf{frame->cmdbuf}; | 335 | const vk::CommandBuffer cmdbuf{frame->cmdbuf}; |
| @@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) { | |||
| 488 | swapchain.Present(render_semaphore); | 479 | swapchain.Present(render_semaphore); |
| 489 | } | 480 | } |
| 490 | 481 | ||
| 491 | } // namespace Vulkan \ No newline at end of file | 482 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index 83e859416..a3d825fe6 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h | |||
| @@ -54,14 +54,15 @@ public: | |||
| 54 | /// Waits for the present thread to finish presenting all queued frames. | 54 | /// Waits for the present thread to finish presenting all queued frames. |
| 55 | void WaitPresent(); | 55 | void WaitPresent(); |
| 56 | 56 | ||
| 57 | /// This is called to notify the rendering backend of a surface change | ||
| 58 | void NotifySurfaceChanged(); | ||
| 59 | |||
| 60 | private: | 57 | private: |
| 61 | void PresentThread(std::stop_token token); | 58 | void PresentThread(std::stop_token token); |
| 62 | 59 | ||
| 63 | void CopyToSwapchain(Frame* frame); | 60 | void CopyToSwapchain(Frame* frame); |
| 64 | 61 | ||
| 62 | void CopyToSwapchainImpl(Frame* frame); | ||
| 63 | |||
| 64 | void RecreateSwapchain(Frame* frame); | ||
| 65 | |||
| 65 | private: | 66 | private: |
| 66 | const vk::Instance& instance; | 67 | const vk::Instance& instance; |
| 67 | Core::Frontend::EmuWindow& render_window; | 68 | Core::Frontend::EmuWindow& render_window; |
| @@ -76,16 +77,13 @@ private: | |||
| 76 | std::queue<Frame*> free_queue; | 77 | std::queue<Frame*> free_queue; |
| 77 | std::condition_variable_any frame_cv; | 78 | std::condition_variable_any frame_cv; |
| 78 | std::condition_variable free_cv; | 79 | std::condition_variable free_cv; |
| 79 | std::condition_variable recreate_surface_cv; | ||
| 80 | std::mutex swapchain_mutex; | 80 | std::mutex swapchain_mutex; |
| 81 | std::mutex recreate_surface_mutex; | ||
| 82 | std::mutex queue_mutex; | 81 | std::mutex queue_mutex; |
| 83 | std::mutex free_mutex; | 82 | std::mutex free_mutex; |
| 84 | std::jthread present_thread; | 83 | std::jthread present_thread; |
| 85 | bool blit_supported; | 84 | bool blit_supported; |
| 86 | bool use_present_thread; | 85 | bool use_present_thread; |
| 87 | std::size_t image_count{}; | 86 | std::size_t image_count{}; |
| 88 | void* last_render_surface{}; | ||
| 89 | }; | 87 | }; |
| 90 | 88 | ||
| 91 | } // namespace Vulkan | 89 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 1628d76d6..83f2b6045 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -422,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) { | |||
| 422 | return; | 422 | return; |
| 423 | } | 423 | } |
| 424 | 424 | ||
| 425 | if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { | 425 | if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF && |
| 426 | regs.stencil_front_mask != 0) { | ||
| 426 | Region2D dst_region = { | 427 | Region2D dst_region = { |
| 427 | Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, | 428 | Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, |
| 428 | Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), | 429 | Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), |
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index ce92f66ab..b278614e6 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp | |||
| @@ -24,25 +24,38 @@ using namespace Common::Literals; | |||
| 24 | 24 | ||
| 25 | // Maximum potential alignment of a Vulkan buffer | 25 | // Maximum potential alignment of a Vulkan buffer |
| 26 | constexpr VkDeviceSize MAX_ALIGNMENT = 256; | 26 | constexpr VkDeviceSize MAX_ALIGNMENT = 256; |
| 27 | // Maximum size to put elements in the stream buffer | ||
| 28 | constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB; | ||
| 29 | // Stream buffer size in bytes | 27 | // Stream buffer size in bytes |
| 30 | constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB; | 28 | constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; |
| 31 | constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS; | ||
| 32 | 29 | ||
| 33 | size_t Region(size_t iterator) noexcept { | 30 | size_t GetStreamBufferSize(const Device& device) { |
| 34 | return iterator / REGION_SIZE; | 31 | VkDeviceSize size{0}; |
| 32 | if (device.HasDebuggingToolAttached()) { | ||
| 33 | ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { | ||
| 34 | size = std::max(size, heap.size); | ||
| 35 | }); | ||
| 36 | // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be | ||
| 37 | // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue | ||
| 38 | // as the heap will be much larger. | ||
| 39 | if (size <= 256_MiB) { | ||
| 40 | size = size * 40 / 100; | ||
| 41 | } | ||
| 42 | } else { | ||
| 43 | size = MAX_STREAM_BUFFER_SIZE; | ||
| 44 | } | ||
| 45 | return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); | ||
| 35 | } | 46 | } |
| 36 | } // Anonymous namespace | 47 | } // Anonymous namespace |
| 37 | 48 | ||
| 38 | StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, | 49 | StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, |
| 39 | Scheduler& scheduler_) | 50 | Scheduler& scheduler_) |
| 40 | : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { | 51 | : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, |
| 52 | stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size / | ||
| 53 | StagingBufferPool::NUM_SYNCS} { | ||
| 41 | VkBufferCreateInfo stream_ci = { | 54 | VkBufferCreateInfo stream_ci = { |
| 42 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | 55 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
| 43 | .pNext = nullptr, | 56 | .pNext = nullptr, |
| 44 | .flags = 0, | 57 | .flags = 0, |
| 45 | .size = STREAM_BUFFER_SIZE, | 58 | .size = stream_buffer_size, |
| 46 | .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | | 59 | .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | |
| 47 | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, | 60 | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, |
| 48 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | 61 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
| @@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem | |||
| 63 | StagingBufferPool::~StagingBufferPool() = default; | 76 | StagingBufferPool::~StagingBufferPool() = default; |
| 64 | 77 | ||
| 65 | StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { | 78 | StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { |
| 66 | if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) { | 79 | if (!deferred && usage == MemoryUsage::Upload && size <= region_size) { |
| 67 | return GetStreamBuffer(size); | 80 | return GetStreamBuffer(size); |
| 68 | } | 81 | } |
| 69 | return GetStagingBuffer(size, usage, deferred); | 82 | return GetStagingBuffer(size, usage, deferred); |
| @@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { | |||
| 101 | used_iterator = iterator; | 114 | used_iterator = iterator; |
| 102 | free_iterator = std::max(free_iterator, iterator + size); | 115 | free_iterator = std::max(free_iterator, iterator + size); |
| 103 | 116 | ||
| 104 | if (iterator + size >= STREAM_BUFFER_SIZE) { | 117 | if (iterator + size >= stream_buffer_size) { |
| 105 | std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, | 118 | std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, |
| 106 | current_tick); | 119 | current_tick); |
| 107 | used_iterator = 0; | 120 | used_iterator = 0; |
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index 5f69f08b1..d3deb9072 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h | |||
| @@ -90,6 +90,9 @@ private: | |||
| 90 | void ReleaseCache(MemoryUsage usage); | 90 | void ReleaseCache(MemoryUsage usage); |
| 91 | 91 | ||
| 92 | void ReleaseLevel(StagingBuffersCache& cache, size_t log2); | 92 | void ReleaseLevel(StagingBuffersCache& cache, size_t log2); |
| 93 | size_t Region(size_t iter) const noexcept { | ||
| 94 | return iter / region_size; | ||
| 95 | } | ||
| 93 | 96 | ||
| 94 | const Device& device; | 97 | const Device& device; |
| 95 | MemoryAllocator& memory_allocator; | 98 | MemoryAllocator& memory_allocator; |
| @@ -97,6 +100,8 @@ private: | |||
| 97 | 100 | ||
| 98 | vk::Buffer stream_buffer; | 101 | vk::Buffer stream_buffer; |
| 99 | std::span<u8> stream_pointer; | 102 | std::span<u8> stream_pointer; |
| 103 | VkDeviceSize stream_buffer_size; | ||
| 104 | VkDeviceSize region_size; | ||
| 100 | 105 | ||
| 101 | size_t iterator = 0; | 106 | size_t iterator = 0; |
| 102 | size_t used_iterator = 0; | 107 | size_t used_iterator = 0; |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 71fdec809..00ab47268 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -238,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { | |||
| 238 | return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; | 238 | return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; |
| 239 | case PixelFormat::D16_UNORM: | 239 | case PixelFormat::D16_UNORM: |
| 240 | case PixelFormat::D32_FLOAT: | 240 | case PixelFormat::D32_FLOAT: |
| 241 | case PixelFormat::X8_D24_UNORM: | ||
| 241 | return VK_IMAGE_ASPECT_DEPTH_BIT; | 242 | return VK_IMAGE_ASPECT_DEPTH_BIT; |
| 242 | case PixelFormat::S8_UINT: | 243 | case PixelFormat::S8_UINT: |
| 243 | return VK_IMAGE_ASPECT_STENCIL_BIT; | 244 | return VK_IMAGE_ASPECT_STENCIL_BIT; |
| @@ -1200,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im | |||
| 1200 | if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { | 1201 | if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { |
| 1201 | return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); | 1202 | return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); |
| 1202 | } | 1203 | } |
| 1204 | if (src_view.format == PixelFormat::D32_FLOAT) { | ||
| 1205 | return blit_image_helper.ConvertD32FToABGR8(dst, src_view); | ||
| 1206 | } | ||
| 1203 | break; | 1207 | break; |
| 1204 | case PixelFormat::R32_FLOAT: | 1208 | case PixelFormat::R32_FLOAT: |
| 1205 | if (src_view.format == PixelFormat::D32_FLOAT) { | 1209 | if (src_view.format == PixelFormat::D32_FLOAT) { |
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index e16cd5e73..5b3c7aa5a 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp | |||
| @@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) { | |||
| 85 | return PixelFormat::S8_UINT; | 85 | return PixelFormat::S8_UINT; |
| 86 | case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT: | 86 | case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT: |
| 87 | return PixelFormat::D32_FLOAT_S8_UINT; | 87 | return PixelFormat::D32_FLOAT_S8_UINT; |
| 88 | case Tegra::DepthFormat::X8Z24_UNORM: | ||
| 89 | return PixelFormat::X8_D24_UNORM; | ||
| 88 | default: | 90 | default: |
| 89 | UNIMPLEMENTED_MSG("Unimplemented format={}", format); | 91 | UNIMPLEMENTED_MSG("Unimplemented format={}", format); |
| 90 | return PixelFormat::S8_UINT_D24_UNORM; | 92 | return PixelFormat::S8_UINT_D24_UNORM; |
| @@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) | |||
| 202 | PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) { | 204 | PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) { |
| 203 | switch (format) { | 205 | switch (format) { |
| 204 | case Service::android::PixelFormat::Rgba8888: | 206 | case Service::android::PixelFormat::Rgba8888: |
| 207 | case Service::android::PixelFormat::Rgbx8888: | ||
| 205 | return PixelFormat::A8B8G8R8_UNORM; | 208 | return PixelFormat::A8B8G8R8_UNORM; |
| 206 | case Service::android::PixelFormat::Rgb565: | 209 | case Service::android::PixelFormat::Rgb565: |
| 207 | return PixelFormat::R5G6B5_UNORM; | 210 | return PixelFormat::R5G6B5_UNORM; |
diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 9b9c4d9bc..a5e8e2f62 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h | |||
| @@ -115,6 +115,7 @@ enum class PixelFormat { | |||
| 115 | // Depth formats | 115 | // Depth formats |
| 116 | D32_FLOAT = MaxColorFormat, | 116 | D32_FLOAT = MaxColorFormat, |
| 117 | D16_UNORM, | 117 | D16_UNORM, |
| 118 | X8_D24_UNORM, | ||
| 118 | 119 | ||
| 119 | MaxDepthFormat, | 120 | MaxDepthFormat, |
| 120 | 121 | ||
| @@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ | |||
| 251 | 1, // E5B9G9R9_FLOAT | 252 | 1, // E5B9G9R9_FLOAT |
| 252 | 1, // D32_FLOAT | 253 | 1, // D32_FLOAT |
| 253 | 1, // D16_UNORM | 254 | 1, // D16_UNORM |
| 255 | 1, // X8_D24_UNORM | ||
| 254 | 1, // S8_UINT | 256 | 1, // S8_UINT |
| 255 | 1, // D24_UNORM_S8_UINT | 257 | 1, // D24_UNORM_S8_UINT |
| 256 | 1, // S8_UINT_D24_UNORM | 258 | 1, // S8_UINT_D24_UNORM |
| @@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ | |||
| 360 | 1, // E5B9G9R9_FLOAT | 362 | 1, // E5B9G9R9_FLOAT |
| 361 | 1, // D32_FLOAT | 363 | 1, // D32_FLOAT |
| 362 | 1, // D16_UNORM | 364 | 1, // D16_UNORM |
| 365 | 1, // X8_D24_UNORM | ||
| 363 | 1, // S8_UINT | 366 | 1, // S8_UINT |
| 364 | 1, // D24_UNORM_S8_UINT | 367 | 1, // D24_UNORM_S8_UINT |
| 365 | 1, // S8_UINT_D24_UNORM | 368 | 1, // S8_UINT_D24_UNORM |
| @@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ | |||
| 469 | 32, // E5B9G9R9_FLOAT | 472 | 32, // E5B9G9R9_FLOAT |
| 470 | 32, // D32_FLOAT | 473 | 32, // D32_FLOAT |
| 471 | 16, // D16_UNORM | 474 | 16, // D16_UNORM |
| 475 | 32, // X8_D24_UNORM | ||
| 472 | 8, // S8_UINT | 476 | 8, // S8_UINT |
| 473 | 32, // D24_UNORM_S8_UINT | 477 | 32, // D24_UNORM_S8_UINT |
| 474 | 32, // S8_UINT_D24_UNORM | 478 | 32, // S8_UINT_D24_UNORM |
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 56307d030..3162c8f5e 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp | |||
| @@ -142,6 +142,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, | |||
| 142 | return PixelFormat::D16_UNORM; | 142 | return PixelFormat::D16_UNORM; |
| 143 | case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): | 143 | case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): |
| 144 | return PixelFormat::D16_UNORM; | 144 | return PixelFormat::D16_UNORM; |
| 145 | case Hash(TextureFormat::X8Z24, UNORM): | ||
| 146 | return PixelFormat::X8_D24_UNORM; | ||
| 147 | case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR): | ||
| 148 | return PixelFormat::X8_D24_UNORM; | ||
| 145 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): | 149 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): |
| 146 | return PixelFormat::S8_UINT_D24_UNORM; | 150 | return PixelFormat::S8_UINT_D24_UNORM; |
| 147 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): | 151 | case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): |
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index 9ee57a076..cabbfcb2d 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h | |||
| @@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str | |||
| 211 | return "D32_FLOAT"; | 211 | return "D32_FLOAT"; |
| 212 | case PixelFormat::D16_UNORM: | 212 | case PixelFormat::D16_UNORM: |
| 213 | return "D16_UNORM"; | 213 | return "D16_UNORM"; |
| 214 | case PixelFormat::X8_D24_UNORM: | ||
| 215 | return "X8_D24_UNORM"; | ||
| 214 | case PixelFormat::S8_UINT: | 216 | case PixelFormat::S8_UINT: |
| 215 | return "S8_UINT"; | 217 | return "S8_UINT"; |
| 216 | case PixelFormat::D24_UNORM_S8_UINT: | 218 | case PixelFormat::D24_UNORM_S8_UINT: |
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp index 0c5f4450d..18b9250f9 100644 --- a/src/video_core/texture_cache/image_view_base.cpp +++ b/src/video_core/texture_cache/image_view_base.cpp | |||
| @@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept { | |||
| 85 | // Depth formats | 85 | // Depth formats |
| 86 | case PixelFormat::D32_FLOAT: | 86 | case PixelFormat::D32_FLOAT: |
| 87 | case PixelFormat::D16_UNORM: | 87 | case PixelFormat::D16_UNORM: |
| 88 | case PixelFormat::X8_D24_UNORM: | ||
| 88 | // Stencil formats | 89 | // Stencil formats |
| 89 | case PixelFormat::S8_UINT: | 90 | case PixelFormat::S8_UINT: |
| 90 | // DepthStencil formats | 91 | // DepthStencil formats |
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 3960b135a..876cec2e8 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -84,9 +84,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ | |||
| 84 | } // namespace Alternatives | 84 | } // namespace Alternatives |
| 85 | 85 | ||
| 86 | enum class NvidiaArchitecture { | 86 | enum class NvidiaArchitecture { |
| 87 | AmpereOrNewer, | 87 | KeplerOrOlder, |
| 88 | Maxwell, | ||
| 89 | Pascal, | ||
| 90 | Volta, | ||
| 88 | Turing, | 91 | Turing, |
| 89 | VoltaOrOlder, | 92 | AmpereOrNewer, |
| 90 | }; | 93 | }; |
| 91 | 94 | ||
| 92 | template <typename T> | 95 | template <typename T> |
| @@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica | |||
| 200 | VK_FORMAT_BC7_UNORM_BLOCK, | 203 | VK_FORMAT_BC7_UNORM_BLOCK, |
| 201 | VK_FORMAT_D16_UNORM, | 204 | VK_FORMAT_D16_UNORM, |
| 202 | VK_FORMAT_D16_UNORM_S8_UINT, | 205 | VK_FORMAT_D16_UNORM_S8_UINT, |
| 206 | VK_FORMAT_X8_D24_UNORM_PACK32, | ||
| 203 | VK_FORMAT_D24_UNORM_S8_UINT, | 207 | VK_FORMAT_D24_UNORM_S8_UINT, |
| 204 | VK_FORMAT_D32_SFLOAT, | 208 | VK_FORMAT_D32_SFLOAT, |
| 205 | VK_FORMAT_D32_SFLOAT_S8_UINT, | 209 | VK_FORMAT_D32_SFLOAT_S8_UINT, |
| @@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical, | |||
| 321 | physical.GetProperties2(physical_properties); | 325 | physical.GetProperties2(physical_properties); |
| 322 | if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { | 326 | if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { |
| 323 | // Only Ampere and newer support this feature | 327 | // Only Ampere and newer support this feature |
| 328 | // TODO: Find a way to differentiate Ampere and Ada | ||
| 324 | return NvidiaArchitecture::AmpereOrNewer; | 329 | return NvidiaArchitecture::AmpereOrNewer; |
| 325 | } | 330 | } |
| 326 | } | ||
| 327 | if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) { | ||
| 328 | return NvidiaArchitecture::Turing; | 331 | return NvidiaArchitecture::Turing; |
| 329 | } | 332 | } |
| 330 | return NvidiaArchitecture::VoltaOrOlder; | 333 | |
| 334 | if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) { | ||
| 335 | VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{}; | ||
| 336 | advanced_blending_props.sType = | ||
| 337 | VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT; | ||
| 338 | VkPhysicalDeviceProperties2 physical_properties{}; | ||
| 339 | physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; | ||
| 340 | physical_properties.pNext = &advanced_blending_props; | ||
| 341 | physical.GetProperties2(physical_properties); | ||
| 342 | if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) { | ||
| 343 | return NvidiaArchitecture::Maxwell; | ||
| 344 | } | ||
| 345 | |||
| 346 | if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) { | ||
| 347 | VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{}; | ||
| 348 | conservative_raster_props.sType = | ||
| 349 | VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT; | ||
| 350 | physical_properties.pNext = &conservative_raster_props; | ||
| 351 | physical.GetProperties2(physical_properties); | ||
| 352 | if (conservative_raster_props.degenerateLinesRasterized) { | ||
| 353 | return NvidiaArchitecture::Volta; | ||
| 354 | } | ||
| 355 | return NvidiaArchitecture::Pascal; | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | return NvidiaArchitecture::KeplerOrOlder; | ||
| 331 | } | 360 | } |
| 332 | 361 | ||
| 333 | std::vector<const char*> ExtensionListForVulkan( | 362 | std::vector<const char*> ExtensionListForVulkan( |
| @@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 504 | if (is_nvidia) { | 533 | if (is_nvidia) { |
| 505 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; | 534 | const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; |
| 506 | const auto arch = GetNvidiaArchitecture(physical, supported_extensions); | 535 | const auto arch = GetNvidiaArchitecture(physical, supported_extensions); |
| 507 | switch (arch) { | 536 | if (arch >= NvidiaArchitecture::AmpereOrNewer) { |
| 508 | case NvidiaArchitecture::AmpereOrNewer: | ||
| 509 | LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); | 537 | LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); |
| 510 | features.shader_float16_int8.shaderFloat16 = false; | 538 | features.shader_float16_int8.shaderFloat16 = false; |
| 511 | break; | 539 | } else if (arch <= NvidiaArchitecture::Volta) { |
| 512 | case NvidiaArchitecture::Turing: | ||
| 513 | break; | ||
| 514 | case NvidiaArchitecture::VoltaOrOlder: | ||
| 515 | if (nv_major_version < 527) { | 540 | if (nv_major_version < 527) { |
| 516 | LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); | 541 | LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); |
| 517 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | 542 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 518 | } | 543 | } |
| 519 | break; | ||
| 520 | } | 544 | } |
| 521 | if (nv_major_version >= 510) { | 545 | if (nv_major_version >= 510) { |
| 522 | LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); | 546 | LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); |
| @@ -661,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR | |||
| 661 | "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); | 685 | "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); |
| 662 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | 686 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); |
| 663 | } | 687 | } |
| 688 | } else if (extensions.push_descriptor && is_nvidia) { | ||
| 689 | const auto arch = GetNvidiaArchitecture(physical, supported_extensions); | ||
| 690 | if (arch <= NvidiaArchitecture::Pascal) { | ||
| 691 | LOG_WARNING(Render_Vulkan, | ||
| 692 | "Pascal and older architectures have broken VK_KHR_push_descriptor"); | ||
| 693 | RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); | ||
| 694 | } | ||
| 664 | } | 695 | } |
| 696 | |||
| 665 | if (is_mvk) { | 697 | if (is_mvk) { |
| 666 | LOG_WARNING(Render_Vulkan, | 698 | LOG_WARNING(Render_Vulkan, |
| 667 | "MVK driver breaks when using more than 16 vertex attributes/bindings"); | 699 | "MVK driver breaks when using more than 16 vertex attributes/bindings"); |
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 3ef381a38..82767fdf0 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/alignment.h" | 9 | #include "common/alignment.h" |
| 10 | #include "common/assert.h" | 10 | #include "common/assert.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/literals.h" | ||
| 12 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 13 | #include "common/polyfill_ranges.h" | 14 | #include "common/polyfill_ranges.h" |
| 14 | #include "video_core/vulkan_common/vma.h" | 15 | #include "video_core/vulkan_common/vma.h" |
| @@ -69,8 +70,7 @@ struct Range { | |||
| 69 | case MemoryUsage::Download: | 70 | case MemoryUsage::Download: |
| 70 | return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; | 71 | return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; |
| 71 | case MemoryUsage::DeviceLocal: | 72 | case MemoryUsage::DeviceLocal: |
| 72 | return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | | 73 | return {}; |
| 73 | VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT; | ||
| 74 | } | 74 | } |
| 75 | return {}; | 75 | return {}; |
| 76 | } | 76 | } |
| @@ -212,7 +212,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_) | |||
| 212 | : device{device_}, allocator{device.GetAllocator()}, | 212 | : device{device_}, allocator{device.GetAllocator()}, |
| 213 | properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, | 213 | properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, |
| 214 | buffer_image_granularity{ | 214 | buffer_image_granularity{ |
| 215 | device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} | 215 | device_.GetPhysical().GetProperties().limits.bufferImageGranularity} { |
| 216 | // GPUs not supporting rebar may only have a region with less than 256MB host visible/device | ||
| 217 | // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to | ||
| 218 | // the heap running out of memory. With RenderDoc attached and only a small host/device region, | ||
| 219 | // only allow the stream buffer in this memory heap. | ||
| 220 | if (device.HasDebuggingToolAttached()) { | ||
| 221 | using namespace Common::Literals; | ||
| 222 | ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) { | ||
| 223 | if (heap.size <= 256_MiB) { | ||
| 224 | valid_memory_types &= ~(1u << index); | ||
| 225 | } | ||
| 226 | }); | ||
| 227 | } | ||
| 228 | } | ||
| 216 | 229 | ||
| 217 | MemoryAllocator::~MemoryAllocator() = default; | 230 | MemoryAllocator::~MemoryAllocator() = default; |
| 218 | 231 | ||
| @@ -244,7 +257,7 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa | |||
| 244 | .usage = MemoryUsageVma(usage), | 257 | .usage = MemoryUsageVma(usage), |
| 245 | .requiredFlags = 0, | 258 | .requiredFlags = 0, |
| 246 | .preferredFlags = MemoryUsagePreferedVmaFlags(usage), | 259 | .preferredFlags = MemoryUsagePreferedVmaFlags(usage), |
| 247 | .memoryTypeBits = 0, | 260 | .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types, |
| 248 | .pool = VK_NULL_HANDLE, | 261 | .pool = VK_NULL_HANDLE, |
| 249 | .pUserData = nullptr, | 262 | .pUserData = nullptr, |
| 250 | .priority = 0.f, | 263 | .priority = 0.f, |
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index f449bc8d0..38a182bcb 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <span> | 7 | #include <span> |
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "video_core/vulkan_common/vulkan_device.h" | ||
| 10 | #include "video_core/vulkan_common/vulkan_wrapper.h" | 11 | #include "video_core/vulkan_common/vulkan_wrapper.h" |
| 11 | 12 | ||
| 12 | VK_DEFINE_HANDLE(VmaAllocator) | 13 | VK_DEFINE_HANDLE(VmaAllocator) |
| @@ -26,6 +27,18 @@ enum class MemoryUsage { | |||
| 26 | Stream, ///< Requests device local host visible buffer, falling back host memory. | 27 | Stream, ///< Requests device local host visible buffer, falling back host memory. |
| 27 | }; | 28 | }; |
| 28 | 29 | ||
| 30 | template <typename F> | ||
| 31 | void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) { | ||
| 32 | auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties; | ||
| 33 | for (size_t i = 0; i < memory_props.memoryTypeCount; i++) { | ||
| 34 | auto& memory_type = memory_props.memoryTypes[i]; | ||
| 35 | if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && | ||
| 36 | (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { | ||
| 37 | f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 29 | /// Ownership handle of a memory commitment. | 42 | /// Ownership handle of a memory commitment. |
| 30 | /// Points to a subregion of a memory allocation. | 43 | /// Points to a subregion of a memory allocation. |
| 31 | class MemoryCommit { | 44 | class MemoryCommit { |
| @@ -124,6 +137,7 @@ private: | |||
| 124 | std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. | 137 | std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. |
| 125 | VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers | 138 | VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers |
| 126 | // and optimal images | 139 | // and optimal images |
| 140 | u32 valid_memory_types{~0u}; | ||
| 127 | }; | 141 | }; |
| 128 | 142 | ||
| 129 | } // namespace Vulkan | 143 | } // namespace Vulkan |
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 1e3c0fa64..0487cd3b6 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h | |||
| @@ -117,6 +117,9 @@ public: | |||
| 117 | virtual ~Exception() = default; | 117 | virtual ~Exception() = default; |
| 118 | 118 | ||
| 119 | const char* what() const noexcept override; | 119 | const char* what() const noexcept override; |
| 120 | VkResult GetResult() const noexcept { | ||
| 121 | return result; | ||
| 122 | } | ||
| 120 | 123 | ||
| 121 | private: | 124 | private: |
| 122 | VkResult result; | 125 | VkResult result; |
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 8f86a1553..9ebece907 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -195,6 +195,8 @@ add_executable(yuzu | |||
| 195 | multiplayer/state.cpp | 195 | multiplayer/state.cpp |
| 196 | multiplayer/state.h | 196 | multiplayer/state.h |
| 197 | multiplayer/validation.h | 197 | multiplayer/validation.h |
| 198 | play_time_manager.cpp | ||
| 199 | play_time_manager.h | ||
| 198 | precompiled_headers.h | 200 | precompiled_headers.h |
| 199 | qt_common.cpp | 201 | qt_common.cpp |
| 200 | qt_common.h | 202 | qt_common.h |
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 9ccfb2435..81dd51ad3 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp | |||
| @@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) { | |||
| 42 | for (auto* setting : Settings::values.linkage.by_category[category]) { | 42 | for (auto* setting : Settings::values.linkage.by_category[category]) { |
| 43 | settings.push_back(setting); | 43 | settings.push_back(setting); |
| 44 | } | 44 | } |
| 45 | for (auto* setting : UISettings::values.linkage.by_category[category]) { | ||
| 46 | settings.push_back(setting); | ||
| 47 | } | ||
| 45 | }; | 48 | }; |
| 46 | 49 | ||
| 47 | push(Settings::Category::Audio); | 50 | push(Settings::Category::Audio); |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index a9fde9f4f..82f3b6e78 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) | |||
| 123 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 123 | connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 124 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 124 | connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 125 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); | 125 | connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); |
| 126 | connect(ui->show_play_time, &QCheckBox::stateChanged, this, | ||
| 127 | &ConfigureUi::RequestGameListUpdate); | ||
| 126 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, | 128 | connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, |
| 127 | &ConfigureUi::RequestGameListUpdate); | 129 | &ConfigureUi::RequestGameListUpdate); |
| 128 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), | 130 | connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), |
| @@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 167 | UISettings::values.show_compat = ui->show_compat->isChecked(); | 169 | UISettings::values.show_compat = ui->show_compat->isChecked(); |
| 168 | UISettings::values.show_size = ui->show_size->isChecked(); | 170 | UISettings::values.show_size = ui->show_size->isChecked(); |
| 169 | UISettings::values.show_types = ui->show_types->isChecked(); | 171 | UISettings::values.show_types = ui->show_types->isChecked(); |
| 172 | UISettings::values.show_play_time = ui->show_play_time->isChecked(); | ||
| 170 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); | 173 | UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); |
| 171 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); | 174 | UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); |
| 172 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); | 175 | UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); |
| @@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 179 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); | 182 | const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); |
| 180 | UISettings::values.screenshot_height.SetValue(height); | 183 | UISettings::values.screenshot_height.SetValue(height); |
| 181 | 184 | ||
| 185 | RequestGameListUpdate(); | ||
| 182 | system.ApplySettings(); | 186 | system.ApplySettings(); |
| 183 | } | 187 | } |
| 184 | 188 | ||
| @@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() { | |||
| 194 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); | 198 | ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); |
| 195 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); | 199 | ui->show_size->setChecked(UISettings::values.show_size.GetValue()); |
| 196 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); | 200 | ui->show_types->setChecked(UISettings::values.show_types.GetValue()); |
| 201 | ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); | ||
| 197 | ui->game_icon_size_combobox->setCurrentIndex( | 202 | ui->game_icon_size_combobox->setCurrentIndex( |
| 198 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); | 203 | ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); |
| 199 | ui->folder_icon_size_combobox->setCurrentIndex( | 204 | ui->folder_icon_size_combobox->setCurrentIndex( |
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index cb66ef104..b8e648381 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui | |||
| @@ -105,6 +105,13 @@ | |||
| 105 | </widget> | 105 | </widget> |
| 106 | </item> | 106 | </item> |
| 107 | <item> | 107 | <item> |
| 108 | <widget class="QCheckBox" name="show_play_time"> | ||
| 109 | <property name="text"> | ||
| 110 | <string>Show Play Time Column</string> | ||
| 111 | </property> | ||
| 112 | </widget> | ||
| 113 | </item> | ||
| 114 | <item> | ||
| 108 | <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> | 115 | <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> |
| 109 | <item> | 116 | <item> |
| 110 | <widget class="QLabel" name="game_icon_size_label"> | 117 | <widget class="QLabel" name="game_icon_size_label"> |
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 276bdbaba..a4e8af1b4 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp | |||
| @@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) { | |||
| 29 | INSERT(Settings, sink_id, "Output Engine:", ""); | 29 | INSERT(Settings, sink_id, "Output Engine:", ""); |
| 30 | INSERT(Settings, audio_output_device_id, "Output Device:", ""); | 30 | INSERT(Settings, audio_output_device_id, "Output Device:", ""); |
| 31 | INSERT(Settings, audio_input_device_id, "Input Device:", ""); | 31 | INSERT(Settings, audio_input_device_id, "Input Device:", ""); |
| 32 | INSERT(Settings, audio_muted, "Mute audio when in background", ""); | 32 | INSERT(Settings, audio_muted, "Mute audio", ""); |
| 33 | INSERT(Settings, volume, "Volume:", ""); | 33 | INSERT(Settings, volume, "Volume:", ""); |
| 34 | INSERT(Settings, dump_audio_commands, "", ""); | 34 | INSERT(Settings, dump_audio_commands, "", ""); |
| 35 | INSERT(UISettings, mute_when_in_background, "Mute audio when in background", ""); | ||
| 35 | 36 | ||
| 36 | // Core | 37 | // Core |
| 37 | INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); | 38 | INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index a9f19e316..74f48031a 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() { | |||
| 312 | } | 312 | } |
| 313 | 313 | ||
| 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, | 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, |
| 315 | Core::System& system_, GMainWindow* parent) | 315 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, |
| 316 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { | 316 | GMainWindow* parent) |
| 317 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, | ||
| 318 | play_time_manager{play_time_manager_}, system{system_} { | ||
| 317 | watcher = new QFileSystemWatcher(this); | 319 | watcher = new QFileSystemWatcher(this); |
| 318 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); | 320 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); |
| 319 | 321 | ||
| @@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid | |||
| 340 | 342 | ||
| 341 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); | 343 | tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); |
| 342 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 344 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 345 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 343 | item_model->setSortRole(GameListItemPath::SortRole); | 346 | item_model->setSortRole(GameListItemPath::SortRole); |
| 344 | 347 | ||
| 345 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); | 348 | connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); |
| @@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 548 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); | 551 | QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); |
| 549 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); | 552 | QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); |
| 550 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); | 553 | QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); |
| 554 | QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data")); | ||
| 551 | QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); | 555 | QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); |
| 552 | QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); | 556 | QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); |
| 553 | QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); | 557 | QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); |
| @@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 622 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { | 626 | connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { |
| 623 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); | 627 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); |
| 624 | }); | 628 | }); |
| 629 | connect(remove_play_time_data, &QAction::triggered, | ||
| 630 | [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); | ||
| 625 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { | 631 | connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { |
| 626 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); | 632 | emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); |
| 627 | }); | 633 | }); |
| @@ -790,6 +796,7 @@ void GameList::RetranslateUI() { | |||
| 790 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); | 796 | item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); |
| 791 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); | 797 | item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); |
| 792 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); | 798 | item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); |
| 799 | item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); | ||
| 793 | } | 800 | } |
| 794 | 801 | ||
| 795 | void GameListSearchField::changeEvent(QEvent* event) { | 802 | void GameListSearchField::changeEvent(QEvent* event) { |
| @@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 817 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); | 824 | tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); |
| 818 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); | 825 | tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); |
| 819 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); | 826 | tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); |
| 827 | tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); | ||
| 820 | 828 | ||
| 821 | // Delete any rows that might already exist if we're repopulating | 829 | // Delete any rows that might already exist if we're repopulating |
| 822 | item_model->removeRows(0, item_model->rowCount()); | 830 | item_model->removeRows(0, item_model->rowCount()); |
| @@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 825 | emit ShouldCancelWorker(); | 833 | emit ShouldCancelWorker(); |
| 826 | 834 | ||
| 827 | GameListWorker* worker = | 835 | GameListWorker* worker = |
| 828 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); | 836 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); |
| 829 | 837 | ||
| 830 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | 838 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
| 831 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, | 839 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 1fcbbf0ba..712570cea 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include "core/core.h" | 18 | #include "core/core.h" |
| 19 | #include "uisettings.h" | 19 | #include "uisettings.h" |
| 20 | #include "yuzu/compatibility_list.h" | 20 | #include "yuzu/compatibility_list.h" |
| 21 | #include "yuzu/play_time_manager.h" | ||
| 21 | 22 | ||
| 22 | namespace Core { | 23 | namespace Core { |
| 23 | class System; | 24 | class System; |
| @@ -75,11 +76,13 @@ public: | |||
| 75 | COLUMN_ADD_ONS, | 76 | COLUMN_ADD_ONS, |
| 76 | COLUMN_FILE_TYPE, | 77 | COLUMN_FILE_TYPE, |
| 77 | COLUMN_SIZE, | 78 | COLUMN_SIZE, |
| 79 | COLUMN_PLAY_TIME, | ||
| 78 | COLUMN_COUNT, // Number of columns | 80 | COLUMN_COUNT, // Number of columns |
| 79 | }; | 81 | }; |
| 80 | 82 | ||
| 81 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 83 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 82 | FileSys::ManualContentProvider* provider_, Core::System& system_, | 84 | FileSys::ManualContentProvider* provider_, |
| 85 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, | ||
| 83 | GMainWindow* parent = nullptr); | 86 | GMainWindow* parent = nullptr); |
| 84 | ~GameList() override; | 87 | ~GameList() override; |
| 85 | 88 | ||
| @@ -113,6 +116,7 @@ signals: | |||
| 113 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); | 116 | void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); |
| 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 117 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 115 | const std::string& game_path); | 118 | const std::string& game_path); |
| 119 | void RemovePlayTimeRequested(u64 program_id); | ||
| 116 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 120 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 117 | void VerifyIntegrityRequested(const std::string& game_path); | 121 | void VerifyIntegrityRequested(const std::string& game_path); |
| 118 | void CopyTIDRequested(u64 program_id); | 122 | void CopyTIDRequested(u64 program_id); |
| @@ -168,6 +172,7 @@ private: | |||
| 168 | 172 | ||
| 169 | friend class GameListSearchField; | 173 | friend class GameListSearchField; |
| 170 | 174 | ||
| 175 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 171 | Core::System& system; | 176 | Core::System& system; |
| 172 | }; | 177 | }; |
| 173 | 178 | ||
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 1800f090f..86a0c41d9 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include "common/common_types.h" | 18 | #include "common/common_types.h" |
| 19 | #include "common/logging/log.h" | 19 | #include "common/logging/log.h" |
| 20 | #include "common/string_util.h" | 20 | #include "common/string_util.h" |
| 21 | #include "yuzu/play_time_manager.h" | ||
| 21 | #include "yuzu/uisettings.h" | 22 | #include "yuzu/uisettings.h" |
| 22 | #include "yuzu/util/util.h" | 23 | #include "yuzu/util/util.h" |
| 23 | 24 | ||
| @@ -221,6 +222,31 @@ public: | |||
| 221 | } | 222 | } |
| 222 | }; | 223 | }; |
| 223 | 224 | ||
| 225 | /** | ||
| 226 | * GameListItem for Play Time values. | ||
| 227 | * This object stores the play time of a game in seconds, and its readable | ||
| 228 | * representation in minutes/hours | ||
| 229 | */ | ||
| 230 | class GameListItemPlayTime : public GameListItem { | ||
| 231 | public: | ||
| 232 | static constexpr int PlayTimeRole = SortRole; | ||
| 233 | |||
| 234 | GameListItemPlayTime() = default; | ||
| 235 | explicit GameListItemPlayTime(const qulonglong time_seconds) { | ||
| 236 | setData(time_seconds, PlayTimeRole); | ||
| 237 | } | ||
| 238 | |||
| 239 | void setData(const QVariant& value, int role) override { | ||
| 240 | qulonglong time_seconds = value.toULongLong(); | ||
| 241 | GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole); | ||
| 242 | GameListItem::setData(value, PlayTimeRole); | ||
| 243 | } | ||
| 244 | |||
| 245 | bool operator<(const QStandardItem& other) const override { | ||
| 246 | return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong(); | ||
| 247 | } | ||
| 248 | }; | ||
| 249 | |||
| 224 | class GameListDir : public GameListItem { | 250 | class GameListDir : public GameListItem { |
| 225 | public: | 251 | public: |
| 226 | static constexpr int GameDirRole = Qt::UserRole + 2; | 252 | static constexpr int GameDirRole = Qt::UserRole + 2; |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index e7fb8a282..588f1dd6e 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 194 | const std::size_t size, const std::vector<u8>& icon, | 194 | const std::size_t size, const std::vector<u8>& icon, |
| 195 | Loader::AppLoader& loader, u64 program_id, | 195 | Loader::AppLoader& loader, u64 program_id, |
| 196 | const CompatibilityList& compatibility_list, | 196 | const CompatibilityList& compatibility_list, |
| 197 | const PlayTime::PlayTimeManager& play_time_manager, | ||
| 197 | const FileSys::PatchManager& patch) { | 198 | const FileSys::PatchManager& patch) { |
| 198 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 199 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 199 | 200 | ||
| @@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 212 | new GameListItemCompat(compatibility), | 213 | new GameListItemCompat(compatibility), |
| 213 | new GameListItem(file_type_string), | 214 | new GameListItem(file_type_string), |
| 214 | new GameListItemSize(size), | 215 | new GameListItemSize(size), |
| 216 | new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), | ||
| 215 | }; | 217 | }; |
| 216 | 218 | ||
| 217 | const auto patch_versions = GetGameListCachedObject( | 219 | const auto patch_versions = GetGameListCachedObject( |
| @@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 227 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, | 229 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, |
| 228 | FileSys::ManualContentProvider* provider_, | 230 | FileSys::ManualContentProvider* provider_, |
| 229 | QVector<UISettings::GameDir>& game_dirs_, | 231 | QVector<UISettings::GameDir>& game_dirs_, |
| 230 | const CompatibilityList& compatibility_list_, Core::System& system_) | 232 | const CompatibilityList& compatibility_list_, |
| 233 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 234 | Core::System& system_) | ||
| 231 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, | 235 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, |
| 232 | compatibility_list{compatibility_list_}, system{system_} {} | 236 | compatibility_list{compatibility_list_}, |
| 237 | play_time_manager{play_time_manager_}, system{system_} {} | ||
| 233 | 238 | ||
| 234 | GameListWorker::~GameListWorker() = default; | 239 | GameListWorker::~GameListWorker() = default; |
| 235 | 240 | ||
| @@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 280 | } | 285 | } |
| 281 | 286 | ||
| 282 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, | 287 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, |
| 283 | program_id, compatibility_list, patch), | 288 | program_id, compatibility_list, play_time_manager, patch), |
| 284 | parent_dir); | 289 | parent_dir); |
| 285 | } | 290 | } |
| 286 | } | 291 | } |
| @@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 357 | 362 | ||
| 358 | emit EntryReady(MakeGameListEntry(physical_name, name, | 363 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 359 | Common::FS::GetSize(physical_name), icon, | 364 | Common::FS::GetSize(physical_name), icon, |
| 360 | *loader, id, compatibility_list, patch), | 365 | *loader, id, compatibility_list, |
| 366 | play_time_manager, patch), | ||
| 361 | parent_dir); | 367 | parent_dir); |
| 362 | } | 368 | } |
| 363 | } else { | 369 | } else { |
| @@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 370 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), | 376 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), |
| 371 | system.GetContentProvider()}; | 377 | system.GetContentProvider()}; |
| 372 | 378 | ||
| 373 | emit EntryReady( | 379 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 374 | MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), | 380 | Common::FS::GetSize(physical_name), icon, |
| 375 | icon, *loader, program_id, compatibility_list, patch), | 381 | *loader, program_id, compatibility_list, |
| 376 | parent_dir); | 382 | play_time_manager, patch), |
| 383 | parent_dir); | ||
| 377 | } | 384 | } |
| 378 | } | 385 | } |
| 379 | } else if (is_dir) { | 386 | } else if (is_dir) { |
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 24a4e92c3..2bb0a0cb6 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include <QString> | 13 | #include <QString> |
| 14 | 14 | ||
| 15 | #include "yuzu/compatibility_list.h" | 15 | #include "yuzu/compatibility_list.h" |
| 16 | #include "yuzu/play_time_manager.h" | ||
| 16 | 17 | ||
| 17 | namespace Core { | 18 | namespace Core { |
| 18 | class System; | 19 | class System; |
| @@ -36,7 +37,9 @@ public: | |||
| 36 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 37 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 37 | FileSys::ManualContentProvider* provider_, | 38 | FileSys::ManualContentProvider* provider_, |
| 38 | QVector<UISettings::GameDir>& game_dirs_, | 39 | QVector<UISettings::GameDir>& game_dirs_, |
| 39 | const CompatibilityList& compatibility_list_, Core::System& system_); | 40 | const CompatibilityList& compatibility_list_, |
| 41 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 42 | Core::System& system_); | ||
| 40 | ~GameListWorker() override; | 43 | ~GameListWorker() override; |
| 41 | 44 | ||
| 42 | /// Starts the processing of directory tree information. | 45 | /// Starts the processing of directory tree information. |
| @@ -76,6 +79,7 @@ private: | |||
| 76 | FileSys::ManualContentProvider* provider; | 79 | FileSys::ManualContentProvider* provider; |
| 77 | QVector<UISettings::GameDir>& game_dirs; | 80 | QVector<UISettings::GameDir>& game_dirs; |
| 78 | const CompatibilityList& compatibility_list; | 81 | const CompatibilityList& compatibility_list; |
| 82 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 79 | 83 | ||
| 80 | QStringList watch_list; | 84 | QStringList watch_list; |
| 81 | std::atomic_bool stop_processing; | 85 | std::atomic_bool stop_processing; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7a93921d7..89361fa3f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -151,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 151 | #include "yuzu/install_dialog.h" | 151 | #include "yuzu/install_dialog.h" |
| 152 | #include "yuzu/loading_screen.h" | 152 | #include "yuzu/loading_screen.h" |
| 153 | #include "yuzu/main.h" | 153 | #include "yuzu/main.h" |
| 154 | #include "yuzu/play_time_manager.h" | ||
| 154 | #include "yuzu/startup_checks.h" | 155 | #include "yuzu/startup_checks.h" |
| 155 | #include "yuzu/uisettings.h" | 156 | #include "yuzu/uisettings.h" |
| 156 | #include "yuzu/util/clickable_label.h" | 157 | #include "yuzu/util/clickable_label.h" |
| @@ -339,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 339 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); | 340 | SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); |
| 340 | discord_rpc->Update(); | 341 | discord_rpc->Update(); |
| 341 | 342 | ||
| 343 | play_time_manager = std::make_unique<PlayTime::PlayTimeManager>(); | ||
| 344 | |||
| 342 | system->GetRoomNetwork().Init(); | 345 | system->GetRoomNetwork().Init(); |
| 343 | 346 | ||
| 344 | RegisterMetaTypes(); | 347 | RegisterMetaTypes(); |
| @@ -987,7 +990,7 @@ void GMainWindow::InitializeWidgets() { | |||
| 987 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); | 990 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); |
| 988 | render_window->hide(); | 991 | render_window->hide(); |
| 989 | 992 | ||
| 990 | game_list = new GameList(vfs, provider.get(), *system, this); | 993 | game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); |
| 991 | ui->horizontalLayout->addWidget(game_list); | 994 | ui->horizontalLayout->addWidget(game_list); |
| 992 | 995 | ||
| 993 | game_list_placeholder = new GameListPlaceholder(this); | 996 | game_list_placeholder = new GameListPlaceholder(this); |
| @@ -1448,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { | |||
| 1448 | Settings::values.audio_muted = false; | 1451 | Settings::values.audio_muted = false; |
| 1449 | auto_muted = false; | 1452 | auto_muted = false; |
| 1450 | } | 1453 | } |
| 1454 | UpdateVolumeUI(); | ||
| 1451 | } | 1455 | } |
| 1452 | } | 1456 | } |
| 1453 | 1457 | ||
| @@ -1461,6 +1465,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1461 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, | 1465 | connect(game_list, &GameList::RemoveInstalledEntryRequested, this, |
| 1462 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1466 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1463 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1467 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1468 | connect(game_list, &GameList::RemovePlayTimeRequested, this, | ||
| 1469 | &GMainWindow::OnGameListRemovePlayTimeData); | ||
| 1464 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1470 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1465 | connect(game_list, &GameList::VerifyIntegrityRequested, this, | 1471 | connect(game_list, &GameList::VerifyIntegrityRequested, this, |
| 1466 | &GMainWindow::OnGameListVerifyIntegrity); | 1472 | &GMainWindow::OnGameListVerifyIntegrity); |
| @@ -2535,6 +2541,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ | |||
| 2535 | } | 2541 | } |
| 2536 | } | 2542 | } |
| 2537 | 2543 | ||
| 2544 | void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { | ||
| 2545 | if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"), | ||
| 2546 | QMessageBox::Yes | QMessageBox::No, | ||
| 2547 | QMessageBox::No) != QMessageBox::Yes) { | ||
| 2548 | return; | ||
| 2549 | } | ||
| 2550 | |||
| 2551 | play_time_manager->ResetProgramPlayTime(program_id); | ||
| 2552 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 2553 | } | ||
| 2554 | |||
| 2538 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { | 2555 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { |
| 2539 | const auto target_file_name = [target] { | 2556 | const auto target_file_name = [target] { |
| 2540 | switch (target) { | 2557 | switch (target) { |
| @@ -3372,6 +3389,9 @@ void GMainWindow::OnStartGame() { | |||
| 3372 | UpdateMenuState(); | 3389 | UpdateMenuState(); |
| 3373 | OnTasStateChanged(); | 3390 | OnTasStateChanged(); |
| 3374 | 3391 | ||
| 3392 | play_time_manager->SetProgramId(system->GetApplicationProcessProgramID()); | ||
| 3393 | play_time_manager->Start(); | ||
| 3394 | |||
| 3375 | discord_rpc->Update(); | 3395 | discord_rpc->Update(); |
| 3376 | } | 3396 | } |
| 3377 | 3397 | ||
| @@ -3387,6 +3407,7 @@ void GMainWindow::OnRestartGame() { | |||
| 3387 | 3407 | ||
| 3388 | void GMainWindow::OnPauseGame() { | 3408 | void GMainWindow::OnPauseGame() { |
| 3389 | emu_thread->SetRunning(false); | 3409 | emu_thread->SetRunning(false); |
| 3410 | play_time_manager->Stop(); | ||
| 3390 | UpdateMenuState(); | 3411 | UpdateMenuState(); |
| 3391 | AllowOSSleep(); | 3412 | AllowOSSleep(); |
| 3392 | } | 3413 | } |
| @@ -3407,6 +3428,9 @@ void GMainWindow::OnStopGame() { | |||
| 3407 | return; | 3428 | return; |
| 3408 | } | 3429 | } |
| 3409 | 3430 | ||
| 3431 | play_time_manager->Stop(); | ||
| 3432 | // Update game list to show new play time | ||
| 3433 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 3410 | if (OnShutdownBegin()) { | 3434 | if (OnShutdownBegin()) { |
| 3411 | OnShutdownBeginDialog(); | 3435 | OnShutdownBeginDialog(); |
| 3412 | } else { | 3436 | } else { |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 52028234c..c1872ecd4 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -81,6 +81,10 @@ namespace DiscordRPC { | |||
| 81 | class DiscordInterface; | 81 | class DiscordInterface; |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | namespace PlayTime { | ||
| 85 | class PlayTimeManager; | ||
| 86 | } | ||
| 87 | |||
| 84 | namespace FileSys { | 88 | namespace FileSys { |
| 85 | class ContentProvider; | 89 | class ContentProvider; |
| 86 | class ManualContentProvider; | 90 | class ManualContentProvider; |
| @@ -323,6 +327,7 @@ private slots: | |||
| 323 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); | 327 | void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); |
| 324 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 328 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 325 | const std::string& game_path); | 329 | const std::string& game_path); |
| 330 | void OnGameListRemovePlayTimeData(u64 program_id); | ||
| 326 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 331 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 327 | void OnGameListVerifyIntegrity(const std::string& game_path); | 332 | void OnGameListVerifyIntegrity(const std::string& game_path); |
| 328 | void OnGameListCopyTID(u64 program_id); | 333 | void OnGameListCopyTID(u64 program_id); |
| @@ -389,6 +394,7 @@ private: | |||
| 389 | void RemoveVulkanDriverPipelineCache(u64 program_id); | 394 | void RemoveVulkanDriverPipelineCache(u64 program_id); |
| 390 | void RemoveAllTransferableShaderCaches(u64 program_id); | 395 | void RemoveAllTransferableShaderCaches(u64 program_id); |
| 391 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); | 396 | void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); |
| 397 | void RemovePlayTimeData(u64 program_id); | ||
| 392 | void RemoveCacheStorage(u64 program_id); | 398 | void RemoveCacheStorage(u64 program_id); |
| 393 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, | 399 | bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |
| 394 | u64* selected_title_id, u8* selected_content_record_type); | 400 | u64* selected_title_id, u8* selected_content_record_type); |
| @@ -428,6 +434,7 @@ private: | |||
| 428 | 434 | ||
| 429 | std::unique_ptr<Core::System> system; | 435 | std::unique_ptr<Core::System> system; |
| 430 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | 436 | std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; |
| 437 | std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager; | ||
| 431 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; | 438 | std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; |
| 432 | 439 | ||
| 433 | MultiplayerState* multiplayer_state = nullptr; | 440 | MultiplayerState* multiplayer_state = nullptr; |
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp new file mode 100644 index 000000000..155c36b7d --- /dev/null +++ b/src/yuzu/play_time_manager.cpp | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "core/hle/service/acc/profile_manager.h" | ||
| 12 | #include "yuzu/play_time_manager.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | struct PlayTimeElement { | ||
| 19 | ProgramId program_id; | ||
| 20 | PlayTime play_time; | ||
| 21 | }; | ||
| 22 | |||
| 23 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() { | ||
| 24 | const Service::Account::ProfileManager manager; | ||
| 25 | const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user)); | ||
| 26 | if (!uuid.has_value()) { | ||
| 27 | return std::nullopt; | ||
| 28 | } | ||
| 29 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / | ||
| 30 | uuid->RawString().append(".bin"); | ||
| 31 | } | ||
| 32 | |||
| 33 | [[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) { | ||
| 34 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 35 | |||
| 36 | if (!filename.has_value()) { | ||
| 37 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | out_play_time_db.clear(); | ||
| 42 | |||
| 43 | if (Common::FS::Exists(filename.value())) { | ||
| 44 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, | ||
| 45 | Common::FS::FileType::BinaryFile}; | ||
| 46 | if (!file.IsOpen()) { | ||
| 47 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 48 | Common::FS::PathToUTF8String(filename.value())); | ||
| 49 | return false; | ||
| 50 | } | ||
| 51 | |||
| 52 | const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement); | ||
| 53 | std::vector<PlayTimeElement> elements(num_elements); | ||
| 54 | |||
| 55 | if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) { | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | |||
| 59 | for (const auto& [program_id, play_time] : elements) { | ||
| 60 | if (program_id != 0) { | ||
| 61 | out_play_time_db[program_id] = play_time; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | return true; | ||
| 67 | } | ||
| 68 | |||
| 69 | [[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) { | ||
| 70 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 71 | |||
| 72 | if (!filename.has_value()) { | ||
| 73 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 74 | return false; | ||
| 75 | } | ||
| 76 | |||
| 77 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, | ||
| 78 | Common::FS::FileType::BinaryFile}; | ||
| 79 | if (!file.IsOpen()) { | ||
| 80 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 81 | Common::FS::PathToUTF8String(filename.value())); | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | std::vector<PlayTimeElement> elements; | ||
| 86 | elements.reserve(play_time_db.size()); | ||
| 87 | |||
| 88 | for (auto& [program_id, play_time] : play_time_db) { | ||
| 89 | if (program_id != 0) { | ||
| 90 | elements.push_back(PlayTimeElement{program_id, play_time}); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | return file.WriteSpan<PlayTimeElement>(elements) == elements.size(); | ||
| 95 | } | ||
| 96 | |||
| 97 | } // namespace | ||
| 98 | |||
| 99 | PlayTimeManager::PlayTimeManager() { | ||
| 100 | if (!ReadPlayTimeFile(database)) { | ||
| 101 | LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default."); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | PlayTimeManager::~PlayTimeManager() { | ||
| 106 | Save(); | ||
| 107 | } | ||
| 108 | |||
| 109 | void PlayTimeManager::SetProgramId(u64 program_id) { | ||
| 110 | running_program_id = program_id; | ||
| 111 | } | ||
| 112 | |||
| 113 | void PlayTimeManager::Start() { | ||
| 114 | play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); }); | ||
| 115 | } | ||
| 116 | |||
| 117 | void PlayTimeManager::Stop() { | ||
| 118 | play_time_thread = {}; | ||
| 119 | } | ||
| 120 | |||
| 121 | void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { | ||
| 122 | Common::SetCurrentThreadName("PlayTimeReport"); | ||
| 123 | |||
| 124 | using namespace std::literals::chrono_literals; | ||
| 125 | using std::chrono::seconds; | ||
| 126 | using std::chrono::steady_clock; | ||
| 127 | |||
| 128 | auto timestamp = steady_clock::now(); | ||
| 129 | |||
| 130 | const auto GetDuration = [&]() -> u64 { | ||
| 131 | const auto last_timestamp = std::exchange(timestamp, steady_clock::now()); | ||
| 132 | const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp); | ||
| 133 | return static_cast<u64>(duration.count()); | ||
| 134 | }; | ||
| 135 | |||
| 136 | while (!stop_token.stop_requested()) { | ||
| 137 | Common::StoppableTimedWait(stop_token, 30s); | ||
| 138 | |||
| 139 | database[running_program_id] += GetDuration(); | ||
| 140 | Save(); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | void PlayTimeManager::Save() { | ||
| 145 | if (!WritePlayTimeFile(database)) { | ||
| 146 | LOG_ERROR(Frontend, "Failed to update play time database!"); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | u64 PlayTimeManager::GetPlayTime(u64 program_id) const { | ||
| 151 | auto it = database.find(program_id); | ||
| 152 | if (it != database.end()) { | ||
| 153 | return it->second; | ||
| 154 | } else { | ||
| 155 | return 0; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void PlayTimeManager::ResetProgramPlayTime(u64 program_id) { | ||
| 160 | database.erase(program_id); | ||
| 161 | Save(); | ||
| 162 | } | ||
| 163 | |||
| 164 | QString ReadablePlayTime(qulonglong time_seconds) { | ||
| 165 | if (time_seconds == 0) { | ||
| 166 | return {}; | ||
| 167 | } | ||
| 168 | const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0); | ||
| 169 | const auto time_hours = static_cast<double>(time_seconds) / 3600; | ||
| 170 | const bool is_minutes = time_minutes < 60; | ||
| 171 | const char* unit = is_minutes ? "m" : "h"; | ||
| 172 | const auto value = is_minutes ? time_minutes : time_hours; | ||
| 173 | |||
| 174 | return QStringLiteral("%L1 %2") | ||
| 175 | .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0) | ||
| 176 | .arg(QString::fromUtf8(unit)); | ||
| 177 | } | ||
| 178 | |||
| 179 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h new file mode 100644 index 000000000..5f96f3447 --- /dev/null +++ b/src/yuzu/play_time_manager.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <QString> | ||
| 7 | |||
| 8 | #include <map> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/polyfill_thread.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | using ProgramId = u64; | ||
| 17 | using PlayTime = u64; | ||
| 18 | using PlayTimeDatabase = std::map<ProgramId, PlayTime>; | ||
| 19 | |||
| 20 | class PlayTimeManager { | ||
| 21 | public: | ||
| 22 | explicit PlayTimeManager(); | ||
| 23 | ~PlayTimeManager(); | ||
| 24 | |||
| 25 | YUZU_NON_COPYABLE(PlayTimeManager); | ||
| 26 | YUZU_NON_MOVEABLE(PlayTimeManager); | ||
| 27 | |||
| 28 | u64 GetPlayTime(u64 program_id) const; | ||
| 29 | void ResetProgramPlayTime(u64 program_id); | ||
| 30 | void SetProgramId(u64 program_id); | ||
| 31 | void Start(); | ||
| 32 | void Stop(); | ||
| 33 | |||
| 34 | private: | ||
| 35 | PlayTimeDatabase database; | ||
| 36 | u64 running_program_id; | ||
| 37 | std::jthread play_time_thread; | ||
| 38 | void AutoTimestamp(std::stop_token stop_token); | ||
| 39 | void Save(); | ||
| 40 | }; | ||
| 41 | |||
| 42 | QString ReadablePlayTime(qulonglong time_seconds); | ||
| 43 | |||
| 44 | } // namespace PlayTime | ||
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 8efd63f31..975008159 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -103,7 +103,7 @@ struct Values { | |||
| 103 | true, | 103 | true, |
| 104 | true}; | 104 | true}; |
| 105 | Setting<bool> mute_when_in_background{ | 105 | Setting<bool> mute_when_in_background{ |
| 106 | linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default, | 106 | linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default, |
| 107 | true, true}; | 107 | true, true}; |
| 108 | Setting<bool> hide_mouse{ | 108 | Setting<bool> hide_mouse{ |
| 109 | linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, | 109 | linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, |
| @@ -183,6 +183,9 @@ struct Values { | |||
| 183 | Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; | 183 | Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; |
| 184 | Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; | 184 | Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; |
| 185 | 185 | ||
| 186 | // Play time | ||
| 187 | Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList}; | ||
| 188 | |||
| 186 | bool configuration_applied; | 189 | bool configuration_applied; |
| 187 | bool reset_to_defaults; | 190 | bool reset_to_defaults; |
| 188 | bool shortcut_already_warned{false}; | 191 | bool shortcut_already_warned{false}; |