diff options
Diffstat (limited to 'src')
146 files changed, 10604 insertions, 1198 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f18239edb..9d0af02fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,15 @@ else() add_compile_options("-static") endif() endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) + # GNU ar: Create thin archive files. + # Requires binutils-2.19 or later. + set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>") + set(CMAKE_C_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>") + set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>") + set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>") + endif() endif() add_subdirectory(common) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9b0c3db68..9afc6105d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -15,6 +15,10 @@ endif () if (DEFINED ENV{DISPLAYVERSION}) set(DISPLAY_VERSION $ENV{DISPLAYVERSION}) endif () + +# Pass the path to git to the GenerateSCMRev.cmake as well +find_package(Git QUIET) + add_custom_command(OUTPUT scm_rev.cpp COMMAND ${CMAKE_COMMAND} -DSRC_DIR="${CMAKE_SOURCE_DIR}" @@ -23,6 +27,7 @@ add_custom_command(OUTPUT scm_rev.cpp -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}" -DBUILD_TAG="${BUILD_TAG}" -DBUILD_ID="${DISPLAY_VERSION}" + -DGIT_EXECUTABLE="${GIT_EXECUTABLE}" -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" DEPENDS # WARNING! It was too much work to try and make a common location for this list, diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp index f53a8d193..200c6489a 100644 --- a/src/common/telemetry.cpp +++ b/src/common/telemetry.cpp @@ -44,20 +44,6 @@ template class Field<std::string>; template class Field<const char*>; template class Field<std::chrono::microseconds>; -#ifdef ARCHITECTURE_x86_64 -static const char* CpuVendorToStr(Common::CPUVendor vendor) { - switch (vendor) { - case Common::CPUVendor::INTEL: - return "Intel"; - case Common::CPUVendor::AMD: - return "Amd"; - case Common::CPUVendor::OTHER: - return "Other"; - } - UNREACHABLE(); -} -#endif - void AppendBuildInfo(FieldCollection& fc) { const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty); @@ -71,7 +57,6 @@ void AppendCPUInfo(FieldCollection& fc) { #ifdef ARCHITECTURE_x86_64 fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string); - fc.AddField(FieldType::UserSystem, "CPU_Vendor", CpuVendorToStr(Common::GetCPUCaps().vendor)); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2); diff --git a/src/common/thread.h b/src/common/thread.h index 0cfd98be6..2fc071685 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -28,6 +28,15 @@ public: is_set = false; } + template <class Duration> + bool WaitFor(const std::chrono::duration<Duration>& time) { + std::unique_lock lk{mutex}; + if (!condvar.wait_for(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + template <class Clock, class Duration> bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { std::unique_lock lk{mutex}; diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 2dfcd39c8..c9349a6b4 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -3,8 +3,6 @@ // Refer to the license.txt file included. #include <cstring> -#include <string> -#include <thread> #include "common/common_types.h" #include "common/x64/cpu_detect.h" @@ -51,8 +49,6 @@ namespace Common { static CPUCaps Detect() { CPUCaps caps = {}; - caps.num_cores = std::thread::hardware_concurrency(); - // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support // yuzu at all anyway @@ -70,12 +66,6 @@ static CPUCaps Detect() { __cpuid(cpu_id, 0x80000000); u32 max_ex_fn = cpu_id[0]; - if (!strcmp(caps.brand_string, "GenuineIntel")) - caps.vendor = CPUVendor::INTEL; - else if (!strcmp(caps.brand_string, "AuthenticAMD")) - caps.vendor = CPUVendor::AMD; - else - caps.vendor = CPUVendor::OTHER; // Set reasonable default brand string even if brand string not available strcpy(caps.cpu_string, caps.brand_string); @@ -96,15 +86,9 @@ static CPUCaps Detect() { caps.sse4_1 = true; if ((cpu_id[2] >> 20) & 1) caps.sse4_2 = true; - if ((cpu_id[2] >> 22) & 1) - caps.movbe = true; if ((cpu_id[2] >> 25) & 1) caps.aes = true; - if ((cpu_id[3] >> 24) & 1) { - caps.fxsave_fxrstor = true; - } - // AVX support requires 3 separate checks: // - Is the AVX bit set in CPUID? // - Is the XSAVE bit set in CPUID? @@ -129,8 +113,6 @@ static CPUCaps Detect() { } } - caps.flush_to_zero = caps.sse; - if (max_ex_fn >= 0x80000004) { // Extract CPU model string __cpuid(cpu_id, 0x80000002); @@ -144,14 +126,8 @@ static CPUCaps Detect() { if (max_ex_fn >= 0x80000001) { // Check for more features __cpuid(cpu_id, 0x80000001); - if (cpu_id[2] & 1) - caps.lahf_sahf_64 = true; - if ((cpu_id[2] >> 5) & 1) - caps.lzcnt = true; if ((cpu_id[2] >> 16) & 1) caps.fma4 = true; - if ((cpu_id[3] >> 29) & 1) - caps.long_mode = true; } return caps; @@ -162,48 +138,4 @@ const CPUCaps& GetCPUCaps() { return caps; } -std::string GetCPUCapsString() { - auto caps = GetCPUCaps(); - - std::string sum(caps.cpu_string); - sum += " ("; - sum += caps.brand_string; - sum += ")"; - - if (caps.sse) - sum += ", SSE"; - if (caps.sse2) { - sum += ", SSE2"; - if (!caps.flush_to_zero) - sum += " (without DAZ)"; - } - - if (caps.sse3) - sum += ", SSE3"; - if (caps.ssse3) - sum += ", SSSE3"; - if (caps.sse4_1) - sum += ", SSE4.1"; - if (caps.sse4_2) - sum += ", SSE4.2"; - if (caps.avx) - sum += ", AVX"; - if (caps.avx2) - sum += ", AVX2"; - if (caps.bmi1) - sum += ", BMI1"; - if (caps.bmi2) - sum += ", BMI2"; - if (caps.fma) - sum += ", FMA"; - if (caps.aes) - sum += ", AES"; - if (caps.movbe) - sum += ", MOVBE"; - if (caps.long_mode) - sum += ", 64-bit support"; - - return sum; -} - } // namespace Common diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index 0af3a8adb..20f2ba234 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h @@ -4,23 +4,12 @@ #pragma once -#include <string> - namespace Common { -/// x86/x64 CPU vendors that may be detected by this module -enum class CPUVendor { - INTEL, - AMD, - OTHER, -}; - /// x86/x64 CPU capabilities that may be detected by this module struct CPUCaps { - CPUVendor vendor; char cpu_string[0x21]; char brand_string[0x41]; - int num_cores; bool sse; bool sse2; bool sse3; @@ -35,20 +24,6 @@ struct CPUCaps { bool fma; bool fma4; bool aes; - - // Support for the FXSAVE and FXRSTOR instructions - bool fxsave_fxrstor; - - bool movbe; - - // This flag indicates that the hardware supports some mode in which denormal inputs and outputs - // are automatically set to (signed) zero. - bool flush_to_zero; - - // Support for LAHF and SAHF instructions in 64-bit mode - bool lahf_sahf_64; - - bool long_mode; }; /** @@ -57,10 +32,4 @@ struct CPUCaps { */ const CPUCaps& GetCPUCaps(); -/** - * Gets a string summary of the name and supported capabilities of the host CPU - * @return String summary - */ -std::string GetCPUCapsString(); - } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7fd226050..1a3647a67 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -96,6 +96,8 @@ add_library(core STATIC file_sys/system_archive/system_archive.h file_sys/system_archive/system_version.cpp file_sys/system_archive/system_version.h + file_sys/system_archive/time_zone_binary.cpp + file_sys/system_archive/time_zone_binary.h file_sys/vfs.cpp file_sys/vfs.h file_sys/vfs_concat.cpp @@ -461,12 +463,40 @@ add_library(core STATIC hle/service/spl/spl.h hle/service/ssl/ssl.cpp hle/service/ssl/ssl.h + hle/service/time/clock_types.h + hle/service/time/ephemeral_network_system_clock_context_writer.h + hle/service/time/ephemeral_network_system_clock_core.h + hle/service/time/errors.h hle/service/time/interface.cpp hle/service/time/interface.h + hle/service/time/local_system_clock_context_writer.h + hle/service/time/network_system_clock_context_writer.h + hle/service/time/standard_local_system_clock_core.h + hle/service/time/standard_network_system_clock_core.h + hle/service/time/standard_steady_clock_core.cpp + hle/service/time/standard_steady_clock_core.h + hle/service/time/standard_user_system_clock_core.cpp + hle/service/time/standard_user_system_clock_core.h + hle/service/time/steady_clock_core.h + hle/service/time/system_clock_context_update_callback.cpp + hle/service/time/system_clock_context_update_callback.h + hle/service/time/system_clock_core.cpp + hle/service/time/system_clock_core.h + hle/service/time/tick_based_steady_clock_core.cpp + hle/service/time/tick_based_steady_clock_core.h hle/service/time/time.cpp hle/service/time/time.h + hle/service/time/time_manager.cpp + hle/service/time/time_manager.h hle/service/time/time_sharedmemory.cpp hle/service/time/time_sharedmemory.h + hle/service/time/time_zone_content_manager.cpp + hle/service/time/time_zone_content_manager.h + hle/service/time/time_zone_manager.cpp + hle/service/time/time_zone_manager.h + hle/service/time/time_zone_service.cpp + hle/service/time/time_zone_service.h + hle/service/time/time_zone_types.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/display/vi_display.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index f8c7f0efd..e825c0526 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -141,6 +141,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit(Common::PageTable& pag config.page_table = reinterpret_cast<void**>(page_table.pointers.data()); config.page_table_address_space_bits = address_space_bits; config.silently_mirror_page_table = false; + config.absolute_offset_page_table = true; // Multi-process state config.processor_id = core_index; diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index e93d100a5..a6696024e 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -9,6 +9,7 @@ #include "core/file_sys/system_archive/shared_font.h" #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" +#include "core/file_sys/system_archive/time_zone_binary.h" namespace FileSys::SystemArchive { @@ -38,7 +39,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI {0x010000000000080B, "LocalNews", nullptr}, {0x010000000000080C, "Eula", nullptr}, {0x010000000000080D, "UrlBlackList", nullptr}, - {0x010000000000080E, "TimeZoneBinary", nullptr}, + {0x010000000000080E, "TimeZoneBinary", &TimeZoneBinary}, {0x010000000000080F, "CertStoreCruiser", nullptr}, {0x0100000000000810, "FontNintendoExtension", &FontNintendoExtension}, {0x0100000000000811, "FontStandard", &FontStandard}, diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp new file mode 100644 index 000000000..9806bd197 --- /dev/null +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -0,0 +1,657 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/swap.h" +#include "core/file_sys/system_archive/time_zone_binary.h" +#include "core/file_sys/vfs_vector.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace FileSys::SystemArchive { + +static constexpr std::array<u8, 9633> LOCATION_NAMES{ + 0x43, 0x45, 0x54, 0x0d, 0x0a, 0x43, 0x53, 0x54, 0x36, 0x43, 0x44, 0x54, 0x0d, 0x0a, 0x43, 0x75, + 0x62, 0x61, 0x0d, 0x0a, 0x45, 0x45, 0x54, 0x0d, 0x0a, 0x45, 0x67, 0x79, 0x70, 0x74, 0x0d, 0x0a, + 0x45, 0x69, 0x72, 0x65, 0x0d, 0x0a, 0x45, 0x53, 0x54, 0x0d, 0x0a, 0x45, 0x53, 0x54, 0x35, 0x45, + 0x44, 0x54, 0x0d, 0x0a, 0x47, 0x42, 0x0d, 0x0a, 0x47, 0x42, 0x2d, 0x45, 0x69, 0x72, 0x65, 0x0d, + 0x0a, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x47, 0x4d, 0x54, 0x2b, 0x30, 0x0d, 0x0a, 0x47, 0x4d, 0x54, + 0x2d, 0x30, 0x0d, 0x0a, 0x47, 0x4d, 0x54, 0x30, 0x0d, 0x0a, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x77, + 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x48, 0x6f, 0x6e, 0x67, 0x6b, 0x6f, 0x6e, 0x67, 0x0d, 0x0a, 0x48, + 0x53, 0x54, 0x0d, 0x0a, 0x49, 0x63, 0x65, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x49, 0x72, 0x61, + 0x6e, 0x0d, 0x0a, 0x49, 0x73, 0x72, 0x61, 0x65, 0x6c, 0x0d, 0x0a, 0x4a, 0x61, 0x6d, 0x61, 0x69, + 0x63, 0x61, 0x0d, 0x0a, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x0d, 0x0a, 0x4b, 0x77, 0x61, 0x6a, 0x61, + 0x6c, 0x65, 0x69, 0x6e, 0x0d, 0x0a, 0x4c, 0x69, 0x62, 0x79, 0x61, 0x0d, 0x0a, 0x4d, 0x45, 0x54, + 0x0d, 0x0a, 0x4d, 0x53, 0x54, 0x0d, 0x0a, 0x4d, 0x53, 0x54, 0x37, 0x4d, 0x44, 0x54, 0x0d, 0x0a, + 0x4e, 0x61, 0x76, 0x61, 0x6a, 0x6f, 0x0d, 0x0a, 0x4e, 0x5a, 0x0d, 0x0a, 0x4e, 0x5a, 0x2d, 0x43, + 0x48, 0x41, 0x54, 0x0d, 0x0a, 0x50, 0x6f, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x50, 0x6f, 0x72, + 0x74, 0x75, 0x67, 0x61, 0x6c, 0x0d, 0x0a, 0x50, 0x52, 0x43, 0x0d, 0x0a, 0x50, 0x53, 0x54, 0x38, + 0x50, 0x44, 0x54, 0x0d, 0x0a, 0x52, 0x4f, 0x43, 0x0d, 0x0a, 0x52, 0x4f, 0x4b, 0x0d, 0x0a, 0x53, + 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x0d, 0x0a, 0x54, 0x75, 0x72, 0x6b, 0x65, 0x79, + 0x0d, 0x0a, 0x55, 0x43, 0x54, 0x0d, 0x0a, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c, + 0x0d, 0x0a, 0x55, 0x54, 0x43, 0x0d, 0x0a, 0x57, 0x2d, 0x53, 0x55, 0x0d, 0x0a, 0x57, 0x45, 0x54, + 0x0d, 0x0a, 0x5a, 0x75, 0x6c, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, + 0x62, 0x69, 0x64, 0x6a, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, + 0x63, 0x63, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x64, + 0x69, 0x73, 0x5f, 0x41, 0x62, 0x61, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x41, 0x6c, 0x67, 0x69, 0x65, 0x72, 0x73, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x41, 0x73, 0x6d, 0x61, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x41, 0x73, 0x6d, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, + 0x61, 0x6d, 0x61, 0x6b, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, + 0x6e, 0x67, 0x75, 0x69, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x6e, + 0x6a, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x73, + 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x74, + 0x79, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x72, 0x61, 0x7a, + 0x7a, 0x61, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x42, 0x75, 0x6a, 0x75, 0x6d, 0x62, 0x75, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x43, 0x61, 0x69, 0x72, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x43, 0x61, 0x73, 0x61, 0x62, 0x6c, 0x61, 0x6e, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x43, 0x65, 0x75, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x43, 0x6f, 0x6e, 0x61, 0x6b, 0x72, 0x79, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x44, 0x61, 0x6b, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, + 0x61, 0x72, 0x5f, 0x65, 0x73, 0x5f, 0x53, 0x61, 0x6c, 0x61, 0x61, 0x6d, 0x0d, 0x0a, 0x41, 0x66, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6a, 0x69, 0x62, 0x6f, 0x75, 0x74, 0x69, 0x0d, 0x0a, 0x41, + 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6f, 0x75, 0x61, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x66, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x6c, 0x5f, 0x41, 0x61, 0x69, 0x75, 0x6e, 0x0d, 0x0a, 0x41, + 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x72, 0x65, 0x65, 0x74, 0x6f, 0x77, 0x6e, 0x0d, 0x0a, + 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x61, 0x62, 0x6f, 0x72, 0x6f, 0x6e, 0x65, 0x0d, + 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x72, 0x61, 0x72, 0x65, 0x0d, 0x0a, + 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x6f, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x73, 0x62, + 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x75, 0x62, 0x61, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x70, 0x61, 0x6c, 0x61, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x68, 0x61, 0x72, 0x74, 0x6f, 0x75, + 0x6d, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x67, 0x61, 0x6c, 0x69, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x6e, 0x73, 0x68, 0x61, 0x73, + 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x61, 0x67, 0x6f, 0x73, 0x0d, + 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x62, 0x72, 0x65, 0x76, 0x69, 0x6c, + 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x6d, 0x65, 0x0d, + 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x61, 0x6e, 0x64, 0x61, 0x0d, 0x0a, + 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x62, 0x75, 0x6d, 0x62, 0x61, 0x73, 0x68, + 0x69, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x73, 0x61, 0x6b, 0x61, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6c, 0x61, 0x62, 0x6f, 0x0d, + 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x70, 0x75, 0x74, 0x6f, 0x0d, 0x0a, + 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x73, 0x65, 0x72, 0x75, 0x0d, 0x0a, 0x41, + 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x62, 0x61, 0x62, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41, + 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x67, 0x61, 0x64, 0x69, 0x73, 0x68, 0x75, 0x0d, + 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x72, 0x6f, 0x76, 0x69, 0x61, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x61, 0x69, 0x72, 0x6f, 0x62, 0x69, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x64, 0x6a, 0x61, 0x6d, 0x65, 0x6e, + 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x61, 0x6d, 0x65, 0x79, + 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x75, 0x61, 0x6b, 0x63, 0x68, + 0x6f, 0x74, 0x74, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x75, 0x61, 0x67, + 0x61, 0x64, 0x6f, 0x75, 0x67, 0x6f, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x50, 0x6f, 0x72, 0x74, 0x6f, 0x2d, 0x4e, 0x6f, 0x76, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6f, 0x5f, 0x54, 0x6f, 0x6d, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6d, 0x62, 0x75, 0x6b, 0x74, 0x75, 0x0d, 0x0a, 0x41, 0x66, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x72, 0x69, 0x70, 0x6f, 0x6c, 0x69, 0x0d, 0x0a, 0x41, 0x66, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x75, 0x6e, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x64, 0x68, 0x6f, 0x65, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x61, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x67, 0x75, 0x69, 0x6c, 0x6c, 0x61, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x74, 0x69, 0x67, 0x75, 0x61, 0x0d, + 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x61, 0x67, 0x75, 0x61, 0x69, + 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x75, 0x62, + 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x73, 0x75, 0x6e, 0x63, + 0x69, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x74, 0x69, + 0x6b, 0x6f, 0x6b, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, + 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68, + 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x69, + 0x61, 0x5f, 0x42, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x61, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x72, 0x62, 0x61, 0x64, 0x6f, 0x73, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x65, 0x6c, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x65, 0x6c, 0x69, 0x7a, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x63, 0x2d, 0x53, 0x61, 0x62, 0x6c, 0x6f, + 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, 0x61, 0x5f, 0x56, + 0x69, 0x73, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, + 0x67, 0x6f, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, + 0x69, 0x73, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x75, 0x65, + 0x6e, 0x6f, 0x73, 0x5f, 0x41, 0x69, 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x42, 0x61, 0x79, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x70, 0x6f, 0x5f, + 0x47, 0x72, 0x61, 0x6e, 0x64, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x43, 0x61, 0x6e, 0x63, 0x75, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x43, 0x61, 0x72, 0x61, 0x63, 0x61, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x43, 0x61, 0x74, 0x61, 0x6d, 0x61, 0x72, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x79, 0x65, 0x6e, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x79, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x63, 0x61, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x68, 0x75, 0x61, 0x68, 0x75, 0x61, 0x0d, + 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x6f, 0x72, 0x61, 0x6c, 0x5f, 0x48, + 0x61, 0x72, 0x62, 0x6f, 0x75, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x43, 0x6f, 0x72, 0x64, 0x6f, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x43, 0x6f, 0x73, 0x74, 0x61, 0x5f, 0x52, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x69, 0x61, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x61, 0x63, 0x61, 0x6f, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x68, + 0x61, 0x76, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x77, + 0x73, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x77, + 0x73, 0x6f, 0x6e, 0x5f, 0x43, 0x72, 0x65, 0x65, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x44, 0x65, 0x6e, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x44, 0x65, 0x74, 0x72, 0x6f, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x64, 0x6d, 0x6f, 0x6e, 0x74, 0x6f, 0x6e, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x69, 0x72, 0x75, 0x6e, 0x65, 0x70, 0x65, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x6c, 0x5f, 0x53, 0x61, 0x6c, + 0x76, 0x61, 0x64, 0x6f, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, + 0x6e, 0x73, 0x65, 0x6e, 0x61, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x65, 0x7a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x5f, 0x4e, 0x65, 0x6c, 0x73, 0x6f, 0x6e, 0x0d, + 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x5f, 0x57, 0x61, + 0x79, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x6c, 0x61, + 0x63, 0x65, 0x5f, 0x42, 0x61, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x47, 0x6f, 0x64, 0x74, 0x68, 0x61, 0x62, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x47, 0x6f, 0x6f, 0x73, 0x65, 0x5f, 0x42, 0x61, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x61, 0x6e, 0x64, 0x5f, 0x54, 0x75, 0x72, 0x6b, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x65, 0x6e, 0x61, 0x64, 0x61, 0x0d, + 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x65, 0x6c, 0x6f, + 0x75, 0x70, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, + 0x74, 0x65, 0x6d, 0x61, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x47, 0x75, 0x61, 0x79, 0x61, 0x71, 0x75, 0x69, 0x6c, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x47, 0x75, 0x79, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x48, 0x61, 0x6c, 0x69, 0x66, 0x61, 0x78, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x76, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x48, 0x65, 0x72, 0x6d, 0x6f, 0x73, 0x69, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, + 0x6f, 0x6c, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, + 0x75, 0x76, 0x69, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x71, + 0x61, 0x6c, 0x75, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, + 0x61, 0x6d, 0x61, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x4a, 0x75, 0x6a, 0x75, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, + 0x75, 0x6e, 0x65, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, + 0x6e, 0x6f, 0x78, 0x5f, 0x49, 0x4e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x4b, 0x72, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6a, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x50, 0x61, 0x7a, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x6d, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x4c, 0x6f, 0x73, 0x5f, 0x41, 0x6e, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x75, 0x69, 0x73, 0x76, 0x69, 0x6c, 0x6c, + 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x77, 0x65, 0x72, + 0x5f, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x65, 0x69, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x75, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x75, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x67, 0x6f, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x6f, + 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x7a, 0x61, 0x74, + 0x6c, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x6e, + 0x64, 0x6f, 0x7a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, + 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x4d, 0x65, 0x72, 0x69, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x4d, 0x65, 0x74, 0x6c, 0x61, 0x6b, 0x61, 0x74, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x43, 0x69, 0x74, 0x79, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x69, 0x71, 0x75, 0x65, 0x6c, + 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x63, + 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, + 0x74, 0x65, 0x72, 0x72, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x4d, 0x6f, 0x6e, 0x74, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x72, 0x65, 0x61, 0x6c, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x72, 0x61, 0x74, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x61, 0x73, 0x73, 0x61, 0x75, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, 0x59, 0x6f, + 0x72, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x70, 0x69, + 0x67, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x6d, + 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x6f, 0x6e, + 0x68, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x6a, 0x69, 0x6e, + 0x61, 0x67, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e, + 0x61, 0x6d, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e, + 0x67, 0x6e, 0x69, 0x72, 0x74, 0x75, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x61, 0x72, 0x69, 0x62, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x68, 0x6f, 0x65, 0x6e, 0x69, 0x78, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x2d, 0x61, 0x75, 0x2d, 0x50, + 0x72, 0x69, 0x6e, 0x63, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, + 0x6f, 0x72, 0x74, 0x6f, 0x5f, 0x41, 0x63, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x6f, 0x5f, 0x56, 0x65, 0x6c, 0x68, 0x6f, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x66, 0x5f, + 0x53, 0x70, 0x61, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, + 0x75, 0x65, 0x72, 0x74, 0x6f, 0x5f, 0x52, 0x69, 0x63, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x50, 0x75, 0x6e, 0x74, 0x61, 0x5f, 0x41, 0x72, 0x65, 0x6e, 0x61, 0x73, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x61, 0x69, 0x6e, 0x79, 0x5f, + 0x52, 0x69, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, + 0x61, 0x6e, 0x6b, 0x69, 0x6e, 0x5f, 0x49, 0x6e, 0x6c, 0x65, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x63, 0x69, 0x66, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x67, 0x69, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x42, 0x72, 0x61, 0x6e, 0x63, + 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x6f, 0x73, 0x61, 0x72, + 0x69, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, + 0x61, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, + 0x6e, 0x74, 0x61, 0x5f, 0x49, 0x73, 0x61, 0x62, 0x65, 0x6c, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x6f, 0x5f, 0x44, 0x6f, 0x6d, 0x69, + 0x6e, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6f, + 0x5f, 0x50, 0x61, 0x75, 0x6c, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, 0x73, 0x75, 0x6e, 0x64, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x68, 0x69, 0x70, 0x72, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x69, 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x42, 0x61, 0x72, 0x74, 0x68, 0x65, + 0x6c, 0x65, 0x6d, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, + 0x5f, 0x4a, 0x6f, 0x68, 0x6e, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x53, 0x74, 0x5f, 0x4b, 0x69, 0x74, 0x74, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x4c, 0x75, 0x63, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x54, 0x68, 0x6f, 0x6d, 0x61, 0x73, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x56, 0x69, 0x6e, 0x63, 0x65, 0x6e, + 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x77, 0x69, 0x66, 0x74, + 0x5f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x54, 0x65, 0x67, 0x75, 0x63, 0x69, 0x67, 0x61, 0x6c, 0x70, 0x61, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x68, 0x75, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x42, 0x61, + 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6a, 0x75, 0x61, + 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x6f, 0x72, 0x6f, + 0x6e, 0x74, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x6f, 0x72, + 0x74, 0x6f, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x56, 0x61, + 0x6e, 0x63, 0x6f, 0x75, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x56, 0x69, 0x72, 0x67, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x57, 0x68, 0x69, 0x74, 0x65, 0x68, 0x6f, 0x72, 0x73, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, + 0x72, 0x69, 0x63, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x6e, 0x69, 0x70, 0x65, 0x67, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x61, 0x74, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x59, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6b, 0x6e, + 0x69, 0x66, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, + 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x42, 0x75, 0x65, 0x6e, 0x6f, 0x73, 0x5f, 0x41, 0x69, + 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, + 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x61, 0x74, 0x61, 0x6d, 0x61, 0x72, 0x63, 0x61, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, + 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x52, 0x69, 0x76, 0x61, 0x64, 0x61, 0x76, + 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, + 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x6f, 0x72, 0x64, 0x6f, 0x62, 0x61, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, + 0x2f, 0x4a, 0x75, 0x6a, 0x75, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x52, 0x69, 0x6f, + 0x6a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, + 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x4d, 0x65, 0x6e, 0x64, 0x6f, 0x7a, 0x61, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, + 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x47, 0x61, 0x6c, 0x6c, 0x65, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, + 0x2f, 0x53, 0x61, 0x6c, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, + 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4a, 0x75, + 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, + 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4c, 0x75, 0x69, 0x73, 0x0d, 0x0a, + 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, + 0x61, 0x2f, 0x54, 0x75, 0x63, 0x75, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x55, 0x73, 0x68, + 0x75, 0x61, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, + 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, 0x6f, 0x6c, + 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, + 0x61, 0x6e, 0x61, 0x2f, 0x4b, 0x6e, 0x6f, 0x78, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x65, 0x6e, 0x67, + 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, + 0x6e, 0x61, 0x2f, 0x50, 0x65, 0x74, 0x65, 0x72, 0x73, 0x62, 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x54, + 0x65, 0x6c, 0x6c, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x56, 0x65, 0x76, 0x61, 0x79, 0x0d, + 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, + 0x2f, 0x56, 0x69, 0x6e, 0x63, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x61, + 0x6d, 0x61, 0x63, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x65, 0x6e, + 0x74, 0x75, 0x63, 0x6b, 0x79, 0x2f, 0x4c, 0x6f, 0x75, 0x69, 0x73, 0x76, 0x69, 0x6c, 0x6c, 0x65, + 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x65, 0x6e, 0x74, 0x75, 0x63, + 0x6b, 0x79, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x69, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a, 0x41, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, + 0x6f, 0x74, 0x61, 0x2f, 0x42, 0x65, 0x75, 0x6c, 0x61, 0x68, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, + 0x2f, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, + 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, 0x2f, 0x4e, 0x65, + 0x77, 0x5f, 0x53, 0x61, 0x6c, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, + 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x73, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, + 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x76, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6e, 0x74, + 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x75, 0x6d, 0x6f, 0x6e, 0x74, 0x44, 0x55, + 0x72, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, + 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x71, 0x75, 0x61, 0x72, 0x69, 0x65, 0x0d, 0x0a, 0x41, 0x6e, + 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x77, 0x73, 0x6f, 0x6e, 0x0d, + 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x63, 0x4d, 0x75, + 0x72, 0x64, 0x6f, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, + 0x50, 0x61, 0x6c, 0x6d, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, + 0x63, 0x61, 0x2f, 0x52, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, + 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x5f, 0x50, 0x6f, 0x6c, + 0x65, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x79, + 0x6f, 0x77, 0x61, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, + 0x54, 0x72, 0x6f, 0x6c, 0x6c, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, + 0x61, 0x2f, 0x56, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0d, 0x0a, 0x41, 0x72, 0x63, 0x74, 0x69, 0x63, + 0x2f, 0x4c, 0x6f, 0x6e, 0x67, 0x79, 0x65, 0x61, 0x72, 0x62, 0x79, 0x65, 0x6e, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, + 0x6c, 0x6d, 0x61, 0x74, 0x79, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6d, 0x6d, 0x61, + 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6e, 0x61, 0x64, 0x79, 0x72, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x71, 0x74, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x41, 0x71, 0x74, 0x6f, 0x62, 0x65, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x73, + 0x68, 0x67, 0x61, 0x62, 0x61, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x73, 0x68, + 0x6b, 0x68, 0x61, 0x62, 0x61, 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x74, 0x79, + 0x72, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x67, 0x68, 0x64, 0x61, + 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x72, 0x61, 0x69, 0x6e, 0x0d, + 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x6b, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x42, 0x61, 0x6e, 0x67, 0x6b, 0x6f, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, + 0x61, 0x72, 0x6e, 0x61, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x65, 0x69, + 0x72, 0x75, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x68, 0x6b, 0x65, + 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x72, 0x75, 0x6e, 0x65, 0x69, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x74, 0x74, 0x61, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, + 0x43, 0x68, 0x6f, 0x69, 0x62, 0x61, 0x6c, 0x73, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x43, 0x68, 0x6f, 0x6e, 0x67, 0x71, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x43, 0x68, 0x75, 0x6e, 0x67, 0x6b, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x43, 0x6f, 0x6c, 0x6f, 0x6d, 0x62, 0x6f, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, + 0x61, 0x63, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x61, 0x6d, 0x61, 0x73, + 0x63, 0x75, 0x73, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x68, 0x61, 0x6b, 0x61, 0x0d, + 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x69, 0x6c, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x44, 0x75, 0x62, 0x61, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x75, 0x73, + 0x68, 0x61, 0x6e, 0x62, 0x65, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x46, 0x61, 0x6d, 0x61, + 0x67, 0x75, 0x73, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x47, 0x61, 0x7a, 0x61, + 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x48, 0x65, 0x62, 0x72, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x48, 0x6f, 0x6e, 0x67, 0x5f, 0x4b, 0x6f, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x48, 0x6f, 0x76, 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x6f, 0x5f, 0x43, + 0x68, 0x69, 0x5f, 0x4d, 0x69, 0x6e, 0x68, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x49, 0x72, + 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x49, 0x73, 0x74, 0x61, + 0x6e, 0x62, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x61, 0x6b, 0x61, 0x72, + 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x61, 0x79, 0x61, 0x70, 0x75, 0x72, + 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x65, 0x72, 0x75, 0x73, 0x61, 0x6c, 0x65, + 0x6d, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x62, 0x75, 0x6c, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x63, 0x68, 0x61, 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x72, 0x61, 0x63, 0x68, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, + 0x61, 0x2f, 0x4b, 0x61, 0x73, 0x68, 0x67, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, + 0x4b, 0x61, 0x74, 0x68, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, + 0x4b, 0x61, 0x74, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, + 0x68, 0x61, 0x6e, 0x64, 0x79, 0x67, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x6f, + 0x6c, 0x6b, 0x61, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x72, 0x61, 0x73, + 0x6e, 0x6f, 0x79, 0x61, 0x72, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x75, + 0x61, 0x6c, 0x61, 0x5f, 0x4c, 0x75, 0x6d, 0x70, 0x75, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x4b, 0x75, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, + 0x75, 0x77, 0x61, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x61, + 0x6f, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x61, 0x75, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x67, 0x61, 0x64, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, + 0x61, 0x2f, 0x4d, 0x61, 0x6b, 0x61, 0x73, 0x73, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x4d, 0x61, 0x6e, 0x69, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x75, + 0x73, 0x63, 0x61, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x69, 0x63, 0x6f, 0x73, + 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f, 0x6b, 0x75, 0x7a, + 0x6e, 0x65, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f, + 0x73, 0x69, 0x62, 0x69, 0x72, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x6d, + 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x50, 0x68, 0x6e, 0x6f, 0x6d, 0x5f, 0x50, 0x65, 0x6e, 0x68, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x6f, 0x6e, 0x74, 0x69, 0x61, 0x6e, 0x61, 0x6b, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x79, 0x6f, 0x6e, 0x67, 0x79, 0x61, 0x6e, 0x67, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x51, 0x61, 0x74, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x51, 0x79, 0x7a, 0x79, 0x6c, 0x6f, 0x72, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x52, 0x61, 0x6e, 0x67, 0x6f, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x52, + 0x69, 0x79, 0x61, 0x64, 0x68, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x69, 0x67, + 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x6b, 0x68, 0x61, 0x6c, 0x69, + 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x6b, 0x61, 0x6e, + 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x65, 0x6f, 0x75, 0x6c, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x53, 0x68, 0x61, 0x6e, 0x67, 0x68, 0x61, 0x69, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x53, 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x53, 0x72, 0x65, 0x64, 0x6e, 0x65, 0x6b, 0x6f, 0x6c, 0x79, 0x6d, 0x73, 0x6b, + 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x69, 0x70, 0x65, 0x69, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x68, 0x6b, 0x65, 0x6e, 0x74, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x54, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, + 0x2f, 0x54, 0x65, 0x68, 0x72, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x65, + 0x6c, 0x5f, 0x41, 0x76, 0x69, 0x76, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x68, 0x69, + 0x6d, 0x62, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x68, 0x69, 0x6d, 0x70, 0x68, + 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x54, 0x6f, 0x6d, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, + 0x55, 0x6a, 0x75, 0x6e, 0x67, 0x5f, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x6e, 0x67, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x61, 0x6e, 0x62, 0x61, 0x61, 0x74, 0x61, 0x72, 0x0d, + 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x6e, 0x5f, 0x42, 0x61, 0x74, 0x6f, 0x72, + 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x72, 0x75, 0x6d, 0x71, 0x69, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x55, 0x73, 0x74, 0x2d, 0x4e, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x56, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x56, 0x6c, 0x61, 0x64, 0x69, 0x76, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0d, 0x0a, + 0x41, 0x73, 0x69, 0x61, 0x2f, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, + 0x69, 0x61, 0x2f, 0x59, 0x61, 0x6e, 0x67, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, + 0x59, 0x65, 0x6b, 0x61, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x62, 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41, + 0x73, 0x69, 0x61, 0x2f, 0x59, 0x65, 0x72, 0x65, 0x76, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x74, 0x6c, + 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x41, 0x7a, 0x6f, 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x74, + 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x42, 0x65, 0x72, 0x6d, 0x75, 0x64, 0x61, 0x0d, 0x0a, + 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x0d, + 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x70, 0x65, 0x5f, 0x56, + 0x65, 0x72, 0x64, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x46, + 0x61, 0x65, 0x72, 0x6f, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, + 0x46, 0x61, 0x72, 0x6f, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, + 0x4a, 0x61, 0x6e, 0x5f, 0x4d, 0x61, 0x79, 0x65, 0x6e, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, + 0x74, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x64, 0x65, 0x69, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x74, 0x6c, + 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x52, 0x65, 0x79, 0x6b, 0x6a, 0x61, 0x76, 0x69, 0x6b, 0x0d, + 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x5f, + 0x47, 0x65, 0x6f, 0x72, 0x67, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, + 0x63, 0x2f, 0x53, 0x74, 0x61, 0x6e, 0x6c, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, + 0x74, 0x69, 0x63, 0x2f, 0x53, 0x74, 0x5f, 0x48, 0x65, 0x6c, 0x65, 0x6e, 0x61, 0x0d, 0x0a, 0x41, + 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x41, 0x43, 0x54, 0x0d, 0x0a, 0x41, 0x75, + 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, 0x6c, 0x61, 0x69, 0x64, 0x65, + 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x42, 0x72, 0x69, 0x73, + 0x62, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, + 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x48, 0x69, 0x6c, 0x6c, 0x0d, 0x0a, 0x41, 0x75, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x43, 0x61, 0x6e, 0x62, 0x65, 0x72, 0x72, 0x61, 0x0d, + 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x72, 0x69, + 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x44, 0x61, 0x72, + 0x77, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x45, + 0x75, 0x63, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, + 0x48, 0x6f, 0x62, 0x61, 0x72, 0x74, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, + 0x61, 0x2f, 0x4c, 0x48, 0x49, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, + 0x2f, 0x4c, 0x69, 0x6e, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, + 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4c, 0x6f, 0x72, 0x64, 0x5f, 0x48, 0x6f, 0x77, 0x65, 0x0d, 0x0a, + 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4d, 0x65, 0x6c, 0x62, 0x6f, 0x75, + 0x72, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4e, + 0x6f, 0x72, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, + 0x4e, 0x53, 0x57, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x50, + 0x65, 0x72, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, + 0x51, 0x75, 0x65, 0x65, 0x6e, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, + 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73, + 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x79, 0x64, 0x6e, 0x65, 0x79, 0x0d, 0x0a, 0x41, + 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x6d, 0x61, 0x6e, 0x69, + 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x56, 0x69, 0x63, + 0x74, 0x6f, 0x72, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, + 0x2f, 0x57, 0x65, 0x73, 0x74, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, + 0x2f, 0x59, 0x61, 0x6e, 0x63, 0x6f, 0x77, 0x69, 0x6e, 0x6e, 0x61, 0x0d, 0x0a, 0x42, 0x72, 0x61, + 0x7a, 0x69, 0x6c, 0x2f, 0x41, 0x63, 0x72, 0x65, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a, 0x69, 0x6c, + 0x2f, 0x44, 0x65, 0x4e, 0x6f, 0x72, 0x6f, 0x6e, 0x68, 0x61, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a, + 0x69, 0x6c, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a, 0x69, 0x6c, 0x2f, + 0x57, 0x65, 0x73, 0x74, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x41, 0x74, 0x6c, + 0x61, 0x6e, 0x74, 0x69, 0x63, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x43, 0x65, + 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x45, 0x61, + 0x73, 0x74, 0x2d, 0x53, 0x61, 0x73, 0x6b, 0x61, 0x74, 0x63, 0x68, 0x65, 0x77, 0x61, 0x6e, 0x0d, + 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x0d, + 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x66, 0x6f, 0x75, 0x6e, + 0x64, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x50, 0x61, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x53, 0x61, + 0x73, 0x6b, 0x61, 0x74, 0x63, 0x68, 0x65, 0x77, 0x61, 0x6e, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, + 0x64, 0x61, 0x2f, 0x59, 0x75, 0x6b, 0x6f, 0x6e, 0x0d, 0x0a, 0x43, 0x68, 0x69, 0x6c, 0x65, 0x2f, + 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x0d, 0x0a, 0x43, 0x68, 0x69, + 0x6c, 0x65, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x0d, + 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, + 0x54, 0x2b, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x0d, 0x0a, + 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, + 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x31, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, + 0x31, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x32, 0x0d, 0x0a, 0x45, + 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x33, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, + 0x54, 0x2b, 0x34, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x35, 0x0d, 0x0a, + 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x36, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, + 0x4d, 0x54, 0x2b, 0x37, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x38, 0x0d, + 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x39, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, + 0x47, 0x4d, 0x54, 0x2d, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, + 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x30, 0x0d, 0x0a, 0x45, 0x74, + 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x31, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, + 0x54, 0x2d, 0x31, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x33, + 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x34, 0x0d, 0x0a, 0x45, 0x74, + 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, + 0x2d, 0x33, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x34, 0x0d, 0x0a, 0x45, + 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x35, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, + 0x54, 0x2d, 0x36, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x37, 0x0d, 0x0a, + 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x38, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, + 0x4d, 0x54, 0x2d, 0x39, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x30, 0x0d, 0x0a, + 0x45, 0x74, 0x63, 0x2f, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x77, 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x45, + 0x74, 0x63, 0x2f, 0x55, 0x43, 0x54, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x55, 0x6e, 0x69, 0x76, + 0x65, 0x72, 0x73, 0x61, 0x6c, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x55, 0x54, 0x43, 0x0d, 0x0a, + 0x45, 0x74, 0x63, 0x2f, 0x5a, 0x75, 0x6c, 0x75, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x41, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x64, 0x61, 0x6d, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, + 0x70, 0x65, 0x2f, 0x41, 0x6e, 0x64, 0x6f, 0x72, 0x72, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, + 0x70, 0x65, 0x2f, 0x41, 0x73, 0x74, 0x72, 0x61, 0x6b, 0x68, 0x61, 0x6e, 0x0d, 0x0a, 0x45, 0x75, + 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x74, 0x68, 0x65, 0x6e, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x6c, 0x66, 0x61, 0x73, 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x6c, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x45, 0x75, + 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x72, 0x61, 0x74, 0x69, 0x73, 0x6c, 0x61, 0x76, 0x61, 0x0d, 0x0a, + 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x72, 0x75, 0x73, 0x73, 0x65, 0x6c, 0x73, 0x0d, + 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x63, 0x68, 0x61, 0x72, 0x65, 0x73, + 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x64, 0x61, 0x70, 0x65, + 0x73, 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x73, 0x69, 0x6e, + 0x67, 0x65, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x43, 0x68, 0x69, 0x73, + 0x69, 0x6e, 0x61, 0x75, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x43, 0x6f, 0x70, + 0x65, 0x6e, 0x68, 0x61, 0x67, 0x65, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x44, 0x75, 0x62, 0x6c, 0x69, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x47, + 0x69, 0x62, 0x72, 0x61, 0x6c, 0x74, 0x61, 0x72, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x47, 0x75, 0x65, 0x72, 0x6e, 0x73, 0x65, 0x79, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, + 0x65, 0x2f, 0x48, 0x65, 0x6c, 0x73, 0x69, 0x6e, 0x6b, 0x69, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, + 0x70, 0x65, 0x2f, 0x49, 0x73, 0x6c, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x4d, 0x61, 0x6e, 0x0d, 0x0a, + 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x49, 0x73, 0x74, 0x61, 0x6e, 0x62, 0x75, 0x6c, 0x0d, + 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4a, 0x65, 0x72, 0x73, 0x65, 0x79, 0x0d, 0x0a, + 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x61, 0x6c, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x72, + 0x61, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x65, 0x76, 0x0d, + 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x72, 0x6f, 0x76, 0x0d, 0x0a, 0x45, + 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x69, 0x73, 0x62, 0x6f, 0x6e, 0x0d, 0x0a, 0x45, 0x75, + 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x6a, 0x75, 0x62, 0x6c, 0x6a, 0x61, 0x6e, 0x61, 0x0d, 0x0a, + 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x0d, 0x0a, 0x45, + 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x75, 0x78, 0x65, 0x6d, 0x62, 0x6f, 0x75, 0x72, 0x67, + 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x64, 0x72, 0x69, 0x64, 0x0d, + 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x6c, 0x74, 0x61, 0x0d, 0x0a, 0x45, + 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x65, 0x68, 0x61, 0x6d, 0x6e, 0x0d, + 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x73, 0x6b, 0x0d, 0x0a, 0x45, + 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x0d, 0x0a, 0x45, 0x75, + 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x73, 0x63, 0x6f, 0x77, 0x0d, 0x0a, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x4e, 0x69, 0x63, 0x6f, 0x73, 0x69, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x4f, 0x73, 0x6c, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x50, 0x61, 0x72, 0x69, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x50, + 0x6f, 0x64, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x50, 0x72, 0x61, 0x67, 0x75, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x52, 0x69, 0x67, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x52, 0x6f, 0x6d, + 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x61, + 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4d, 0x61, 0x72, + 0x69, 0x6e, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72, 0x61, + 0x6a, 0x65, 0x76, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72, + 0x61, 0x74, 0x6f, 0x76, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x69, 0x6d, + 0x66, 0x65, 0x72, 0x6f, 0x70, 0x6f, 0x6c, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x53, 0x6b, 0x6f, 0x70, 0x6a, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, + 0x6f, 0x66, 0x69, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x6d, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54, + 0x61, 0x6c, 0x6c, 0x69, 0x6e, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54, + 0x69, 0x72, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54, 0x69, + 0x72, 0x61, 0x73, 0x70, 0x6f, 0x6c, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x55, + 0x6c, 0x79, 0x61, 0x6e, 0x6f, 0x76, 0x73, 0x6b, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x55, 0x7a, 0x68, 0x67, 0x6f, 0x72, 0x6f, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, + 0x65, 0x2f, 0x56, 0x61, 0x64, 0x75, 0x7a, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x56, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x56, 0x69, 0x65, 0x6e, 0x6e, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, + 0x69, 0x6c, 0x6e, 0x69, 0x75, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56, + 0x6f, 0x6c, 0x67, 0x6f, 0x67, 0x72, 0x61, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, + 0x2f, 0x57, 0x61, 0x72, 0x73, 0x61, 0x77, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, + 0x5a, 0x61, 0x67, 0x72, 0x65, 0x62, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x5a, + 0x61, 0x70, 0x6f, 0x72, 0x6f, 0x7a, 0x68, 0x79, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, + 0x65, 0x2f, 0x5a, 0x75, 0x72, 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, + 0x2f, 0x41, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x6e, 0x61, 0x72, 0x69, 0x76, 0x6f, 0x0d, 0x0a, 0x49, + 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x68, 0x61, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x49, 0x6e, + 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x0d, 0x0a, + 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x63, 0x6f, 0x73, 0x0d, 0x0a, 0x49, 0x6e, + 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x72, 0x6f, 0x0d, 0x0a, 0x49, 0x6e, 0x64, + 0x69, 0x61, 0x6e, 0x2f, 0x4b, 0x65, 0x72, 0x67, 0x75, 0x65, 0x6c, 0x65, 0x6e, 0x0d, 0x0a, 0x49, + 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x68, 0x65, 0x0d, 0x0a, 0x49, 0x6e, 0x64, 0x69, + 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x6c, 0x64, 0x69, 0x76, 0x65, 0x73, 0x0d, 0x0a, 0x49, 0x6e, 0x64, + 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x75, 0x72, 0x69, 0x74, 0x69, 0x75, 0x73, 0x0d, 0x0a, 0x49, + 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x79, 0x6f, 0x74, 0x74, 0x65, 0x0d, 0x0a, 0x49, + 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x52, 0x65, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x0d, 0x0a, 0x4d, + 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x42, 0x61, 0x6a, 0x61, 0x4e, 0x6f, 0x72, 0x74, 0x65, 0x0d, + 0x0a, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x42, 0x61, 0x6a, 0x61, 0x53, 0x75, 0x72, 0x0d, + 0x0a, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0d, + 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x41, 0x70, 0x69, 0x61, 0x0d, 0x0a, 0x50, + 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x41, 0x75, 0x63, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x0d, + 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x42, 0x6f, 0x75, 0x67, 0x61, 0x69, 0x6e, + 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x43, + 0x68, 0x61, 0x74, 0x68, 0x61, 0x6d, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, + 0x43, 0x68, 0x75, 0x75, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, + 0x66, 0x61, 0x74, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, 0x6e, + 0x64, 0x65, 0x72, 0x62, 0x75, 0x72, 0x79, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x2f, 0x46, 0x61, 0x6b, 0x61, 0x6f, 0x66, 0x6f, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2f, 0x46, 0x69, 0x6a, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, + 0x46, 0x75, 0x6e, 0x61, 0x66, 0x75, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2f, 0x47, 0x61, 0x6c, 0x61, 0x70, 0x61, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x50, 0x61, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x61, 0x6d, 0x62, 0x69, 0x65, 0x72, 0x0d, 0x0a, 0x50, 0x61, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x61, 0x6c, 0x63, 0x61, 0x6e, 0x61, + 0x6c, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x6d, 0x0d, + 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x48, 0x6f, 0x6e, 0x6f, 0x6c, 0x75, 0x6c, + 0x75, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4a, 0x6f, 0x68, 0x6e, 0x73, + 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4b, 0x69, 0x72, + 0x69, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x2f, 0x4b, 0x6f, 0x73, 0x72, 0x61, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x2f, 0x4b, 0x77, 0x61, 0x6a, 0x61, 0x6c, 0x65, 0x69, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x6a, 0x75, 0x72, 0x6f, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x72, 0x71, 0x75, 0x65, 0x73, 0x61, 0x73, 0x0d, 0x0a, 0x50, + 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x69, 0x64, 0x77, 0x61, 0x79, 0x0d, 0x0a, 0x50, + 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x61, 0x75, 0x72, 0x75, 0x0d, 0x0a, 0x50, 0x61, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x69, 0x75, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x6f, 0x72, 0x66, 0x6f, 0x6c, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x6f, 0x75, 0x6d, 0x65, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x67, 0x6f, 0x5f, 0x50, 0x61, 0x67, 0x6f, 0x0d, 0x0a, + 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x6c, 0x61, 0x75, 0x0d, 0x0a, 0x50, + 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x69, 0x74, 0x63, 0x61, 0x69, 0x72, 0x6e, 0x0d, + 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x68, 0x6e, 0x70, 0x65, 0x69, + 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x6e, 0x61, 0x70, 0x65, + 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x4d, + 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, + 0x52, 0x61, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x67, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x2f, 0x53, 0x61, 0x69, 0x70, 0x61, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x2f, 0x53, 0x61, 0x6d, 0x6f, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2f, 0x54, 0x61, 0x68, 0x69, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2f, 0x54, 0x61, 0x72, 0x61, 0x77, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2f, 0x54, 0x6f, 0x6e, 0x67, 0x61, 0x74, 0x61, 0x70, 0x75, 0x0d, 0x0a, 0x50, 0x61, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x2f, 0x54, 0x72, 0x75, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x2f, 0x57, 0x61, 0x6b, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x2f, 0x57, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x2f, 0x59, 0x61, 0x70, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x41, 0x6c, 0x61, 0x73, 0x6b, 0x61, 0x0d, + 0x0a, 0x55, 0x53, 0x2f, 0x41, 0x6c, 0x65, 0x75, 0x74, 0x69, 0x61, 0x6e, 0x0d, 0x0a, 0x55, 0x53, + 0x2f, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x43, 0x65, 0x6e, + 0x74, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x2d, 0x49, 0x6e, + 0x64, 0x69, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x6e, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x48, 0x61, 0x77, 0x61, 0x69, 0x69, 0x0d, 0x0a, 0x55, 0x53, + 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x6b, 0x65, 0x0d, + 0x0a, 0x55, 0x53, 0x2f, 0x4d, 0x69, 0x63, 0x68, 0x69, 0x67, 0x61, 0x6e, 0x0d, 0x0a, 0x55, 0x53, + 0x2f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x50, 0x61, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x2d, 0x4e, 0x65, 0x77, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x53, 0x61, 0x6d, 0x6f, 0x61, 0x0d, + 0x0a}; + +static VirtualFile GenerateDefaultTimeZoneFile() { + struct { + s64_be at; + INSERT_PADDING_BYTES(7); + std::array<char, 4> time_zone_chars; + INSERT_PADDING_BYTES(2); + std::array<char, 6> time_zone_name; + } time_zone_info{}; + + const VirtualFile file{std::make_shared<VectorVfsFile>( + std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(time_zone_info)), + "GMT")}; + + Service::Time::TimeZone::TzifHeader header{}; + header.magic = 0x545a6966; + header.version = 0x32; + header.ttis_gmt_count = 0x1; + header.ttis_std_count = 0x1; + header.time_count = 0x1; + header.type_count = 0x1; + header.char_count = 0x4; + file->WriteObject(header, 0); + + time_zone_info.at = 0xf8; + time_zone_info.time_zone_chars = {'G', 'M', 'T', '\0'}; + time_zone_info.time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'}; + file->WriteObject(time_zone_info, sizeof(Service::Time::TimeZone::TzifHeader)); + + return file; +} + +VirtualDir TimeZoneBinary() { + const std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>( + std::vector<VirtualFile>{GenerateDefaultTimeZoneFile()}, std::vector<VirtualDir>{}, + "zoneinfo")}; + const std::vector<VirtualFile> root_files{ + std::make_shared<ArrayVfsFile<LOCATION_NAMES.size()>>(LOCATION_NAMES, "binaryList.txt")}; + return std::make_shared<VectorVfsDirectory>(root_files, root_dirs, "data"); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/time_zone_binary.h b/src/core/file_sys/system_archive/time_zone_binary.h new file mode 100644 index 000000000..ed2b78227 --- /dev/null +++ b/src/core/file_sys/system_archive/time_zone_binary.h @@ -0,0 +1,14 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualDir TimeZoneBinary(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h index 090565310..b689e8e8b 100644 --- a/src/core/hle/kernel/physical_memory.h +++ b/src/core/hle/kernel/physical_memory.h @@ -14,6 +14,9 @@ namespace Kernel { // - Second to ensure all host backing memory used is aligned to 256 bytes due // to strict alignment restrictions on GPU memory. -using PhysicalMemory = std::vector<u8, Common::AlignmentAllocator<u8, 256>>; +using PhysicalMemoryVector = std::vector<u8, Common::AlignmentAllocator<u8, 256>>; +class PhysicalMemory final : public PhysicalMemoryVector { + using PhysicalMemoryVector::PhysicalMemoryVector; +}; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 12ea4ebe3..b9035a0be 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -317,6 +317,8 @@ void Process::FreeTLSRegion(VAddr tls_address) { } void Process::LoadModule(CodeSet module_, VAddr base_addr) { + code_memory_size += module_.memory.size(); + const auto memory = std::make_shared<PhysicalMemory>(std::move(module_.memory)); const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions, @@ -332,8 +334,6 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) { MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code); MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData); MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData); - - code_memory_size += module_.memory.size(); } Process::Process(Core::System& system) diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index a9a20ef76..0b3500fce 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cstring> #include <iterator> #include <utility> #include "common/alignment.h" @@ -269,18 +270,9 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { // If necessary, expand backing vector to cover new heap extents in // the case of allocating. Otherwise, shrink the backing memory, // if a smaller heap has been requested. - const u64 old_heap_size = GetCurrentHeapSize(); - if (size > old_heap_size) { - const u64 alloc_size = size - old_heap_size; - - heap_memory->insert(heap_memory->end(), alloc_size, 0); - RefreshMemoryBlockMappings(heap_memory.get()); - } else if (size < old_heap_size) { - heap_memory->resize(size); - heap_memory->shrink_to_fit(); - - RefreshMemoryBlockMappings(heap_memory.get()); - } + heap_memory->resize(size); + heap_memory->shrink_to_fit(); + RefreshMemoryBlockMappings(heap_memory.get()); heap_end = heap_region_base + size; ASSERT(GetCurrentHeapSize() == heap_memory->size()); @@ -752,24 +744,20 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre // Always merge allocated memory blocks, even when they don't share the same backing block. if (left.type == VMAType::AllocatedMemoryBlock && (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { - const auto right_begin = right.backing_block->begin() + right.offset; - const auto right_end = right_begin + right.size; // Check if we can save work. if (left.offset == 0 && left.size == left.backing_block->size()) { // Fast case: left is an entire backing block. - left.backing_block->insert(left.backing_block->end(), right_begin, right_end); + left.backing_block->resize(left.size + right.size); + std::memcpy(left.backing_block->data() + left.size, + right.backing_block->data() + right.offset, right.size); } else { // Slow case: make a new memory block for left and right. - const auto left_begin = left.backing_block->begin() + left.offset; - const auto left_end = left_begin + left.size; - const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end)); - const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end)); - auto new_memory = std::make_shared<PhysicalMemory>(); - new_memory->reserve(left_size + right_size); - new_memory->insert(new_memory->end(), left_begin, left_end); - new_memory->insert(new_memory->end(), right_begin, right_end); + new_memory->resize(left.size + right.size); + std::memcpy(new_memory->data(), left.backing_block->data() + left.offset, left.size); + std::memcpy(new_memory->data() + left.size, right.backing_block->data() + right.offset, + right.size); left.backing_block = std::move(new_memory); left.offset = 0; @@ -792,8 +780,7 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { memory.UnmapRegion(page_table, vma.base, vma.size); break; case VMAType::AllocatedMemoryBlock: - memory.MapMemoryRegion(page_table, vma.base, vma.size, - vma.backing_block->data() + vma.offset); + memory.MapMemoryRegion(page_table, vma.base, vma.size, *vma.backing_block, vma.offset); break; case VMAType::BackingMemory: memory.MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory); diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 7e3e311fb..cfac8ca9a 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -211,7 +211,7 @@ protected: } ProfileManager& profile_manager; - Common::UUID user_id; ///< The user id this profile refers to. + Common::UUID user_id{Common::INVALID_UUID}; ///< The user id this profile refers to. }; class IProfile final : public IProfileCommon { diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 3e756e59e..eb8c81645 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -16,17 +16,17 @@ namespace Service::Account { using Common::UUID; struct UserRaw { - UUID uuid; - UUID uuid2; - u64 timestamp; - ProfileUsername username; - ProfileData extra_data; + UUID uuid{Common::INVALID_UUID}; + UUID uuid2{Common::INVALID_UUID}; + u64 timestamp{}; + ProfileUsername username{}; + ProfileData extra_data{}; }; static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size."); struct ProfileDataRaw { INSERT_PADDING_BYTES(0x10); - std::array<UserRaw, MAX_USERS> users; + std::array<UserRaw, MAX_USERS> users{}; }; static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size."); @@ -238,7 +238,7 @@ UserIDArray ProfileManager::GetOpenUsers() const { std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) { if (p.is_open) return p.user_uuid; - return UUID{}; + return UUID{Common::INVALID_UUID}; }); std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; }); return output; diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 5a6d28925..5310637a6 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -13,9 +13,10 @@ #include "core/hle/result.h" namespace Service::Account { -constexpr std::size_t MAX_USERS = 8; -constexpr std::size_t profile_username_size = 32; +constexpr std::size_t MAX_USERS{8}; +constexpr std::size_t profile_username_size{32}; + using ProfileUsername = std::array<u8, profile_username_size>; using UserIDArray = std::array<Common::UUID, MAX_USERS>; @@ -23,8 +24,8 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>; /// TODO: RE this structure struct ProfileData { INSERT_PADDING_WORDS(1); - u32 icon_id; - u8 bg_color_id; + u32 icon_id{}; + u8 bg_color_id{}; INSERT_PADDING_BYTES(0x7); INSERT_PADDING_BYTES(0x10); INSERT_PADDING_BYTES(0x60); @@ -34,17 +35,17 @@ static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect /// This holds general information about a users profile. This is where we store all the information /// based on a specific user struct ProfileInfo { - Common::UUID user_uuid; - ProfileUsername username; - u64 creation_time; - ProfileData data; // TODO(ognik): Work out what this is - bool is_open; + Common::UUID user_uuid{Common::INVALID_UUID}; + ProfileUsername username{}; + u64 creation_time{}; + ProfileData data{}; // TODO(ognik): Work out what this is + bool is_open{}; }; struct ProfileBase { - Common::UUID user_uuid; - u64_le timestamp; - ProfileUsername username; + Common::UUID user_uuid{Common::INVALID_UUID}; + u64_le timestamp{}; + ProfileUsername username{}; // Zero out all the fields to make the profile slot considered "Empty" void Invalidate() { @@ -101,7 +102,7 @@ private: bool RemoveProfileAtIndex(std::size_t index); std::array<ProfileInfo, MAX_USERS> profiles{}; - std::size_t user_count = 0; + std::size_t user_count{}; Common::UUID last_opened_user{Common::INVALID_UUID}; }; diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 219176c31..6aadb3ea8 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -241,7 +241,7 @@ private: bool has_received_friend_request; }; - Common::UUID uuid; + Common::UUID uuid{Common::INVALID_UUID}; Kernel::EventPair notification_event; std::queue<SizedNotificationInfo> notifications; States states{}; diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 38ad78a0d..fc742816a 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -10,13 +10,13 @@ namespace Service::Mii { -constexpr std::size_t MAX_MIIS = 100; -constexpr u32 INVALID_INDEX = 0xFFFFFFFF; +constexpr std::size_t MAX_MIIS{100}; +constexpr u32 INVALID_INDEX{0xFFFFFFFF}; struct RandomParameters { - u32 unknown_1; - u32 unknown_2; - u32 unknown_3; + u32 unknown_1{}; + u32 unknown_2{}; + u32 unknown_3{}; }; static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size."); @@ -30,57 +30,57 @@ enum class Source : u32 { std::ostream& operator<<(std::ostream& os, Source source); struct MiiInfo { - Common::UUID uuid; - std::array<char16_t, 11> name; - u8 font_region; - u8 favorite_color; - u8 gender; - u8 height; - u8 weight; - u8 mii_type; - u8 mii_region; - u8 face_type; - u8 face_color; - u8 face_wrinkle; - u8 face_makeup; - u8 hair_type; - u8 hair_color; - bool hair_flip; - u8 eye_type; - u8 eye_color; - u8 eye_scale; - u8 eye_aspect_ratio; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - u8 eyebrow_type; - u8 eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect_ratio; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - u8 nose_type; - u8 nose_scale; - u8 nose_y; - u8 mouth_type; - u8 mouth_color; - u8 mouth_scale; - u8 mouth_aspect_ratio; - u8 mouth_y; - u8 facial_hair_color; - u8 beard_type; - u8 mustache_type; - u8 mustache_scale; - u8 mustache_y; - u8 glasses_type; - u8 glasses_color; - u8 glasses_scale; - u8 glasses_y; - u8 mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; + Common::UUID uuid{Common::INVALID_UUID}; + std::array<char16_t, 11> name{}; + u8 font_region{}; + u8 favorite_color{}; + u8 gender{}; + u8 height{}; + u8 weight{}; + u8 mii_type{}; + u8 mii_region{}; + u8 face_type{}; + u8 face_color{}; + u8 face_wrinkle{}; + u8 face_makeup{}; + u8 hair_type{}; + u8 hair_color{}; + bool hair_flip{}; + u8 eye_type{}; + u8 eye_color{}; + u8 eye_scale{}; + u8 eye_aspect_ratio{}; + u8 eye_rotate{}; + u8 eye_x{}; + u8 eye_y{}; + u8 eyebrow_type{}; + u8 eyebrow_color{}; + u8 eyebrow_scale{}; + u8 eyebrow_aspect_ratio{}; + u8 eyebrow_rotate{}; + u8 eyebrow_x{}; + u8 eyebrow_y{}; + u8 nose_type{}; + u8 nose_scale{}; + u8 nose_y{}; + u8 mouth_type{}; + u8 mouth_color{}; + u8 mouth_scale{}; + u8 mouth_aspect_ratio{}; + u8 mouth_y{}; + u8 facial_hair_color{}; + u8 beard_type{}; + u8 mustache_type{}; + u8 mustache_scale{}; + u8 mustache_y{}; + u8 glasses_type{}; + u8 glasses_color{}; + u8 glasses_scale{}; + u8 glasses_y{}; + u8 mole_type{}; + u8 mole_scale{}; + u8 mole_x{}; + u8 mole_y{}; INSERT_PADDING_BYTES(1); std::u16string Name() const; @@ -94,14 +94,14 @@ bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs); #pragma pack(push, 4) struct MiiInfoElement { - MiiInfo info; - Source source; + MiiInfo info{}; + Source source{}; }; static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size."); struct MiiStoreBitFields { union { - u32 word_0; + u32 word_0{}; BitField<24, 8, u32> hair_type; BitField<23, 1, u32> mole_type; @@ -112,7 +112,7 @@ struct MiiStoreBitFields { }; union { - u32 word_1; + u32 word_1{}; BitField<31, 1, u32> gender; BitField<24, 7, u32> eye_color; @@ -122,7 +122,7 @@ struct MiiStoreBitFields { }; union { - u32 word_2; + u32 word_2{}; BitField<31, 1, u32> mii_type; BitField<24, 7, u32> glasses_color; @@ -135,7 +135,7 @@ struct MiiStoreBitFields { }; union { - u32 word_3; + u32 word_3{}; BitField<29, 3, u32> mustache_type; BitField<24, 5, u32> eyebrow_type; @@ -148,7 +148,7 @@ struct MiiStoreBitFields { }; union { - u32 word_4; + u32 word_4{}; BitField<29, 3, u32> eye_rotate; BitField<24, 5, u32> mustache_y; @@ -160,7 +160,7 @@ struct MiiStoreBitFields { }; union { - u32 word_5; + u32 word_5{}; BitField<24, 5, u32> glasses_type; BitField<20, 4, u32> face_type; @@ -172,7 +172,7 @@ struct MiiStoreBitFields { }; union { - u32 word_6; + u32 word_6{}; BitField<28, 4, u32> eyebrow_rotate; BitField<24, 4, u32> eyebrow_scale; @@ -192,30 +192,30 @@ struct MiiStoreData { // This corresponds to the above structure MiiStoreBitFields. I did it like this because the // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is // not suitable for our uses. - std::array<u8, 0x1C> data; + std::array<u8, 0x1C> data{}; static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); - std::array<char16_t, 10> name; - Common::UUID uuid; - u16 crc_1; - u16 crc_2; + std::array<char16_t, 10> name{}; + Common::UUID uuid{Common::INVALID_UUID}; + u16 crc_1{}; + u16 crc_2{}; std::u16string Name() const; }; static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); struct MiiStoreDataElement { - MiiStoreData data; - Source source; + MiiStoreData data{}; + Source source{}; }; static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); struct MiiDatabase { - u32 magic; // 'NFDB' - std::array<MiiStoreData, MAX_MIIS> miis; + u32 magic{}; // 'NFDB' + std::array<MiiStoreData, MAX_MIIS> miis{}; INSERT_PADDING_BYTES(1); - u8 count; - u16 crc; + u8 count{}; + u16 crc{}; }; static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); #pragma pack(pop) @@ -266,8 +266,8 @@ private: void EnsureDatabasePartition(); MiiDatabase database; - bool updated_flag = false; - bool is_test_mode_enabled = false; + bool updated_flag{}; + bool is_test_mode_enabled{}; }; }; // namespace Service::Mii diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 2e53b3221..767158444 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -9,6 +9,7 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/service.h" +#include "core/settings.h" namespace Service::NIFM { @@ -86,7 +87,12 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.PushEnum(RequestState::Connected); + + if (Settings::values.bcat_backend == "none") { + rb.PushEnum(RequestState::NotSubmitted); + } else { + rb.PushEnum(RequestState::Connected); + } } void GetResult(Kernel::HLERequestContext& ctx) { @@ -194,14 +200,22 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u8>(1); + if (Settings::values.bcat_backend == "none") { + rb.Push<u8>(0); + } else { + rb.Push<u8>(1); + } } void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u8>(1); + if (Settings::values.bcat_backend == "none") { + rb.Push<u8>(0); + } else { + rb.Push<u8>(1); + } } Core::System& system; }; diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 52623cf89..62752e419 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -88,6 +88,12 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { return layer_id; } +void NVFlinger::CloseLayer(u64 layer_id) { + for (auto& display : displays) { + display.CloseLayer(layer_id); + } +} + std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const { const auto* const layer = FindLayer(display_id, layer_id); @@ -192,7 +198,7 @@ void NVFlinger::Compose() { const auto& igbp_buffer = buffer->get().igbp_buffer; - const auto& gpu = system.GPU(); + auto& gpu = system.GPU(); const auto& multi_fence = buffer->get().multi_fence; for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) { const auto& fence = multi_fence.fences[fence_id]; diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index e3cc14bdc..57a21f33b 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -54,6 +54,9 @@ public: /// If an invalid display ID is specified, then an empty optional is returned. std::optional<u64> CreateLayer(u64 display_id); + /// Closes a layer on all displays for the given layer ID. + void CloseLayer(u64 layer_id); + /// Finds the buffer queue ID of the specified layer in the specified display. /// /// If an invalid display ID or layer ID is provided, then an empty optional is returned. diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h new file mode 100644 index 000000000..72e1921ec --- /dev/null +++ b/src/core/hle/service/time/clock_types.h @@ -0,0 +1,103 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/uuid.h" +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time::Clock { + +/// https://switchbrew.org/wiki/Glue_services#SteadyClockTimePoint +struct SteadyClockTimePoint { + s64 time_point; + Common::UUID clock_source_id; + + ResultCode GetSpanBetween(SteadyClockTimePoint other, s64& span) const { + span = 0; + + if (clock_source_id != other.clock_source_id) { + return ERROR_TIME_MISMATCH; + } + + span = other.time_point - time_point; + + return RESULT_SUCCESS; + } + + static SteadyClockTimePoint GetRandom() { + return {0, Common::UUID::Generate()}; + } +}; +static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); +static_assert(std::is_trivially_copyable_v<SteadyClockTimePoint>, + "SteadyClockTimePoint must be trivially copyable"); + +struct SteadyClockContext { + u64 internal_offset; + Common::UUID steady_time_point; +}; +static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size"); +static_assert(std::is_trivially_copyable_v<SteadyClockContext>, + "SteadyClockContext must be trivially copyable"); + +struct SystemClockContext { + s64 offset; + SteadyClockTimePoint steady_time_point; +}; +static_assert(sizeof(SystemClockContext) == 0x20, "SystemClockContext is incorrect size"); +static_assert(std::is_trivially_copyable_v<SystemClockContext>, + "SystemClockContext must be trivially copyable"); + +/// https://switchbrew.org/wiki/Glue_services#TimeSpanType +struct TimeSpanType { + s64 nanoseconds{}; + static constexpr s64 ns_per_second{1000000000ULL}; + + s64 ToSeconds() const { + return nanoseconds / ns_per_second; + } + + static TimeSpanType FromSeconds(s64 seconds) { + return {seconds * ns_per_second}; + } + + static TimeSpanType FromTicks(u64 ticks, u64 frequency) { + return FromSeconds(static_cast<s64>(ticks) / static_cast<s64>(frequency)); + } +}; +static_assert(sizeof(TimeSpanType) == 8, "TimeSpanType is incorrect size"); + +struct ClockSnapshot { + SystemClockContext user_context{}; + SystemClockContext network_context{}; + s64 user_time{}; + s64 network_time{}; + TimeZone::CalendarTime user_calendar_time{}; + TimeZone::CalendarTime network_calendar_time{}; + TimeZone::CalendarAdditionalInfo user_calendar_additional_time{}; + TimeZone::CalendarAdditionalInfo network_calendar_additional_time{}; + SteadyClockTimePoint steady_clock_time_point{}; + TimeZone::LocationName location_name{}; + u8 is_automatic_correction_enabled{}; + u8 type{}; + INSERT_PADDING_BYTES(0x2); + + static ResultCode GetCurrentTime(s64& current_time, + const SteadyClockTimePoint& steady_clock_time_point, + const SystemClockContext& context) { + if (steady_clock_time_point.clock_source_id != context.steady_time_point.clock_source_id) { + current_time = 0; + return ERROR_TIME_MISMATCH; + } + current_time = steady_clock_time_point.time_point + context.offset; + return RESULT_SUCCESS; + } +}; +static_assert(sizeof(ClockSnapshot) == 0xD0, "ClockSnapshot is incorrect size"); + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h new file mode 100644 index 000000000..42893e3f6 --- /dev/null +++ b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_context_update_callback.h" + +namespace Service::Time::Clock { + +class EphemeralNetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + EphemeralNetworkSystemClockContextWriter() : SystemClockContextUpdateCallback{} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_core.h b/src/core/hle/service/time/ephemeral_network_system_clock_core.h new file mode 100644 index 000000000..4c6cdef86 --- /dev/null +++ b/src/core/hle/service/time/ephemeral_network_system_clock_core.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +class EphemeralNetworkSystemClockCore final : public SystemClockCore { +public: + explicit EphemeralNetworkSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/errors.h b/src/core/hle/service/time/errors.h new file mode 100644 index 000000000..8501a3e8c --- /dev/null +++ b/src/core/hle/service/time/errors.h @@ -0,0 +1,22 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Time { + +constexpr ResultCode ERROR_PERMISSION_DENIED{ErrorModule::Time, 1}; +constexpr ResultCode ERROR_TIME_MISMATCH{ErrorModule::Time, 102}; +constexpr ResultCode ERROR_UNINITIALIZED_CLOCK{ErrorModule::Time, 103}; +constexpr ResultCode ERROR_TIME_NOT_FOUND{ErrorModule::Time, 200}; +constexpr ResultCode ERROR_OVERFLOW{ErrorModule::Time, 201}; +constexpr ResultCode ERROR_LOCATION_NAME_TOO_LONG{ErrorModule::Time, 801}; +constexpr ResultCode ERROR_OUT_OF_RANGE{ErrorModule::Time, 902}; +constexpr ResultCode ERROR_TIME_ZONE_CONVERSION_FAILED{ErrorModule::Time, 903}; +constexpr ResultCode ERROR_TIME_ZONE_NOT_FOUND{ErrorModule::Time, 989}; +constexpr ResultCode ERROR_NOT_IMPLEMENTED{ErrorModule::Time, 990}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index bc74f1e1d..1660bbdb8 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -6,9 +6,8 @@ namespace Service::Time { -Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, - Core::System& system, const char* name) - : Module::Interface(std::move(time), std::move(shared_memory), system, name) { +Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* name) + : Module::Interface(std::move(module), system, name) { // clang-format off static const FunctionInfo functions[] = { {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, @@ -22,15 +21,15 @@ Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_me {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"}, {50, nullptr, "SetStandardSteadyClockInternalOffset"}, {51, nullptr, "GetStandardSteadyClockRtcValue"}, - {100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, - {101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, {102, nullptr, "GetStandardUserSystemClockInitialYear"}, - {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"}, + {200, &Time::IsStandardNetworkSystemClockAccuracySufficient, "IsStandardNetworkSystemClockAccuracySufficient"}, {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"}, - {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, + {300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"}, {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, - {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, - {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"}, + {401, &Time::GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"}, + {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, {501, nullptr, "CalculateSpanBetween"}, }; // clang-format on diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h index 5c63a07f4..4f49e1f07 100644 --- a/src/core/hle/service/time/interface.h +++ b/src/core/hle/service/time/interface.h @@ -1,4 +1,4 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -6,14 +6,15 @@ #include "core/hle/service/time/time.h" -namespace Service::Time { +namespace Core { +class System; +} -class SharedMemory; +namespace Service::Time { class Time final : public Module::Interface { public: - explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, - Core::System& system, const char* name); + explicit Time(std::shared_ptr<Module> time, Core::System& system, const char* name); ~Time() override; }; diff --git a/src/core/hle/service/time/local_system_clock_context_writer.h b/src/core/hle/service/time/local_system_clock_context_writer.h new file mode 100644 index 000000000..7050844c6 --- /dev/null +++ b/src/core/hle/service/time/local_system_clock_context_writer.h @@ -0,0 +1,28 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/time_sharedmemory.h" + +namespace Service::Time::Clock { + +class LocalSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + explicit LocalSystemClockContextWriter(SharedMemory& shared_memory) + : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {} + +protected: + ResultCode Update() override { + shared_memory.UpdateLocalSystemClockContext(context); + return RESULT_SUCCESS; + } + +private: + SharedMemory& shared_memory; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/network_system_clock_context_writer.h b/src/core/hle/service/time/network_system_clock_context_writer.h new file mode 100644 index 000000000..94d8788ff --- /dev/null +++ b/src/core/hle/service/time/network_system_clock_context_writer.h @@ -0,0 +1,28 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/time_sharedmemory.h" + +namespace Service::Time::Clock { + +class NetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + explicit NetworkSystemClockContextWriter(SharedMemory& shared_memory) + : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {} + +protected: + ResultCode Update() override { + shared_memory.UpdateNetworkSystemClockContext(context); + return RESULT_SUCCESS; + } + +private: + SharedMemory& shared_memory; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_local_system_clock_core.h b/src/core/hle/service/time/standard_local_system_clock_core.h new file mode 100644 index 000000000..8c1882eb1 --- /dev/null +++ b/src/core/hle/service/time/standard_local_system_clock_core.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +class StandardLocalSystemClockCore final : public SystemClockCore { +public: + explicit StandardLocalSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_network_system_clock_core.h b/src/core/hle/service/time/standard_network_system_clock_core.h new file mode 100644 index 000000000..3f505c37c --- /dev/null +++ b/src/core/hle/service/time/standard_network_system_clock_core.h @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class StandardNetworkSystemClockCore final : public SystemClockCore { +public: + explicit StandardNetworkSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} + + void SetStandardNetworkClockSufficientAccuracy(TimeSpanType value) { + standard_network_clock_sufficient_accuracy = value; + } + + bool IsStandardNetworkSystemClockAccuracySufficient(Core::System& system) { + SystemClockContext context{}; + if (GetClockContext(system, context) != RESULT_SUCCESS) { + return {}; + } + + s64 span{}; + if (context.steady_time_point.GetSpanBetween( + GetSteadyClockCore().GetCurrentTimePoint(system), span) != RESULT_SUCCESS) { + return {}; + } + + return TimeSpanType{span}.nanoseconds < + standard_network_clock_sufficient_accuracy.nanoseconds; + } + +private: + TimeSpanType standard_network_clock_sufficient_accuracy{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp new file mode 100644 index 000000000..ca1a783fc --- /dev/null +++ b/src/core/hle/service/time/standard_steady_clock_core.cpp @@ -0,0 +1,26 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/standard_steady_clock_core.h" + +namespace Service::Time::Clock { + +TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { + const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds}; + + if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) { + raw_time_point.nanoseconds = cached_raw_time_point.nanoseconds; + } + + cached_raw_time_point = raw_time_point; + return raw_time_point; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_steady_clock_core.h b/src/core/hle/service/time/standard_steady_clock_core.h new file mode 100644 index 000000000..f56f3fd95 --- /dev/null +++ b/src/core/hle/service/time/standard_steady_clock_core.h @@ -0,0 +1,42 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class StandardSteadyClockCore final : public SteadyClockCore { +public: + SteadyClockTimePoint GetTimePoint(Core::System& system) override { + return {GetCurrentRawTimePoint(system).ToSeconds(), GetClockSourceId()}; + } + + TimeSpanType GetInternalOffset() const override { + return internal_offset; + } + + void SetInternalOffset(TimeSpanType value) override { + internal_offset = value; + } + + TimeSpanType GetCurrentRawTimePoint(Core::System& system) override; + + void SetSetupValue(TimeSpanType value) { + setup_value = value; + } + +private: + TimeSpanType setup_value{}; + TimeSpanType internal_offset{}; + TimeSpanType cached_raw_time_point{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp new file mode 100644 index 000000000..8af17091c --- /dev/null +++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp @@ -0,0 +1,77 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/standard_local_system_clock_core.h" +#include "core/hle/service/time/standard_network_system_clock_core.h" +#include "core/hle/service/time/standard_user_system_clock_core.h" + +namespace Service::Time::Clock { + +StandardUserSystemClockCore::StandardUserSystemClockCore( + StandardLocalSystemClockCore& local_system_clock_core, + StandardNetworkSystemClockCore& network_system_clock_core, Core::System& system) + : SystemClockCore(local_system_clock_core.GetSteadyClockCore()), + local_system_clock_core{local_system_clock_core}, + network_system_clock_core{network_system_clock_core}, auto_correction_enabled{}, + auto_correction_time{SteadyClockTimePoint::GetRandom()}, + auto_correction_event{Kernel::WritableEvent::CreateEventPair( + system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {} + +ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system, + bool value) { + if (const ResultCode result{ApplyAutomaticCorrection(system, value)}; + result != RESULT_SUCCESS) { + return result; + } + + auto_correction_enabled = value; + + return RESULT_SUCCESS; +} + +ResultCode StandardUserSystemClockCore::GetClockContext(Core::System& system, + SystemClockContext& context) const { + if (const ResultCode result{ApplyAutomaticCorrection(system, false)}; + result != RESULT_SUCCESS) { + return result; + } + + return local_system_clock_core.GetClockContext(system, context); +} + +ResultCode StandardUserSystemClockCore::Flush(const SystemClockContext& context) { + UNREACHABLE(); + return ERROR_NOT_IMPLEMENTED; +} + +ResultCode StandardUserSystemClockCore::SetClockContext(const SystemClockContext& context) { + UNREACHABLE(); + return ERROR_NOT_IMPLEMENTED; +} + +ResultCode StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& system, + bool value) const { + if (auto_correction_enabled == value) { + return RESULT_SUCCESS; + } + + if (!network_system_clock_core.IsClockSetup(system)) { + return ERROR_UNINITIALIZED_CLOCK; + } + + SystemClockContext context{}; + if (const ResultCode result{network_system_clock_core.GetClockContext(system, context)}; + result != RESULT_SUCCESS) { + return result; + } + + local_system_clock_core.SetClockContext(context); + + return RESULT_SUCCESS; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h new file mode 100644 index 000000000..ef3d468b7 --- /dev/null +++ b/src/core/hle/service/time/standard_user_system_clock_core.h @@ -0,0 +1,57 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class StandardLocalSystemClockCore; +class StandardNetworkSystemClockCore; + +class StandardUserSystemClockCore final : public SystemClockCore { +public: + StandardUserSystemClockCore(StandardLocalSystemClockCore& local_system_clock_core, + StandardNetworkSystemClockCore& network_system_clock_core, + Core::System& system); + + ResultCode SetAutomaticCorrectionEnabled(Core::System& system, bool value); + + ResultCode GetClockContext(Core::System& system, SystemClockContext& context) const override; + + bool IsAutomaticCorrectionEnabled() const { + return auto_correction_enabled; + } + + void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steady_clock_time_point) { + auto_correction_time = steady_clock_time_point; + } + +protected: + ResultCode Flush(const SystemClockContext& context) override; + + ResultCode SetClockContext(const SystemClockContext&) override; + + ResultCode ApplyAutomaticCorrection(Core::System& system, bool value) const; + + const SteadyClockTimePoint& GetAutomaticCorrectionUpdatedTime() const { + return auto_correction_time; + } + +private: + StandardLocalSystemClockCore& local_system_clock_core; + StandardNetworkSystemClockCore& network_system_clock_core; + bool auto_correction_enabled{}; + SteadyClockTimePoint auto_correction_time; + Kernel::EventPair auto_correction_event; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/steady_clock_core.h b/src/core/hle/service/time/steady_clock_core.h new file mode 100644 index 000000000..84af3d105 --- /dev/null +++ b/src/core/hle/service/time/steady_clock_core.h @@ -0,0 +1,55 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/uuid.h" +#include "core/hle/service/time/clock_types.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class SteadyClockCore { +public: + SteadyClockCore() = default; + + const Common::UUID& GetClockSourceId() const { + return clock_source_id; + } + + void SetClockSourceId(const Common::UUID& value) { + clock_source_id = value; + } + + virtual TimeSpanType GetInternalOffset() const = 0; + + virtual void SetInternalOffset(TimeSpanType internal_offset) = 0; + + virtual SteadyClockTimePoint GetTimePoint(Core::System& system) = 0; + + virtual TimeSpanType GetCurrentRawTimePoint(Core::System& system) = 0; + + SteadyClockTimePoint GetCurrentTimePoint(Core::System& system) { + SteadyClockTimePoint result{GetTimePoint(system)}; + result.time_point += GetInternalOffset().ToSeconds(); + return result; + } + + bool IsInitialized() const { + return is_initialized; + } + + void MarkAsInitialized() { + is_initialized = true; + } + +private: + Common::UUID clock_source_id{Common::UUID::Generate()}; + bool is_initialized{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp new file mode 100644 index 000000000..5cdb80703 --- /dev/null +++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp @@ -0,0 +1,55 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" + +namespace Service::Time::Clock { + +SystemClockContextUpdateCallback::SystemClockContextUpdateCallback() = default; +SystemClockContextUpdateCallback::~SystemClockContextUpdateCallback() = default; + +bool SystemClockContextUpdateCallback::NeedUpdate(const SystemClockContext& value) const { + if (has_context) { + return context.offset != value.offset || + context.steady_time_point.clock_source_id != value.steady_time_point.clock_source_id; + } + + return true; +} + +void SystemClockContextUpdateCallback::RegisterOperationEvent( + std::shared_ptr<Kernel::WritableEvent>&& writable_event) { + operation_event_list.emplace_back(std::move(writable_event)); +} + +void SystemClockContextUpdateCallback::BroadcastOperationEvent() { + for (const auto& writable_event : operation_event_list) { + writable_event->Signal(); + } +} + +ResultCode SystemClockContextUpdateCallback::Update(const SystemClockContext& value) { + ResultCode result{RESULT_SUCCESS}; + + if (NeedUpdate(value)) { + context = value; + has_context = true; + + result = Update(); + + if (result == RESULT_SUCCESS) { + BroadcastOperationEvent(); + } + } + + return result; +} + +ResultCode SystemClockContextUpdateCallback::Update() { + return RESULT_SUCCESS; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h new file mode 100644 index 000000000..6260de6c3 --- /dev/null +++ b/src/core/hle/service/time/system_clock_context_update_callback.h @@ -0,0 +1,43 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "core/hle/service/time/clock_types.h" + +namespace Kernel { +class WritableEvent; +} + +namespace Service::Time::Clock { + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class SystemClockContextUpdateCallback { +public: + SystemClockContextUpdateCallback(); + ~SystemClockContextUpdateCallback(); + + bool NeedUpdate(const SystemClockContext& value) const; + + void RegisterOperationEvent(std::shared_ptr<Kernel::WritableEvent>&& writable_event); + + void BroadcastOperationEvent(); + + ResultCode Update(const SystemClockContext& value); + +protected: + virtual ResultCode Update(); + + SystemClockContext context{}; + +private: + bool has_context{}; + std::vector<std::shared_ptr<Kernel::WritableEvent>> operation_event_list; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp new file mode 100644 index 000000000..1a3ab8cfa --- /dev/null +++ b/src/core/hle/service/time/system_clock_core.cpp @@ -0,0 +1,72 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/time/steady_clock_core.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core) + : steady_clock_core{steady_clock_core}, is_initialized{} { + context.steady_time_point.clock_source_id = steady_clock_core.GetClockSourceId(); +} + +SystemClockCore ::~SystemClockCore() = default; + +ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const { + posix_time = 0; + + const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)}; + + SystemClockContext clock_context{}; + if (const ResultCode result{GetClockContext(system, clock_context)}; result != RESULT_SUCCESS) { + return result; + } + + if (current_time_point.clock_source_id != clock_context.steady_time_point.clock_source_id) { + return ERROR_TIME_MISMATCH; + } + + posix_time = clock_context.offset + current_time_point.time_point; + + return RESULT_SUCCESS; +} + +ResultCode SystemClockCore::SetCurrentTime(Core::System& system, s64 posix_time) { + const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)}; + const SystemClockContext clock_context{posix_time - current_time_point.time_point, + current_time_point}; + + if (const ResultCode result{SetClockContext(clock_context)}; result != RESULT_SUCCESS) { + return result; + } + return Flush(clock_context); +} + +ResultCode SystemClockCore::Flush(const SystemClockContext& context) { + if (!system_clock_context_update_callback) { + return RESULT_SUCCESS; + } + return system_clock_context_update_callback->Update(context); +} + +ResultCode SystemClockCore::SetSystemClockContext(const SystemClockContext& context) { + if (const ResultCode result{SetClockContext(context)}; result != RESULT_SUCCESS) { + return result; + } + return Flush(context); +} + +bool SystemClockCore::IsClockSetup(Core::System& system) const { + SystemClockContext value{}; + if (GetClockContext(system, value) == RESULT_SUCCESS) { + const SteadyClockTimePoint steady_clock_time_point{ + steady_clock_core.GetCurrentTimePoint(system)}; + return steady_clock_time_point.clock_source_id == value.steady_time_point.clock_source_id; + } + return {}; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_core.h b/src/core/hle/service/time/system_clock_core.h new file mode 100644 index 000000000..54407a6c5 --- /dev/null +++ b/src/core/hle/service/time/system_clock_core.h @@ -0,0 +1,71 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/time/clock_types.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class SteadyClockCore; +class SystemClockContextUpdateCallback; + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class SystemClockCore { +public: + explicit SystemClockCore(SteadyClockCore& steady_clock_core); + ~SystemClockCore(); + + SteadyClockCore& GetSteadyClockCore() const { + return steady_clock_core; + } + + ResultCode GetCurrentTime(Core::System& system, s64& posix_time) const; + + ResultCode SetCurrentTime(Core::System& system, s64 posix_time); + + virtual ResultCode GetClockContext([[maybe_unused]] Core::System& system, + SystemClockContext& value) const { + value = context; + return RESULT_SUCCESS; + } + + virtual ResultCode SetClockContext(const SystemClockContext& value) { + context = value; + return RESULT_SUCCESS; + } + + virtual ResultCode Flush(const SystemClockContext& context); + + void SetUpdateCallbackInstance(std::shared_ptr<SystemClockContextUpdateCallback> callback) { + system_clock_context_update_callback = std::move(callback); + } + + ResultCode SetSystemClockContext(const SystemClockContext& context); + + bool IsInitialized() const { + return is_initialized; + } + + void MarkAsInitialized() { + is_initialized = true; + } + + bool IsClockSetup(Core::System& system) const; + +private: + SteadyClockCore& steady_clock_core; + SystemClockContext context{}; + bool is_initialized{}; + std::shared_ptr<SystemClockContextUpdateCallback> system_clock_context_update_callback; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp new file mode 100644 index 000000000..c77b98189 --- /dev/null +++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp @@ -0,0 +1,24 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/tick_based_steady_clock_core.h" + +namespace Service::Time::Clock { + +SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) { + const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + + return {ticks_time_span.ToSeconds(), GetClockSourceId()}; +} + +TimeSpanType TickBasedSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { + return TimeSpanType::FromSeconds(GetTimePoint(system).time_point); +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.h b/src/core/hle/service/time/tick_based_steady_clock_core.h new file mode 100644 index 000000000..1a5a53fd7 --- /dev/null +++ b/src/core/hle/service/time/tick_based_steady_clock_core.h @@ -0,0 +1,29 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class TickBasedSteadyClockCore final : public SteadyClockCore { +public: + TimeSpanType GetInternalOffset() const override { + return {}; + } + + void SetInternalOffset(TimeSpanType internal_offset) override {} + + SteadyClockTimePoint GetTimePoint(Core::System& system) override; + + TimeSpanType GetCurrentRawTimePoint(Core::System& system) override; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 6ee77c5f9..8ef4efcef 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -1,9 +1,7 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <chrono> -#include <ctime> #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" @@ -11,429 +9,321 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/scheduler.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" #include "core/hle/service/time/time_sharedmemory.h" -#include "core/settings.h" +#include "core/hle/service/time/time_zone_service.h" namespace Service::Time { -static std::chrono::seconds GetSecondsSinceEpoch() { - return std::chrono::duration_cast<std::chrono::seconds>( - std::chrono::system_clock::now().time_since_epoch()) + - Settings::values.custom_rtc_differential; -} - -static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time, - CalendarAdditionalInfo& additional_info, - [[maybe_unused]] const TimeZoneRule& /*rule*/) { - const std::time_t time(posix_time); - const std::tm* tm = std::localtime(&time); - if (tm == nullptr) { - calendar_time = {}; - additional_info = {}; - return; - } - calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900); - calendar_time.month = static_cast<u8>(tm->tm_mon + 1); - calendar_time.day = static_cast<u8>(tm->tm_mday); - calendar_time.hour = static_cast<u8>(tm->tm_hour); - calendar_time.minute = static_cast<u8>(tm->tm_min); - calendar_time.second = static_cast<u8>(tm->tm_sec); - - additional_info.day_of_week = tm->tm_wday; - additional_info.day_of_year = tm->tm_yday; - std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC")); - additional_info.utc_offset = 0; -} - -static u64 CalendarToPosix(const CalendarTime& calendar_time, - [[maybe_unused]] const TimeZoneRule& /*rule*/) { - std::tm time{}; - time.tm_year = calendar_time.year - 1900; - time.tm_mon = calendar_time.month - 1; - time.tm_mday = calendar_time.day; - - time.tm_hour = calendar_time.hour; - time.tm_min = calendar_time.minute; - time.tm_sec = calendar_time.second; - - std::time_t epoch_time = std::mktime(&time); - return static_cast<u64>(epoch_time); -} - -enum class ClockContextType { - StandardSteady, - StandardUserSystem, - StandardNetworkSystem, - StandardLocalSystem, -}; - class ISystemClock final : public ServiceFramework<ISystemClock> { public: - ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory, - ClockContextType clock_type) - : ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) { + ISystemClock(Clock::SystemClockCore& clock_core) + : ServiceFramework("ISystemClock"), clock_core{clock_core} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, {1, nullptr, "SetCurrentTime"}, - {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}, + {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}, {3, nullptr, "SetSystemClockContext"}, {4, nullptr, "GetOperationEventReadableHandle"}, }; // clang-format on RegisterHandlers(functions); - UpdateSharedMemoryContext(system_clock_context); } private: void GetCurrentTime(Kernel::HLERequestContext& ctx) { - const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; LOG_DEBUG(Service_Time, "called"); + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } + + s64 posix_time{}; + if (const ResultCode result{ + clock_core.GetCurrentTime(Core::System::GetInstance(), posix_time)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(time_since_epoch); + rb.Push<s64>(posix_time); } void GetSystemClockContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + LOG_DEBUG(Service_Time, "called"); + + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } - // TODO(ogniK): This should be updated periodically however since we have it stubbed we'll - // only update when we get a new context - UpdateSharedMemoryContext(system_clock_context); + Clock::SystemClockContext system_clock_context{}; + if (const ResultCode result{ + clock_core.GetClockContext(Core::System::GetInstance(), system_clock_context)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } - IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; + IPC::ResponseBuilder rb{ctx, sizeof(Clock::SystemClockContext) / 4 + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(system_clock_context); } - void UpdateSharedMemoryContext(const SystemClockContext& clock_context) { - switch (clock_type) { - case ClockContextType::StandardLocalSystem: - shared_memory->SetStandardLocalSystemClockContext(clock_context); - break; - case ClockContextType::StandardNetworkSystem: - shared_memory->SetStandardNetworkSystemClockContext(clock_context); - break; - } - } - - SystemClockContext system_clock_context{}; - std::shared_ptr<Service::Time::SharedMemory> shared_memory; - ClockContextType clock_type; + Clock::SystemClockCore& clock_core; }; class ISteadyClock final : public ServiceFramework<ISteadyClock> { public: - ISteadyClock(std::shared_ptr<SharedMemory> shared_memory, Core::System& system) - : ServiceFramework("ISteadyClock"), shared_memory(shared_memory), system(system) { + ISteadyClock(Clock::SteadyClockCore& clock_core) + : ServiceFramework("ISteadyClock"), clock_core{clock_core} { static const FunctionInfo functions[] = { {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, }; RegisterHandlers(functions); - - shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint()); } private: void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - const auto time_point = GetCurrentTimePoint(); - // TODO(ogniK): This should be updated periodically - shared_memory->SetStandardSteadyClockTimepoint(time_point); + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } - IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; + const Clock::SteadyClockTimePoint time_point{ + clock_core.GetCurrentTimePoint(Core::System::GetInstance())}; + IPC::ResponseBuilder rb{ctx, (sizeof(Clock::SteadyClockTimePoint) / 4) + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(time_point); } - SteadyClockTimePoint GetCurrentTimePoint() const { - const auto& core_timing = system.CoreTiming(); - const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); - return {static_cast<u64_le>(ms.count() / 1000), {}}; - } - - std::shared_ptr<SharedMemory> shared_memory; - Core::System& system; + Clock::SteadyClockCore& clock_core; }; -class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { -public: - ITimeZoneService() : ServiceFramework("ITimeZoneService") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, - {1, nullptr, "SetDeviceLocationName"}, - {2, &ITimeZoneService::GetTotalLocationNameCount, "GetTotalLocationNameCount"}, - {3, nullptr, "LoadLocationNameList"}, - {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, - {5, nullptr, "GetTimeZoneRuleVersion"}, - {6, nullptr, "GetDeviceLocationNameAndUpdatedTime"}, - {7, nullptr, "SetDeviceLocationNameWithTimeZoneRule"}, - {8, nullptr, "ParseTimeZoneBinary"}, - {20, nullptr, "GetDeviceLocationNameOperationEventReadableHandle"}, - {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, - {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, - {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"}, - {202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"}, - }; - // clang-format on - - RegisterHandlers(functions); - } +ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal( + Kernel::Thread* thread, Clock::SystemClockContext user_context, + Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) { -private: - LocationName location_name{"UTC"}; - TimeZoneRule my_time_zone_rule{}; + auto& time_manager{module->GetTimeManager()}; - void GetDeviceLocationName(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Time, "called"); + clock_snapshot.is_automatic_correction_enabled = + time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled(); + clock_snapshot.user_context = user_context; + clock_snapshot.network_context = network_context; - IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(location_name); + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().GetDeviceLocationName( + clock_snapshot.location_name)}; + result != RESULT_SUCCESS) { + return result; } - void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + const auto current_time_point{ + time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(Core::System::GetInstance())}; + if (const ResultCode result{Clock::ClockSnapshot::GetCurrentTime( + clock_snapshot.user_time, current_time_point, clock_snapshot.user_context)}; + result != RESULT_SUCCESS) { + return result; } - void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - - ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule)); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + TimeZone::CalendarInfo userCalendarInfo{}; + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules( + clock_snapshot.user_time, userCalendarInfo)}; + result != RESULT_SUCCESS) { + return result; } - void ToCalendarTime(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); - - TimeZoneRule time_zone_rule{}; - auto buffer = ctx.ReadBuffer(); - std::memcpy(&time_zone_rule, buffer.data(), buffer.size()); + clock_snapshot.user_calendar_time = userCalendarInfo.time; + clock_snapshot.user_calendar_additional_time = userCalendarInfo.additiona_info; - CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; - CalendarAdditionalInfo additional_info{}; - - PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule); - - IPC::ResponseBuilder rb{ctx, 10}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(calendar_time); - rb.PushRaw(additional_info); - } - - void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); - - CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; - CalendarAdditionalInfo additional_info{}; - - PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule); - - IPC::ResponseBuilder rb{ctx, 10}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(calendar_time); - rb.PushRaw(additional_info); + if (Clock::ClockSnapshot::GetCurrentTime(clock_snapshot.network_time, current_time_point, + clock_snapshot.network_context) != RESULT_SUCCESS) { + clock_snapshot.network_time = 0; } - void ToPosixTime(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Figure out how to handle multiple times - LOG_WARNING(Service_Time, "(STUBBED) called"); - - IPC::RequestParser rp{ctx}; - auto calendar_time = rp.PopRaw<CalendarTime>(); - auto posix_time = CalendarToPosix(calendar_time, {}); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u32>(1); // Amount of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(u64)); + TimeZone::CalendarInfo networkCalendarInfo{}; + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules( + clock_snapshot.network_time, networkCalendarInfo)}; + result != RESULT_SUCCESS) { + return result; } - void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - - IPC::RequestParser rp{ctx}; - auto calendar_time = rp.PopRaw<CalendarTime>(); - auto posix_time = CalendarToPosix(calendar_time, {}); + clock_snapshot.network_calendar_time = networkCalendarInfo.time; + clock_snapshot.network_calendar_additional_time = networkCalendarInfo.additiona_info; + clock_snapshot.type = type; - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u32>(1); // Amount of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(u64)); - } -}; + return RESULT_SUCCESS; +} void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem); + rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore()); } void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem); + rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore()); } void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISteadyClock>(shared_memory, system); + rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore()); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ITimeZoneService>(); + rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager()); } void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem); + rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardLocalSystemClockCore()); } -void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { +void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient( + Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); + auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system)); +} - IPC::RequestParser rp{ctx}; - const auto initial_type = rp.PopRaw<u8>(); +void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); - const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; - const std::time_t time(time_since_epoch); - const std::tm* tm = std::localtime(&time); - if (tm == nullptr) { - LOG_ERROR(Service_Time, "tm is a nullptr"); + auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()}; + if (!steady_clock_core.IsInitialized()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Find appropriate error code + rb.Push(ERROR_UNINITIALIZED_CLOCK); return; } - const auto& core_timing = system.CoreTiming(); - const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); - const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}}; - - CalendarTime calendar_time{}; - calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900); - calendar_time.month = static_cast<u8>(tm->tm_mon + 1); - calendar_time.day = static_cast<u8>(tm->tm_mday); - calendar_time.hour = static_cast<u8>(tm->tm_hour); - calendar_time.minute = static_cast<u8>(tm->tm_min); - calendar_time.second = static_cast<u8>(tm->tm_sec); + IPC::RequestParser rp{ctx}; + const auto context{rp.PopRaw<Clock::SystemClockContext>()}; + const auto current_time_point{ + steady_clock_core.GetCurrentTimePoint(Core::System::GetInstance())}; + + if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) { + const auto ticks{Clock::TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + const s64 base_time_point{context.offset + current_time_point.time_point - + ticks.ToSeconds()}; + IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(base_time_point); + return; + } - ClockSnapshot clock_snapshot{}; - clock_snapshot.system_posix_time = time_since_epoch; - clock_snapshot.network_posix_time = time_since_epoch; - clock_snapshot.system_calendar_time = calendar_time; - clock_snapshot.network_calendar_time = calendar_time; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_TIME_MISMATCH); +} - CalendarAdditionalInfo additional_info{}; - PosixToCalendar(time_since_epoch, calendar_time, additional_info, {}); +void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::RequestParser rp{ctx}; + const auto type{rp.PopRaw<u8>()}; - clock_snapshot.system_calendar_info = additional_info; - clock_snapshot.network_calendar_info = additional_info; + Clock::SystemClockContext user_context{}; + if (const ResultCode result{ + module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext( + Core::System::GetInstance(), user_context)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + Clock::SystemClockContext network_context{}; + if (const ResultCode result{ + module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( + Core::System::GetInstance(), network_context)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } - clock_snapshot.steady_clock_timepoint = steady_clock_time_point; - clock_snapshot.location_name = LocationName{"UTC"}; - clock_snapshot.clock_auto_adjustment_enabled = 1; - clock_snapshot.type = initial_type; + Clock::ClockSnapshot clock_snapshot{}; + if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal( + &ctx.GetThread(), user_context, network_context, type, clock_snapshot)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot)); + ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot)); } -void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser( - Kernel::HLERequestContext& ctx) { +void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::RequestParser rp{ctx}; - const auto snapshot_a = rp.PopRaw<ClockSnapshot>(); - const auto snapshot_b = rp.PopRaw<ClockSnapshot>(); - const u64 difference = - snapshot_b.user_clock_context.offset - snapshot_a.user_clock_context.offset; + const auto type{rp.PopRaw<u8>()}; + rp.AlignWithPadding(); + + const Clock::SystemClockContext user_context{rp.PopRaw<Clock::SystemClockContext>()}; + const Clock::SystemClockContext network_context{rp.PopRaw<Clock::SystemClockContext>()}; - IPC::ResponseBuilder rb{ctx, 4}; + Clock::ClockSnapshot clock_snapshot{}; + if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal( + &ctx.GetThread(), user_context, network_context, type, clock_snapshot)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(difference); + ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot)); } void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder()); -} - -void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled( - Kernel::HLERequestContext& ctx) { - // ogniK(TODO): When clock contexts are implemented, the value should be read from the context - // instead of our shared memory holder - LOG_DEBUG(Service_Time, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled()); -} - -void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled( - Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto enabled = rp.Pop<u8>(); - - LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called"); - - // TODO(ogniK): Update clock contexts and correct timespans - - shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder()); } -Module::Interface::Interface(std::shared_ptr<Module> time, - std::shared_ptr<SharedMemory> shared_memory, Core::System& system, - const char* name) - : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)), - system(system) {} +Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) + : ServiceFramework(name), module{std::move(module)}, system{system} {} Module::Interface::~Interface() = default; void InstallInterfaces(Core::System& system) { - auto time = std::make_shared<Module>(); - auto shared_mem = std::make_shared<SharedMemory>(system); - - std::make_shared<Time>(time, shared_mem, system, "time:a") - ->InstallAsService(system.ServiceManager()); - std::make_shared<Time>(time, shared_mem, system, "time:s") - ->InstallAsService(system.ServiceManager()); - std::make_shared<Time>(std::move(time), shared_mem, system, "time:u") - ->InstallAsService(system.ServiceManager()); + auto module{std::make_shared<Module>(system)}; + std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager()); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index c32d32860..aadc2df60 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -4,84 +4,23 @@ #pragma once -#include <array> -#include "common/common_funcs.h" #include "core/hle/service/service.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/time_manager.h" -namespace Service::Time { - -class SharedMemory; - -struct LocationName { - std::array<u8, 0x24> name; -}; -static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size"); - -struct CalendarTime { - u16_le year; - u8 month; // Starts at 1 - u8 day; // Starts at 1 - u8 hour; - u8 minute; - u8 second; -}; -static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size"); - -struct CalendarAdditionalInfo { - u32_le day_of_week; - u32_le day_of_year; - std::array<u8, 8> name; - u8 is_dst; - s32_le utc_offset; -}; -static_assert(sizeof(CalendarAdditionalInfo) == 0x18, - "CalendarAdditionalInfo structure has incorrect size"); - -// TODO(mailwl) RE this structure -struct TimeZoneRule { - INSERT_PADDING_BYTES(0x4000); -}; - -struct SteadyClockTimePoint { - using SourceID = std::array<u8, 16>; +namespace Core { +class System; +} - u64_le value; - SourceID source_id; -}; -static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); - -struct SystemClockContext { - u64_le offset; - SteadyClockTimePoint time_point; -}; -static_assert(sizeof(SystemClockContext) == 0x20, - "SystemClockContext structure has incorrect size"); - -struct ClockSnapshot { - SystemClockContext user_clock_context; - SystemClockContext network_clock_context; - s64_le system_posix_time; - s64_le network_posix_time; - CalendarTime system_calendar_time; - CalendarTime network_calendar_time; - CalendarAdditionalInfo system_calendar_info; - CalendarAdditionalInfo network_calendar_info; - SteadyClockTimePoint steady_clock_timepoint; - LocationName location_name; - u8 clock_auto_adjustment_enabled; - u8 type; - u8 version; - INSERT_PADDING_BYTES(1); -}; -static_assert(sizeof(ClockSnapshot) == 0xd0, "ClockSnapshot is an invalid size"); +namespace Service::Time { class Module final { public: + Module(Core::System& system) : time_manager{system} {} + class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> time, - std::shared_ptr<SharedMemory> shared_memory, Core::System& system, - const char* name); + explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); @@ -89,17 +28,29 @@ public: void GetStandardSteadyClock(Kernel::HLERequestContext& ctx); void GetTimeZoneService(Kernel::HLERequestContext& ctx); void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); + void IsStandardNetworkSystemClockAccuracySufficient(Kernel::HLERequestContext& ctx); + void CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx); void GetClockSnapshot(Kernel::HLERequestContext& ctx); - void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx); + void GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx); void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); - void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); - void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); + + private: + ResultCode GetClockSnapshotFromSystemClockContextInternal( + Kernel::Thread* thread, Clock::SystemClockContext user_context, + Clock::SystemClockContext network_context, u8 type, + Clock::ClockSnapshot& cloc_snapshot); protected: - std::shared_ptr<Module> time; - std::shared_ptr<SharedMemory> shared_memory; + std::shared_ptr<Module> module; Core::System& system; }; + + TimeManager& GetTimeManager() { + return time_manager; + } + +private: + TimeManager time_manager; }; /// Registers all Time services with the specified service manager. diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp new file mode 100644 index 000000000..9d6c55865 --- /dev/null +++ b/src/core/hle/service/time/time_manager.cpp @@ -0,0 +1,137 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <ctime> + +#include "core/hle/service/time/ephemeral_network_system_clock_context_writer.h" +#include "core/hle/service/time/local_system_clock_context_writer.h" +#include "core/hle/service/time/network_system_clock_context_writer.h" +#include "core/hle/service/time/time_manager.h" +#include "core/settings.h" + +namespace Service::Time { + +constexpr Clock::TimeSpanType standard_network_clock_accuracy{0x0009356907420000ULL}; + +static std::chrono::seconds GetSecondsSinceEpoch() { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()) + + Settings::values.custom_rtc_differential; +} + +static s64 GetExternalRtcValue() { + return GetSecondsSinceEpoch().count(); +} + +TimeManager::TimeManager(Core::System& system) + : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core}, + standard_network_system_clock_core{standard_steady_clock_core}, + standard_user_system_clock_core{standard_local_system_clock_core, + standard_network_system_clock_core, system}, + ephemeral_network_system_clock_core{tick_based_steady_clock_core}, + local_system_clock_context_writer{ + std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)}, + network_system_clock_context_writer{ + std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)}, + ephemeral_network_system_clock_context_writer{ + std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()}, + time_zone_content_manager{*this, system} { + + const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())}; + SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {}); + SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds()); + SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy); + SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom()); + SetupEphemeralNetworkSystemClock(); +} + +TimeManager::~TimeManager() = default; + +void TimeManager::SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, + u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file) { + if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( + location_name, vfs_file) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point); + time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount( + total_location_name_count); + time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version); + time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized(); +} + +void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, + Clock::TimeSpanType setup_value, + Clock::TimeSpanType internal_offset, + bool is_rtc_reset_detected) { + standard_steady_clock_core.SetClockSourceId(clock_source_id); + standard_steady_clock_core.SetSetupValue(setup_value); + standard_steady_clock_core.SetInternalOffset(internal_offset); + standard_steady_clock_core.MarkAsInitialized(); + + const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)}; + shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point); +} + +void TimeManager::SetupStandardLocalSystemClock(Core::System& system, + Clock::SystemClockContext clock_context, + s64 posix_time) { + standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer); + + const auto current_time_point{ + standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)}; + if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) { + standard_local_system_clock_core.SetSystemClockContext(clock_context); + } else { + if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + } + + standard_local_system_clock_core.MarkAsInitialized(); +} + +void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, + Clock::TimeSpanType sufficient_accuracy) { + standard_network_system_clock_core.SetUpdateCallbackInstance( + network_system_clock_context_writer); + + if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy( + sufficient_accuracy); + standard_network_system_clock_core.MarkAsInitialized(); +} + +void TimeManager::SetupStandardUserSystemClock( + Core::System& system, bool is_automatic_correction_enabled, + Clock::SteadyClockTimePoint steady_clock_time_point) { + if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled( + system, is_automatic_correction_enabled) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point); + standard_user_system_clock_core.MarkAsInitialized(); + shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled); +} + +void TimeManager::SetupEphemeralNetworkSystemClock() { + ephemeral_network_system_clock_core.SetUpdateCallbackInstance( + ephemeral_network_system_clock_context_writer); + ephemeral_network_system_clock_core.MarkAsInitialized(); +} + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h new file mode 100644 index 000000000..8e65f0d22 --- /dev/null +++ b/src/core/hle/service/time/time_manager.h @@ -0,0 +1,117 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/ephemeral_network_system_clock_core.h" +#include "core/hle/service/time/standard_local_system_clock_core.h" +#include "core/hle/service/time/standard_network_system_clock_core.h" +#include "core/hle/service/time/standard_steady_clock_core.h" +#include "core/hle/service/time/standard_user_system_clock_core.h" +#include "core/hle/service/time/tick_based_steady_clock_core.h" +#include "core/hle/service/time/time_sharedmemory.h" +#include "core/hle/service/time/time_zone_content_manager.h" + +namespace Service::Time { + +namespace Clock { +class EphemeralNetworkSystemClockContextWriter; +class LocalSystemClockContextWriter; +class NetworkSystemClockContextWriter; +} // namespace Clock + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class TimeManager final { +public: + explicit TimeManager(Core::System& system); + ~TimeManager(); + + Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() { + return standard_steady_clock_core; + } + + const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const { + return standard_steady_clock_core; + } + + Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() { + return standard_local_system_clock_core; + } + + const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const { + return standard_local_system_clock_core; + } + + Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() { + return standard_network_system_clock_core; + } + + const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const { + return standard_network_system_clock_core; + } + + Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() { + return standard_user_system_clock_core; + } + + const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const { + return standard_user_system_clock_core; + } + + TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() { + return time_zone_content_manager; + } + + const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const { + return time_zone_content_manager; + } + + SharedMemory& GetSharedMemory() { + return shared_memory; + } + + const SharedMemory& GetSharedMemory() const { + return shared_memory; + } + + void SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file); + +private: + void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, + Clock::TimeSpanType setup_value, + Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected); + void SetupStandardLocalSystemClock(Core::System& system, + Clock::SystemClockContext clock_context, s64 posix_time); + void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, + Clock::TimeSpanType sufficient_accuracy); + void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled, + Clock::SteadyClockTimePoint steady_clock_time_point); + void SetupEphemeralNetworkSystemClock(); + + SharedMemory shared_memory; + + Clock::StandardSteadyClockCore standard_steady_clock_core; + Clock::TickBasedSteadyClockCore tick_based_steady_clock_core; + Clock::StandardLocalSystemClockCore standard_local_system_clock_core; + Clock::StandardNetworkSystemClockCore standard_network_system_clock_core; + Clock::StandardUserSystemClockCore standard_user_system_clock_core; + Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core; + + std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer; + std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer; + std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter> + ephemeral_network_system_clock_context_writer; + + TimeZone::TimeZoneContentManager time_zone_content_manager; +}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp index 4035f5072..9b03191bf 100644 --- a/src/core/hle/service/time/time_sharedmemory.cpp +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -3,20 +3,21 @@ // Refer to the license.txt file included. #include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" #include "core/hle/service/time/time_sharedmemory.h" namespace Service::Time { -const std::size_t SHARED_MEMORY_SIZE = 0x1000; + +static constexpr std::size_t SHARED_MEMORY_SIZE{0x1000}; SharedMemory::SharedMemory(Core::System& system) : system(system) { shared_memory_holder = Kernel::SharedMemory::Create( system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory"); - - // Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash - // if it's set to anything else - shared_memory_format.format_version = 14; - std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format)); + std::memset(shared_memory_holder->GetPointer(), 0, SHARED_MEMORY_SIZE); } SharedMemory::~SharedMemory() = default; @@ -25,44 +26,32 @@ std::shared_ptr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() cons return shared_memory_holder; } -void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) { +void SharedMemory::SetupStandardSteadyClock(Core::System& system, + const Common::UUID& clock_source_id, + Clock::TimeSpanType current_time_point) { + const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + const Clock::SteadyClockContext context{ + static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), + clock_source_id}; shared_memory_format.standard_steady_clock_timepoint.StoreData( - shared_memory_holder->GetPointer(), timepoint); + shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) { +void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) { shared_memory_format.standard_local_system_clock_context.StoreData( shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) { +void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) { shared_memory_format.standard_network_system_clock_context.StoreData( shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) { +void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) { shared_memory_format.standard_user_system_clock_automatic_correction.StoreData( - shared_memory_holder->GetPointer(), enabled); -} - -SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() { - return shared_memory_format.standard_steady_clock_timepoint.ReadData( - shared_memory_holder->GetPointer()); -} - -SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() { - return shared_memory_format.standard_local_system_clock_context.ReadData( - shared_memory_holder->GetPointer()); -} - -SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() { - return shared_memory_format.standard_network_system_clock_context.ReadData( - shared_memory_holder->GetPointer()); -} - -bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() { - return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData( - shared_memory_holder->GetPointer()); + shared_memory_holder->GetPointer(), is_enabled); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h index 904a96430..5976b2046 100644 --- a/src/core/hle/service/time/time_sharedmemory.h +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -5,11 +5,14 @@ #pragma once #include "common/common_types.h" +#include "common/uuid.h" #include "core/hle/kernel/shared_memory.h" -#include "core/hle/service/time/time.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/service/time/clock_types.h" namespace Service::Time { -class SharedMemory { + +class SharedMemory final { public: explicit SharedMemory(Core::System& system); ~SharedMemory(); @@ -17,22 +20,10 @@ public: // Return the shared memory handle std::shared_ptr<Kernel::SharedMemory> GetSharedMemoryHolder() const; - // Set memory barriers in shared memory and update them - void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint); - void SetStandardLocalSystemClockContext(const SystemClockContext& context); - void SetStandardNetworkSystemClockContext(const SystemClockContext& context); - void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled); - - // Pull from memory barriers in the shared memory - SteadyClockTimePoint GetStandardSteadyClockTimepoint(); - SystemClockContext GetStandardLocalSystemClockContext(); - SystemClockContext GetStandardNetworkSystemClockContext(); - bool GetStandardUserSystemClockAutomaticCorrectionEnabled(); - // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this? template <typename T, std::size_t Offset> struct MemoryBarrier { - static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable"); + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); u32_le read_attempt{}; std::array<T, 2> data{}; @@ -57,16 +48,22 @@ public: // Shared memory format struct Format { - MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint; - MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context; - MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context; + MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint; + MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context; + MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context; MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; u32_le format_version; }; static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); + void SetupStandardSteadyClock(Core::System& system, const Common::UUID& clock_source_id, + Clock::TimeSpanType currentTimePoint); + void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); + void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); + void SetAutomaticCorrectionEnabled(bool is_enabled); + private: - std::shared_ptr<Kernel::SharedMemory> shared_memory_holder{}; + std::shared_ptr<Kernel::SharedMemory> shared_memory_holder; Core::System& system; Format shared_memory_format{}; }; diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp new file mode 100644 index 000000000..57b1a2bca --- /dev/null +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -0,0 +1,125 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <sstream> + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/system_archive.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/time/time_manager.h" +#include "core/hle/service/time/time_zone_content_manager.h" + +namespace Service::Time::TimeZone { + +constexpr u64 time_zone_binary_titleid{0x010000000000080E}; + +static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) { + const auto* nand{system.GetFileSystemController().GetSystemNANDContents()}; + const auto nca{nand->GetEntry(time_zone_binary_titleid, FileSys::ContentRecordType::Data)}; + + FileSys::VirtualFile romfs; + if (nca) { + romfs = nca->GetRomFS(); + } + + if (!romfs) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(time_zone_binary_titleid); + } + + if (!romfs) { + LOG_ERROR(Service_Time, "Failed to find or synthesize {:016X!}", time_zone_binary_titleid); + return {}; + } + + return FileSys::ExtractRomFS(romfs); +} + +static std::vector<std::string> BuildLocationNameCache(Core::System& system) { + const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; + if (!extracted_romfs) { + LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); + return {}; + } + + const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")}; + if (!binary_list) { + LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid); + return {}; + } + + std::vector<char> raw_data(binary_list->GetSize()); + binary_list->ReadBytes<char>(raw_data.data(), binary_list->GetSize()); + + std::stringstream data_stream{raw_data.data()}; + std::string name; + std::vector<std::string> location_name_cache; + while (std::getline(data_stream, name)) { + name.pop_back(); // Remove carriage return + location_name_cache.emplace_back(std::move(name)); + } + return location_name_cache; +} + +TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system) + : system{system}, location_name_cache{BuildLocationNameCache(system)} { + if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile("GMT", vfs_file) == RESULT_SUCCESS) { + const auto time_point{ + time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)}; + time_manager.SetupTimeZoneManager("GMT", time_point, location_name_cache.size(), {}, + vfs_file); + } else { + time_zone_manager.MarkAsInitialized(); + } +} + +ResultCode TimeZoneContentManager::LoadTimeZoneRule(TimeZoneRule& rules, + const std::string& location_name) const { + FileSys::VirtualFile vfs_file; + if (const ResultCode result{GetTimeZoneInfoFile(location_name, vfs_file)}; + result != RESULT_SUCCESS) { + return result; + } + + return time_zone_manager.ParseTimeZoneRuleBinary(rules, vfs_file); +} + +bool TimeZoneContentManager::IsLocationNameValid(const std::string& location_name) const { + return std::find(location_name_cache.begin(), location_name_cache.end(), location_name) != + location_name_cache.end(); +} + +ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_name, + FileSys::VirtualFile& vfs_file) const { + if (!IsLocationNameValid(location_name)) { + return ERROR_TIME_NOT_FOUND; + } + + const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; + if (!extracted_romfs) { + LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); + return ERROR_TIME_NOT_FOUND; + } + + const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")}; + if (!zoneinfo_dir) { + LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid); + return ERROR_TIME_NOT_FOUND; + } + + vfs_file = zoneinfo_dir->GetFile(location_name); + if (!vfs_file) { + LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"!", time_zone_binary_titleid, + location_name); + return ERROR_TIME_NOT_FOUND; + } + + return RESULT_SUCCESS; +} + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h new file mode 100644 index 000000000..4f302c3b9 --- /dev/null +++ b/src/core/hle/service/time/time_zone_content_manager.h @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <vector> + +#include "core/hle/service/time/time_zone_manager.h" + +namespace Core { +class System; +} + +namespace Service::Time { +class TimeManager; +} + +namespace Service::Time::TimeZone { + +class TimeZoneContentManager final { +public: + TimeZoneContentManager(TimeManager& time_manager, Core::System& system); + + TimeZoneManager& GetTimeZoneManager() { + return time_zone_manager; + } + + const TimeZoneManager& GetTimeZoneManager() const { + return time_zone_manager; + } + + ResultCode LoadTimeZoneRule(TimeZoneRule& rules, const std::string& location_name) const; + +private: + bool IsLocationNameValid(const std::string& location_name) const; + ResultCode GetTimeZoneInfoFile(const std::string& location_name, + FileSys::VirtualFile& vfs_file) const; + + Core::System& system; + TimeZoneManager time_zone_manager; + const std::vector<std::string> location_name_cache; +}; + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp new file mode 100644 index 000000000..07b553a43 --- /dev/null +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -0,0 +1,1039 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <climits> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/system_archive.h" +#include "core/hle/service/time/time_zone_manager.h" + +namespace Service::Time::TimeZone { + +static constexpr s32 epoch_year{1970}; +static constexpr s32 year_base{1900}; +static constexpr s32 epoch_week_day{4}; +static constexpr s32 seconds_per_minute{60}; +static constexpr s32 minutes_per_hour{60}; +static constexpr s32 hours_per_day{24}; +static constexpr s32 days_per_week{7}; +static constexpr s32 days_per_normal_year{365}; +static constexpr s32 days_per_leap_year{366}; +static constexpr s32 months_per_year{12}; +static constexpr s32 seconds_per_hour{seconds_per_minute * minutes_per_hour}; +static constexpr s32 seconds_per_day{seconds_per_hour * hours_per_day}; +static constexpr s32 years_per_repeat{400}; +static constexpr s64 average_seconds_per_year{31556952}; +static constexpr s64 seconds_per_repeat{years_per_repeat * average_seconds_per_year}; + +struct Rule { + enum class Type : u32 { JulianDay, DayOfYear, MonthNthDayOfWeek }; + Type rule_type{}; + s32 day{}; + s32 week{}; + s32 month{}; + s32 transition_time{}; +}; + +struct CalendarTimeInternal { + s64 year{}; + s8 month{}; + s8 day{}; + s8 hour{}; + s8 minute{}; + s8 second{}; + int Compare(const CalendarTimeInternal& other) const { + if (year != other.year) { + if (year < other.year) { + return -1; + } + return 1; + } + if (month != other.month) { + return month - other.month; + } + if (day != other.day) { + return day - other.day; + } + if (hour != other.hour) { + return hour - other.hour; + } + if (minute != other.minute) { + return minute - other.minute; + } + if (second != other.second) { + return second - other.second; + } + return {}; + } +}; + +template <typename TResult, typename TOperand> +static bool SafeAdd(TResult& result, TOperand op) { + result = result + op; + return true; +} + +template <typename TResult, typename TUnit, typename TBase> +static bool SafeNormalize(TResult& result, TUnit& unit, TBase base) { + TUnit delta{}; + if (unit >= 0) { + delta = unit / base; + } else { + delta = -1 - (-1 - unit) / base; + } + unit -= delta * base; + return SafeAdd(result, delta); +} + +template <typename T> +static constexpr bool IsLeapYear(T year) { + return ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0); +} + +template <typename T> +static constexpr T GetYearLengthInDays(T year) { + return IsLeapYear(year) ? days_per_leap_year : days_per_normal_year; +} + +static constexpr s64 GetLeapDaysFromYearPositive(s64 year) { + return year / 4 - year / 100 + year / years_per_repeat; +} + +static constexpr s64 GetLeapDaysFromYear(s64 year) { + if (year < 0) { + return -1 - GetLeapDaysFromYearPositive(-1 - year); + } else { + return GetLeapDaysFromYearPositive(year); + } +} + +static constexpr int GetMonthLength(bool is_leap_year, int month) { + constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + return is_leap_year ? month_lengths_leap[month] : month_lengths[month]; +} + +static constexpr bool IsDigit(char value) { + return value >= '0' && value <= '9'; +} + +static constexpr int GetQZName(const char* name, int offset, char delimiter) { + while (name[offset] != '\0' && name[offset] != delimiter) { + offset++; + } + return offset; +} + +static constexpr int GetTZName(const char* name, int offset) { + for (char value{name[offset]}; + value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+'; + offset++) { + value = name[offset]; + } + return offset; +} + +static constexpr bool GetInteger(const char* name, int& offset, int& value, int min, int max) { + value = 0; + char temp{name[offset]}; + if (!IsDigit(temp)) { + return {}; + } + do { + value = value * 10 + (temp - '0'); + if (value > max) { + return {}; + } + temp = name[offset]; + } while (IsDigit(temp)); + + return value >= min; +} + +static constexpr bool GetSeconds(const char* name, int& offset, int& seconds) { + seconds = 0; + int value{}; + if (!GetInteger(name, offset, value, 0, hours_per_day * days_per_week - 1)) { + return {}; + } + seconds = value * seconds_per_hour; + + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, minutes_per_hour - 1)) { + return {}; + } + seconds += value * seconds_per_minute; + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, seconds_per_minute)) { + return {}; + } + seconds += value; + } + } + return true; +} + +static constexpr bool GetOffset(const char* name, int& offset, int& value) { + bool is_negative{}; + if (name[offset] == '-') { + is_negative = true; + offset++; + } else if (name[offset] == '+') { + offset++; + } + if (!GetSeconds(name, offset, value)) { + return {}; + } + if (is_negative) { + value = -value; + } + return true; +} + +static constexpr bool GetRule(const char* name, int& position, Rule& rule) { + bool is_valid{}; + if (name[position] == 'J') { + position++; + rule.rule_type = Rule::Type::JulianDay; + is_valid = GetInteger(name, position, rule.day, 1, days_per_normal_year); + } else if (name[position] == 'M') { + position++; + rule.rule_type = Rule::Type::MonthNthDayOfWeek; + is_valid = GetInteger(name, position, rule.month, 1, months_per_year); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.week, 1, 5); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.day, 0, days_per_week - 1); + } else if (isdigit(name[position])) { + rule.rule_type = Rule::Type::DayOfYear; + is_valid = GetInteger(name, position, rule.day, 0, days_per_leap_year - 1); + } else { + return {}; + } + if (!is_valid) { + return {}; + } + if (name[position] == '/') { + position++; + return GetOffset(name, position, rule.transition_time); + } else { + rule.transition_time = 2 * seconds_per_hour; + } + return true; +} + +static constexpr int TransitionTime(int year, Rule rule, int offset) { + int value{}; + switch (rule.rule_type) { + case Rule::Type::JulianDay: + value = (rule.day - 1) * seconds_per_day; + if (IsLeapYear(year) && rule.day >= 60) { + value += seconds_per_day; + } + break; + case Rule::Type::DayOfYear: + value = rule.day * seconds_per_day; + break; + case Rule::Type::MonthNthDayOfWeek: { + // Use Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence) to + // calculate the day of the week for any Julian or Gregorian calendar date. + const int m1{(rule.month + 9) % 12 + 1}; + const int yy0{(rule.month <= 2) ? (year - 1) : year}; + const int yy1{yy0 / 100}; + const int yy2{yy0 % 100}; + int day_of_week{((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7}; + + if (day_of_week < 0) { + day_of_week += days_per_week; + } + int day{rule.day - day_of_week}; + if (day < 0) { + day += days_per_week; + } + for (int i{1}; i < rule.week; i++) { + if (day + days_per_week >= GetMonthLength(IsLeapYear(year), rule.month - 1)) { + break; + } + day += days_per_week; + } + + value = day * seconds_per_day; + for (int index{}; index < rule.month - 1; ++index) { + value += GetMonthLength(IsLeapYear(year), index) * seconds_per_day; + } + break; + } + default: + UNREACHABLE(); + } + return value + rule.transition_time + offset; +} + +static bool ParsePosixName(const char* name, TimeZoneRule& rule) { + constexpr char default_rule[]{",M4.1.0,M10.5.0"}; + const char* std_name{name}; + int std_len{}; + int offset{}; + int std_offset{}; + + if (name[offset] == '<') { + offset++; + std_name = name + offset; + const int std_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + std_len = offset - std_name_offset; + offset++; + } else { + offset = GetTZName(name, offset); + std_len = offset; + } + if (!std_len) { + return {}; + } + if (!GetOffset(name, offset, std_offset)) { + return {}; + } + + int char_count{std_len + 1}; + int dest_len{}; + int dest_offset{}; + const char* dest_name{name + offset}; + if (rule.chars.size() < char_count) { + return {}; + } + + if (name[offset] != '\0') { + if (name[offset] == '<') { + dest_name = name + (++offset); + const int dest_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + dest_len = offset - dest_name_offset; + offset++; + } else { + dest_name = name + (offset); + offset = GetTZName(name, offset); + dest_len = offset; + } + if (dest_len == 0) { + return {}; + } + char_count += dest_len + 1; + if (rule.chars.size() < char_count) { + return {}; + } + if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') { + if (!GetOffset(name, offset, dest_offset)) { + return {}; + } + } else { + dest_offset = std_offset - seconds_per_hour; + } + if (name[offset] == '\0') { + name = default_rule; + offset = 0; + } + if (name[offset] == ',' || name[offset] == ';') { + offset++; + + Rule start{}; + if (!GetRule(name, offset, start)) { + return {}; + } + if (name[offset++] != ',') { + return {}; + } + + Rule end{}; + if (!GetRule(name, offset, end)) { + return {}; + } + if (name[offset] != '\0') { + return {}; + } + + rule.type_count = 2; + rule.ttis[0].gmt_offset = -dest_offset; + rule.ttis[0].is_dst = true; + rule.ttis[0].abbreviation_list_index = std_len + 1; + rule.ttis[1].gmt_offset = -std_offset; + rule.ttis[1].is_dst = false; + rule.ttis[1].abbreviation_list_index = 0; + rule.default_type = 0; + + s64 jan_first{}; + int time_count{}; + int jan_offset{}; + int year_beginning{epoch_year}; + do { + const int year_seconds{GetYearLengthInDays(year_beginning - 1) * seconds_per_day}; + year_beginning--; + if (!SafeAdd(jan_first, -year_seconds)) { + jan_offset = -year_seconds; + break; + } + } while (epoch_year - years_per_repeat / 2 < year_beginning); + + int year_limit{year_beginning + years_per_repeat + 1}; + int year{}; + for (year = year_beginning; year < year_limit; year++) { + int start_time{TransitionTime(year, start, std_offset)}; + int end_time{TransitionTime(year, end, dest_offset)}; + const int year_seconds{GetYearLengthInDays(year) * seconds_per_day}; + const bool is_reversed{end_time < start_time}; + if (is_reversed) { + int swap{start_time}; + start_time = end_time; + end_time = swap; + } + + if (is_reversed || + (start_time < end_time && + (end_time - start_time < (year_seconds + (std_offset - dest_offset))))) { + if (rule.ats.size() - 2 < time_count) { + break; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + start_time)) { + rule.types[time_count++] = is_reversed ? 1 : 0; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 1 : 0; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + end_time)) { + rule.types[time_count++] = is_reversed ? 0 : 1; + year_limit = year + years_per_repeat + 1; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 0 : 1; + } + } + if (!SafeAdd(jan_first, jan_offset + year_seconds)) { + break; + } + jan_offset = 0; + } + rule.time_count = time_count; + if (time_count == 0) { + rule.type_count = 1; + } else if (years_per_repeat < year - year_beginning) { + rule.go_back = true; + rule.go_ahead = true; + } + } else { + if (name[offset] == '\0') { + return {}; + } + + s64 their_std_offset{}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + if (rule.ttis[type].is_standard_time_daylight) { + their_std_offset = -rule.ttis[type].gmt_offset; + } + } + + s64 their_offset{their_std_offset}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + rule.types[index] = rule.ttis[type].is_dst ? 1 : 0; + if (!rule.ttis[type].is_gmt) { + if (!rule.ttis[type].is_standard_time_daylight) { + rule.ats[index] += dest_offset - their_std_offset; + } else { + rule.ats[index] += std_offset - their_std_offset; + } + } + their_offset = -rule.ttis[type].gmt_offset; + if (!rule.ttis[type].is_dst) { + their_std_offset = their_offset; + } + } + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + rule.ttis[1].gmt_offset = -dest_offset; + rule.ttis[1].is_dst = true; + rule.ttis[1].abbreviation_list_index = std_len + 1; + rule.type_count = 2; + rule.default_type = 0; + } + } else { + // Default is standard time + rule.type_count = 1; + rule.time_count = 0; + rule.default_type = 0; + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + } + + rule.char_count = char_count; + for (int index{}; index < std_len; ++index) { + rule.chars[index] = std_name[index]; + } + + rule.chars[std_len++] = '\0'; + if (dest_len != 0) { + for (int index{}; index < dest_len; ++index) { + rule.chars[std_len + index] = dest_name[index]; + } + rule.chars[std_len + dest_len] = '\0'; + } + + return true; +} + +static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFile& vfs_file) { + TzifHeader header{}; + if (vfs_file->ReadObject<TzifHeader>(&header) != sizeof(TzifHeader)) { + return {}; + } + + constexpr s32 time_zone_max_leaps{50}; + constexpr s32 time_zone_max_chars{50}; + if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps && + 0 < header.type_count && header.type_count < time_zone_rule.ttis.size() && + 0 <= header.time_count && header.time_count < time_zone_rule.ats.size() && + 0 <= header.char_count && header.char_count < time_zone_max_chars && + (header.ttis_std_count == header.type_count || header.ttis_std_count == 0) && + (header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) { + return {}; + } + time_zone_rule.time_count = header.time_count; + time_zone_rule.type_count = header.type_count; + time_zone_rule.char_count = header.char_count; + + int time_count{}; + u64 read_offset = sizeof(TzifHeader); + for (int index{}; index < time_zone_rule.time_count; ++index) { + s64_be at{}; + vfs_file->ReadObject<s64_be>(&at, read_offset); + time_zone_rule.types[index] = 1; + if (time_count != 0 && at <= time_zone_rule.ats[time_count - 1]) { + if (at < time_zone_rule.ats[time_count - 1]) { + return {}; + } + time_zone_rule.types[index - 1] = 0; + time_count--; + } + time_zone_rule.ats[time_count++] = at; + read_offset += sizeof(s64_be); + } + time_count = 0; + for (int index{}; index < time_zone_rule.time_count; ++index) { + const u8 type{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (time_zone_rule.time_count <= type) { + return {}; + } + if (time_zone_rule.types[index] != 0) { + time_zone_rule.types[time_count++] = type; + } + } + time_zone_rule.time_count = time_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + TimeTypeInfo& ttis{time_zone_rule.ttis[index]}; + u32_be gmt_offset{}; + vfs_file->ReadObject<u32_be>(&gmt_offset, read_offset); + read_offset += sizeof(u32_be); + ttis.gmt_offset = gmt_offset; + + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + ttis.is_dst = dst != 0; + + const s32 abbreviation_list_index{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (abbreviation_list_index >= time_zone_rule.char_count) { + return {}; + } + ttis.abbreviation_list_index = abbreviation_list_index; + } + + vfs_file->ReadArray(time_zone_rule.chars.data(), time_zone_rule.char_count, read_offset); + time_zone_rule.chars[time_zone_rule.char_count] = '\0'; + read_offset += time_zone_rule.char_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_standard_time_daylight = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_standard_time_daylight = dst != 0; + } + } + + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_gmt = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_gmt = dst != 0; + } + } + + const u64 position{(read_offset - sizeof(TzifHeader))}; + const std::size_t bytes_read{vfs_file->GetSize() - sizeof(TzifHeader) - position}; + if (bytes_read < 0) { + return {}; + } + constexpr s32 time_zone_name_max{255}; + if (bytes_read > (time_zone_name_max + 1)) { + return {}; + } + + std::array<char, time_zone_name_max + 1> temp_name{}; + vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset); + if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' && + time_zone_rule.type_count + 2 <= time_zone_rule.ttis.size()) { + temp_name[bytes_read - 1] = '\0'; + + std::array<char, time_zone_name_max> name{}; + std::memcpy(name.data(), temp_name.data() + 1, bytes_read - 1); + + TimeZoneRule temp_rule; + if (ParsePosixName(name.data(), temp_rule)) { + UNIMPLEMENTED(); + } + } + if (time_zone_rule.type_count == 0) { + return {}; + } + if (time_zone_rule.time_count > 1) { + UNIMPLEMENTED(); + } + + s32 default_type{}; + + for (default_type = 0; default_type < time_zone_rule.time_count; default_type++) { + if (time_zone_rule.types[default_type] == 0) { + break; + } + } + + default_type = default_type < time_zone_rule.time_count ? -1 : 0; + if (default_type < 0 && time_zone_rule.time_count > 0 && + time_zone_rule.ttis[time_zone_rule.types[0]].is_dst) { + default_type = time_zone_rule.types[0]; + while (--default_type >= 0) { + if (!time_zone_rule.ttis[default_type].is_dst) { + break; + } + } + } + if (default_type < 0) { + default_type = 0; + while (time_zone_rule.ttis[default_type].is_dst) { + if (++default_type >= time_zone_rule.type_count) { + default_type = 0; + break; + } + } + } + time_zone_rule.default_type = default_type; + return true; +} + +static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + s64 year{epoch_year}; + s64 time_days{time / seconds_per_day}; + s64 remaining_seconds{time % seconds_per_day}; + while (time_days < 0 || time_days >= GetYearLengthInDays(year)) { + s64 delta = time_days / days_per_leap_year; + if (!delta) { + delta = time_days < 0 ? -1 : 1; + } + s64 new_year{year}; + if (!SafeAdd(new_year, delta)) { + return ERROR_OUT_OF_RANGE; + } + time_days -= (new_year - year) * days_per_normal_year; + time_days -= GetLeapDaysFromYear(new_year - 1) - GetLeapDaysFromYear(year - 1); + year = new_year; + } + + s64 day_of_year{time_days}; + remaining_seconds += gmt_offset; + while (remaining_seconds < 0) { + remaining_seconds += seconds_per_day; + day_of_year--; + } + + while (remaining_seconds >= seconds_per_day) { + remaining_seconds -= seconds_per_day; + day_of_year++; + } + + while (day_of_year < 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OUT_OF_RANGE; + } + day_of_year += GetYearLengthInDays(year); + } + + while (day_of_year >= GetYearLengthInDays(year)) { + day_of_year -= GetYearLengthInDays(year); + if (!SafeAdd(year, 1)) { + return ERROR_OUT_OF_RANGE; + } + } + + calendar_time.year = year; + calendar_additional_info.day_of_year = static_cast<u32>(day_of_year); + s64 day_of_week{ + (epoch_week_day + + ((year - epoch_year) % days_per_week) * (days_per_normal_year % days_per_week) + + GetLeapDaysFromYear(year - 1) - GetLeapDaysFromYear(epoch_year - 1) + day_of_year) % + days_per_week}; + if (day_of_week < 0) { + day_of_week += days_per_week; + } + + calendar_additional_info.day_of_week = static_cast<u32>(day_of_week); + calendar_time.hour = static_cast<s8>((remaining_seconds / seconds_per_hour) % seconds_per_hour); + remaining_seconds %= seconds_per_hour; + calendar_time.minute = static_cast<s8>(remaining_seconds / seconds_per_minute); + calendar_time.second = static_cast<s8>(remaining_seconds % seconds_per_minute); + + for (calendar_time.month = 0; + day_of_year >= GetMonthLength(IsLeapYear(year), calendar_time.month); + ++calendar_time.month) { + day_of_year -= GetMonthLength(IsLeapYear(year), calendar_time.month); + } + + calendar_time.day = static_cast<s8>(day_of_year + 1); + calendar_additional_info.is_dst = false; + calendar_additional_info.gmt_offset = gmt_offset; + + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, + CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + if ((rules.go_ahead && time < rules.ats[0]) || + (rules.go_back && time > rules.ats[rules.time_count - 1])) { + s64 seconds{}; + if (time < rules.ats[0]) { + seconds = rules.ats[0] - time; + } else { + seconds = time - rules.ats[rules.time_count - 1]; + } + seconds--; + + const s64 years{(seconds / seconds_per_repeat + 1) * years_per_repeat}; + seconds = years * average_seconds_per_year; + + s64 new_time{time}; + if (time < rules.ats[0]) { + new_time += seconds; + } else { + new_time -= seconds; + } + if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) { + return ERROR_TIME_NOT_FOUND; + } + if (const ResultCode result{ + ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + if (time < rules.ats[0]) { + calendar_time.year -= years; + } else { + calendar_time.year += years; + } + + return RESULT_SUCCESS; + } + + s32 tti_index{}; + if (rules.time_count == 0 || time < rules.ats[0]) { + tti_index = rules.default_type; + } else { + s32 low{1}; + s32 high{rules.time_count}; + while (low < high) { + s32 mid{(low + high) >> 1}; + if (time < rules.ats[mid]) { + high = mid; + } else { + low = mid + 1; + } + } + tti_index = rules.types[low - 1]; + } + + if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset, + calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + + calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; + const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; + for (int index{}; time_zone[index] != '\0'; ++index) { + calendar_additional_info.timezone_name[index] = time_zone[index]; + } + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) { + CalendarTimeInternal calendar_time{}; + const ResultCode result{ + ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)}; + calendar.time.year = static_cast<s16>(calendar_time.year); + calendar.time.month = calendar_time.month + 1; // Internal impl. uses 0-indexed month + calendar.time.day = calendar_time.day; + calendar.time.hour = calendar_time.hour; + calendar.time.minute = calendar_time.minute; + calendar.time.second = calendar_time.second; + return result; +} + +TimeZoneManager::TimeZoneManager() = default; +TimeZoneManager::~TimeZoneManager() = default; + +ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time, + CalendarInfo& calendar) const { + return ToCalendarTimeImpl(rules, time, calendar); +} + +ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name, + FileSys::VirtualFile& vfs_file) { + TimeZoneRule rule{}; + if (ParseTimeZoneBinary(rule, vfs_file)) { + device_location_name = location_name; + time_zone_rule = rule; + return RESULT_SUCCESS; + } + return ERROR_TIME_ZONE_CONVERSION_FAILED; +} + +ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) { + time_zone_update_time_point = value; + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const { + if (is_initialized) { + return ToCalendarTime(time_zone_rule, time, calendar); + } else { + return ERROR_UNINITIALIZED_CLOCK; + } +} + +ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules, + FileSys::VirtualFile& vfs_file) const { + if (!ParseTimeZoneBinary(rules, vfs_file)) { + return ERROR_TIME_ZONE_CONVERSION_FAILED; + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules, + const CalendarTime& calendar_time, s64& posix_time) const { + posix_time = 0; + + CalendarTimeInternal internal_time{}; + internal_time.year = calendar_time.year; + internal_time.month = calendar_time.month - 1; // Internal impl. uses 0-indexed month + internal_time.day = calendar_time.day; + internal_time.hour = calendar_time.hour; + internal_time.minute = calendar_time.minute; + internal_time.second = calendar_time.second; + + s32 hour{internal_time.hour}; + s32 minute{internal_time.minute}; + if (!SafeNormalize(hour, minute, minutes_per_hour)) { + return ERROR_OVERFLOW; + } + internal_time.minute = static_cast<s8>(minute); + + s32 day{internal_time.day}; + if (!SafeNormalize(day, hour, hours_per_day)) { + return ERROR_OVERFLOW; + } + internal_time.day = static_cast<s8>(day); + internal_time.hour = static_cast<s8>(hour); + + s64 year{internal_time.year}; + s64 month{internal_time.month}; + if (!SafeNormalize(year, month, months_per_year)) { + return ERROR_OVERFLOW; + } + internal_time.month = static_cast<s8>(month); + + if (!SafeAdd(year, year_base)) { + return ERROR_OVERFLOW; + } + + while (day <= 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OVERFLOW; + } + s64 temp_year{year}; + if (1 < internal_time.month) { + ++temp_year; + } + day += static_cast<s32>(GetYearLengthInDays(temp_year)); + } + + while (day > days_per_leap_year) { + s64 temp_year{year}; + if (1 < internal_time.month) { + temp_year++; + } + day -= static_cast<s32>(GetYearLengthInDays(temp_year)); + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + + while (true) { + const s32 month_length{GetMonthLength(IsLeapYear(year), internal_time.month)}; + if (day <= month_length) { + break; + } + day -= month_length; + internal_time.month++; + if (internal_time.month >= months_per_year) { + internal_time.month = 0; + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + } + internal_time.day = static_cast<s8>(day); + + if (!SafeAdd(year, -year_base)) { + return ERROR_OVERFLOW; + } + internal_time.year = year; + + s32 saved_seconds{}; + if (internal_time.second >= 0 && internal_time.second < seconds_per_minute) { + saved_seconds = 0; + } else if (year + year_base < epoch_year) { + s32 second{internal_time.second}; + if (!SafeAdd(second, 1 - seconds_per_minute)) { + return ERROR_OVERFLOW; + } + saved_seconds = second; + internal_time.second = 1 - seconds_per_minute; + } else { + saved_seconds = internal_time.second; + internal_time.second = 0; + } + + s64 low{LLONG_MIN}; + s64 high{LLONG_MAX}; + while (true) { + s64 pivot{low / 2 + high / 2}; + if (pivot < low) { + pivot = low; + } else if (pivot > high) { + pivot = high; + } + s32 direction{}; + CalendarTimeInternal candidate_calendar_time{}; + CalendarAdditionalInfo unused{}; + if (ToCalendarTimeInternal(rules, pivot, candidate_calendar_time, unused) != + RESULT_SUCCESS) { + if (pivot > 0) { + direction = 1; + } else { + direction = -1; + } + } else { + direction = candidate_calendar_time.Compare(internal_time); + } + if (!direction) { + const s64 time_result{pivot + saved_seconds}; + if ((time_result < pivot) != (saved_seconds < 0)) { + return ERROR_OVERFLOW; + } + posix_time = time_result; + break; + } else { + if (pivot == low) { + if (pivot == LLONG_MAX) { + return ERROR_TIME_NOT_FOUND; + } + pivot++; + low++; + } else if (pivot == high) { + if (pivot == LLONG_MIN) { + return ERROR_TIME_NOT_FOUND; + } + pivot--; + high--; + } + if (low > high) { + return ERROR_TIME_NOT_FOUND; + } + if (direction > 0) { + high = pivot; + } else { + low = pivot; + } + } + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time, + s64& posix_time) const { + if (is_initialized) { + return ToPosixTime(time_zone_rule, calendar_time, posix_time); + } + posix_time = 0; + return ERROR_UNINITIALIZED_CLOCK; +} + +ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + std::memcpy(value.data(), device_location_name.c_str(), device_location_name.size()); + return RESULT_SUCCESS; +} + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h new file mode 100644 index 000000000..aaab0a1e0 --- /dev/null +++ b/src/core/hle/service/time/time_zone_manager.h @@ -0,0 +1,54 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time::TimeZone { + +class TimeZoneManager final { +public: + TimeZoneManager(); + ~TimeZoneManager(); + + void SetTotalLocationNameCount(std::size_t value) { + total_location_name_count = value; + } + + void SetTimeZoneRuleVersion(const u128& value) { + time_zone_rule_version = value; + } + + void MarkAsInitialized() { + is_initialized = true; + } + + ResultCode SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name, + FileSys::VirtualFile& vfs_file); + ResultCode SetUpdatedTime(const Clock::SteadyClockTimePoint& value); + ResultCode GetDeviceLocationName(TimeZone::LocationName& value) const; + ResultCode ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const; + ResultCode ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const; + ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const; + ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time, + s64& posix_time) const; + ResultCode ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const; + +private: + bool is_initialized{}; + TimeZoneRule time_zone_rule{}; + std::string device_location_name{"GMT"}; + u128 time_zone_rule_version{}; + std::size_t total_location_name_count{}; + Clock::SteadyClockTimePoint time_zone_update_time_point{ + Clock::SteadyClockTimePoint::GetRandom()}; +}; + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp new file mode 100644 index 000000000..db57ae069 --- /dev/null +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -0,0 +1,170 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/time/time_zone_content_manager.h" +#include "core/hle/service/time/time_zone_service.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time { + +ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_content_manager) + : ServiceFramework("ITimeZoneService"), time_zone_content_manager{time_zone_content_manager} { + static const FunctionInfo functions[] = { + {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, + {1, nullptr, "SetDeviceLocationName"}, + {2, nullptr, "GetTotalLocationNameCount"}, + {3, nullptr, "LoadLocationNameList"}, + {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, + {5, nullptr, "GetTimeZoneRuleVersion"}, + {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, + {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, + {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"}, + {202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"}, + }; + RegisterHandlers(functions); +} + +void ITimeZoneService::GetDeviceLocationName(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + TimeZone::LocationName location_name{}; + if (const ResultCode result{ + time_zone_content_manager.GetTimeZoneManager().GetDeviceLocationName(location_name)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, (sizeof(location_name) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(location_name); +} + +void ITimeZoneService::LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto raw_location_name{rp.PopRaw<std::array<u8, 0x24>>()}; + + std::string location_name; + for (const auto& byte : raw_location_name) { + // Strip extra bytes + if (byte == '\0') { + break; + } + location_name.push_back(byte); + } + + LOG_DEBUG(Service_Time, "called, location_name={}", location_name); + + TimeZone::TimeZoneRule time_zone_rule{}; + if (const ResultCode result{ + time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); + std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); + ctx.WriteBuffer(time_zone_rule_outbuffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ITimeZoneService::ToCalendarTime(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto posix_time{rp.Pop<s64>()}; + + LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time); + + TimeZone::TimeZoneRule time_zone_rule{}; + const auto buffer{ctx.ReadBuffer()}; + std::memcpy(&time_zone_rule, buffer.data(), buffer.size()); + + TimeZone::CalendarInfo calendar_info{}; + if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToCalendarTime( + time_zone_rule, posix_time, calendar_info)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_info); +} + +void ITimeZoneService::ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto posix_time{rp.Pop<s64>()}; + + LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time); + + TimeZone::CalendarInfo calendar_info{}; + if (const ResultCode result{ + time_zone_content_manager.GetTimeZoneManager().ToCalendarTimeWithMyRules( + posix_time, calendar_info)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_info); +} + +void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + IPC::RequestParser rp{ctx}; + const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()}; + TimeZone::TimeZoneRule time_zone_rule{}; + std::memcpy(&time_zone_rule, ctx.ReadBuffer().data(), sizeof(TimeZone::TimeZoneRule)); + + s64 posix_time{}; + if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToPosixTime( + time_zone_rule, calendar_time, posix_time)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + // TODO(bunnei): Handle multiple times + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(1); // Number of times we're returning + ctx.WriteBuffer(&posix_time, sizeof(s64)); +} + +void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + IPC::RequestParser rp{ctx}; + const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()}; + + s64 posix_time{}; + if (const ResultCode result{ + time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(calendar_time, + posix_time)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(1); // Number of times we're returning + ctx.WriteBuffer(&posix_time, sizeof(s64)); +} + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h new file mode 100644 index 000000000..cb495748b --- /dev/null +++ b/src/core/hle/service/time/time_zone_service.h @@ -0,0 +1,31 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Time { + +namespace TimeZone { +class TimeZoneContentManager; +} + +class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { +public: + explicit ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_manager); + +private: + void GetDeviceLocationName(Kernel::HLERequestContext& ctx); + void LoadTimeZoneRule(Kernel::HLERequestContext& ctx); + void ToCalendarTime(Kernel::HLERequestContext& ctx); + void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx); + void ToPosixTime(Kernel::HLERequestContext& ctx); + void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx); + +private: + TimeZone::TimeZoneContentManager& time_zone_content_manager; +}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h new file mode 100644 index 000000000..9be15b53e --- /dev/null +++ b/src/core/hle/service/time/time_zone_types.h @@ -0,0 +1,87 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::Time::TimeZone { + +using LocationName = std::array<char, 0x24>; + +/// https://switchbrew.org/wiki/Glue_services#ttinfo +struct TimeTypeInfo { + s32 gmt_offset{}; + u8 is_dst{}; + INSERT_PADDING_BYTES(3); + s32 abbreviation_list_index{}; + u8 is_standard_time_daylight{}; + u8 is_gmt{}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(TimeTypeInfo) == 0x10, "TimeTypeInfo is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#TimeZoneRule +struct TimeZoneRule { + s32 time_count{}; + s32 type_count{}; + s32 char_count{}; + u8 go_back{}; + u8 go_ahead{}; + INSERT_PADDING_BYTES(2); + std::array<s64, 1000> ats{}; + std::array<s8, 1000> types{}; + std::array<TimeTypeInfo, 128> ttis{}; + std::array<char, 512> chars{}; + s32 default_type{}; + INSERT_PADDING_BYTES(0x12C4); +}; +static_assert(sizeof(TimeZoneRule) == 0x4000, "TimeZoneRule is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#CalendarAdditionalInfo +struct CalendarAdditionalInfo { + u32 day_of_week{}; + u32 day_of_year{}; + std::array<char, 8> timezone_name; + u32 is_dst{}; + s32 gmt_offset{}; +}; +static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#CalendarTime +struct CalendarTime { + s16 year{}; + s8 month{}; + s8 day{}; + s8 hour{}; + s8 minute{}; + s8 second{}; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime is incorrect size"); + +struct CalendarInfo { + CalendarTime time{}; + CalendarAdditionalInfo additiona_info{}; +}; +static_assert(sizeof(CalendarInfo) == 0x20, "CalendarInfo is incorrect size"); + +struct TzifHeader { + u32_be magic{}; + u8 version{}; + INSERT_PADDING_BYTES(15); + s32_be ttis_gmt_count{}; + s32_be ttis_std_count{}; + s32_be leap_count{}; + s32_be time_count{}; + s32_be type_count{}; + s32_be char_count{}; +}; +static_assert(sizeof(TzifHeader) == 0x2C, "TzifHeader is incorrect size"); + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index cd18c1610..5a202ac81 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -24,11 +24,11 @@ Display::Display(u64 id, std::string name, Core::System& system) : id{id}, name{ Display::~Display() = default; Layer& Display::GetLayer(std::size_t index) { - return layers.at(index); + return *layers.at(index); } const Layer& Display::GetLayer(std::size_t index) const { - return layers.at(index); + return *layers.at(index); } std::shared_ptr<Kernel::ReadableEvent> Display::GetVSyncEvent() const { @@ -43,29 +43,38 @@ void Display::CreateLayer(u64 id, NVFlinger::BufferQueue& buffer_queue) { // TODO(Subv): Support more than 1 layer. ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment"); - layers.emplace_back(id, buffer_queue); + layers.emplace_back(std::make_shared<Layer>(id, buffer_queue)); +} + +void Display::CloseLayer(u64 id) { + layers.erase( + std::remove_if(layers.begin(), layers.end(), + [id](const std::shared_ptr<Layer>& layer) { return layer->GetID() == id; }), + layers.end()); } Layer* Display::FindLayer(u64 id) { - const auto itr = std::find_if(layers.begin(), layers.end(), - [id](const VI::Layer& layer) { return layer.GetID() == id; }); + const auto itr = + std::find_if(layers.begin(), layers.end(), + [id](const std::shared_ptr<Layer>& layer) { return layer->GetID() == id; }); if (itr == layers.end()) { return nullptr; } - return &*itr; + return itr->get(); } const Layer* Display::FindLayer(u64 id) const { - const auto itr = std::find_if(layers.begin(), layers.end(), - [id](const VI::Layer& layer) { return layer.GetID() == id; }); + const auto itr = + std::find_if(layers.begin(), layers.end(), + [id](const std::shared_ptr<Layer>& layer) { return layer->GetID() == id; }); if (itr == layers.end()) { return nullptr; } - return &*itr; + return itr->get(); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 8bb966a85..a3855d8cd 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -4,6 +4,7 @@ #pragma once +#include <memory> #include <string> #include <vector> @@ -69,6 +70,12 @@ public: /// void CreateLayer(u64 id, NVFlinger::BufferQueue& buffer_queue); + /// Closes and removes a layer from this display with the given ID. + /// + /// @param id The ID assigned to the layer to close. + /// + void CloseLayer(u64 id); + /// Attempts to find a layer with the given ID. /// /// @param id The layer ID. @@ -91,7 +98,7 @@ private: u64 id; std::string name; - std::vector<Layer> layers; + std::vector<std::shared_ptr<Layer>> layers; Kernel::EventPair vsync_event; }; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 651c89dc0..519da74e0 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -1066,6 +1066,18 @@ private: rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); } + void CloseLayer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto layer_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id); + + nv_flinger->CloseLayer(layer_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + void CreateStrayLayer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 flags = rp.Pop<u32>(); @@ -1178,7 +1190,7 @@ IApplicationDisplayService::IApplicationDisplayService( {1101, &IApplicationDisplayService::SetDisplayEnabled, "SetDisplayEnabled"}, {1102, &IApplicationDisplayService::GetDisplayResolution, "GetDisplayResolution"}, {2020, &IApplicationDisplayService::OpenLayer, "OpenLayer"}, - {2021, nullptr, "CloseLayer"}, + {2021, &IApplicationDisplayService::CloseLayer, "CloseLayer"}, {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"}, {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"}, {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index f1795fdd6..8908e5328 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -335,7 +335,8 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { codeset_segment->addr = segment_addr; codeset_segment->size = aligned_size; - memcpy(&program_image[current_image_position], GetSegmentPtr(i), p->p_filesz); + std::memcpy(program_image.data() + current_image_position, GetSegmentPtr(i), + p->p_filesz); current_image_position += aligned_size; } } diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index 474b55cb1..092103abe 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cstring> #include "core/file_sys/kernel_executable.h" #include "core/file_sys/program_metadata.h" #include "core/gdbstub/gdbstub.h" @@ -76,8 +77,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { segment.addr = offset; segment.offset = offset; segment.size = PageAlignSize(static_cast<u32>(data.size())); - program_image.resize(offset); - program_image.insert(program_image.end(), data.begin(), data.end()); + program_image.resize(offset + data.size()); + std::memcpy(program_image.data() + offset, data.data(), data.size()); }; load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset()); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index f629892ae..044067a5b 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <cinttypes> +#include <cstring> #include <vector> #include "common/common_funcs.h" @@ -96,15 +97,21 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, if (nso_header.IsSegmentCompressed(i)) { data = DecompressSegment(data, nso_header.segments[i]); } - program_image.resize(nso_header.segments[i].location); - program_image.insert(program_image.end(), data.begin(), data.end()); + program_image.resize(nso_header.segments[i].location + + PageAlignSize(static_cast<u32>(data.size()))); + std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(), + data.size()); codeset.segments[i].addr = nso_header.segments[i].location; codeset.segments[i].offset = nso_header.segments[i].location; codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size())); } - if (should_pass_arguments && !Settings::values.program_args.empty()) { - const auto arg_data = Settings::values.program_args; + if (should_pass_arguments) { + std::vector<u8> arg_data{Settings::values.program_args.begin(), + Settings::values.program_args.end()}; + if (arg_data.empty()) { + arg_data.resize(NSO_ARGUMENT_DEFAULT_SIZE); + } codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; NSOArgumentHeader args_header{ NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; @@ -139,12 +146,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, std::vector<u8> pi_header; pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); - pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), - program_image.end()); + pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.data(), + program_image.data() + program_image.size()); pi_header = pm->PatchNSO(pi_header, file.GetName()); - std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); + std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); } // Apply cheats if they exist and the program has a valid title ID diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 58cbe162d..d2d600cd9 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -56,6 +56,8 @@ static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; +// NOTE: Official software default argument state is unverified. +constexpr u64 NSO_ARGUMENT_DEFAULT_SIZE = 1; struct NSOArgumentHeader { u32_le allocated_size; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 91bf07a92..f0888327f 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -14,6 +14,7 @@ #include "common/swap.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/hle/kernel/physical_memory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" @@ -38,6 +39,11 @@ struct Memory::Impl { system.ArmInterface(3).PageTableChanged(*current_page_table, address_space_width); } + void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, + Kernel::PhysicalMemory& memory, VAddr offset) { + MapMemoryRegion(page_table, base, size, memory.data() + offset); + } + void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); @@ -146,7 +152,7 @@ struct Memory::Impl { u8* GetPointer(const VAddr vaddr) { u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer != nullptr) { - return page_pointer + (vaddr & PAGE_MASK); + return page_pointer + vaddr; } if (current_page_table->attributes[vaddr >> PAGE_BITS] == @@ -229,7 +235,8 @@ struct Memory::Impl { case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); - const u8* const src_ptr = page_table.pointers[page_index] + page_offset; + const u8* const src_ptr = + page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_buffer, src_ptr, copy_amount); break; } @@ -276,7 +283,8 @@ struct Memory::Impl { case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); - u8* const dest_ptr = page_table.pointers[page_index] + page_offset; + u8* const dest_ptr = + page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); std::memcpy(dest_ptr, src_buffer, copy_amount); break; } @@ -322,7 +330,8 @@ struct Memory::Impl { case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); - u8* dest_ptr = page_table.pointers[page_index] + page_offset; + u8* dest_ptr = + page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); std::memset(dest_ptr, 0, copy_amount); break; } @@ -368,7 +377,8 @@ struct Memory::Impl { } case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); - const u8* src_ptr = page_table.pointers[page_index] + page_offset; + const u8* src_ptr = + page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS); WriteBlock(process, dest_addr, src_ptr, copy_amount); break; } @@ -446,7 +456,8 @@ struct Memory::Impl { page_type = Common::PageType::Unmapped; } else { page_type = Common::PageType::Memory; - current_page_table->pointers[vaddr >> PAGE_BITS] = pointer; + current_page_table->pointers[vaddr >> PAGE_BITS] = + pointer - (vaddr & ~PAGE_MASK); } break; } @@ -493,7 +504,9 @@ struct Memory::Impl { memory); } else { while (base != end) { - page_table.pointers[base] = memory; + page_table.pointers[base] = memory - (base << PAGE_BITS); + ASSERT_MSG(page_table.pointers[base], + "memory mapping base yield a nullptr within the table"); base += 1; memory += PAGE_SIZE; @@ -518,7 +531,7 @@ struct Memory::Impl { if (page_pointer != nullptr) { // NOTE: Avoid adding any extra logic to this fast-path block T value; - std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); + std::memcpy(&value, &page_pointer[vaddr], sizeof(T)); return value; } @@ -559,7 +572,7 @@ struct Memory::Impl { u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer != nullptr) { // NOTE: Avoid adding any extra logic to this fast-path block - std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); + std::memcpy(&page_pointer[vaddr], &data, sizeof(T)); return; } @@ -594,6 +607,11 @@ void Memory::SetCurrentPageTable(Kernel::Process& process) { impl->SetCurrentPageTable(process); } +void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, + Kernel::PhysicalMemory& memory, VAddr offset) { + impl->MapMemoryRegion(page_table, base, size, memory, offset); +} + void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) { impl->MapMemoryRegion(page_table, base, size, target); } diff --git a/src/core/memory.h b/src/core/memory.h index 1428a6d60..8913a9da4 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -19,8 +19,9 @@ class System; } namespace Kernel { +class PhysicalMemory; class Process; -} +} // namespace Kernel namespace Memory { @@ -66,6 +67,19 @@ public: void SetCurrentPageTable(Kernel::Process& process); /** + * Maps an physical buffer onto a region of the emulated process address space. + * + * @param page_table The page table of the emulated process. + * @param base The address to start mapping at. Must be page-aligned. + * @param size The amount of bytes to map. Must be page-aligned. + * @param memory Physical buffer with the memory backing the mapping. Must be of length + * at least `size + offset`. + * @param offset The offset within the physical memory. Must be page-aligned. + */ + void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, + Kernel::PhysicalMemory& memory, VAddr offset); + + /** * Maps an allocated buffer onto a region of the emulated process address space. * * @param page_table The page table of the emulated process. diff --git a/src/core/settings.h b/src/core/settings.h index 9c98a9287..421e76f5f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -401,6 +401,9 @@ struct Values { std::string motion_device; TouchscreenInput touchscreen; std::atomic_bool is_device_reload_pending{true}; + std::string udp_input_address; + u16 udp_input_port; + u8 udp_pad_index; // Core bool use_multi_core; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 5b4e032bd..2520ba321 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -9,6 +9,12 @@ add_library(input_common STATIC motion_emu.h sdl/sdl.cpp sdl/sdl.h + udp/client.cpp + udp/client.h + udp/protocol.cpp + udp/protocol.h + udp/udp.cpp + udp/udp.h ) if(SDL2_FOUND) @@ -21,4 +27,4 @@ if(SDL2_FOUND) endif() create_target_directory_groups(input_common) -target_link_libraries(input_common PUBLIC core PRIVATE common) +target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 8e66c1b15..9e028da89 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -9,6 +9,7 @@ #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" +#include "input_common/udp/udp.h" #ifdef HAVE_SDL2 #include "input_common/sdl/sdl.h" #endif @@ -18,6 +19,7 @@ namespace InputCommon { static std::shared_ptr<Keyboard> keyboard; static std::shared_ptr<MotionEmu> motion_emu; static std::unique_ptr<SDL::State> sdl; +static std::unique_ptr<CemuhookUDP::State> udp; void Init() { keyboard = std::make_shared<Keyboard>(); @@ -28,6 +30,8 @@ void Init() { Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); sdl = SDL::Init(); + + udp = CemuhookUDP::Init(); } void Shutdown() { @@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, namespace Polling { std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { + std::vector<std::unique_ptr<DevicePoller>> pollers; + #ifdef HAVE_SDL2 - return sdl->GetPollers(type); -#else - return {}; + pollers = sdl->GetPollers(type); #endif + + return pollers; } } // namespace Polling diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp new file mode 100644 index 000000000..5f5a9989c --- /dev/null +++ b/src/input_common/udp/client.cpp @@ -0,0 +1,287 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <chrono> +#include <cstring> +#include <functional> +#include <thread> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include "common/logging/log.h" +#include "input_common/udp/client.h" +#include "input_common/udp/protocol.h" + +using boost::asio::ip::address_v4; +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { + std::function<void(Response::Version)> version; + std::function<void(Response::PortInfo)> port_info; + std::function<void(Response::PadData)> pad_data; +}; + +class Socket { +public: + using clock = std::chrono::system_clock; + + explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, + SocketCallback callback) + : client_id(client_id), timer(io_service), + send_endpoint(udp::endpoint(address_v4::from_string(host), port)), + socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index), + callback(std::move(callback)) {} + + void Stop() { + io_service.stop(); + } + + void Loop() { + io_service.run(); + } + + void StartSend(const clock::time_point& from) { + timer.expires_at(from + std::chrono::seconds(3)); + timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); + } + + void StartReceive() { + socket.async_receive_from( + boost::asio::buffer(receive_buffer), receive_endpoint, + [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + HandleReceive(error, bytes_transferred); + }); + } + +private: + void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { + if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { + switch (*type) { + case Type::Version: { + Response::Version version; + std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); + callback.version(std::move(version)); + break; + } + case Type::PortInfo: { + Response::PortInfo port_info; + std::memcpy(&port_info, &receive_buffer[sizeof(Header)], + sizeof(Response::PortInfo)); + callback.port_info(std::move(port_info)); + break; + } + case Type::PadData: { + Response::PadData pad_data; + std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + callback.pad_data(std::move(pad_data)); + break; + } + } + } + StartReceive(); + } + + void HandleSend(const boost::system::error_code& error) { + // Send a request for getting port info for the pad + Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; + const auto port_message = Request::Create(port_info, client_id); + std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); + socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); + + // Send a request for getting pad data for the pad + Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; + const auto pad_message = Request::Create(pad_data, client_id); + std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); + socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); + StartSend(timer.expiry()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer<clock> timer; + udp::socket socket; + + u32 client_id{}; + u8 pad_index{}; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); + std::array<u8, PORT_INFO_SIZE> send_buffer1; + std::array<u8, PAD_DATA_SIZE> send_buffer2; + udp::endpoint send_endpoint; + + std::array<u8, MAX_PACKET_SIZE> receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, + u8 pad_index, u32 client_id) + : status(status) { + StartCommunication(host, port, pad_index, client_id); +} + +Client::~Client() { + socket->Stop(); + thread.join(); +} + +void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { + socket->Stop(); + thread.join(); + StartCommunication(host, port, pad_index, client_id); +} + +void Client::OnVersion(Response::Version data) { + LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void Client::OnPortInfo(Response::PortInfo data) { + LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void Client::OnPadData(Response::PadData data) { + LOG_TRACE(Input, "PadData packet received"); + if (data.packet_counter <= packet_sequence) { + LOG_WARNING( + Input, + "PadData packet dropped because its stale info. Current count: {} Packet count: {}", + packet_sequence, data.packet_counter); + return; + } + packet_sequence = data.packet_counter; + // TODO: Check how the Switch handles motions and how the CemuhookUDP motion + // directions correspond to the ones of the Switch + Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); + Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); + { + std::lock_guard guard(status->update_mutex); + + status->motion_status = {accel, gyro}; + + // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates + // between a simple "tap" and a hard press that causes the touch screen to click. + const bool is_active = data.touch_1.is_active != 0; + + float x = 0; + float y = 0; + + if (is_active && status->touch_calibration) { + const u16 min_x = status->touch_calibration->min_x; + const u16 max_x = status->touch_calibration->max_x; + const u16 min_y = status->touch_calibration->min_y; + const u16 max_y = status->touch_calibration->max_y; + + x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / + static_cast<float>(max_x - min_x); + y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / + static_cast<float>(max_y - min_y); + } + + status->touch_status = {x, y, is_active}; + } +} + +void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this](Response::PadData data) { OnPadData(data); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); + socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); + thread = std::thread{SocketLoop, this->socket.get()}; +} + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function<void()> success_callback, + std::function<void()> failure_callback) { + std::thread([=] { + Common::Event success_event; + SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, + [&](Response::PadData data) { success_event.Set(); }}; + Socket socket{host, port, pad_index, client_id, callback}; + std::thread worker_thread{SocketLoop, &socket}; + bool result = success_event.WaitFor(std::chrono::seconds(8)); + socket.Stop(); + worker_thread.join(); + if (result) { + success_callback(); + } else { + failure_callback(); + } + }) + .detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( + const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function<void(Status)> status_callback, + std::function<void(u16, u16, u16, u16)> data_callback) { + + std::thread([=] { + constexpr u16 CALIBRATION_THRESHOLD = 100; + + u16 min_x{UINT16_MAX}; + u16 min_y{UINT16_MAX}; + u16 max_x{}; + u16 max_y{}; + + Status current_status{Status::Initialized}; + SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, + [&](Response::PadData data) { + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + if (!data.touch_1.is_active) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, + data.touch_1.y); + min_x = std::min(min_x, static_cast<u16>(data.touch_1.x)); + min_y = std::min(min_y, static_cast<u16>(data.touch_1.y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && + data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes + // configuration + max_x = data.touch_1.x; + max_y = data.touch_1.y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); + + complete_event.Set(); + } + }}; + Socket socket{host, port, pad_index, client_id, callback}; + std::thread worker_thread{SocketLoop, &socket}; + complete_event.Wait(); + socket.Stop(); + worker_thread.join(); + }) + .detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { + Stop(); +} + +void CalibrationConfigurationJob::Stop() { + complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h new file mode 100644 index 000000000..0b21f4da6 --- /dev/null +++ b/src/input_common/udp/client.h @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <thread> +#include <tuple> +#include <vector> +#include "common/common_types.h" +#include "common/thread.h" +#include "common/vector_math.h" + +namespace InputCommon::CemuhookUDP { + +constexpr u16 DEFAULT_PORT = 26760; +constexpr char DEFAULT_ADDR[] = "127.0.0.1"; + +class Socket; + +namespace Response { +struct PadData; +struct PortInfo; +struct Version; +} // namespace Response + +struct DeviceStatus { + std::mutex update_mutex; + std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; + std::tuple<float, float, bool> touch_status; + + // calibration data for scaling the device's touch area to 3ds + struct CalibrationData { + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; + }; + std::optional<CalibrationData> touch_calibration; +}; + +class Client { +public: + explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, + u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); + ~Client(); + void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, + u32 client_id = 24872); + +private: + void OnVersion(Response::Version); + void OnPortInfo(Response::PortInfo); + void OnPadData(Response::PadData); + void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + + std::unique_ptr<Socket> socket; + std::shared_ptr<DeviceStatus> status; + std::thread thread; + u64 packet_sequence = 0; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: + enum class Status { + Initialized, + Ready, + Stage1Completed, + Completed, + }; + /** + * Constructs and starts the job with the specified parameter. + * + * @param status_callback Callback for job status updates + * @param data_callback Called when calibration data is ready + */ + explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, + u32 client_id, std::function<void(Status)> status_callback, + std::function<void(u16, u16, u16, u16)> data_callback); + ~CalibrationConfigurationJob(); + void Stop(); + +private: + Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function<void()> success_callback, + std::function<void()> failure_callback); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp new file mode 100644 index 000000000..a982ac49d --- /dev/null +++ b/src/input_common/udp/protocol.cpp @@ -0,0 +1,79 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstddef> +#include <cstring> +#include "common/logging/log.h" +#include "input_common/udp/protocol.h" + +namespace InputCommon::CemuhookUDP { + +static constexpr std::size_t GetSizeOfResponseType(Type t) { + switch (t) { + case Type::Version: + return sizeof(Response::Version); + case Type::PortInfo: + return sizeof(Response::PortInfo); + case Type::PadData: + return sizeof(Response::PadData); + } + return 0; +} + +namespace Response { + +/** + * Returns Type if the packet is valid, else none + * + * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without + * copying the buffer) + */ +std::optional<Type> Validate(u8* data, std::size_t size) { + if (size < sizeof(Header)) { + LOG_DEBUG(Input, "Invalid UDP packet received"); + return std::nullopt; + } + Header header{}; + std::memcpy(&header, data, sizeof(Header)); + if (header.magic != SERVER_MAGIC) { + LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); + return std::nullopt; + } + if (header.protocol_version != PROTOCOL_VERSION) { + LOG_ERROR(Input, "UDP Packet protocol mismatch"); + return std::nullopt; + } + if (header.type < Type::Version || header.type > Type::PadData) { + LOG_ERROR(Input, "UDP Packet is an unknown type"); + return std::nullopt; + } + + // Packet size must equal sizeof(Header) + sizeof(Data) + // and also verify that the packet info mentions the correct size. Since the spec includes the + // type of the packet as part of the data, we need to include it in size calculations here + // ie: payload_length == sizeof(T) + sizeof(Type) + const std::size_t data_len = GetSizeOfResponseType(header.type); + if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) { + LOG_ERROR( + Input, + "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}", + size, header.payload_length, data_len + sizeof(Type)); + return std::nullopt; + } + + const u32 crc32 = header.crc; + boost::crc_32_type result; + // zero out the crc in the buffer and then run the crc against it + std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le)); + + result.process_bytes(data, data_len + sizeof(Header)); + if (crc32 != result.checksum()) { + LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc)); + return std::nullopt; + } + return header.type; +} +} // namespace Response + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h new file mode 100644 index 000000000..1b521860a --- /dev/null +++ b/src/input_common/udp/protocol.h @@ -0,0 +1,256 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <optional> +#include <type_traits> +#include <vector> +#include <boost/crc.hpp> +#include "common/bit_field.h" +#include "common/swap.h" + +namespace InputCommon::CemuhookUDP { + +constexpr std::size_t MAX_PACKET_SIZE = 100; +constexpr u16 PROTOCOL_VERSION = 1001; +constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE) +constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE) + +enum class Type : u32 { + Version = 0x00100000, + PortInfo = 0x00100001, + PadData = 0x00100002, +}; + +struct Header { + u32_le magic{}; + u16_le protocol_version{}; + u16_le payload_length{}; + u32_le crc{}; + u32_le id{}; + ///> In the protocol, the type of the packet is not part of the header, but its convenient to + ///> include in the header so the callee doesn't have to duplicate the type twice when building + ///> the data + Type type{}; +}; +static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size"); +static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable"); + +using MacAddress = std::array<u8, 6>; +constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; + +#pragma pack(push, 1) +template <typename T> +struct Message { + Header header{}; + T data; +}; +#pragma pack(pop) + +template <typename T> +constexpr Type GetMessageType(); + +namespace Request { + +struct Version {}; +/** + * Requests the server to send information about what controllers are plugged into the ports + * In citra's case, we only have one controller, so for simplicity's sake, we can just send a + * request explicitly for the first controller port and leave it at that. In the future it would be + * nice to make this configurable + */ +constexpr u32 MAX_PORTS = 4; +struct PortInfo { + u32_le pad_count{}; ///> Number of ports to request data for + std::array<u8, MAX_PORTS> port; +}; +static_assert(std::is_trivially_copyable_v<PortInfo>, + "UDP Request PortInfo is not trivially copyable"); + +/** + * Request the latest pad information from the server. If the server hasn't received this message + * from the client in a reasonable time frame, the server will stop sending updates. The default + * timeout seems to be 5 seconds. + */ +struct PadData { + enum class Flags : u8 { + AllPorts, + Id, + Mac, + }; + /// Determines which method will be used as a look up for the controller + Flags flags{}; + /// Index of the port of the controller to retrieve data about + u8 port_id{}; + /// Mac address of the controller to retrieve data about + MacAddress mac; +}; +static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size"); +static_assert(std::is_trivially_copyable_v<PadData>, + "UDP Request PadData is not trivially copyable"); + +/** + * Creates a message with the proper header data that can be sent to the server. + * @param T data Request body to send + * @param client_id ID of the udp client (usually not checked on the server) + */ +template <typename T> +Message<T> Create(const T data, const u32 client_id = 0) { + boost::crc_32_type crc; + Header header{ + CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(), + }; + Message<T> message{header, data}; + crc.process_bytes(&message, sizeof(Message<T>)); + message.header.crc = crc.checksum(); + return message; +} +} // namespace Request + +namespace Response { + +struct Version { + u16_le version{}; +}; +static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size"); +static_assert(std::is_trivially_copyable_v<Version>, + "UDP Response Version is not trivially copyable"); + +struct PortInfo { + u8 id{}; + u8 state{}; + u8 model{}; + u8 connection_type{}; + MacAddress mac; + u8 battery{}; + u8 is_pad_active{}; +}; +static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); +static_assert(std::is_trivially_copyable_v<PortInfo>, + "UDP Response PortInfo is not trivially copyable"); + +#pragma pack(push, 1) +struct PadData { + PortInfo info{}; + u32_le packet_counter{}; + + u16_le digital_button{}; + // The following union isn't trivially copyable but we don't use this input anyway. + // union DigitalButton { + // u16_le button; + // BitField<0, 1, u16> button_1; // Share + // BitField<1, 1, u16> button_2; // L3 + // BitField<2, 1, u16> button_3; // R3 + // BitField<3, 1, u16> button_4; // Options + // BitField<4, 1, u16> button_5; // Up + // BitField<5, 1, u16> button_6; // Right + // BitField<6, 1, u16> button_7; // Down + // BitField<7, 1, u16> button_8; // Left + // BitField<8, 1, u16> button_9; // L2 + // BitField<9, 1, u16> button_10; // R2 + // BitField<10, 1, u16> button_11; // L1 + // BitField<11, 1, u16> button_12; // R1 + // BitField<12, 1, u16> button_13; // Triangle + // BitField<13, 1, u16> button_14; // Circle + // BitField<14, 1, u16> button_15; // Cross + // BitField<15, 1, u16> button_16; // Square + // } digital_button; + + u8 home; + /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens + u8 touch_hard_press{}; + u8 left_stick_x{}; + u8 left_stick_y{}; + u8 right_stick_x{}; + u8 right_stick_y{}; + + struct AnalogButton { + u8 button_8{}; + u8 button_7{}; + u8 button_6{}; + u8 button_5{}; + u8 button_12{}; + u8 button_11{}; + u8 button_10{}; + u8 button_9{}; + u8 button_16{}; + u8 button_15{}; + u8 button_14{}; + u8 button_13{}; + } analog_button; + + struct TouchPad { + u8 is_active{}; + u8 id{}; + u16_le x{}; + u16_le y{}; + } touch_1, touch_2; + + u64_le motion_timestamp; + + struct Accelerometer { + float x{}; + float y{}; + float z{}; + } accel; + + struct Gyroscope { + float pitch{}; + float yaw{}; + float roll{}; + } gyro; +}; +#pragma pack(pop) + +static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size "); +static_assert(std::is_trivially_copyable_v<PadData>, + "UDP Response PadData is not trivially copyable"); + +static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE, + "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>"); + +static_assert(sizeof(PadData::AnalogButton) == 12, + "UDP Response AnalogButton struct has wrong size "); +static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); +static_assert(sizeof(PadData::Accelerometer) == 12, + "UDP Response Accelerometer struct has wrong size "); +static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); + +/** + * Create a Response Message from the data + * @param data array of bytes sent from the server + * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely + * copy the data into the appropriate struct for that Type + */ +std::optional<Type> Validate(u8* data, std::size_t size); + +} // namespace Response + +template <> +constexpr Type GetMessageType<Request::Version>() { + return Type::Version; +} +template <> +constexpr Type GetMessageType<Request::PortInfo>() { + return Type::PortInfo; +} +template <> +constexpr Type GetMessageType<Request::PadData>() { + return Type::PadData; +} +template <> +constexpr Type GetMessageType<Response::Version>() { + return Type::Version; +} +template <> +constexpr Type GetMessageType<Response::PortInfo>() { + return Type::PortInfo; +} +template <> +constexpr Type GetMessageType<Response::PadData>() { + return Type::PadData; +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp new file mode 100644 index 000000000..a80f38614 --- /dev/null +++ b/src/input_common/udp/udp.cpp @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "core/frontend/input.h" +#include "core/settings.h" +#include "input_common/udp/client.h" +#include "input_common/udp/udp.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice final : public Input::TouchDevice { +public: + explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + std::tuple<float, float, bool> GetStatus() const { + std::lock_guard guard(status->update_mutex); + return status->touch_status; + } + +private: + std::shared_ptr<DeviceStatus> status; +}; + +class UDPMotionDevice final : public Input::MotionDevice { +public: + explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const { + std::lock_guard guard(status->update_mutex); + return status->motion_status; + } + +private: + std::shared_ptr<DeviceStatus> status; +}; + +class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: + explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { + { + std::lock_guard guard(status->update_mutex); + status->touch_calibration.emplace(); + // These default values work well for DS4 but probably not other touch inputs + status->touch_calibration->min_x = params.Get("min_x", 100); + status->touch_calibration->min_y = params.Get("min_y", 50); + status->touch_calibration->max_x = params.Get("max_x", 1800); + status->touch_calibration->max_y = params.Get("max_y", 850); + } + return std::make_unique<UDPTouchDevice>(status); + } + +private: + std::shared_ptr<DeviceStatus> status; +}; + +class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { +public: + explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { + return std::make_unique<UDPMotionDevice>(status); + } + +private: + std::shared_ptr<DeviceStatus> status; +}; + +State::State() { + auto status = std::make_shared<DeviceStatus>(); + client = + std::make_unique<Client>(status, Settings::values.udp_input_address, + Settings::values.udp_input_port, Settings::values.udp_pad_index); + + Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", + std::make_shared<UDPTouchFactory>(status)); + Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", + std::make_shared<UDPMotionFactory>(status)); +} + +State::~State() { + Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); + Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); +} + +void State::ReloadUDPClient() { + client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, + Settings::values.udp_pad_index); +} + +std::unique_ptr<State> Init() { + return std::make_unique<State>(); +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h new file mode 100644 index 000000000..ea3de60bb --- /dev/null +++ b/src/input_common/udp/udp.h @@ -0,0 +1,27 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <unordered_map> +#include "input_common/main.h" +#include "input_common/udp/client.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice; +class UDPMotionDevice; + +class State { +public: + State(); + ~State(); + void ReloadUDPClient(); + +private: + std::unique_ptr<Client> client; +}; + +std::unique_ptr<State> Init(); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 65d7b9f93..ccfed4f2e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -153,14 +153,31 @@ if (ENABLE_VULKAN) renderer_vulkan/fixed_pipeline_state.h renderer_vulkan/maxwell_to_vk.cpp renderer_vulkan/maxwell_to_vk.h + renderer_vulkan/renderer_vulkan.h + renderer_vulkan/vk_blit_screen.cpp + renderer_vulkan/vk_blit_screen.h renderer_vulkan/vk_buffer_cache.cpp renderer_vulkan/vk_buffer_cache.h + renderer_vulkan/vk_compute_pass.cpp + renderer_vulkan/vk_compute_pass.h + renderer_vulkan/vk_compute_pipeline.cpp + renderer_vulkan/vk_compute_pipeline.h + renderer_vulkan/vk_descriptor_pool.cpp + renderer_vulkan/vk_descriptor_pool.h renderer_vulkan/vk_device.cpp renderer_vulkan/vk_device.h + renderer_vulkan/vk_graphics_pipeline.cpp + renderer_vulkan/vk_graphics_pipeline.h renderer_vulkan/vk_image.cpp renderer_vulkan/vk_image.h renderer_vulkan/vk_memory_manager.cpp renderer_vulkan/vk_memory_manager.h + renderer_vulkan/vk_pipeline_cache.cpp + renderer_vulkan/vk_pipeline_cache.h + renderer_vulkan/vk_rasterizer.cpp + renderer_vulkan/vk_rasterizer.h + renderer_vulkan/vk_renderpass_cache.cpp + renderer_vulkan/vk_renderpass_cache.h renderer_vulkan/vk_resource_manager.cpp renderer_vulkan/vk_resource_manager.h renderer_vulkan/vk_sampler_cache.cpp @@ -169,12 +186,19 @@ if (ENABLE_VULKAN) renderer_vulkan/vk_scheduler.h renderer_vulkan/vk_shader_decompiler.cpp renderer_vulkan/vk_shader_decompiler.h + renderer_vulkan/vk_shader_util.cpp + renderer_vulkan/vk_shader_util.h renderer_vulkan/vk_staging_buffer_pool.cpp renderer_vulkan/vk_staging_buffer_pool.h renderer_vulkan/vk_stream_buffer.cpp renderer_vulkan/vk_stream_buffer.h renderer_vulkan/vk_swapchain.cpp - renderer_vulkan/vk_swapchain.h) + renderer_vulkan/vk_swapchain.h + renderer_vulkan/vk_texture_cache.cpp + renderer_vulkan/vk_texture_cache.h + renderer_vulkan/vk_update_descriptor.cpp + renderer_vulkan/vk_update_descriptor.h + ) target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include) target_compile_definitions(video_core PRIVATE HAS_VULKAN) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 1d1f780e7..58dfa8033 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -91,6 +91,7 @@ void Maxwell3D::InitializeRegisterDefaults() { regs.rasterize_enable = 1; regs.rt_separate_frag_data = 1; regs.framebuffer_srgb = 1; + regs.cull.front_face = Maxwell3D::Regs::Cull::FrontFace::ClockWise; mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_end_gl)] = true; mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)] = true; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index a35e7a195..ee79260fc 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1018,7 +1018,14 @@ public: } } instanced_arrays; - INSERT_UNION_PADDING_WORDS(0x6); + INSERT_UNION_PADDING_WORDS(0x4); + + union { + BitField<0, 1, u32> enable; + BitField<4, 8, u32> unk4; + } vp_point_size; + + INSERT_UNION_PADDING_WORDS(1); Cull cull; @@ -1271,8 +1278,6 @@ public: } dirty{}; - std::array<u8, Regs::NUM_REGS> dirty_pointers{}; - /// Reads a register value located at the input method address u32 GetRegisterValue(u32 method) const; @@ -1367,6 +1372,8 @@ private: bool execute_on{true}; + std::array<u8, Regs::NUM_REGS> dirty_pointers{}; + /// Retrieves information about a specific TIC entry from the TIC buffer. Texture::TICEntry GetTICEntry(u32 tic_index) const; @@ -1503,6 +1510,7 @@ ASSERT_REG_POSITION(primitive_restart, 0x591); ASSERT_REG_POSITION(index_array, 0x5F2); ASSERT_REG_POSITION(polygon_offset_clamp, 0x61F); ASSERT_REG_POSITION(instanced_arrays, 0x620); +ASSERT_REG_POSITION(vp_point_size, 0x644); ASSERT_REG_POSITION(cull, 0x646); ASSERT_REG_POSITION(pixel_center_integer, 0x649); ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 57b57c647..6f98bd827 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -215,6 +215,18 @@ enum class F2fRoundingOp : u64 { Trunc = 11, }; +enum class AtomicOp : u64 { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + Exch = 8, +}; + enum class UniformType : u64 { UnsignedByte = 0, SignedByte = 1, @@ -236,6 +248,13 @@ enum class StoreType : u64 { Bits128 = 6, }; +enum class AtomicType : u64 { + U32 = 0, + S32 = 1, + U64 = 2, + S64 = 3, +}; + enum class IMinMaxExchange : u64 { None = 0, XLo = 1, @@ -939,6 +958,16 @@ union Instruction { } stg; union { + BitField<52, 4, AtomicOp> operation; + BitField<28, 2, AtomicType> type; + BitField<30, 22, s64> offset; + + s32 GetImmediateOffset() const { + return static_cast<s32>(offset << 2); + } + } atoms; + + union { BitField<32, 1, PhysicalAttributeDirection> direction; BitField<47, 3, AttributeSize> size; BitField<20, 11, u64> address; @@ -1659,9 +1688,10 @@ public: ST_A, ST_L, ST_S, - ST, // Store in generic memory - STG, // Store in global memory - AL2P, // Transforms attribute memory into physical memory + ST, // Store in generic memory + STG, // Store in global memory + ATOMS, // Atomic operation on shared memory + AL2P, // Transforms attribute memory into physical memory TEX, TEX_B, // Texture Load Bindless TXQ, // Texture Query @@ -1964,6 +1994,7 @@ private: INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"), INST("101-------------", Id::ST, Type::Memory, "ST"), INST("1110111011011---", Id::STG, Type::Memory, "STG"), + INST("11101100--------", Id::ATOMS, Type::Memory, "ATOMS"), INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"), INST("110000----111---", Id::TEX, Type::Texture, "TEX"), INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 095660115..b9c5c41a2 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -66,19 +66,20 @@ const DmaPusher& GPU::DmaPusher() const { return *dma_pusher; } -void GPU::WaitFence(u32 syncpoint_id, u32 value) const { +void GPU::WaitFence(u32 syncpoint_id, u32 value) { // Synced GPU, is always in sync if (!is_async) { return; } MICROPROFILE_SCOPE(GPU_wait); - while (syncpoints[syncpoint_id].load(std::memory_order_relaxed) < value) { - } + std::unique_lock lock{sync_mutex}; + sync_cv.wait(lock, [=]() { return syncpoints[syncpoint_id].load() >= value; }); } void GPU::IncrementSyncPoint(const u32 syncpoint_id) { syncpoints[syncpoint_id]++; std::lock_guard lock{sync_mutex}; + sync_cv.notify_all(); if (!syncpt_interrupts[syncpoint_id].empty()) { u32 value = syncpoints[syncpoint_id].load(); auto it = syncpt_interrupts[syncpoint_id].begin(); diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index ecc338ae9..b648317bb 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -6,6 +6,7 @@ #include <array> #include <atomic> +#include <condition_variable> #include <list> #include <memory> #include <mutex> @@ -181,7 +182,7 @@ public: virtual void WaitIdle() const = 0; /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame. - void WaitFence(u32 syncpoint_id, u32 value) const; + void WaitFence(u32 syncpoint_id, u32 value); void IncrementSyncPoint(u32 syncpoint_id); @@ -312,6 +313,8 @@ private: std::mutex sync_mutex; + std::condition_variable sync_cv; + const bool is_async; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 672051102..c428f06e4 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1272,6 +1272,7 @@ void RasterizerOpenGL::SyncPointState() { const auto& regs = system.GPU().Maxwell3D().regs; // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid // in OpenGL). + state.point.program_control = regs.vp_point_size.enable != 0; state.point.size = std::max(1.0f, regs.point_size); } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index de742d11c..3c5bdd377 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -34,9 +34,6 @@ using VideoCommon::Shader::ShaderIR; namespace { -// One UBO is always reserved for emulation values on staged shaders -constexpr u32 STAGE_RESERVED_UBOS = 1; - constexpr u32 STAGE_MAIN_OFFSET = 10; constexpr u32 KERNEL_MAIN_OFFSET = 0; @@ -243,7 +240,6 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp if (!code_b.empty()) { ir_b.emplace(code_b, main_offset, COMPILER_SETTINGS, locker); } - const auto entries = GLShader::GetEntries(ir); std::string source = fmt::format(R"(// {} #version 430 core @@ -264,6 +260,10 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp "#extension GL_NV_shader_thread_group : require\n" "#extension GL_NV_shader_thread_shuffle : require\n"; } + // This pragma stops Nvidia's driver from over optimizing math (probably using fp16 operations) + // on places where we don't want to. + // Thanks to Ryujinx for finding this workaround. + source += "#pragma optionNV(fastmath off)\n"; if (shader_type == ShaderType::Geometry) { const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode); @@ -314,9 +314,10 @@ std::unordered_set<GLenum> GetSupportedFormats() { CachedShader::CachedShader(const ShaderParameters& params, ShaderType shader_type, GLShader::ShaderEntries entries, ProgramCode code, ProgramCode code_b) - : RasterizerCacheObject{params.host_ptr}, system{params.system}, disk_cache{params.disk_cache}, - device{params.device}, cpu_addr{params.cpu_addr}, unique_identifier{params.unique_identifier}, - shader_type{shader_type}, entries{entries}, code{std::move(code)}, code_b{std::move(code_b)} { + : RasterizerCacheObject{params.host_ptr}, system{params.system}, + disk_cache{params.disk_cache}, device{params.device}, cpu_addr{params.cpu_addr}, + unique_identifier{params.unique_identifier}, shader_type{shader_type}, + entries{std::move(entries)}, code{std::move(code)}, code_b{std::move(code_b)} { if (!params.precompiled_variants) { return; } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index a311dbcfe..2996aaf08 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -751,6 +751,9 @@ private: Expression Visit(const Node& node) { if (const auto operation = std::get_if<OperationNode>(&*node)) { + if (const auto amend_index = operation->GetAmendIndex()) { + Visit(ir.GetAmendNode(*amend_index)).CheckVoid(); + } const auto operation_index = static_cast<std::size_t>(operation->GetCode()); if (operation_index >= operation_decompilers.size()) { UNREACHABLE_MSG("Out of bounds operation: {}", operation_index); @@ -872,6 +875,9 @@ private: } if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { + if (const auto amend_index = conditional->GetAmendIndex()) { + Visit(ir.GetAmendNode(*amend_index)).CheckVoid(); + } // It's invalid to call conditional on nested nodes, use an operation instead code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool()); ++code.scope; @@ -1850,6 +1856,16 @@ private: Type::Uint}; } + template <const std::string_view& opname, Type type> + Expression Atomic(Operation operation) { + ASSERT(stage == ShaderType::Compute); + auto& smem = std::get<SmemNode>(*operation[0]); + + return {fmt::format("atomic{}(smem[{} >> 2], {})", opname, Visit(smem.GetAddress()).AsInt(), + Visit(operation[1]).As(type)), + type}; + } + Expression Branch(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); @@ -2188,6 +2204,8 @@ private: &GLSLDecompiler::AtomicImage<Func::Xor>, &GLSLDecompiler::AtomicImage<Func::Exchange>, + &GLSLDecompiler::Atomic<Func::Add, Type::Uint>, + &GLSLDecompiler::Branch, &GLSLDecompiler::BranchIndirect, &GLSLDecompiler::PushFlowStack, @@ -2307,7 +2325,7 @@ public: explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {} void operator()(const ExprAnd& expr) { - inner += "( "; + inner += '('; std::visit(*this, *expr.operand1); inner += " && "; std::visit(*this, *expr.operand2); @@ -2315,7 +2333,7 @@ public: } void operator()(const ExprOr& expr) { - inner += "( "; + inner += '('; std::visit(*this, *expr.operand1); inner += " || "; std::visit(*this, *expr.operand2); @@ -2333,28 +2351,7 @@ public: } void operator()(const ExprCondCode& expr) { - const Node cc = decomp.ir.GetConditionCode(expr.cc); - std::string target; - - if (const auto pred = std::get_if<PredicateNode>(&*cc)) { - const auto index = pred->GetIndex(); - switch (index) { - case Tegra::Shader::Pred::NeverExecute: - target = "false"; - break; - case Tegra::Shader::Pred::UnusedIndex: - target = "true"; - break; - default: - target = decomp.GetPredicate(index); - break; - } - } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) { - target = decomp.GetInternalFlag(flag->GetFlag()); - } else { - UNREACHABLE(); - } - inner += target; + inner += decomp.Visit(decomp.ir.GetConditionCode(expr.cc)).AsBool(); } void operator()(const ExprVar& expr) { @@ -2366,8 +2363,7 @@ public: } void operator()(VideoCommon::Shader::ExprGprEqual& expr) { - inner += - "( ftou(" + decomp.GetRegister(expr.gpr) + ") == " + std::to_string(expr.value) + ')'; + inner += fmt::format("(ftou({}) == {})", decomp.GetRegister(expr.gpr), expr.value); } const std::string& GetResult() const { @@ -2375,8 +2371,8 @@ public: } private: - std::string inner; GLSLDecompiler& decomp; + std::string inner; }; class ASTDecompiler { diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index df2e2395a..cc185e9e1 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -127,6 +127,7 @@ void OpenGLState::ApplyClipDistances() { } void OpenGLState::ApplyPointSize() { + Enable(GL_PROGRAM_POINT_SIZE, cur_state.point.program_control, point.program_control); if (UpdateValue(cur_state.point.size, point.size)) { glPointSize(point.size); } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index fb180f302..678e5cd89 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -131,7 +131,8 @@ public: std::array<Viewport, Tegra::Engines::Maxwell3D::Regs::NumViewports> viewports; struct { - float size = 1.0f; // GL_POINT_SIZE + bool program_control = false; // GL_PROGRAM_POINT_SIZE + GLfloat size = 1.0f; // GL_POINT_SIZE } point; struct { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index b790b0ef4..e95eb069e 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -44,7 +44,7 @@ struct FormatTuple { constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // ABGR8U - {GL_RGBA8, GL_RGBA, GL_BYTE, false}, // ABGR8S + {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE, false}, // ABGR8S {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, false}, // ABGR8UI {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, false}, // B5G6R5U {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, false}, // A2B10G10R10U @@ -83,9 +83,9 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format {GL_RGB32F, GL_RGB, GL_FLOAT, false}, // RGB32F {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // RGBA8_SRGB {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, false}, // RG8U - {GL_RG8, GL_RG, GL_BYTE, false}, // RG8S + {GL_RG8_SNORM, GL_RG, GL_BYTE, false}, // RG8S {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, false}, // RG32UI - {GL_RGB16F, GL_RGBA16, GL_HALF_FLOAT, false}, // RGBX16F + {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBX16F {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, false}, // R32UI {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X8 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X5 @@ -253,14 +253,12 @@ void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level))); glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level))); const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level); + u8* const mip_data = staging_buffer.data() + mip_offset; + const GLsizei size = static_cast<GLsizei>(params.GetHostMipmapSize(level)); if (is_compressed) { - glGetCompressedTextureImage(texture.handle, level, - static_cast<GLsizei>(params.GetHostMipmapSize(level)), - staging_buffer.data() + mip_offset); + glGetCompressedTextureImage(texture.handle, level, size, mip_data); } else { - glGetTextureImage(texture.handle, level, format, type, - static_cast<GLsizei>(params.GetHostMipmapSize(level)), - staging_buffer.data() + mip_offset); + glGetTextureImage(texture.handle, level, format, type, size, mip_data); } } } diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp index 9770dda1c..ac99e6385 100644 --- a/src/video_core/renderer_opengl/utils.cpp +++ b/src/video_core/renderer_opengl/utils.cpp @@ -6,16 +6,20 @@ #include <vector> #include <fmt/format.h> - #include <glad/glad.h> -#include "common/assert.h" #include "common/common_types.h" -#include "common/scope_exit.h" #include "video_core/renderer_opengl/utils.h" namespace OpenGL { +struct VertexArrayPushBuffer::Entry { + GLuint binding_index{}; + const GLuint* buffer{}; + GLintptr offset{}; + GLsizei stride{}; +}; + VertexArrayPushBuffer::VertexArrayPushBuffer() = default; VertexArrayPushBuffer::~VertexArrayPushBuffer() = default; @@ -47,6 +51,13 @@ void VertexArrayPushBuffer::Bind() { } } +struct BindBuffersRangePushBuffer::Entry { + GLuint binding; + const GLuint* buffer; + GLintptr offset; + GLsizeiptr size; +}; + BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {} BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default; diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h index d56153fe7..3ad7c02d4 100644 --- a/src/video_core/renderer_opengl/utils.h +++ b/src/video_core/renderer_opengl/utils.h @@ -26,12 +26,7 @@ public: void Bind(); private: - struct Entry { - GLuint binding_index{}; - const GLuint* buffer{}; - GLintptr offset{}; - GLsizei stride{}; - }; + struct Entry; GLuint vao{}; const GLuint* index_buffer{}; @@ -50,12 +45,7 @@ public: void Bind(); private: - struct Entry { - GLuint binding; - const GLuint* buffer; - GLintptr offset; - GLsizeiptr size; - }; + struct Entry; GLenum target; std::vector<Entry> entries; diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 5a490f6ef..4e3ff231e 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -109,6 +109,9 @@ constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) const auto topology = static_cast<std::size_t>(regs.draw.topology.Value()); const bool depth_bias_enabled = enabled_lut[PolygonOffsetEnableLUT[topology]]; + const auto& clip = regs.view_volume_clip_control; + const bool depth_clamp_enabled = clip.depth_clamp_near == 1 || clip.depth_clamp_far == 1; + Maxwell::Cull::FrontFace front_face = regs.cull.front_face; if (regs.screen_y_control.triangle_rast_flip != 0 && regs.viewport_transform[0].scale_y > 0.0f) { @@ -119,8 +122,9 @@ constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) } const bool gl_ndc = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne; - return FixedPipelineState::Rasterizer(regs.cull.enabled, depth_bias_enabled, gl_ndc, - regs.cull.cull_face, front_face); + return FixedPipelineState::Rasterizer(regs.cull.enabled, depth_bias_enabled, + depth_clamp_enabled, gl_ndc, regs.cull.cull_face, + front_face); } } // Anonymous namespace @@ -222,15 +226,17 @@ bool FixedPipelineState::Tessellation::operator==(const Tessellation& rhs) const std::size_t FixedPipelineState::Rasterizer::Hash() const noexcept { return static_cast<std::size_t>(cull_enable) ^ (static_cast<std::size_t>(depth_bias_enable) << 1) ^ - (static_cast<std::size_t>(ndc_minus_one_to_one) << 2) ^ + (static_cast<std::size_t>(depth_clamp_enable) << 2) ^ + (static_cast<std::size_t>(ndc_minus_one_to_one) << 3) ^ (static_cast<std::size_t>(cull_face) << 24) ^ (static_cast<std::size_t>(front_face) << 48); } bool FixedPipelineState::Rasterizer::operator==(const Rasterizer& rhs) const noexcept { - return std::tie(cull_enable, depth_bias_enable, ndc_minus_one_to_one, cull_face, front_face) == - std::tie(rhs.cull_enable, rhs.depth_bias_enable, rhs.ndc_minus_one_to_one, rhs.cull_face, - rhs.front_face); + return std::tie(cull_enable, depth_bias_enable, depth_clamp_enable, ndc_minus_one_to_one, + cull_face, front_face) == + std::tie(rhs.cull_enable, rhs.depth_bias_enable, rhs.depth_clamp_enable, + rhs.ndc_minus_one_to_one, rhs.cull_face, rhs.front_face); } std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 04152c0d4..87056ef37 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -170,15 +170,17 @@ struct FixedPipelineState { }; struct Rasterizer { - constexpr Rasterizer(bool cull_enable, bool depth_bias_enable, bool ndc_minus_one_to_one, - Maxwell::Cull::CullFace cull_face, Maxwell::Cull::FrontFace front_face) + constexpr Rasterizer(bool cull_enable, bool depth_bias_enable, bool depth_clamp_enable, + bool ndc_minus_one_to_one, Maxwell::Cull::CullFace cull_face, + Maxwell::Cull::FrontFace front_face) : cull_enable{cull_enable}, depth_bias_enable{depth_bias_enable}, - ndc_minus_one_to_one{ndc_minus_one_to_one}, cull_face{cull_face}, front_face{ - front_face} {} + depth_clamp_enable{depth_clamp_enable}, ndc_minus_one_to_one{ndc_minus_one_to_one}, + cull_face{cull_face}, front_face{front_face} {} Rasterizer() = default; bool cull_enable; bool depth_bias_enable; + bool depth_clamp_enable; bool ndc_minus_one_to_one; Maxwell::Cull::CullFace cull_face; Maxwell::Cull::FrontFace front_face; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 000e3616d..331808113 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -44,7 +44,7 @@ vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filt return {}; } -vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode, +vk::SamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode, Tegra::Texture::TextureFilter filter) { switch (wrap_mode) { case Tegra::Texture::WrapMode::Wrap: @@ -56,7 +56,12 @@ vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode, case Tegra::Texture::WrapMode::Border: return vk::SamplerAddressMode::eClampToBorder; case Tegra::Texture::WrapMode::Clamp: - // TODO(Rodrigo): Emulate GL_CLAMP properly + if (device.GetDriverID() == vk::DriverIdKHR::eNvidiaProprietary) { + // Nvidia's Vulkan driver defaults to GL_CLAMP on invalid enumerations, we can hack this + // by sending an invalid enumeration. + return static_cast<vk::SamplerAddressMode>(0xcafe); + } + // TODO(Rodrigo): Emulate GL_CLAMP properly on other vendors switch (filter) { case Tegra::Texture::TextureFilter::Nearest: return vk::SamplerAddressMode::eClampToEdge; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h index 1534b738b..7e9678b7b 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.h +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h @@ -22,7 +22,7 @@ vk::Filter Filter(Tegra::Texture::TextureFilter filter); vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter); -vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode, +vk::SamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode, Tegra::Texture::TextureFilter filter); vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h new file mode 100644 index 000000000..a472c5dc9 --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -0,0 +1,72 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <vector> +#include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/declarations.h" + +namespace Core { +class System; +} + +namespace Vulkan { + +class VKBlitScreen; +class VKDevice; +class VKFence; +class VKMemoryManager; +class VKResourceManager; +class VKSwapchain; +class VKScheduler; +class VKImage; + +struct VKScreenInfo { + VKImage* image{}; + u32 width{}; + u32 height{}; + bool is_srgb{}; +}; + +class RendererVulkan final : public VideoCore::RendererBase { +public: + explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); + ~RendererVulkan() override; + + /// Swap buffers (render frame) + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; + + /// Initialize the renderer + bool Init() override; + + /// Shutdown the renderer + void ShutDown() override; + +private: + std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( + const vk::DispatchLoaderDynamic& dldi); + + bool PickDevices(const vk::DispatchLoaderDynamic& dldi); + + void Report() const; + + Core::System& system; + + vk::Instance instance; + vk::SurfaceKHR surface; + + VKScreenInfo screen_info; + + UniqueDebugUtilsMessengerEXT debug_callback; + std::unique_ptr<VKDevice> device; + std::unique_ptr<VKSwapchain> swapchain; + std::unique_ptr<VKMemoryManager> memory_manager; + std::unique_ptr<VKResourceManager> resource_manager; + std::unique_ptr<VKScheduler> scheduler; + std::unique_ptr<VKBlitScreen> blit_screen; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp new file mode 100644 index 000000000..855cfc883 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -0,0 +1,627 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstring> +#include <memory> +#include <tuple> +#include <vector> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/math_util.h" + +#include "core/core.h" +#include "core/frontend/emu_window.h" +#include "core/memory.h" + +#include "video_core/gpu.h" +#include "video_core/morton.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/renderer_vulkan/vk_blit_screen.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_image.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" +#include "video_core/surface.h" + +namespace Vulkan { + +namespace { + +// Generated from the "shaders/" directory, read the instructions there. +constexpr u8 blit_vertex_code[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, + 0x38, 0x00, 0x01, 0x00}; + +constexpr u8 blit_fragment_code[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; + +struct ScreenRectVertex { + ScreenRectVertex() = default; + explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {} + + std::array<f32, 2> position; + std::array<f32, 2> tex_coord; + + static vk::VertexInputBindingDescription GetDescription() { + return vk::VertexInputBindingDescription(0, sizeof(ScreenRectVertex), + vk::VertexInputRate::eVertex); + } + + static std::array<vk::VertexInputAttributeDescription, 2> GetAttributes() { + return {vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, + offsetof(ScreenRectVertex, position)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32Sfloat, + offsetof(ScreenRectVertex, tex_coord))}; + } +}; + +constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { + // clang-format off + return { 2.f / width, 0.f, 0.f, 0.f, + 0.f, 2.f / height, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + -1.f, -1.f, 0.f, 1.f}; + // clang-format on +} + +std::size_t GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { + using namespace VideoCore::Surface; + return GetBytesPerPixel(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); +} + +std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { + return static_cast<std::size_t>(framebuffer.stride) * + static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer); +} + +vk::Format GetFormat(const Tegra::FramebufferConfig& framebuffer) { + switch (framebuffer.pixel_format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: + return vk::Format::eA8B8G8R8UnormPack32; + case Tegra::FramebufferConfig::PixelFormat::RGB565: + return vk::Format::eR5G6B5UnormPack16; + default: + UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", + static_cast<u32>(framebuffer.pixel_format)); + return vk::Format::eA8B8G8R8UnormPack32; + } +} + +} // Anonymous namespace + +struct VKBlitScreen::BufferData { + struct { + std::array<f32, 4 * 4> modelview_matrix; + } uniform; + + std::array<ScreenRectVertex, 4> vertices; + + // Unaligned image data goes here +}; + +VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window, + VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, + VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + VKSwapchain& swapchain, VKScheduler& scheduler, + const VKScreenInfo& screen_info) + : system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device}, + resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain}, + scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} { + watches.resize(image_count); + std::generate(watches.begin(), watches.end(), + []() { return std::make_unique<VKFenceWatch>(); }); + + CreateStaticResources(); + CreateDynamicResources(); +} + +VKBlitScreen::~VKBlitScreen() = default; + +void VKBlitScreen::Recreate() { + CreateDynamicResources(); +} + +std::tuple<VKFence&, vk::Semaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated) { + RefreshResources(framebuffer); + + // Finish any pending renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + const std::size_t image_index = swapchain.GetImageIndex(); + watches[image_index]->Watch(scheduler.GetFence()); + + VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get(); + + UpdateDescriptorSet(image_index, blit_image->GetPresentView()); + + BufferData data; + SetUniformData(data, framebuffer); + SetVertexData(data, framebuffer); + + auto map = buffer_commit->Map(); + std::memcpy(map.GetAddress(), &data, sizeof(data)); + + if (!use_accelerated) { + const u64 image_offset = GetRawImageOffset(framebuffer, image_index); + + const auto pixel_format = + VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format); + const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset; + const auto host_ptr = system.Memory().GetPointer(framebuffer_addr); + rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer)); + + // TODO(Rodrigo): Read this from HLE + constexpr u32 block_height_log2 = 4; + VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format, + framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1, + map.GetAddress() + image_offset, host_ptr); + + blit_image->Transition(0, 1, 0, 1, vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite, + vk::ImageLayout::eTransferDstOptimal); + + const vk::BufferImageCopy copy(image_offset, 0, 0, + {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, {0, 0, 0}, + {framebuffer.width, framebuffer.height, 1}); + scheduler.Record([buffer_handle = *buffer, image = blit_image->GetHandle(), + copy](auto cmdbuf, auto& dld) { + cmdbuf.copyBufferToImage(buffer_handle, image, vk::ImageLayout::eTransferDstOptimal, + {copy}, dld); + }); + } + map.Release(); + + blit_image->Transition(0, 1, 0, 1, vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eShaderRead, + vk::ImageLayout::eShaderReadOnlyOptimal); + + scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index], + descriptor_set = descriptor_sets[image_index], buffer = *buffer, + size = swapchain.GetSize(), pipeline = *pipeline, + layout = *pipeline_layout](auto cmdbuf, auto& dld) { + const vk::ClearValue clear_color{std::array{0.0f, 0.0f, 0.0f, 1.0f}}; + const vk::RenderPassBeginInfo renderpass_bi(renderpass, framebuffer, {{0, 0}, size}, 1, + &clear_color); + + cmdbuf.beginRenderPass(renderpass_bi, vk::SubpassContents::eInline, dld); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, dld); + cmdbuf.setViewport( + 0, + {{0.0f, 0.0f, static_cast<f32>(size.width), static_cast<f32>(size.height), 0.0f, 1.0f}}, + dld); + cmdbuf.setScissor(0, {{{0, 0}, size}}, dld); + + cmdbuf.bindVertexBuffers(0, {buffer}, {offsetof(BufferData, vertices)}, dld); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, {descriptor_set}, {}, + dld); + cmdbuf.draw(4, 1, 0, 0, dld); + cmdbuf.endRenderPass(dld); + }); + + return {scheduler.GetFence(), *semaphores[image_index]}; +} + +void VKBlitScreen::CreateStaticResources() { + CreateShaders(); + CreateSemaphores(); + CreateDescriptorPool(); + CreateDescriptorSetLayout(); + CreateDescriptorSets(); + CreatePipelineLayout(); + CreateSampler(); +} + +void VKBlitScreen::CreateDynamicResources() { + CreateRenderPass(); + CreateFramebuffers(); + CreateGraphicsPipeline(); +} + +void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { + if (framebuffer.width == raw_width && framebuffer.height == raw_height && !raw_images.empty()) { + return; + } + raw_width = framebuffer.width; + raw_height = framebuffer.height; + ReleaseRawImages(); + + CreateStagingBuffer(framebuffer); + CreateRawImages(framebuffer); +} + +void VKBlitScreen::CreateShaders() { + vertex_shader = BuildShader(device, sizeof(blit_vertex_code), blit_vertex_code); + fragment_shader = BuildShader(device, sizeof(blit_fragment_code), blit_fragment_code); +} + +void VKBlitScreen::CreateSemaphores() { + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + + semaphores.resize(image_count); + for (std::size_t i = 0; i < image_count; ++i) { + semaphores[i] = dev.createSemaphoreUnique({}, nullptr, dld); + } +} + +void VKBlitScreen::CreateDescriptorPool() { + const std::array<vk::DescriptorPoolSize, 2> pool_sizes{ + vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, static_cast<u32>(image_count)}, + vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler, + static_cast<u32>(image_count)}}; + const vk::DescriptorPoolCreateInfo pool_ci( + {}, static_cast<u32>(image_count), static_cast<u32>(pool_sizes.size()), pool_sizes.data()); + const auto dev = device.GetLogical(); + descriptor_pool = dev.createDescriptorPoolUnique(pool_ci, nullptr, device.GetDispatchLoader()); +} + +void VKBlitScreen::CreateRenderPass() { + const vk::AttachmentDescription color_attachment( + {}, swapchain.GetImageFormat(), vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, + vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, + vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, + vk::ImageLayout::ePresentSrcKHR); + + const vk::AttachmentReference color_attachment_ref(0, vk::ImageLayout::eColorAttachmentOptimal); + + const vk::SubpassDescription subpass_description({}, vk::PipelineBindPoint::eGraphics, 0, + nullptr, 1, &color_attachment_ref, nullptr, + nullptr, 0, nullptr); + + const vk::SubpassDependency dependency( + VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, + vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, {}); + + const vk::RenderPassCreateInfo renderpass_ci({}, 1, &color_attachment, 1, &subpass_description, + 1, &dependency); + + const auto dev = device.GetLogical(); + renderpass = dev.createRenderPassUnique(renderpass_ci, nullptr, device.GetDispatchLoader()); +} + +void VKBlitScreen::CreateDescriptorSetLayout() { + const std::array<vk::DescriptorSetLayoutBinding, 2> layout_bindings{ + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, + vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, + vk::ShaderStageFlagBits::eFragment, nullptr)}; + const vk::DescriptorSetLayoutCreateInfo descriptor_layout_ci( + {}, static_cast<u32>(layout_bindings.size()), layout_bindings.data()); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + descriptor_set_layout = dev.createDescriptorSetLayoutUnique(descriptor_layout_ci, nullptr, dld); +} + +void VKBlitScreen::CreateDescriptorSets() { + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + + descriptor_sets.resize(image_count); + for (std::size_t i = 0; i < image_count; ++i) { + const vk::DescriptorSetLayout layout = *descriptor_set_layout; + const vk::DescriptorSetAllocateInfo descriptor_set_ai(*descriptor_pool, 1, &layout); + const vk::Result result = + dev.allocateDescriptorSets(&descriptor_set_ai, &descriptor_sets[i], dld); + ASSERT(result == vk::Result::eSuccess); + } +} + +void VKBlitScreen::CreatePipelineLayout() { + const vk::PipelineLayoutCreateInfo pipeline_layout_ci({}, 1, &descriptor_set_layout.get(), 0, + nullptr); + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + pipeline_layout = dev.createPipelineLayoutUnique(pipeline_layout_ci, nullptr, dld); +} + +void VKBlitScreen::CreateGraphicsPipeline() { + const std::array shader_stages = { + vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eVertex, *vertex_shader, + "main", nullptr), + vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eFragment, *fragment_shader, + "main", nullptr)}; + + const auto vertex_binding_description = ScreenRectVertex::GetDescription(); + const auto vertex_attrs_description = ScreenRectVertex::GetAttributes(); + const vk::PipelineVertexInputStateCreateInfo vertex_input( + {}, 1, &vertex_binding_description, static_cast<u32>(vertex_attrs_description.size()), + vertex_attrs_description.data()); + + const vk::PipelineInputAssemblyStateCreateInfo input_assembly( + {}, vk::PrimitiveTopology::eTriangleStrip, false); + + // Set a dummy viewport, it's going to be replaced by dynamic states. + const vk::Viewport viewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f); + const vk::Rect2D scissor({0, 0}, {1, 1}); + + const vk::PipelineViewportStateCreateInfo viewport_state({}, 1, &viewport, 1, &scissor); + + const vk::PipelineRasterizationStateCreateInfo rasterizer( + {}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, + vk::FrontFace::eClockwise, false, 0.0f, 0.0f, 0.0f, 1.0f); + + const vk::PipelineMultisampleStateCreateInfo multisampling({}, vk::SampleCountFlagBits::e1, + false, 0.0f, nullptr, false, false); + + const vk::PipelineColorBlendAttachmentState color_blend_attachment( + false, vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd, + vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd, + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA); + + const vk::PipelineColorBlendStateCreateInfo color_blending( + {}, false, vk::LogicOp::eCopy, 1, &color_blend_attachment, {0.0f, 0.0f, 0.0f, 0.0f}); + + const std::array<vk::DynamicState, 2> dynamic_states = {vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + + const vk::PipelineDynamicStateCreateInfo dynamic_state( + {}, static_cast<u32>(dynamic_states.size()), dynamic_states.data()); + + const vk::GraphicsPipelineCreateInfo pipeline_ci( + {}, static_cast<u32>(shader_stages.size()), shader_stages.data(), &vertex_input, + &input_assembly, nullptr, &viewport_state, &rasterizer, &multisampling, nullptr, + &color_blending, &dynamic_state, *pipeline_layout, *renderpass, 0, nullptr, 0); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + pipeline = dev.createGraphicsPipelineUnique({}, pipeline_ci, nullptr, dld); +} + +void VKBlitScreen::CreateSampler() { + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + const vk::SamplerCreateInfo sampler_ci( + {}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear, + vk::SamplerAddressMode::eClampToBorder, vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eClampToBorder, 0.0f, false, 0.0f, false, vk::CompareOp::eNever, + 0.0f, 0.0f, vk::BorderColor::eFloatOpaqueBlack, false); + sampler = dev.createSamplerUnique(sampler_ci, nullptr, dld); +} + +void VKBlitScreen::CreateFramebuffers() { + const vk::Extent2D size{swapchain.GetSize()}; + framebuffers.clear(); + framebuffers.resize(image_count); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + + for (std::size_t i = 0; i < image_count; ++i) { + const vk::ImageView image_view{swapchain.GetImageViewIndex(i)}; + const vk::FramebufferCreateInfo framebuffer_ci({}, *renderpass, 1, &image_view, size.width, + size.height, 1); + framebuffers[i] = dev.createFramebufferUnique(framebuffer_ci, nullptr, dld); + } +} + +void VKBlitScreen::ReleaseRawImages() { + for (std::size_t i = 0; i < raw_images.size(); ++i) { + watches[i]->Wait(); + } + raw_images.clear(); + raw_buffer_commits.clear(); + buffer.reset(); + buffer_commit.reset(); +} + +void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + + const vk::BufferCreateInfo buffer_ci({}, CalculateBufferSize(framebuffer), + vk::BufferUsageFlagBits::eTransferSrc | + vk::BufferUsageFlagBits::eVertexBuffer | + vk::BufferUsageFlagBits::eUniformBuffer, + vk::SharingMode::eExclusive, 0, nullptr); + buffer = dev.createBufferUnique(buffer_ci, nullptr, dld); + buffer_commit = memory_manager.Commit(*buffer, true); +} + +void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { + raw_images.resize(image_count); + raw_buffer_commits.resize(image_count); + + const auto format = GetFormat(framebuffer); + for (std::size_t i = 0; i < image_count; ++i) { + const vk::ImageCreateInfo image_ci( + {}, vk::ImageType::e2D, format, {framebuffer.width, framebuffer.height, 1}, 1, 1, + vk::SampleCountFlagBits::e1, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::SharingMode::eExclusive, 0, nullptr, vk::ImageLayout::eUndefined); + + raw_images[i] = + std::make_unique<VKImage>(device, scheduler, image_ci, vk::ImageAspectFlagBits::eColor); + raw_buffer_commits[i] = memory_manager.Commit(raw_images[i]->GetHandle(), false); + } +} + +void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, vk::ImageView image_view) const { + const vk::DescriptorSet descriptor_set = descriptor_sets[image_index]; + + const vk::DescriptorBufferInfo buffer_info(*buffer, offsetof(BufferData, uniform), + sizeof(BufferData::uniform)); + const vk::WriteDescriptorSet ubo_write(descriptor_set, 0, 0, 1, + vk::DescriptorType::eUniformBuffer, nullptr, + &buffer_info, nullptr); + + const vk::DescriptorImageInfo image_info(*sampler, image_view, + vk::ImageLayout::eShaderReadOnlyOptimal); + const vk::WriteDescriptorSet sampler_write(descriptor_set, 1, 0, 1, + vk::DescriptorType::eCombinedImageSampler, + &image_info, nullptr, nullptr); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + dev.updateDescriptorSets({ubo_write, sampler_write}, {}, dld); +} + +void VKBlitScreen::SetUniformData(BufferData& data, + const Tegra::FramebufferConfig& framebuffer) const { + const auto& layout = render_window.GetFramebufferLayout(); + data.uniform.modelview_matrix = + MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height)); +} + +void VKBlitScreen::SetVertexData(BufferData& data, + const Tegra::FramebufferConfig& framebuffer) const { + const auto& framebuffer_transform_flags = framebuffer.transform_flags; + const auto& framebuffer_crop_rect = framebuffer.crop_rect; + + static constexpr Common::Rectangle<f32> texcoords{0.f, 0.f, 1.f, 1.f}; + auto left = texcoords.left; + auto right = texcoords.right; + + switch (framebuffer_transform_flags) { + case Tegra::FramebufferConfig::TransformFlags::Unset: + break; + case Tegra::FramebufferConfig::TransformFlags::FlipV: + // Flip the framebuffer vertically + left = texcoords.right; + right = texcoords.left; + break; + default: + UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}", + static_cast<u32>(framebuffer_transform_flags)); + break; + } + + UNIMPLEMENTED_IF(framebuffer_crop_rect.top != 0); + UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0); + + // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering + // (e.g. handheld mode) on a 1920x1080 framebuffer. + f32 scale_u = 1.0f; + f32 scale_v = 1.0f; + if (framebuffer_crop_rect.GetWidth() > 0) { + scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / + static_cast<f32>(screen_info.width); + } + if (framebuffer_crop_rect.GetHeight() > 0) { + scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / + static_cast<f32>(screen_info.height); + } + + const auto& screen = render_window.GetFramebufferLayout().screen; + const auto x = static_cast<f32>(screen.left); + const auto y = static_cast<f32>(screen.top); + const auto w = static_cast<f32>(screen.GetWidth()); + const auto h = static_cast<f32>(screen.GetHeight()); + data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v); + data.vertices[1] = ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v); + data.vertices[2] = ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v); + data.vertices[3] = ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v); +} + +u64 VKBlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const { + return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count; +} + +u64 VKBlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, + std::size_t image_index) const { + constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData)); + return first_image_offset + GetSizeInBytes(framebuffer) * image_index; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h new file mode 100644 index 000000000..ea680b3f5 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -0,0 +1,119 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <tuple> + +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" + +namespace Core { +class System; +} + +namespace Core::Frontend { +class EmuWindow; +} + +namespace Tegra { +struct FramebufferConfig; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace Vulkan { + +struct ScreenInfo; +class RasterizerVulkan; +class VKDevice; +class VKFence; +class VKImage; +class VKScheduler; +class VKSwapchain; + +class VKBlitScreen final { +public: + explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window, + VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, + VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + VKSwapchain& swapchain, VKScheduler& scheduler, + const VKScreenInfo& screen_info); + ~VKBlitScreen(); + + void Recreate(); + + std::tuple<VKFence&, vk::Semaphore> Draw(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated); + +private: + struct BufferData; + + void CreateStaticResources(); + void CreateShaders(); + void CreateSemaphores(); + void CreateDescriptorPool(); + void CreateRenderPass(); + void CreateDescriptorSetLayout(); + void CreateDescriptorSets(); + void CreatePipelineLayout(); + void CreateGraphicsPipeline(); + void CreateSampler(); + + void CreateDynamicResources(); + void CreateFramebuffers(); + + void RefreshResources(const Tegra::FramebufferConfig& framebuffer); + void ReleaseRawImages(); + void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer); + void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); + + void UpdateDescriptorSet(std::size_t image_index, vk::ImageView image_view) const; + void SetUniformData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; + void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; + + u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; + u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, + std::size_t image_index) const; + + Core::System& system; + Core::Frontend::EmuWindow& render_window; + VideoCore::RasterizerInterface& rasterizer; + const VKDevice& device; + VKResourceManager& resource_manager; + VKMemoryManager& memory_manager; + VKSwapchain& swapchain; + VKScheduler& scheduler; + const std::size_t image_count; + const VKScreenInfo& screen_info; + + UniqueShaderModule vertex_shader; + UniqueShaderModule fragment_shader; + UniqueDescriptorPool descriptor_pool; + UniqueDescriptorSetLayout descriptor_set_layout; + UniquePipelineLayout pipeline_layout; + UniquePipeline pipeline; + UniqueRenderPass renderpass; + std::vector<UniqueFramebuffer> framebuffers; + std::vector<vk::DescriptorSet> descriptor_sets; + UniqueSampler sampler; + + UniqueBuffer buffer; + VKMemoryCommit buffer_commit; + + std::vector<std::unique_ptr<VKFenceWatch>> watches; + + std::vector<UniqueSemaphore> semaphores; + std::vector<std::unique_ptr<VKImage>> raw_images; + std::vector<VKMemoryCommit> raw_buffer_commits; + u32 raw_width = 0; + u32 raw_height = 0; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 46da81aaa..1ba544943 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -2,124 +2,145 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <cstring> #include <memory> #include <optional> #include <tuple> -#include "common/alignment.h" #include "common/assert.h" -#include "core/memory.h" -#include "video_core/memory_manager.h" +#include "common/bit_util.h" +#include "core/core.h" #include "video_core/renderer_vulkan/declarations.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_stream_buffer.h" namespace Vulkan { -CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, - std::size_t alignment, u8* host_ptr) - : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, size{size}, offset{offset}, - alignment{alignment} {} - -VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager, - Memory::Memory& cpu_memory_, - VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, - VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size) - : RasterizerCache{rasterizer}, tegra_memory_manager{tegra_memory_manager}, cpu_memory{ - cpu_memory_} { - const auto usage = vk::BufferUsageFlagBits::eVertexBuffer | - vk::BufferUsageFlagBits::eIndexBuffer | - vk::BufferUsageFlagBits::eUniformBuffer; - const auto access = vk::AccessFlagBits::eVertexAttributeRead | vk::AccessFlagBits::eIndexRead | - vk::AccessFlagBits::eUniformRead; - stream_buffer = - std::make_unique<VKStreamBuffer>(device, memory_manager, scheduler, size, usage, access, - vk::PipelineStageFlagBits::eAllCommands); - buffer_handle = stream_buffer->GetBuffer(); -} +namespace { -VKBufferCache::~VKBufferCache() = default; +const auto BufferUsage = + vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | + vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer; + +const auto UploadPipelineStage = + vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eVertexInput | + vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader | + vk::PipelineStageFlagBits::eComputeShader; -u64 VKBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment, bool cache) { - const auto cpu_addr{tegra_memory_manager.GpuToCpuAddress(gpu_addr)}; - ASSERT_MSG(cpu_addr, "Invalid GPU address"); - - // Cache management is a big overhead, so only cache entries with a given size. - // TODO: Figure out which size is the best for given games. - cache &= size >= 2048; - - u8* const host_ptr{cpu_memory.GetPointer(*cpu_addr)}; - if (cache) { - const auto entry = TryGet(host_ptr); - if (entry) { - if (entry->GetSize() >= size && entry->GetAlignment() == alignment) { - return entry->GetOffset(); - } - Unregister(entry); - } - } - - AlignBuffer(alignment); - const u64 uploaded_offset = buffer_offset; - - if (host_ptr == nullptr) { - return uploaded_offset; - } - - std::memcpy(buffer_ptr, host_ptr, size); - buffer_ptr += size; - buffer_offset += size; - - if (cache) { - auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset, - alignment, host_ptr); - Register(entry); - } - - return uploaded_offset; +const auto UploadAccessBarriers = + vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eShaderRead | + vk::AccessFlagBits::eUniformRead | vk::AccessFlagBits::eVertexAttributeRead | + vk::AccessFlagBits::eIndexRead; + +auto CreateStreamBuffer(const VKDevice& device, VKScheduler& scheduler) { + return std::make_unique<VKStreamBuffer>(device, scheduler, BufferUsage); } -u64 VKBufferCache::UploadHostMemory(const u8* raw_pointer, std::size_t size, u64 alignment) { - AlignBuffer(alignment); - std::memcpy(buffer_ptr, raw_pointer, size); - const u64 uploaded_offset = buffer_offset; +} // Anonymous namespace + +CachedBufferBlock::CachedBufferBlock(const VKDevice& device, VKMemoryManager& memory_manager, + CacheAddr cache_addr, std::size_t size) + : VideoCommon::BufferBlock{cache_addr, size} { + const vk::BufferCreateInfo buffer_ci({}, static_cast<vk::DeviceSize>(size), + BufferUsage | vk::BufferUsageFlagBits::eTransferSrc | + vk::BufferUsageFlagBits::eTransferDst, + vk::SharingMode::eExclusive, 0, nullptr); - buffer_ptr += size; - buffer_offset += size; - return uploaded_offset; + const auto& dld{device.GetDispatchLoader()}; + const auto dev{device.GetLogical()}; + buffer.handle = dev.createBufferUnique(buffer_ci, nullptr, dld); + buffer.commit = memory_manager.Commit(*buffer.handle, false); } -std::tuple<u8*, u64> VKBufferCache::ReserveMemory(std::size_t size, u64 alignment) { - AlignBuffer(alignment); - u8* const uploaded_ptr = buffer_ptr; - const u64 uploaded_offset = buffer_offset; +CachedBufferBlock::~CachedBufferBlock() = default; + +VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, + const VKDevice& device, VKMemoryManager& memory_manager, + VKScheduler& scheduler, VKStagingBufferPool& staging_pool) + : VideoCommon::BufferCache<Buffer, vk::Buffer, VKStreamBuffer>{rasterizer, system, + CreateStreamBuffer(device, + scheduler)}, + device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{ + staging_pool} {} - buffer_ptr += size; - buffer_offset += size; - return {uploaded_ptr, uploaded_offset}; +VKBufferCache::~VKBufferCache() = default; + +Buffer VKBufferCache::CreateBlock(CacheAddr cache_addr, std::size_t size) { + return std::make_shared<CachedBufferBlock>(device, memory_manager, cache_addr, size); } -void VKBufferCache::Reserve(std::size_t max_size) { - bool invalidate; - std::tie(buffer_ptr, buffer_offset_base, invalidate) = stream_buffer->Reserve(max_size); - buffer_offset = buffer_offset_base; +const vk::Buffer* VKBufferCache::ToHandle(const Buffer& buffer) { + return buffer->GetHandle(); +} + +const vk::Buffer* VKBufferCache::GetEmptyBuffer(std::size_t size) { + size = std::max(size, std::size_t(4)); + const auto& empty = staging_pool.GetUnusedBuffer(size, false); + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf, auto& dld) { + cmdbuf.fillBuffer(buffer, 0, size, 0, dld); + }); + return &*empty.handle; +} - if (invalidate) { - InvalidateAll(); - } +void VKBufferCache::UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + const u8* data) { + const auto& staging = staging_pool.GetUnusedBuffer(size, true); + std::memcpy(staging.commit->Map(size), data, size); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([staging = *staging.handle, buffer = *buffer->GetHandle(), offset, + size](auto cmdbuf, auto& dld) { + cmdbuf.copyBuffer(staging, buffer, {{0, offset, size}}, dld); + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, UploadPipelineStage, {}, {}, + {vk::BufferMemoryBarrier(vk::AccessFlagBits::eTransferWrite, UploadAccessBarriers, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, buffer, + offset, size)}, + {}, dld); + }); } -void VKBufferCache::Send() { - stream_buffer->Send(buffer_offset - buffer_offset_base); +void VKBufferCache::DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + u8* data) { + const auto& staging = staging_pool.GetUnusedBuffer(size, true); + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([staging = *staging.handle, buffer = *buffer->GetHandle(), offset, + size](auto cmdbuf, auto& dld) { + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader | + vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eTransfer, {}, {}, + {vk::BufferMemoryBarrier(vk::AccessFlagBits::eShaderWrite, + vk::AccessFlagBits::eTransferRead, VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, buffer, offset, size)}, + {}, dld); + cmdbuf.copyBuffer(buffer, staging, {{offset, 0, size}}, dld); + }); + scheduler.Finish(); + + std::memcpy(data, staging.commit->Map(size), size); } -void VKBufferCache::AlignBuffer(std::size_t alignment) { - // Align the offset, not the mapped pointer - const u64 offset_aligned = Common::AlignUp(buffer_offset, alignment); - buffer_ptr += offset_aligned - buffer_offset; - buffer_offset = offset_aligned; +void VKBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset, + std::size_t dst_offset, std::size_t size) { + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([src_buffer = *src->GetHandle(), dst_buffer = *dst->GetHandle(), src_offset, + dst_offset, size](auto cmdbuf, auto& dld) { + cmdbuf.copyBuffer(src_buffer, dst_buffer, {{src_offset, dst_offset, size}}, dld); + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, UploadPipelineStage, {}, {}, + {vk::BufferMemoryBarrier(vk::AccessFlagBits::eTransferRead, + vk::AccessFlagBits::eShaderWrite, VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, src_buffer, src_offset, size), + vk::BufferMemoryBarrier(vk::AccessFlagBits::eTransferWrite, UploadAccessBarriers, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, dst_buffer, + dst_offset, size)}, + {}, dld); + }); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index daa8ccf66..3f38eed0c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -5,105 +5,74 @@ #pragma once #include <memory> -#include <tuple> +#include <unordered_map> +#include <vector> #include "common/common_types.h" -#include "video_core/gpu.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/rasterizer_cache.h" #include "video_core/renderer_vulkan/declarations.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_stream_buffer.h" -namespace Memory { -class Memory; -} - -namespace Tegra { -class MemoryManager; +namespace Core { +class System; } namespace Vulkan { class VKDevice; -class VKFence; class VKMemoryManager; -class VKStreamBuffer; +class VKScheduler; -class CachedBufferEntry final : public RasterizerCacheObject { +class CachedBufferBlock final : public VideoCommon::BufferBlock { public: - explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, std::size_t alignment, - u8* host_ptr); + explicit CachedBufferBlock(const VKDevice& device, VKMemoryManager& memory_manager, + CacheAddr cache_addr, std::size_t size); + ~CachedBufferBlock(); - VAddr GetCpuAddr() const override { - return cpu_addr; - } - - std::size_t GetSizeInBytes() const override { - return size; - } - - std::size_t GetSize() const { - return size; - } - - u64 GetOffset() const { - return offset; - } - - std::size_t GetAlignment() const { - return alignment; + const vk::Buffer* GetHandle() const { + return &*buffer.handle; } private: - VAddr cpu_addr{}; - std::size_t size{}; - u64 offset{}; - std::size_t alignment{}; + VKBuffer buffer; }; -class VKBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> { +using Buffer = std::shared_ptr<CachedBufferBlock>; + +class VKBufferCache final : public VideoCommon::BufferCache<Buffer, vk::Buffer, VKStreamBuffer> { public: - explicit VKBufferCache(Tegra::MemoryManager& tegra_memory_manager, Memory::Memory& cpu_memory_, - VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, - VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size); + explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, + const VKDevice& device, VKMemoryManager& memory_manager, + VKScheduler& scheduler, VKStagingBufferPool& staging_pool); ~VKBufferCache(); - /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been - /// allocated. - u64 UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4, bool cache = true); + const vk::Buffer* GetEmptyBuffer(std::size_t size) override; - /// Uploads from a host memory. Returns host's buffer offset where it's been allocated. - u64 UploadHostMemory(const u8* raw_pointer, std::size_t size, u64 alignment = 4); +protected: + void WriteBarrier() override {} - /// Reserves memory to be used by host's CPU. Returns mapped address and offset. - std::tuple<u8*, u64> ReserveMemory(std::size_t size, u64 alignment = 4); + Buffer CreateBlock(CacheAddr cache_addr, std::size_t size) override; - /// Reserves a region of memory to be used in subsequent upload/reserve operations. - void Reserve(std::size_t max_size); + const vk::Buffer* ToHandle(const Buffer& buffer) override; - /// Ensures that the set data is sent to the device. - void Send(); + void UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + const u8* data) override; - /// Returns the buffer cache handle. - vk::Buffer GetBuffer() const { - return buffer_handle; - } + void DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + u8* data) override; -protected: - // We do not have to flush this cache as things in it are never modified by us. - void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {} + void CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset, + std::size_t dst_offset, std::size_t size) override; private: - void AlignBuffer(std::size_t alignment); - - Tegra::MemoryManager& tegra_memory_manager; - Memory::Memory& cpu_memory; - - std::unique_ptr<VKStreamBuffer> stream_buffer; - vk::Buffer buffer_handle; - - u8* buffer_ptr = nullptr; - u64 buffer_offset = 0; - u64 buffer_offset_base = 0; + const VKDevice& device; + VKMemoryManager& memory_manager; + VKScheduler& scheduler; + VKStagingBufferPool& staging_pool; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp new file mode 100644 index 000000000..7bdda3d79 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -0,0 +1,339 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> +#include <optional> +#include <utility> +#include <vector> +#include "common/alignment.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +namespace { + +// Quad array SPIR-V module. Generated from the "shaders/" directory, read the instructions there. +constexpr u8 quad_array[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x09, 0x00, 0x34, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0xf8, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0xf8, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0xae, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, + 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x03, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x03, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, + 0xf9, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x23, 0x00, 0x00, 0x00, + 0xf9, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4e, 0x00, 0x00, 0x00, + 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; + +// Uint8 SPIR-V module. Generated from the "shaders/" directory. +constexpr u8 uint8_pass[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x51, 0x11, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x61, 0x11, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00, + 0x53, 0x50, 0x56, 0x5f, 0x4b, 0x48, 0x52, 0x5f, 0x31, 0x36, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x53, 0x50, 0x56, 0x5f, + 0x4b, 0x48, 0x52, 0x5f, 0x38, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, + 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0xf7, 0x00, 0x03, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x71, 0x00, 0x04, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0xf9, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00}; + +} // Anonymous namespace + +VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool, + const std::vector<vk::DescriptorSetLayoutBinding>& bindings, + const std::vector<vk::DescriptorUpdateTemplateEntry>& templates, + const std::vector<vk::PushConstantRange> push_constants, + std::size_t code_size, const u8* code) { + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + + const vk::DescriptorSetLayoutCreateInfo descriptor_layout_ci( + {}, static_cast<u32>(bindings.size()), bindings.data()); + descriptor_set_layout = dev.createDescriptorSetLayoutUnique(descriptor_layout_ci, nullptr, dld); + + const vk::PipelineLayoutCreateInfo pipeline_layout_ci({}, 1, &*descriptor_set_layout, + static_cast<u32>(push_constants.size()), + push_constants.data()); + layout = dev.createPipelineLayoutUnique(pipeline_layout_ci, nullptr, dld); + + if (!templates.empty()) { + const vk::DescriptorUpdateTemplateCreateInfo template_ci( + {}, static_cast<u32>(templates.size()), templates.data(), + vk::DescriptorUpdateTemplateType::eDescriptorSet, *descriptor_set_layout, + vk::PipelineBindPoint::eGraphics, *layout, 0); + descriptor_template = dev.createDescriptorUpdateTemplateUnique(template_ci, nullptr, dld); + + descriptor_allocator.emplace(descriptor_pool, *descriptor_set_layout); + } + + auto code_copy = std::make_unique<u32[]>(code_size / sizeof(u32) + 1); + std::memcpy(code_copy.get(), code, code_size); + const vk::ShaderModuleCreateInfo module_ci({}, code_size, code_copy.get()); + module = dev.createShaderModuleUnique(module_ci, nullptr, dld); + + const vk::PipelineShaderStageCreateInfo stage_ci({}, vk::ShaderStageFlagBits::eCompute, *module, + "main", nullptr); + + const vk::ComputePipelineCreateInfo pipeline_ci({}, stage_ci, *layout, nullptr, 0); + pipeline = dev.createComputePipelineUnique(nullptr, pipeline_ci, nullptr, dld); +} + +VKComputePass::~VKComputePass() = default; + +vk::DescriptorSet VKComputePass::CommitDescriptorSet( + VKUpdateDescriptorQueue& update_descriptor_queue, VKFence& fence) { + if (!descriptor_template) { + return {}; + } + const auto set = descriptor_allocator->Commit(fence); + update_descriptor_queue.Send(*descriptor_template, set); + return set; +} + +QuadArrayPass::QuadArrayPass(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKStagingBufferPool& staging_buffer_pool, + VKUpdateDescriptorQueue& update_descriptor_queue) + : VKComputePass(device, descriptor_pool, + {vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eStorageBuffer, 1, + vk::ShaderStageFlagBits::eCompute, nullptr)}, + {vk::DescriptorUpdateTemplateEntry(0, 0, 1, vk::DescriptorType::eStorageBuffer, + 0, sizeof(DescriptorUpdateEntry))}, + {vk::PushConstantRange(vk::ShaderStageFlagBits::eCompute, 0, sizeof(u32))}, + std::size(quad_array), quad_array), + scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool}, + update_descriptor_queue{update_descriptor_queue} {} + +QuadArrayPass::~QuadArrayPass() = default; + +std::pair<const vk::Buffer&, vk::DeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) { + const u32 num_triangle_vertices = num_vertices * 6 / 4; + const std::size_t staging_size = num_triangle_vertices * sizeof(u32); + auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); + + update_descriptor_queue.Acquire(); + update_descriptor_queue.AddBuffer(&*buffer.handle, 0, staging_size); + const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); + + scheduler.RequestOutsideRenderPassOperationContext(); + + ASSERT(num_vertices % 4 == 0); + const u32 num_quads = num_vertices / 4; + scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, num_quads, + first, set](auto cmdbuf, auto& dld) { + constexpr u32 dispatch_size = 1024; + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline, dld); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, layout, 0, {set}, {}, dld); + cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(first), &first, + dld); + cmdbuf.dispatch(Common::AlignUp(num_quads, dispatch_size) / dispatch_size, 1, 1, dld); + + const vk::BufferMemoryBarrier barrier( + vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eVertexAttributeRead, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, buffer, 0, + static_cast<vk::DeviceSize>(num_quads) * 6 * sizeof(u32)); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eVertexInput, {}, {}, {barrier}, {}, dld); + }); + return {*buffer.handle, 0}; +} + +Uint8Pass::Uint8Pass(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool, + VKUpdateDescriptorQueue& update_descriptor_queue) + : VKComputePass(device, descriptor_pool, + {vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eStorageBuffer, 1, + vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, + vk::ShaderStageFlagBits::eCompute, nullptr)}, + {vk::DescriptorUpdateTemplateEntry(0, 0, 2, vk::DescriptorType::eStorageBuffer, + 0, sizeof(DescriptorUpdateEntry))}, + {}, std::size(uint8_pass), uint8_pass), + scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool}, + update_descriptor_queue{update_descriptor_queue} {} + +Uint8Pass::~Uint8Pass() = default; + +std::pair<const vk::Buffer*, u64> Uint8Pass::Assemble(u32 num_vertices, vk::Buffer src_buffer, + u64 src_offset) { + const auto staging_size = static_cast<u32>(num_vertices * sizeof(u16)); + auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false); + + update_descriptor_queue.Acquire(); + update_descriptor_queue.AddBuffer(&src_buffer, src_offset, num_vertices); + update_descriptor_queue.AddBuffer(&*buffer.handle, 0, staging_size); + const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence()); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set, + num_vertices](auto cmdbuf, auto& dld) { + constexpr u32 dispatch_size = 1024; + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline, dld); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, layout, 0, {set}, {}, dld); + cmdbuf.dispatch(Common::AlignUp(num_vertices, dispatch_size) / dispatch_size, 1, 1, dld); + + const vk::BufferMemoryBarrier barrier( + vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eVertexAttributeRead, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, buffer, 0, + static_cast<vk::DeviceSize>(num_vertices) * sizeof(u16)); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eVertexInput, {}, {}, {barrier}, {}, dld); + }); + return {&*buffer.handle, 0}; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h new file mode 100644 index 000000000..7057eb837 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -0,0 +1,77 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <optional> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" + +namespace Vulkan { + +class VKDevice; +class VKFence; +class VKScheduler; +class VKStagingBufferPool; +class VKUpdateDescriptorQueue; + +class VKComputePass { +public: + explicit VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool, + const std::vector<vk::DescriptorSetLayoutBinding>& bindings, + const std::vector<vk::DescriptorUpdateTemplateEntry>& templates, + const std::vector<vk::PushConstantRange> push_constants, + std::size_t code_size, const u8* code); + ~VKComputePass(); + +protected: + vk::DescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue, + VKFence& fence); + + UniqueDescriptorUpdateTemplate descriptor_template; + UniquePipelineLayout layout; + UniquePipeline pipeline; + +private: + UniqueDescriptorSetLayout descriptor_set_layout; + std::optional<DescriptorAllocator> descriptor_allocator; + UniqueShaderModule module; +}; + +class QuadArrayPass final : public VKComputePass { +public: + explicit QuadArrayPass(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKStagingBufferPool& staging_buffer_pool, + VKUpdateDescriptorQueue& update_descriptor_queue); + ~QuadArrayPass(); + + std::pair<const vk::Buffer&, vk::DeviceSize> Assemble(u32 num_vertices, u32 first); + +private: + VKScheduler& scheduler; + VKStagingBufferPool& staging_buffer_pool; + VKUpdateDescriptorQueue& update_descriptor_queue; +}; + +class Uint8Pass final : public VKComputePass { +public: + explicit Uint8Pass(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool, + VKUpdateDescriptorQueue& update_descriptor_queue); + ~Uint8Pass(); + + std::pair<const vk::Buffer*, u64> Assemble(u32 num_vertices, vk::Buffer src_buffer, + u64 src_offset); + +private: + VKScheduler& scheduler; + VKStagingBufferPool& staging_buffer_pool; + VKUpdateDescriptorQueue& update_descriptor_queue; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp new file mode 100644 index 000000000..9d5b8de7a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -0,0 +1,112 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <vector> + +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_compute_pipeline.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_decompiler.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +VKComputePipeline::VKComputePipeline(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue, + const SPIRVShader& shader) + : device{device}, scheduler{scheduler}, entries{shader.entries}, + descriptor_set_layout{CreateDescriptorSetLayout()}, + descriptor_allocator{descriptor_pool, *descriptor_set_layout}, + update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, + descriptor_template{CreateDescriptorUpdateTemplate()}, + shader_module{CreateShaderModule(shader.code)}, pipeline{CreatePipeline()} {} + +VKComputePipeline::~VKComputePipeline() = default; + +vk::DescriptorSet VKComputePipeline::CommitDescriptorSet() { + if (!descriptor_template) { + return {}; + } + const auto set = descriptor_allocator.Commit(scheduler.GetFence()); + update_descriptor_queue.Send(*descriptor_template, set); + return set; +} + +UniqueDescriptorSetLayout VKComputePipeline::CreateDescriptorSetLayout() const { + std::vector<vk::DescriptorSetLayoutBinding> bindings; + u32 binding = 0; + const auto AddBindings = [&](vk::DescriptorType descriptor_type, std::size_t num_entries) { + // TODO(Rodrigo): Maybe make individual bindings here? + for (u32 bindpoint = 0; bindpoint < static_cast<u32>(num_entries); ++bindpoint) { + bindings.emplace_back(binding++, descriptor_type, 1, vk::ShaderStageFlagBits::eCompute, + nullptr); + } + }; + AddBindings(vk::DescriptorType::eUniformBuffer, entries.const_buffers.size()); + AddBindings(vk::DescriptorType::eStorageBuffer, entries.global_buffers.size()); + AddBindings(vk::DescriptorType::eUniformTexelBuffer, entries.texel_buffers.size()); + AddBindings(vk::DescriptorType::eCombinedImageSampler, entries.samplers.size()); + AddBindings(vk::DescriptorType::eStorageImage, entries.images.size()); + + const vk::DescriptorSetLayoutCreateInfo descriptor_set_layout_ci( + {}, static_cast<u32>(bindings.size()), bindings.data()); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createDescriptorSetLayoutUnique(descriptor_set_layout_ci, nullptr, dld); +} + +UniquePipelineLayout VKComputePipeline::CreatePipelineLayout() const { + const vk::PipelineLayoutCreateInfo layout_ci({}, 1, &*descriptor_set_layout, 0, nullptr); + const auto dev = device.GetLogical(); + return dev.createPipelineLayoutUnique(layout_ci, nullptr, device.GetDispatchLoader()); +} + +UniqueDescriptorUpdateTemplate VKComputePipeline::CreateDescriptorUpdateTemplate() const { + std::vector<vk::DescriptorUpdateTemplateEntry> template_entries; + u32 binding = 0; + u32 offset = 0; + FillDescriptorUpdateTemplateEntries(device, entries, binding, offset, template_entries); + if (template_entries.empty()) { + // If the shader doesn't use descriptor sets, skip template creation. + return UniqueDescriptorUpdateTemplate{}; + } + + const vk::DescriptorUpdateTemplateCreateInfo template_ci( + {}, static_cast<u32>(template_entries.size()), template_entries.data(), + vk::DescriptorUpdateTemplateType::eDescriptorSet, *descriptor_set_layout, + vk::PipelineBindPoint::eGraphics, *layout, DESCRIPTOR_SET); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createDescriptorUpdateTemplateUnique(template_ci, nullptr, dld); +} + +UniqueShaderModule VKComputePipeline::CreateShaderModule(const std::vector<u32>& code) const { + const vk::ShaderModuleCreateInfo module_ci({}, code.size() * sizeof(u32), code.data()); + const auto dev = device.GetLogical(); + return dev.createShaderModuleUnique(module_ci, nullptr, device.GetDispatchLoader()); +} + +UniquePipeline VKComputePipeline::CreatePipeline() const { + vk::PipelineShaderStageCreateInfo shader_stage_ci({}, vk::ShaderStageFlagBits::eCompute, + *shader_module, "main", nullptr); + vk::PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci; + subgroup_size_ci.requiredSubgroupSize = GuestWarpSize; + if (entries.uses_warps && device.IsGuestWarpSizeSupported(vk::ShaderStageFlagBits::eCompute)) { + shader_stage_ci.pNext = &subgroup_size_ci; + } + + const vk::ComputePipelineCreateInfo create_info({}, shader_stage_ci, *layout, {}, 0); + const auto dev = device.GetLogical(); + return dev.createComputePipelineUnique({}, create_info, nullptr, device.GetDispatchLoader()); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h new file mode 100644 index 000000000..22235c6c9 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -0,0 +1,66 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_shader_decompiler.h" + +namespace Vulkan { + +class VKDevice; +class VKScheduler; +class VKUpdateDescriptorQueue; + +class VKComputePipeline final { +public: + explicit VKComputePipeline(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue, + const SPIRVShader& shader); + ~VKComputePipeline(); + + vk::DescriptorSet CommitDescriptorSet(); + + vk::Pipeline GetHandle() const { + return *pipeline; + } + + vk::PipelineLayout GetLayout() const { + return *layout; + } + + const ShaderEntries& GetEntries() { + return entries; + } + +private: + UniqueDescriptorSetLayout CreateDescriptorSetLayout() const; + + UniquePipelineLayout CreatePipelineLayout() const; + + UniqueDescriptorUpdateTemplate CreateDescriptorUpdateTemplate() const; + + UniqueShaderModule CreateShaderModule(const std::vector<u32>& code) const; + + UniquePipeline CreatePipeline() const; + + const VKDevice& device; + VKScheduler& scheduler; + ShaderEntries entries; + + UniqueDescriptorSetLayout descriptor_set_layout; + DescriptorAllocator descriptor_allocator; + VKUpdateDescriptorQueue& update_descriptor_queue; + UniquePipelineLayout layout; + UniqueDescriptorUpdateTemplate descriptor_template; + UniqueShaderModule shader_module; + UniquePipeline pipeline; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp new file mode 100644 index 000000000..cc7c281a0 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -0,0 +1,89 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <vector> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" + +namespace Vulkan { + +// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines. +constexpr std::size_t SETS_GROW_RATE = 0x20; + +DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool, + vk::DescriptorSetLayout layout) + : VKFencedPool{SETS_GROW_RATE}, descriptor_pool{descriptor_pool}, layout{layout} {} + +DescriptorAllocator::~DescriptorAllocator() = default; + +vk::DescriptorSet DescriptorAllocator::Commit(VKFence& fence) { + return *descriptors[CommitResource(fence)]; +} + +void DescriptorAllocator::Allocate(std::size_t begin, std::size_t end) { + auto new_sets = descriptor_pool.AllocateDescriptors(layout, end - begin); + descriptors.insert(descriptors.end(), std::make_move_iterator(new_sets.begin()), + std::make_move_iterator(new_sets.end())); +} + +VKDescriptorPool::VKDescriptorPool(const VKDevice& device) + : device{device}, active_pool{AllocateNewPool()} {} + +VKDescriptorPool::~VKDescriptorPool() = default; + +vk::DescriptorPool VKDescriptorPool::AllocateNewPool() { + static constexpr u32 num_sets = 0x20000; + static constexpr vk::DescriptorPoolSize pool_sizes[] = { + {vk::DescriptorType::eUniformBuffer, num_sets * 90}, + {vk::DescriptorType::eStorageBuffer, num_sets * 60}, + {vk::DescriptorType::eUniformTexelBuffer, num_sets * 64}, + {vk::DescriptorType::eCombinedImageSampler, num_sets * 64}, + {vk::DescriptorType::eStorageImage, num_sets * 40}}; + + const vk::DescriptorPoolCreateInfo create_info( + vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, num_sets, + static_cast<u32>(std::size(pool_sizes)), std::data(pool_sizes)); + const auto dev = device.GetLogical(); + return *pools.emplace_back( + dev.createDescriptorPoolUnique(create_info, nullptr, device.GetDispatchLoader())); +} + +std::vector<UniqueDescriptorSet> VKDescriptorPool::AllocateDescriptors( + vk::DescriptorSetLayout layout, std::size_t count) { + std::vector layout_copies(count, layout); + vk::DescriptorSetAllocateInfo allocate_info(active_pool, static_cast<u32>(count), + layout_copies.data()); + + std::vector<vk::DescriptorSet> sets(count); + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + switch (const auto result = dev.allocateDescriptorSets(&allocate_info, sets.data(), dld)) { + case vk::Result::eSuccess: + break; + case vk::Result::eErrorOutOfPoolMemory: + active_pool = AllocateNewPool(); + allocate_info.descriptorPool = active_pool; + if (dev.allocateDescriptorSets(&allocate_info, sets.data(), dld) == vk::Result::eSuccess) { + break; + } + [[fallthrough]]; + default: + vk::throwResultException(result, "vk::Device::allocateDescriptorSetsUnique"); + } + + vk::PoolFree deleter(dev, active_pool, dld); + std::vector<UniqueDescriptorSet> unique_sets; + unique_sets.reserve(count); + for (const auto set : sets) { + unique_sets.push_back(UniqueDescriptorSet{set, deleter}); + } + return unique_sets; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h new file mode 100644 index 000000000..a441dbc0f --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h @@ -0,0 +1,56 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" + +namespace Vulkan { + +class VKDescriptorPool; + +class DescriptorAllocator final : public VKFencedPool { +public: + explicit DescriptorAllocator(VKDescriptorPool& descriptor_pool, vk::DescriptorSetLayout layout); + ~DescriptorAllocator() override; + + DescriptorAllocator(const DescriptorAllocator&) = delete; + + vk::DescriptorSet Commit(VKFence& fence); + +protected: + void Allocate(std::size_t begin, std::size_t end) override; + +private: + VKDescriptorPool& descriptor_pool; + const vk::DescriptorSetLayout layout; + + std::vector<UniqueDescriptorSet> descriptors; +}; + +class VKDescriptorPool final { + friend DescriptorAllocator; + +public: + explicit VKDescriptorPool(const VKDevice& device); + ~VKDescriptorPool(); + +private: + vk::DescriptorPool AllocateNewPool(); + + std::vector<UniqueDescriptorSet> AllocateDescriptors(vk::DescriptorSetLayout layout, + std::size_t count); + + const VKDevice& device; + + std::vector<UniqueDescriptorPool> pools; + vk::DescriptorPool active_pool; +}; + +} // namespace Vulkan
\ No newline at end of file diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp new file mode 100644 index 000000000..b155dfb49 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -0,0 +1,270 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> +#include "common/assert.h" +#include "common/common_types.h" +#include "common/microprofile.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +MICROPROFILE_DECLARE(Vulkan_PipelineCache); + +namespace { + +vk::StencilOpState GetStencilFaceState(const FixedPipelineState::StencilFace& face) { + return vk::StencilOpState(MaxwellToVK::StencilOp(face.action_stencil_fail), + MaxwellToVK::StencilOp(face.action_depth_pass), + MaxwellToVK::StencilOp(face.action_depth_fail), + MaxwellToVK::ComparisonOp(face.test_func), 0, 0, 0); +} + +bool SupportsPrimitiveRestart(vk::PrimitiveTopology topology) { + static constexpr std::array unsupported_topologies = { + vk::PrimitiveTopology::ePointList, + vk::PrimitiveTopology::eLineList, + vk::PrimitiveTopology::eTriangleList, + vk::PrimitiveTopology::eLineListWithAdjacency, + vk::PrimitiveTopology::eTriangleListWithAdjacency, + vk::PrimitiveTopology::ePatchList}; + return std::find(std::begin(unsupported_topologies), std::end(unsupported_topologies), + topology) == std::end(unsupported_topologies); +} + +} // Anonymous namespace + +VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue, + VKRenderPassCache& renderpass_cache, + const GraphicsPipelineCacheKey& key, + const std::vector<vk::DescriptorSetLayoutBinding>& bindings, + const SPIRVProgram& program) + : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()}, + descriptor_set_layout{CreateDescriptorSetLayout(bindings)}, + descriptor_allocator{descriptor_pool, *descriptor_set_layout}, + update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, + descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules( + program)}, + renderpass{renderpass_cache.GetRenderPass(key.renderpass_params)}, pipeline{CreatePipeline( + key.renderpass_params, + program)} {} + +VKGraphicsPipeline::~VKGraphicsPipeline() = default; + +vk::DescriptorSet VKGraphicsPipeline::CommitDescriptorSet() { + if (!descriptor_template) { + return {}; + } + const auto set = descriptor_allocator.Commit(scheduler.GetFence()); + update_descriptor_queue.Send(*descriptor_template, set); + return set; +} + +UniqueDescriptorSetLayout VKGraphicsPipeline::CreateDescriptorSetLayout( + const std::vector<vk::DescriptorSetLayoutBinding>& bindings) const { + const vk::DescriptorSetLayoutCreateInfo descriptor_set_layout_ci( + {}, static_cast<u32>(bindings.size()), bindings.data()); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createDescriptorSetLayoutUnique(descriptor_set_layout_ci, nullptr, dld); +} + +UniquePipelineLayout VKGraphicsPipeline::CreatePipelineLayout() const { + const vk::PipelineLayoutCreateInfo pipeline_layout_ci({}, 1, &*descriptor_set_layout, 0, + nullptr); + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createPipelineLayoutUnique(pipeline_layout_ci, nullptr, dld); +} + +UniqueDescriptorUpdateTemplate VKGraphicsPipeline::CreateDescriptorUpdateTemplate( + const SPIRVProgram& program) const { + std::vector<vk::DescriptorUpdateTemplateEntry> template_entries; + u32 binding = 0; + u32 offset = 0; + for (const auto& stage : program) { + if (stage) { + FillDescriptorUpdateTemplateEntries(device, stage->entries, binding, offset, + template_entries); + } + } + if (template_entries.empty()) { + // If the shader doesn't use descriptor sets, skip template creation. + return UniqueDescriptorUpdateTemplate{}; + } + + const vk::DescriptorUpdateTemplateCreateInfo template_ci( + {}, static_cast<u32>(template_entries.size()), template_entries.data(), + vk::DescriptorUpdateTemplateType::eDescriptorSet, *descriptor_set_layout, + vk::PipelineBindPoint::eGraphics, *layout, DESCRIPTOR_SET); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createDescriptorUpdateTemplateUnique(template_ci, nullptr, dld); +} + +std::vector<UniqueShaderModule> VKGraphicsPipeline::CreateShaderModules( + const SPIRVProgram& program) const { + std::vector<UniqueShaderModule> modules; + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + for (std::size_t i = 0; i < Maxwell::MaxShaderStage; ++i) { + const auto& stage = program[i]; + if (!stage) { + continue; + } + const vk::ShaderModuleCreateInfo module_ci({}, stage->code.size() * sizeof(u32), + stage->code.data()); + modules.emplace_back(dev.createShaderModuleUnique(module_ci, nullptr, dld)); + } + return modules; +} + +UniquePipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, + const SPIRVProgram& program) const { + const auto& vi = fixed_state.vertex_input; + const auto& ia = fixed_state.input_assembly; + const auto& ds = fixed_state.depth_stencil; + const auto& cd = fixed_state.color_blending; + const auto& ts = fixed_state.tessellation; + const auto& rs = fixed_state.rasterizer; + + std::vector<vk::VertexInputBindingDescription> vertex_bindings; + std::vector<vk::VertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; + for (std::size_t i = 0; i < vi.num_bindings; ++i) { + const auto& binding = vi.bindings[i]; + const bool instanced = binding.divisor != 0; + const auto rate = instanced ? vk::VertexInputRate::eInstance : vk::VertexInputRate::eVertex; + vertex_bindings.emplace_back(binding.index, binding.stride, rate); + if (instanced) { + vertex_binding_divisors.emplace_back(binding.index, binding.divisor); + } + } + + std::vector<vk::VertexInputAttributeDescription> vertex_attributes; + const auto& input_attributes = program[0]->entries.attributes; + for (std::size_t i = 0; i < vi.num_attributes; ++i) { + const auto& attribute = vi.attributes[i]; + if (input_attributes.find(attribute.index) == input_attributes.end()) { + // Skip attributes not used by the vertex shaders. + continue; + } + vertex_attributes.emplace_back(attribute.index, attribute.buffer, + MaxwellToVK::VertexFormat(attribute.type, attribute.size), + attribute.offset); + } + + vk::PipelineVertexInputStateCreateInfo vertex_input_ci( + {}, static_cast<u32>(vertex_bindings.size()), vertex_bindings.data(), + static_cast<u32>(vertex_attributes.size()), vertex_attributes.data()); + + const vk::PipelineVertexInputDivisorStateCreateInfoEXT vertex_input_divisor_ci( + static_cast<u32>(vertex_binding_divisors.size()), vertex_binding_divisors.data()); + if (!vertex_binding_divisors.empty()) { + vertex_input_ci.pNext = &vertex_input_divisor_ci; + } + + const auto primitive_topology = MaxwellToVK::PrimitiveTopology(device, ia.topology); + const vk::PipelineInputAssemblyStateCreateInfo input_assembly_ci( + {}, primitive_topology, + ia.primitive_restart_enable && SupportsPrimitiveRestart(primitive_topology)); + + const vk::PipelineTessellationStateCreateInfo tessellation_ci({}, ts.patch_control_points); + + const vk::PipelineViewportStateCreateInfo viewport_ci({}, Maxwell::NumViewports, nullptr, + Maxwell::NumViewports, nullptr); + + // TODO(Rodrigo): Find out what's the default register value for front face + const vk::PipelineRasterizationStateCreateInfo rasterizer_ci( + {}, rs.depth_clamp_enable, false, vk::PolygonMode::eFill, + rs.cull_enable ? MaxwellToVK::CullFace(rs.cull_face) : vk::CullModeFlagBits::eNone, + MaxwellToVK::FrontFace(rs.front_face), rs.depth_bias_enable, 0.0f, 0.0f, 0.0f, 1.0f); + + const vk::PipelineMultisampleStateCreateInfo multisampling_ci( + {}, vk::SampleCountFlagBits::e1, false, 0.0f, nullptr, false, false); + + const vk::CompareOp depth_test_compare = ds.depth_test_enable + ? MaxwellToVK::ComparisonOp(ds.depth_test_function) + : vk::CompareOp::eAlways; + + const vk::PipelineDepthStencilStateCreateInfo depth_stencil_ci( + {}, ds.depth_test_enable, ds.depth_write_enable, depth_test_compare, ds.depth_bounds_enable, + ds.stencil_enable, GetStencilFaceState(ds.front_stencil), + GetStencilFaceState(ds.back_stencil), 0.0f, 0.0f); + + std::array<vk::PipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments; + const std::size_t num_attachments = + std::min(cd.attachments_count, renderpass_params.color_attachments.size()); + for (std::size_t i = 0; i < num_attachments; ++i) { + constexpr std::array component_table{ + vk::ColorComponentFlagBits::eR, vk::ColorComponentFlagBits::eG, + vk::ColorComponentFlagBits::eB, vk::ColorComponentFlagBits::eA}; + const auto& blend = cd.attachments[i]; + + vk::ColorComponentFlags color_components{}; + for (std::size_t j = 0; j < component_table.size(); ++j) { + if (blend.components[j]) + color_components |= component_table[j]; + } + + cb_attachments[i] = vk::PipelineColorBlendAttachmentState( + blend.enable, MaxwellToVK::BlendFactor(blend.src_rgb_func), + MaxwellToVK::BlendFactor(blend.dst_rgb_func), + MaxwellToVK::BlendEquation(blend.rgb_equation), + MaxwellToVK::BlendFactor(blend.src_a_func), MaxwellToVK::BlendFactor(blend.dst_a_func), + MaxwellToVK::BlendEquation(blend.a_equation), color_components); + } + const vk::PipelineColorBlendStateCreateInfo color_blending_ci({}, false, vk::LogicOp::eCopy, + static_cast<u32>(num_attachments), + cb_attachments.data(), {}); + + constexpr std::array dynamic_states = { + vk::DynamicState::eViewport, vk::DynamicState::eScissor, + vk::DynamicState::eDepthBias, vk::DynamicState::eBlendConstants, + vk::DynamicState::eDepthBounds, vk::DynamicState::eStencilCompareMask, + vk::DynamicState::eStencilWriteMask, vk::DynamicState::eStencilReference}; + const vk::PipelineDynamicStateCreateInfo dynamic_state_ci( + {}, static_cast<u32>(dynamic_states.size()), dynamic_states.data()); + + vk::PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci; + subgroup_size_ci.requiredSubgroupSize = GuestWarpSize; + + std::vector<vk::PipelineShaderStageCreateInfo> shader_stages; + std::size_t module_index = 0; + for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { + if (!program[stage]) { + continue; + } + const auto stage_enum = static_cast<Tegra::Engines::ShaderType>(stage); + const auto vk_stage = MaxwellToVK::ShaderStage(stage_enum); + auto& stage_ci = shader_stages.emplace_back(vk::PipelineShaderStageCreateFlags{}, vk_stage, + *modules[module_index++], "main", nullptr); + if (program[stage]->entries.uses_warps && device.IsGuestWarpSizeSupported(vk_stage)) { + stage_ci.pNext = &subgroup_size_ci; + } + } + + const vk::GraphicsPipelineCreateInfo create_info( + {}, static_cast<u32>(shader_stages.size()), shader_stages.data(), &vertex_input_ci, + &input_assembly_ci, &tessellation_ci, &viewport_ci, &rasterizer_ci, &multisampling_ci, + &depth_stencil_ci, &color_blending_ci, &dynamic_state_ci, *layout, renderpass, 0, {}, 0); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createGraphicsPipelineUnique(nullptr, create_info, nullptr, dld); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h new file mode 100644 index 000000000..4f5e4ea2d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -0,0 +1,90 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <optional> +#include <unordered_map> +#include <vector> + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_shader_decompiler.h" + +namespace Vulkan { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +struct GraphicsPipelineCacheKey; + +class VKDescriptorPool; +class VKDevice; +class VKRenderPassCache; +class VKScheduler; +class VKUpdateDescriptorQueue; + +using SPIRVProgram = std::array<std::optional<SPIRVShader>, Maxwell::MaxShaderStage>; + +class VKGraphicsPipeline final { +public: + explicit VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue, + VKRenderPassCache& renderpass_cache, + const GraphicsPipelineCacheKey& key, + const std::vector<vk::DescriptorSetLayoutBinding>& bindings, + const SPIRVProgram& program); + ~VKGraphicsPipeline(); + + vk::DescriptorSet CommitDescriptorSet(); + + vk::Pipeline GetHandle() const { + return *pipeline; + } + + vk::PipelineLayout GetLayout() const { + return *layout; + } + + vk::RenderPass GetRenderPass() const { + return renderpass; + } + +private: + UniqueDescriptorSetLayout CreateDescriptorSetLayout( + const std::vector<vk::DescriptorSetLayoutBinding>& bindings) const; + + UniquePipelineLayout CreatePipelineLayout() const; + + UniqueDescriptorUpdateTemplate CreateDescriptorUpdateTemplate( + const SPIRVProgram& program) const; + + std::vector<UniqueShaderModule> CreateShaderModules(const SPIRVProgram& program) const; + + UniquePipeline CreatePipeline(const RenderPassParams& renderpass_params, + const SPIRVProgram& program) const; + + const VKDevice& device; + VKScheduler& scheduler; + const FixedPipelineState fixed_state; + const u64 hash; + + UniqueDescriptorSetLayout descriptor_set_layout; + DescriptorAllocator descriptor_allocator; + VKUpdateDescriptorQueue& update_descriptor_queue; + UniquePipelineLayout layout; + UniqueDescriptorUpdateTemplate descriptor_template; + std::vector<UniqueShaderModule> modules; + + vk::RenderPass renderpass; + UniquePipeline pipeline; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.cpp b/src/video_core/renderer_vulkan/vk_memory_manager.cpp index 0451babbf..9cc9979d0 100644 --- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_memory_manager.cpp @@ -6,6 +6,7 @@ #include <optional> #include <tuple> #include <vector> + #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" @@ -16,34 +17,32 @@ namespace Vulkan { -// TODO(Rodrigo): Fine tune this number -constexpr u64 ALLOC_CHUNK_SIZE = 64 * 1024 * 1024; +namespace { + +u64 GetAllocationChunkSize(u64 required_size) { + static constexpr u64 sizes[] = {16ULL << 20, 32ULL << 20, 64ULL << 20, 128ULL << 20}; + auto it = std::lower_bound(std::begin(sizes), std::end(sizes), required_size); + return it != std::end(sizes) ? *it : Common::AlignUp(required_size, 256ULL << 20); +} + +} // Anonymous namespace class VKMemoryAllocation final { public: explicit VKMemoryAllocation(const VKDevice& device, vk::DeviceMemory memory, - vk::MemoryPropertyFlags properties, u64 alloc_size, u32 type) - : device{device}, memory{memory}, properties{properties}, alloc_size{alloc_size}, - shifted_type{ShiftType(type)}, is_mappable{properties & - vk::MemoryPropertyFlagBits::eHostVisible} { - if (is_mappable) { - const auto dev = device.GetLogical(); - const auto& dld = device.GetDispatchLoader(); - base_address = static_cast<u8*>(dev.mapMemory(memory, 0, alloc_size, {}, dld)); - } - } + vk::MemoryPropertyFlags properties, u64 allocation_size, u32 type) + : device{device}, memory{memory}, properties{properties}, allocation_size{allocation_size}, + shifted_type{ShiftType(type)} {} ~VKMemoryAllocation() { const auto dev = device.GetLogical(); const auto& dld = device.GetDispatchLoader(); - if (is_mappable) - dev.unmapMemory(memory, dld); dev.free(memory, nullptr, dld); } VKMemoryCommit Commit(vk::DeviceSize commit_size, vk::DeviceSize alignment) { - auto found = TryFindFreeSection(free_iterator, alloc_size, static_cast<u64>(commit_size), - static_cast<u64>(alignment)); + auto found = TryFindFreeSection(free_iterator, allocation_size, + static_cast<u64>(commit_size), static_cast<u64>(alignment)); if (!found) { found = TryFindFreeSection(0, free_iterator, static_cast<u64>(commit_size), static_cast<u64>(alignment)); @@ -52,8 +51,7 @@ public: return nullptr; } } - u8* address = is_mappable ? base_address + *found : nullptr; - auto commit = std::make_unique<VKMemoryCommitImpl>(this, memory, address, *found, + auto commit = std::make_unique<VKMemoryCommitImpl>(device, this, memory, *found, *found + commit_size); commits.push_back(commit.get()); @@ -65,12 +63,10 @@ public: void Free(const VKMemoryCommitImpl* commit) { ASSERT(commit); - const auto it = - std::find_if(commits.begin(), commits.end(), - [&](const auto& stored_commit) { return stored_commit == commit; }); + + const auto it = std::find(std::begin(commits), std::end(commits), commit); if (it == commits.end()) { - LOG_CRITICAL(Render_Vulkan, "Freeing unallocated commit!"); - UNREACHABLE(); + UNREACHABLE_MSG("Freeing unallocated commit!"); return; } commits.erase(it); @@ -88,11 +84,11 @@ private: } /// A memory allocator, it may return a free region between "start" and "end" with the solicited - /// requeriments. + /// requirements. std::optional<u64> TryFindFreeSection(u64 start, u64 end, u64 size, u64 alignment) const { - u64 iterator = start; - while (iterator + size < end) { - const u64 try_left = Common::AlignUp(iterator, alignment); + u64 iterator = Common::AlignUp(start, alignment); + while (iterator + size <= end) { + const u64 try_left = iterator; const u64 try_right = try_left + size; bool overlap = false; @@ -100,7 +96,7 @@ private: const auto [commit_left, commit_right] = commit->interval; if (try_left < commit_right && commit_left < try_right) { // There's an overlap, continue the search where the overlapping commit ends. - iterator = commit_right; + iterator = Common::AlignUp(commit_right, alignment); overlap = true; break; } @@ -110,6 +106,7 @@ private: return try_left; } } + // No free regions where found, return an empty optional. return std::nullopt; } @@ -117,12 +114,8 @@ private: const VKDevice& device; ///< Vulkan device. const vk::DeviceMemory memory; ///< Vulkan memory allocation handler. const vk::MemoryPropertyFlags properties; ///< Vulkan properties. - const u64 alloc_size; ///< Size of this allocation. + const u64 allocation_size; ///< Size of this allocation. const u32 shifted_type; ///< Stored Vulkan type of this allocation, shifted. - const bool is_mappable; ///< Whether the allocation is mappable. - - /// Base address of the mapped pointer. - u8* base_address{}; /// Hints where the next free region is likely going to be. u64 free_iterator{}; @@ -132,13 +125,15 @@ private: }; VKMemoryManager::VKMemoryManager(const VKDevice& device) - : device{device}, props{device.GetPhysical().getMemoryProperties(device.GetDispatchLoader())}, - is_memory_unified{GetMemoryUnified(props)} {} + : device{device}, properties{device.GetPhysical().getMemoryProperties( + device.GetDispatchLoader())}, + is_memory_unified{GetMemoryUnified(properties)} {} VKMemoryManager::~VKMemoryManager() = default; -VKMemoryCommit VKMemoryManager::Commit(const vk::MemoryRequirements& reqs, bool host_visible) { - ASSERT(reqs.size < ALLOC_CHUNK_SIZE); +VKMemoryCommit VKMemoryManager::Commit(const vk::MemoryRequirements& requirements, + bool host_visible) { + const u64 chunk_size = GetAllocationChunkSize(requirements.size); // When a host visible commit is asked, search for host visible and coherent, otherwise search // for a fast device local type. @@ -147,32 +142,21 @@ VKMemoryCommit VKMemoryManager::Commit(const vk::MemoryRequirements& reqs, bool ? vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent : vk::MemoryPropertyFlagBits::eDeviceLocal; - const auto TryCommit = [&]() -> VKMemoryCommit { - for (auto& alloc : allocs) { - if (!alloc->IsCompatible(wanted_properties, reqs.memoryTypeBits)) - continue; - - if (auto commit = alloc->Commit(reqs.size, reqs.alignment); commit) { - return commit; - } - } - return {}; - }; - - if (auto commit = TryCommit(); commit) { + if (auto commit = TryAllocCommit(requirements, wanted_properties)) { return commit; } // Commit has failed, allocate more memory. - if (!AllocMemory(wanted_properties, reqs.memoryTypeBits, ALLOC_CHUNK_SIZE)) { - // TODO(Rodrigo): Try to use host memory. - LOG_CRITICAL(Render_Vulkan, "Ran out of memory!"); - UNREACHABLE(); + if (!AllocMemory(wanted_properties, requirements.memoryTypeBits, chunk_size)) { + // TODO(Rodrigo): Handle these situations in some way like flushing to guest memory. + // Allocation has failed, panic. + UNREACHABLE_MSG("Ran out of VRAM!"); + return {}; } // Commit again, this time it won't fail since there's a fresh allocation above. If it does, // there's a bug. - auto commit = TryCommit(); + auto commit = TryAllocCommit(requirements, wanted_properties); ASSERT(commit); return commit; } @@ -180,8 +164,7 @@ VKMemoryCommit VKMemoryManager::Commit(const vk::MemoryRequirements& reqs, bool VKMemoryCommit VKMemoryManager::Commit(vk::Buffer buffer, bool host_visible) { const auto dev = device.GetLogical(); const auto& dld = device.GetDispatchLoader(); - const auto requeriments = dev.getBufferMemoryRequirements(buffer, dld); - auto commit = Commit(requeriments, host_visible); + auto commit = Commit(dev.getBufferMemoryRequirements(buffer, dld), host_visible); dev.bindBufferMemory(buffer, commit->GetMemory(), commit->GetOffset(), dld); return commit; } @@ -189,25 +172,23 @@ VKMemoryCommit VKMemoryManager::Commit(vk::Buffer buffer, bool host_visible) { VKMemoryCommit VKMemoryManager::Commit(vk::Image image, bool host_visible) { const auto dev = device.GetLogical(); const auto& dld = device.GetDispatchLoader(); - const auto requeriments = dev.getImageMemoryRequirements(image, dld); - auto commit = Commit(requeriments, host_visible); + auto commit = Commit(dev.getImageMemoryRequirements(image, dld), host_visible); dev.bindImageMemory(image, commit->GetMemory(), commit->GetOffset(), dld); return commit; } bool VKMemoryManager::AllocMemory(vk::MemoryPropertyFlags wanted_properties, u32 type_mask, u64 size) { - const u32 type = [&]() { - for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) { - const auto flags = props.memoryTypes[type_index].propertyFlags; + const u32 type = [&] { + for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) { + const auto flags = properties.memoryTypes[type_index].propertyFlags; if ((type_mask & (1U << type_index)) && (flags & wanted_properties)) { // The type matches in type and in the wanted properties. return type_index; } } - LOG_CRITICAL(Render_Vulkan, "Couldn't find a compatible memory type!"); - UNREACHABLE(); - return 0u; + UNREACHABLE_MSG("Couldn't find a compatible memory type!"); + return 0U; }(); const auto dev = device.GetLogical(); @@ -216,19 +197,33 @@ bool VKMemoryManager::AllocMemory(vk::MemoryPropertyFlags wanted_properties, u32 // Try to allocate found type. const vk::MemoryAllocateInfo memory_ai(size, type); vk::DeviceMemory memory; - if (const vk::Result res = dev.allocateMemory(&memory_ai, nullptr, &memory, dld); + if (const auto res = dev.allocateMemory(&memory_ai, nullptr, &memory, dld); res != vk::Result::eSuccess) { LOG_CRITICAL(Render_Vulkan, "Device allocation failed with code {}!", vk::to_string(res)); return false; } - allocs.push_back( + allocations.push_back( std::make_unique<VKMemoryAllocation>(device, memory, wanted_properties, size, type)); return true; } -/*static*/ bool VKMemoryManager::GetMemoryUnified(const vk::PhysicalDeviceMemoryProperties& props) { - for (u32 heap_index = 0; heap_index < props.memoryHeapCount; ++heap_index) { - if (!(props.memoryHeaps[heap_index].flags & vk::MemoryHeapFlagBits::eDeviceLocal)) { +VKMemoryCommit VKMemoryManager::TryAllocCommit(const vk::MemoryRequirements& requirements, + vk::MemoryPropertyFlags wanted_properties) { + for (auto& allocation : allocations) { + if (!allocation->IsCompatible(wanted_properties, requirements.memoryTypeBits)) { + continue; + } + if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) { + return commit; + } + } + return {}; +} + +/*static*/ bool VKMemoryManager::GetMemoryUnified( + const vk::PhysicalDeviceMemoryProperties& properties) { + for (u32 heap_index = 0; heap_index < properties.memoryHeapCount; ++heap_index) { + if (!(properties.memoryHeaps[heap_index].flags & vk::MemoryHeapFlagBits::eDeviceLocal)) { // Memory is considered unified when heaps are device local only. return false; } @@ -236,17 +231,28 @@ bool VKMemoryManager::AllocMemory(vk::MemoryPropertyFlags wanted_properties, u32 return true; } -VKMemoryCommitImpl::VKMemoryCommitImpl(VKMemoryAllocation* allocation, vk::DeviceMemory memory, - u8* data, u64 begin, u64 end) - : interval(std::make_pair(begin, end)), memory{memory}, allocation{allocation}, data{data} {} +VKMemoryCommitImpl::VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation, + vk::DeviceMemory memory, u64 begin, u64 end) + : device{device}, interval{begin, end}, memory{memory}, allocation{allocation} {} VKMemoryCommitImpl::~VKMemoryCommitImpl() { allocation->Free(this); } -u8* VKMemoryCommitImpl::GetData() const { - ASSERT_MSG(data != nullptr, "Trying to access an unmapped commit."); - return data; +MemoryMap VKMemoryCommitImpl::Map(u64 size, u64 offset_) const { + const auto dev = device.GetLogical(); + const auto address = reinterpret_cast<u8*>( + dev.mapMemory(memory, interval.first + offset_, size, {}, device.GetDispatchLoader())); + return MemoryMap{this, address}; +} + +void VKMemoryCommitImpl::Unmap() const { + const auto dev = device.GetLogical(); + dev.unmapMemory(memory, device.GetDispatchLoader()); +} + +MemoryMap VKMemoryCommitImpl::Map() const { + return Map(interval.second - interval.first); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.h b/src/video_core/renderer_vulkan/vk_memory_manager.h index 073597b35..cd00bb91b 100644 --- a/src/video_core/renderer_vulkan/vk_memory_manager.h +++ b/src/video_core/renderer_vulkan/vk_memory_manager.h @@ -12,6 +12,7 @@ namespace Vulkan { +class MemoryMap; class VKDevice; class VKMemoryAllocation; class VKMemoryCommitImpl; @@ -21,13 +22,14 @@ using VKMemoryCommit = std::unique_ptr<VKMemoryCommitImpl>; class VKMemoryManager final { public: explicit VKMemoryManager(const VKDevice& device); + VKMemoryManager(const VKMemoryManager&) = delete; ~VKMemoryManager(); /** * Commits a memory with the specified requeriments. - * @param reqs Requeriments returned from a Vulkan call. + * @param requirements Requirements returned from a Vulkan call. * @param host_visible Signals the allocator that it *must* use host visible and coherent - * memory. When passing false, it will try to allocate device local memory. + * memory. When passing false, it will try to allocate device local memory. * @returns A memory commit. */ VKMemoryCommit Commit(const vk::MemoryRequirements& reqs, bool host_visible); @@ -47,25 +49,35 @@ private: /// Allocates a chunk of memory. bool AllocMemory(vk::MemoryPropertyFlags wanted_properties, u32 type_mask, u64 size); + /// Tries to allocate a memory commit. + VKMemoryCommit TryAllocCommit(const vk::MemoryRequirements& requirements, + vk::MemoryPropertyFlags wanted_properties); + /// Returns true if the device uses an unified memory model. - static bool GetMemoryUnified(const vk::PhysicalDeviceMemoryProperties& props); + static bool GetMemoryUnified(const vk::PhysicalDeviceMemoryProperties& properties); - const VKDevice& device; ///< Device handler. - const vk::PhysicalDeviceMemoryProperties props; ///< Physical device properties. - const bool is_memory_unified; ///< True if memory model is unified. - std::vector<std::unique_ptr<VKMemoryAllocation>> allocs; ///< Current allocations. + const VKDevice& device; ///< Device handler. + const vk::PhysicalDeviceMemoryProperties properties; ///< Physical device properties. + const bool is_memory_unified; ///< True if memory model is unified. + std::vector<std::unique_ptr<VKMemoryAllocation>> allocations; ///< Current allocations. }; class VKMemoryCommitImpl final { friend VKMemoryAllocation; + friend MemoryMap; public: - explicit VKMemoryCommitImpl(VKMemoryAllocation* allocation, vk::DeviceMemory memory, u8* data, - u64 begin, u64 end); + explicit VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation, + vk::DeviceMemory memory, u64 begin, u64 end); ~VKMemoryCommitImpl(); - /// Returns the writeable memory map. The commit has to be mappable. - u8* GetData() const; + /// Maps a memory region and returns a pointer to it. + /// It's illegal to have more than one memory map at the same time. + MemoryMap Map(u64 size, u64 offset = 0) const; + + /// Maps the whole commit and returns a pointer to it. + /// It's illegal to have more than one memory map at the same time. + MemoryMap Map() const; /// Returns the Vulkan memory handler. vk::DeviceMemory GetMemory() const { @@ -78,10 +90,46 @@ public: } private: + /// Unmaps memory. + void Unmap() const; + + const VKDevice& device; ///< Vulkan device. std::pair<u64, u64> interval{}; ///< Interval where the commit exists. vk::DeviceMemory memory; ///< Vulkan device memory handler. VKMemoryAllocation* allocation{}; ///< Pointer to the large memory allocation. - u8* data{}; ///< Pointer to the host mapped memory, it has the commit offset included. +}; + +/// Holds ownership of a memory map. +class MemoryMap final { +public: + explicit MemoryMap(const VKMemoryCommitImpl* commit, u8* address) + : commit{commit}, address{address} {} + + ~MemoryMap() { + if (commit) { + commit->Unmap(); + } + } + + /// Prematurely releases the memory map. + void Release() { + commit->Unmap(); + commit = nullptr; + } + + /// Returns the address of the memory map. + u8* GetAddress() const { + return address; + } + + /// Returns the address of the memory map; + operator u8*() const { + return address; + } + +private: + const VKMemoryCommitImpl* commit{}; ///< Mapped memory commit. + u8* address{}; ///< Address to the mapped memory. }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp new file mode 100644 index 000000000..48e23d4cd --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -0,0 +1,395 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <vector> + +#include "common/microprofile.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_compute_pipeline.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/shader/compiler_settings.h" + +namespace Vulkan { + +MICROPROFILE_DECLARE(Vulkan_PipelineCache); + +using Tegra::Engines::ShaderType; + +namespace { + +constexpr VideoCommon::Shader::CompilerSettings compiler_settings{ + VideoCommon::Shader::CompileDepth::FullDecompile}; + +/// Gets the address for the specified shader stage program +GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program) { + const auto& gpu{system.GPU().Maxwell3D()}; + const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]}; + return gpu.regs.code_address.CodeAddress() + shader_config.offset; +} + +/// Gets if the current instruction offset is a scheduler instruction +constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { + // Sched instructions appear once every 4 instructions. + constexpr std::size_t SchedPeriod = 4; + const std::size_t absolute_offset = offset - main_offset; + return (absolute_offset % SchedPeriod) == 0; +} + +/// Calculates the size of a program stream +std::size_t CalculateProgramSize(const ProgramCode& program, bool is_compute) { + const std::size_t start_offset = is_compute ? 0 : 10; + // This is the encoded version of BRA that jumps to itself. All Nvidia + // shaders end with one. + constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL; + constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL; + std::size_t offset = start_offset; + while (offset < program.size()) { + const u64 instruction = program[offset]; + if (!IsSchedInstruction(offset, start_offset)) { + if ((instruction & mask) == self_jumping_branch) { + // End on Maxwell's "nop" instruction + break; + } + if (instruction == 0) { + break; + } + } + ++offset; + } + // The last instruction is included in the program size + return std::min(offset + 1, program.size()); +} + +/// Gets the shader program code from memory for the specified address +ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr, + const u8* host_ptr, bool is_compute) { + ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH); + ASSERT_OR_EXECUTE(host_ptr != nullptr, { + std::fill(program_code.begin(), program_code.end(), 0); + return program_code; + }); + memory_manager.ReadBlockUnsafe(gpu_addr, program_code.data(), + program_code.size() * sizeof(u64)); + program_code.resize(CalculateProgramSize(program_code, is_compute)); + return program_code; +} + +constexpr std::size_t GetStageFromProgram(std::size_t program) { + return program == 0 ? 0 : program - 1; +} + +constexpr ShaderType GetStageFromProgram(Maxwell::ShaderProgram program) { + return static_cast<ShaderType>(GetStageFromProgram(static_cast<std::size_t>(program))); +} + +ShaderType GetShaderType(Maxwell::ShaderProgram program) { + switch (program) { + case Maxwell::ShaderProgram::VertexB: + return ShaderType::Vertex; + case Maxwell::ShaderProgram::TesselationControl: + return ShaderType::TesselationControl; + case Maxwell::ShaderProgram::TesselationEval: + return ShaderType::TesselationEval; + case Maxwell::ShaderProgram::Geometry: + return ShaderType::Geometry; + case Maxwell::ShaderProgram::Fragment: + return ShaderType::Fragment; + default: + UNIMPLEMENTED_MSG("program={}", static_cast<u32>(program)); + return ShaderType::Vertex; + } +} + +u32 FillDescriptorLayout(const ShaderEntries& entries, + std::vector<vk::DescriptorSetLayoutBinding>& bindings, + Maxwell::ShaderProgram program_type, u32 base_binding) { + const ShaderType stage = GetStageFromProgram(program_type); + const vk::ShaderStageFlags stage_flags = MaxwellToVK::ShaderStage(stage); + + u32 binding = base_binding; + const auto AddBindings = [&](vk::DescriptorType descriptor_type, std::size_t num_entries) { + for (std::size_t i = 0; i < num_entries; ++i) { + bindings.emplace_back(binding++, descriptor_type, 1, stage_flags, nullptr); + } + }; + AddBindings(vk::DescriptorType::eUniformBuffer, entries.const_buffers.size()); + AddBindings(vk::DescriptorType::eStorageBuffer, entries.global_buffers.size()); + AddBindings(vk::DescriptorType::eUniformTexelBuffer, entries.texel_buffers.size()); + AddBindings(vk::DescriptorType::eCombinedImageSampler, entries.samplers.size()); + AddBindings(vk::DescriptorType::eStorageImage, entries.images.size()); + return binding; +} + +} // Anonymous namespace + +CachedShader::CachedShader(Core::System& system, Tegra::Engines::ShaderType stage, + GPUVAddr gpu_addr, VAddr cpu_addr, u8* host_ptr, + ProgramCode program_code, u32 main_offset) + : RasterizerCacheObject{host_ptr}, gpu_addr{gpu_addr}, cpu_addr{cpu_addr}, + program_code{std::move(program_code)}, locker{stage, GetEngine(system, stage)}, + shader_ir{this->program_code, main_offset, compiler_settings, locker}, + entries{GenerateShaderEntries(shader_ir)} {} + +CachedShader::~CachedShader() = default; + +Tegra::Engines::ConstBufferEngineInterface& CachedShader::GetEngine( + Core::System& system, Tegra::Engines::ShaderType stage) { + if (stage == Tegra::Engines::ShaderType::Compute) { + return system.GPU().KeplerCompute(); + } else { + return system.GPU().Maxwell3D(); + } +} + +VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer, + const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue) + : RasterizerCache{rasterizer}, system{system}, device{device}, scheduler{scheduler}, + descriptor_pool{descriptor_pool}, update_descriptor_queue{update_descriptor_queue}, + renderpass_cache(device) {} + +VKPipelineCache::~VKPipelineCache() = default; + +std::array<Shader, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() { + const auto& gpu = system.GPU().Maxwell3D(); + auto& dirty = system.GPU().Maxwell3D().dirty.shaders; + if (!dirty) { + return last_shaders; + } + dirty = false; + + std::array<Shader, Maxwell::MaxShaderProgram> shaders; + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { + const auto& shader_config = gpu.regs.shader_config[index]; + const auto program{static_cast<Maxwell::ShaderProgram>(index)}; + + // Skip stages that are not enabled + if (!gpu.regs.IsShaderConfigEnabled(index)) { + continue; + } + + auto& memory_manager{system.GPU().MemoryManager()}; + const GPUVAddr program_addr{GetShaderAddress(system, program)}; + const auto host_ptr{memory_manager.GetPointer(program_addr)}; + auto shader = TryGet(host_ptr); + if (!shader) { + // No shader found - create a new one + constexpr u32 stage_offset = 10; + const auto stage = static_cast<Tegra::Engines::ShaderType>(index == 0 ? 0 : index - 1); + auto code = GetShaderCode(memory_manager, program_addr, host_ptr, false); + + const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr); + ASSERT(cpu_addr); + + shader = std::make_shared<CachedShader>(system, stage, program_addr, *cpu_addr, + host_ptr, std::move(code), stage_offset); + Register(shader); + } + shaders[index] = std::move(shader); + } + return last_shaders = shaders; +} + +VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) { + MICROPROFILE_SCOPE(Vulkan_PipelineCache); + + if (last_graphics_pipeline && last_graphics_key == key) { + return *last_graphics_pipeline; + } + last_graphics_key = key; + + const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); + auto& entry = pair->second; + if (is_cache_miss) { + LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); + const auto [program, bindings] = DecompileShaders(key); + entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool, + update_descriptor_queue, renderpass_cache, key, + bindings, program); + } + return *(last_graphics_pipeline = entry.get()); +} + +VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) { + MICROPROFILE_SCOPE(Vulkan_PipelineCache); + + const auto [pair, is_cache_miss] = compute_cache.try_emplace(key); + auto& entry = pair->second; + if (!is_cache_miss) { + return *entry; + } + LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); + + auto& memory_manager = system.GPU().MemoryManager(); + const auto program_addr = key.shader; + const auto host_ptr = memory_manager.GetPointer(program_addr); + + auto shader = TryGet(host_ptr); + if (!shader) { + // No shader found - create a new one + const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr); + ASSERT(cpu_addr); + + auto code = GetShaderCode(memory_manager, program_addr, host_ptr, true); + constexpr u32 kernel_main_offset = 0; + shader = std::make_shared<CachedShader>(system, Tegra::Engines::ShaderType::Compute, + program_addr, *cpu_addr, host_ptr, std::move(code), + kernel_main_offset); + Register(shader); + } + + Specialization specialization; + specialization.workgroup_size = key.workgroup_size; + specialization.shared_memory_size = key.shared_memory_size; + + const SPIRVShader spirv_shader{ + Decompile(device, shader->GetIR(), ShaderType::Compute, specialization), + shader->GetEntries()}; + entry = std::make_unique<VKComputePipeline>(device, scheduler, descriptor_pool, + update_descriptor_queue, spirv_shader); + return *entry; +} + +void VKPipelineCache::Unregister(const Shader& shader) { + bool finished = false; + const auto Finish = [&] { + // TODO(Rodrigo): Instead of finishing here, wait for the fences that use this pipeline and + // flush. + if (finished) { + return; + } + finished = true; + scheduler.Finish(); + }; + + const GPUVAddr invalidated_addr = shader->GetGpuAddr(); + for (auto it = graphics_cache.begin(); it != graphics_cache.end();) { + auto& entry = it->first; + if (std::find(entry.shaders.begin(), entry.shaders.end(), invalidated_addr) == + entry.shaders.end()) { + ++it; + continue; + } + Finish(); + it = graphics_cache.erase(it); + } + for (auto it = compute_cache.begin(); it != compute_cache.end();) { + auto& entry = it->first; + if (entry.shader != invalidated_addr) { + ++it; + continue; + } + Finish(); + it = compute_cache.erase(it); + } + + RasterizerCache::Unregister(shader); +} + +std::pair<SPIRVProgram, std::vector<vk::DescriptorSetLayoutBinding>> +VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { + const auto& fixed_state = key.fixed_state; + auto& memory_manager = system.GPU().MemoryManager(); + const auto& gpu = system.GPU().Maxwell3D(); + + Specialization specialization; + specialization.primitive_topology = fixed_state.input_assembly.topology; + if (specialization.primitive_topology == Maxwell::PrimitiveTopology::Points) { + ASSERT(fixed_state.input_assembly.point_size != 0.0f); + specialization.point_size = fixed_state.input_assembly.point_size; + } + for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) { + specialization.attribute_types[i] = fixed_state.vertex_input.attributes[i].type; + } + specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one; + specialization.tessellation.primitive = fixed_state.tessellation.primitive; + specialization.tessellation.spacing = fixed_state.tessellation.spacing; + specialization.tessellation.clockwise = fixed_state.tessellation.clockwise; + for (const auto& rt : key.renderpass_params.color_attachments) { + specialization.enabled_rendertargets.set(rt.index); + } + + SPIRVProgram program; + std::vector<vk::DescriptorSetLayoutBinding> bindings; + + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { + const auto program_enum = static_cast<Maxwell::ShaderProgram>(index); + + // Skip stages that are not enabled + if (!gpu.regs.IsShaderConfigEnabled(index)) { + continue; + } + + const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum); + const auto host_ptr = memory_manager.GetPointer(gpu_addr); + const auto shader = TryGet(host_ptr); + ASSERT(shader); + + const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5 + const auto program_type = GetShaderType(program_enum); + const auto& entries = shader->GetEntries(); + program[stage] = {Decompile(device, shader->GetIR(), program_type, specialization), + entries}; + + if (program_enum == Maxwell::ShaderProgram::VertexA) { + // VertexB was combined with VertexA, so we skip the VertexB iteration + ++index; + } + + const u32 old_binding = specialization.base_binding; + specialization.base_binding = + FillDescriptorLayout(entries, bindings, program_enum, specialization.base_binding); + ASSERT(old_binding + entries.NumBindings() == specialization.base_binding); + } + return {std::move(program), std::move(bindings)}; +} + +void FillDescriptorUpdateTemplateEntries( + const VKDevice& device, const ShaderEntries& entries, u32& binding, u32& offset, + std::vector<vk::DescriptorUpdateTemplateEntry>& template_entries) { + static constexpr auto entry_size = static_cast<u32>(sizeof(DescriptorUpdateEntry)); + const auto AddEntry = [&](vk::DescriptorType descriptor_type, std::size_t count_) { + const u32 count = static_cast<u32>(count_); + if (descriptor_type == vk::DescriptorType::eUniformTexelBuffer && + device.GetDriverID() == vk::DriverIdKHR::eNvidiaProprietary) { + // Nvidia has a bug where updating multiple uniform texels at once causes the driver to + // crash. + for (u32 i = 0; i < count; ++i) { + template_entries.emplace_back(binding + i, 0, 1, descriptor_type, + offset + i * entry_size, entry_size); + } + } else if (count != 0) { + template_entries.emplace_back(binding, 0, count, descriptor_type, offset, entry_size); + } + offset += count * entry_size; + binding += count; + }; + + AddEntry(vk::DescriptorType::eUniformBuffer, entries.const_buffers.size()); + AddEntry(vk::DescriptorType::eStorageBuffer, entries.global_buffers.size()); + AddEntry(vk::DescriptorType::eUniformTexelBuffer, entries.texel_buffers.size()); + AddEntry(vk::DescriptorType::eCombinedImageSampler, entries.samplers.size()); + AddEntry(vk::DescriptorType::eStorageImage, entries.images.size()); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h new file mode 100644 index 000000000..8678fc9c3 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -0,0 +1,200 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <memory> +#include <tuple> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <boost/functional/hash.hpp> + +#include "common/common_types.h" +#include "video_core/engines/const_buffer_engine_interface.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/rasterizer_cache.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_shader_decompiler.h" +#include "video_core/shader/const_buffer_locker.h" +#include "video_core/shader/shader_ir.h" +#include "video_core/surface.h" + +namespace Core { +class System; +} + +namespace Vulkan { + +class RasterizerVulkan; +class VKComputePipeline; +class VKDescriptorPool; +class VKDevice; +class VKFence; +class VKScheduler; +class VKUpdateDescriptorQueue; + +class CachedShader; +using Shader = std::shared_ptr<CachedShader>; +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +using ProgramCode = std::vector<u64>; + +struct GraphicsPipelineCacheKey { + FixedPipelineState fixed_state; + std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; + RenderPassParams renderpass_params; + + std::size_t Hash() const noexcept { + std::size_t hash = fixed_state.Hash(); + for (const auto& shader : shaders) { + boost::hash_combine(hash, shader); + } + boost::hash_combine(hash, renderpass_params.Hash()); + return hash; + } + + bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept { + return std::tie(fixed_state, shaders, renderpass_params) == + std::tie(rhs.fixed_state, rhs.shaders, rhs.renderpass_params); + } +}; + +struct ComputePipelineCacheKey { + GPUVAddr shader{}; + u32 shared_memory_size{}; + std::array<u32, 3> workgroup_size{}; + + std::size_t Hash() const noexcept { + return static_cast<std::size_t>(shader) ^ + ((static_cast<std::size_t>(shared_memory_size) >> 7) << 40) ^ + static_cast<std::size_t>(workgroup_size[0]) ^ + (static_cast<std::size_t>(workgroup_size[1]) << 16) ^ + (static_cast<std::size_t>(workgroup_size[2]) << 24); + } + + bool operator==(const ComputePipelineCacheKey& rhs) const noexcept { + return std::tie(shader, shared_memory_size, workgroup_size) == + std::tie(rhs.shader, rhs.shared_memory_size, rhs.workgroup_size); + } +}; + +} // namespace Vulkan + +namespace std { + +template <> +struct hash<Vulkan::GraphicsPipelineCacheKey> { + std::size_t operator()(const Vulkan::GraphicsPipelineCacheKey& k) const noexcept { + return k.Hash(); + } +}; + +template <> +struct hash<Vulkan::ComputePipelineCacheKey> { + std::size_t operator()(const Vulkan::ComputePipelineCacheKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace Vulkan { + +class CachedShader final : public RasterizerCacheObject { +public: + explicit CachedShader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, + VAddr cpu_addr, u8* host_ptr, ProgramCode program_code, u32 main_offset); + ~CachedShader(); + + GPUVAddr GetGpuAddr() const { + return gpu_addr; + } + + VAddr GetCpuAddr() const override { + return cpu_addr; + } + + std::size_t GetSizeInBytes() const override { + return program_code.size() * sizeof(u64); + } + + VideoCommon::Shader::ShaderIR& GetIR() { + return shader_ir; + } + + const VideoCommon::Shader::ShaderIR& GetIR() const { + return shader_ir; + } + + const ShaderEntries& GetEntries() const { + return entries; + } + +private: + static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system, + Tegra::Engines::ShaderType stage); + + GPUVAddr gpu_addr{}; + VAddr cpu_addr{}; + ProgramCode program_code; + VideoCommon::Shader::ConstBufferLocker locker; + VideoCommon::Shader::ShaderIR shader_ir; + ShaderEntries entries; +}; + +class VKPipelineCache final : public RasterizerCache<Shader> { +public: + explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer, + const VKDevice& device, VKScheduler& scheduler, + VKDescriptorPool& descriptor_pool, + VKUpdateDescriptorQueue& update_descriptor_queue); + ~VKPipelineCache(); + + std::array<Shader, Maxwell::MaxShaderProgram> GetShaders(); + + VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key); + + VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); + +protected: + void Unregister(const Shader& shader) override; + + void FlushObjectInner(const Shader& object) override {} + +private: + std::pair<SPIRVProgram, std::vector<vk::DescriptorSetLayoutBinding>> DecompileShaders( + const GraphicsPipelineCacheKey& key); + + Core::System& system; + const VKDevice& device; + VKScheduler& scheduler; + VKDescriptorPool& descriptor_pool; + VKUpdateDescriptorQueue& update_descriptor_queue; + + VKRenderPassCache renderpass_cache; + + std::array<Shader, Maxwell::MaxShaderProgram> last_shaders; + + GraphicsPipelineCacheKey last_graphics_key; + VKGraphicsPipeline* last_graphics_pipeline = nullptr; + + std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>> + graphics_cache; + std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache; +}; + +void FillDescriptorUpdateTemplateEntries( + const VKDevice& device, const ShaderEntries& entries, u32& binding, u32& offset, + std::vector<vk::DescriptorUpdateTemplateEntry>& template_entries); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp new file mode 100644 index 000000000..d2c6b1189 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -0,0 +1,1141 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <memory> +#include <mutex> +#include <vector> + +#include <boost/container/static_vector.hpp> +#include <boost/functional/hash.hpp> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_compute_pipeline.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_sampler_cache.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192)); +MICROPROFILE_DEFINE(Vulkan_Drawing, "Vulkan", "Record drawing", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_Compute, "Vulkan", "Record compute", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_Clearing, "Vulkan", "Record clearing", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_Geometry, "Vulkan", "Setup geometry", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_ConstBuffers, "Vulkan", "Setup constant buffers", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_GlobalBuffers, "Vulkan", "Setup global buffers", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_RenderTargets, "Vulkan", "Setup render targets", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_Textures, "Vulkan", "Setup textures", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_Images, "Vulkan", "Setup images", MP_RGB(192, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_PipelineCache, "Vulkan", "Pipeline cache", MP_RGB(192, 128, 128)); + +namespace { + +constexpr auto ComputeShaderIndex = static_cast<std::size_t>(Tegra::Engines::ShaderType::Compute); + +vk::Viewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::size_t index) { + const auto& viewport = regs.viewport_transform[index]; + const float x = viewport.translate_x - viewport.scale_x; + const float y = viewport.translate_y - viewport.scale_y; + const float width = viewport.scale_x * 2.0f; + const float height = viewport.scale_y * 2.0f; + + const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne; + float near = viewport.translate_z - viewport.scale_z * reduce_z; + float far = viewport.translate_z + viewport.scale_z; + if (!device.IsExtDepthRangeUnrestrictedSupported()) { + near = std::clamp(near, 0.0f, 1.0f); + far = std::clamp(far, 0.0f, 1.0f); + } + + return vk::Viewport(x, y, width != 0 ? width : 1.0f, height != 0 ? height : 1.0f, near, far); +} + +constexpr vk::Rect2D GetScissorState(const Maxwell& regs, std::size_t index) { + const auto& scissor = regs.scissor_test[index]; + if (!scissor.enable) { + return {{0, 0}, {INT32_MAX, INT32_MAX}}; + } + const u32 width = scissor.max_x - scissor.min_x; + const u32 height = scissor.max_y - scissor.min_y; + return {{static_cast<s32>(scissor.min_x), static_cast<s32>(scissor.min_y)}, {width, height}}; +} + +std::array<GPUVAddr, Maxwell::MaxShaderProgram> GetShaderAddresses( + const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) { + std::array<GPUVAddr, Maxwell::MaxShaderProgram> addresses; + for (std::size_t i = 0; i < std::size(addresses); ++i) { + addresses[i] = shaders[i] ? shaders[i]->GetGpuAddr() : 0; + } + return addresses; +} + +void TransitionImages(const std::vector<ImageView>& views, vk::PipelineStageFlags pipeline_stage, + vk::AccessFlags access) { + for (auto& [view, layout] : views) { + view->Transition(*layout, pipeline_stage, access); + } +} + +template <typename Engine, typename Entry> +Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry, + std::size_t stage) { + const auto stage_type = static_cast<Tegra::Engines::ShaderType>(stage); + if (entry.IsBindless()) { + const Tegra::Texture::TextureHandle tex_handle = + engine.AccessConstBuffer32(stage_type, entry.GetBuffer(), entry.GetOffset()); + return engine.GetTextureInfo(tex_handle); + } + if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) { + return engine.GetStageTexture(stage_type, entry.GetOffset()); + } else { + return engine.GetTexture(entry.GetOffset()); + } +} + +} // Anonymous namespace + +class BufferBindings final { +public: + void AddVertexBinding(const vk::Buffer* buffer, vk::DeviceSize offset) { + vertex.buffer_ptrs[vertex.num_buffers] = buffer; + vertex.offsets[vertex.num_buffers] = offset; + ++vertex.num_buffers; + } + + void SetIndexBinding(const vk::Buffer* buffer, vk::DeviceSize offset, vk::IndexType type) { + index.buffer = buffer; + index.offset = offset; + index.type = type; + } + + void Bind(VKScheduler& scheduler) const { + // Use this large switch case to avoid dispatching more memory in the record lambda than + // what we need. It looks horrible, but it's the best we can do on standard C++. + switch (vertex.num_buffers) { + case 0: + return BindStatic<0>(scheduler); + case 1: + return BindStatic<1>(scheduler); + case 2: + return BindStatic<2>(scheduler); + case 3: + return BindStatic<3>(scheduler); + case 4: + return BindStatic<4>(scheduler); + case 5: + return BindStatic<5>(scheduler); + case 6: + return BindStatic<6>(scheduler); + case 7: + return BindStatic<7>(scheduler); + case 8: + return BindStatic<8>(scheduler); + case 9: + return BindStatic<9>(scheduler); + case 10: + return BindStatic<10>(scheduler); + case 11: + return BindStatic<11>(scheduler); + case 12: + return BindStatic<12>(scheduler); + case 13: + return BindStatic<13>(scheduler); + case 14: + return BindStatic<14>(scheduler); + case 15: + return BindStatic<15>(scheduler); + case 16: + return BindStatic<16>(scheduler); + case 17: + return BindStatic<17>(scheduler); + case 18: + return BindStatic<18>(scheduler); + case 19: + return BindStatic<19>(scheduler); + case 20: + return BindStatic<20>(scheduler); + case 21: + return BindStatic<21>(scheduler); + case 22: + return BindStatic<22>(scheduler); + case 23: + return BindStatic<23>(scheduler); + case 24: + return BindStatic<24>(scheduler); + case 25: + return BindStatic<25>(scheduler); + case 26: + return BindStatic<26>(scheduler); + case 27: + return BindStatic<27>(scheduler); + case 28: + return BindStatic<28>(scheduler); + case 29: + return BindStatic<29>(scheduler); + case 30: + return BindStatic<30>(scheduler); + case 31: + return BindStatic<31>(scheduler); + case 32: + return BindStatic<32>(scheduler); + } + UNREACHABLE(); + } + +private: + // Some of these fields are intentionally left uninitialized to avoid initializing them twice. + struct { + std::size_t num_buffers = 0; + std::array<const vk::Buffer*, Maxwell::NumVertexArrays> buffer_ptrs; + std::array<vk::DeviceSize, Maxwell::NumVertexArrays> offsets; + } vertex; + + struct { + const vk::Buffer* buffer = nullptr; + vk::DeviceSize offset; + vk::IndexType type; + } index; + + template <std::size_t N> + void BindStatic(VKScheduler& scheduler) const { + if (index.buffer != nullptr) { + BindStatic<N, true>(scheduler); + } else { + BindStatic<N, false>(scheduler); + } + } + + template <std::size_t N, bool is_indexed> + void BindStatic(VKScheduler& scheduler) const { + static_assert(N <= Maxwell::NumVertexArrays); + if constexpr (N == 0) { + return; + } + + std::array<vk::Buffer, N> buffers; + std::transform(vertex.buffer_ptrs.begin(), vertex.buffer_ptrs.begin() + N, buffers.begin(), + [](const auto ptr) { return *ptr; }); + + std::array<vk::DeviceSize, N> offsets; + std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin()); + + if constexpr (is_indexed) { + // Indexed draw + scheduler.Record([buffers, offsets, index_buffer = *index.buffer, + index_offset = index.offset, + index_type = index.type](auto cmdbuf, auto& dld) { + cmdbuf.bindIndexBuffer(index_buffer, index_offset, index_type, dld); + cmdbuf.bindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data(), + dld); + }); + } else { + // Array draw + scheduler.Record([buffers, offsets](auto cmdbuf, auto& dld) { + cmdbuf.bindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data(), + dld); + }); + } + } +}; + +void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf, + const vk::DispatchLoaderDynamic& dld) const { + if (is_indexed) { + cmdbuf.drawIndexed(num_vertices, num_instances, 0, base_vertex, base_instance, dld); + } else { + cmdbuf.draw(num_vertices, num_instances, base_vertex, base_instance, dld); + } +} + +RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer, + VKScreenInfo& screen_info, const VKDevice& device, + VKResourceManager& resource_manager, + VKMemoryManager& memory_manager, VKScheduler& scheduler) + : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer}, + screen_info{screen_info}, device{device}, resource_manager{resource_manager}, + memory_manager{memory_manager}, scheduler{scheduler}, + staging_pool(device, memory_manager, scheduler), descriptor_pool(device), + update_descriptor_queue(device, scheduler), + quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), + uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue), + texture_cache(system, *this, device, resource_manager, memory_manager, scheduler, + staging_pool), + pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue), + buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), + sampler_cache(device) {} + +RasterizerVulkan::~RasterizerVulkan() = default; + +bool RasterizerVulkan::DrawBatch(bool is_indexed) { + Draw(is_indexed, false); + return true; +} + +bool RasterizerVulkan::DrawMultiBatch(bool is_indexed) { + Draw(is_indexed, true); + return true; +} + +void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { + MICROPROFILE_SCOPE(Vulkan_Drawing); + + FlushWork(); + + const auto& gpu = system.GPU().Maxwell3D(); + GraphicsPipelineCacheKey key{GetFixedPipelineState(gpu.regs)}; + + buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed)); + + BufferBindings buffer_bindings; + const DrawParameters draw_params = + SetupGeometry(key.fixed_state, buffer_bindings, is_indexed, is_instanced); + + update_descriptor_queue.Acquire(); + sampled_views.clear(); + image_views.clear(); + + const auto shaders = pipeline_cache.GetShaders(); + key.shaders = GetShaderAddresses(shaders); + SetupShaderDescriptors(shaders); + + buffer_cache.Unmap(); + + const auto texceptions = UpdateAttachments(); + SetupImageTransitions(texceptions, color_attachments, zeta_attachment); + + key.renderpass_params = GetRenderPassParams(texceptions); + + auto& pipeline = pipeline_cache.GetGraphicsPipeline(key); + scheduler.BindGraphicsPipeline(pipeline.GetHandle()); + + const auto renderpass = pipeline.GetRenderPass(); + const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); + scheduler.RequestRenderpass({renderpass, framebuffer, {{0, 0}, render_area}, 0, nullptr}); + + UpdateDynamicStates(); + + buffer_bindings.Bind(scheduler); + + if (device.IsNvDeviceDiagnosticCheckpoints()) { + scheduler.Record( + [&pipeline](auto cmdbuf, auto& dld) { cmdbuf.setCheckpointNV(&pipeline, dld); }); + } + + const auto pipeline_layout = pipeline.GetLayout(); + const auto descriptor_set = pipeline.CommitDescriptorSet(); + scheduler.Record([pipeline_layout, descriptor_set, draw_params](auto cmdbuf, auto& dld) { + if (descriptor_set) { + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, + DESCRIPTOR_SET, 1, &descriptor_set, 0, nullptr, dld); + } + draw_params.Draw(cmdbuf, dld); + }); +} + +void RasterizerVulkan::Clear() { + MICROPROFILE_SCOPE(Vulkan_Clearing); + + const auto& gpu = system.GPU().Maxwell3D(); + if (!system.GPU().Maxwell3D().ShouldExecute()) { + return; + } + + const auto& regs = gpu.regs; + const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B || + regs.clear_buffers.A; + const bool use_depth = regs.clear_buffers.Z; + const bool use_stencil = regs.clear_buffers.S; + if (!use_color && !use_depth && !use_stencil) { + return; + } + // Clearing images requires to be out of a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + // TODO(Rodrigo): Implement clears rendering a quad or using beginning a renderpass. + + if (use_color) { + View color_view; + { + MICROPROFILE_SCOPE(Vulkan_RenderTargets); + color_view = texture_cache.GetColorBufferSurface(regs.clear_buffers.RT.Value(), false); + } + + color_view->Transition(vk::ImageLayout::eTransferDstOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + + const std::array clear_color = {regs.clear_color[0], regs.clear_color[1], + regs.clear_color[2], regs.clear_color[3]}; + const vk::ClearColorValue clear(clear_color); + scheduler.Record([image = color_view->GetImage(), + subresource = color_view->GetImageSubresourceRange(), + clear](auto cmdbuf, auto& dld) { + cmdbuf.clearColorImage(image, vk::ImageLayout::eTransferDstOptimal, clear, subresource, + dld); + }); + } + if (use_depth || use_stencil) { + View zeta_surface; + { + MICROPROFILE_SCOPE(Vulkan_RenderTargets); + zeta_surface = texture_cache.GetDepthBufferSurface(false); + } + + zeta_surface->Transition(vk::ImageLayout::eTransferDstOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + + const vk::ClearDepthStencilValue clear(regs.clear_depth, + static_cast<u32>(regs.clear_stencil)); + scheduler.Record([image = zeta_surface->GetImage(), + subresource = zeta_surface->GetImageSubresourceRange(), + clear](auto cmdbuf, auto& dld) { + cmdbuf.clearDepthStencilImage(image, vk::ImageLayout::eTransferDstOptimal, clear, + subresource, dld); + }); + } +} + +void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) { + MICROPROFILE_SCOPE(Vulkan_Compute); + update_descriptor_queue.Acquire(); + sampled_views.clear(); + image_views.clear(); + + const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + const ComputePipelineCacheKey key{ + code_addr, + launch_desc.shared_alloc, + {launch_desc.block_dim_x, launch_desc.block_dim_y, launch_desc.block_dim_z}}; + auto& pipeline = pipeline_cache.GetComputePipeline(key); + + // Compute dispatches can't be executed inside a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + buffer_cache.Map(CalculateComputeStreamBufferSize()); + + const auto& entries = pipeline.GetEntries(); + SetupComputeConstBuffers(entries); + SetupComputeGlobalBuffers(entries); + SetupComputeTexelBuffers(entries); + SetupComputeTextures(entries); + SetupComputeImages(entries); + + buffer_cache.Unmap(); + + TransitionImages(sampled_views, vk::PipelineStageFlagBits::eComputeShader, + vk::AccessFlagBits::eShaderRead); + TransitionImages(image_views, vk::PipelineStageFlagBits::eComputeShader, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); + + if (device.IsNvDeviceDiagnosticCheckpoints()) { + scheduler.Record( + [&pipeline](auto cmdbuf, auto& dld) { cmdbuf.setCheckpointNV(nullptr, dld); }); + } + + scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y, + grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(), + layout = pipeline.GetLayout(), + descriptor_set = pipeline.CommitDescriptorSet()](auto cmdbuf, auto& dld) { + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline_handle, dld); + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, layout, DESCRIPTOR_SET, 1, + &descriptor_set, 0, nullptr, dld); + cmdbuf.dispatch(grid_x, grid_y, grid_z, dld); + }); +} + +void RasterizerVulkan::FlushAll() {} + +void RasterizerVulkan::FlushRegion(CacheAddr addr, u64 size) { + texture_cache.FlushRegion(addr, size); + buffer_cache.FlushRegion(addr, size); +} + +void RasterizerVulkan::InvalidateRegion(CacheAddr addr, u64 size) { + texture_cache.InvalidateRegion(addr, size); + pipeline_cache.InvalidateRegion(addr, size); + buffer_cache.InvalidateRegion(addr, size); +} + +void RasterizerVulkan::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { + FlushRegion(addr, size); + InvalidateRegion(addr, size); +} + +void RasterizerVulkan::FlushCommands() { + if (draw_counter > 0) { + draw_counter = 0; + scheduler.Flush(); + } +} + +void RasterizerVulkan::TickFrame() { + draw_counter = 0; + update_descriptor_queue.TickFrame(); + buffer_cache.TickFrame(); + staging_pool.TickFrame(); +} + +bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) { + texture_cache.DoFermiCopy(src, dst, copy_config); + return true; +} + +bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config, + VAddr framebuffer_addr, u32 pixel_stride) { + if (!framebuffer_addr) { + return false; + } + + const u8* host_ptr{system.Memory().GetPointer(framebuffer_addr)}; + const auto surface{texture_cache.TryFindFramebufferSurface(host_ptr)}; + if (!surface) { + return false; + } + + // Verify that the cached surface is the same size and format as the requested framebuffer + const auto& params{surface->GetSurfaceParams()}; + const auto& pixel_format{ + VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)}; + ASSERT_MSG(params.width == config.width, "Framebuffer width is different"); + ASSERT_MSG(params.height == config.height, "Framebuffer height is different"); + + screen_info.image = &surface->GetImage(); + screen_info.width = params.width; + screen_info.height = params.height; + screen_info.is_srgb = surface->GetSurfaceParams().srgb_conversion; + return true; +} + +void RasterizerVulkan::FlushWork() { + static constexpr u32 DRAWS_TO_DISPATCH = 4096; + + // Only check multiples of 8 draws + static_assert(DRAWS_TO_DISPATCH % 8 == 0); + if ((++draw_counter & 7) != 7) { + return; + } + + if (draw_counter < DRAWS_TO_DISPATCH) { + // Send recorded tasks to the worker thread + scheduler.DispatchWork(); + return; + } + + // Otherwise (every certain number of draws) flush execution. + // This submits commands to the Vulkan driver. + scheduler.Flush(); + draw_counter = 0; +} + +RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() { + MICROPROFILE_SCOPE(Vulkan_RenderTargets); + auto& dirty = system.GPU().Maxwell3D().dirty; + const bool update_rendertargets = dirty.render_settings; + dirty.render_settings = false; + + texture_cache.GuardRenderTargets(true); + + Texceptions texceptions; + for (std::size_t rt = 0; rt < Maxwell::NumRenderTargets; ++rt) { + if (update_rendertargets) { + color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, true); + } + if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) { + texceptions.set(rt); + } + } + + if (update_rendertargets) { + zeta_attachment = texture_cache.GetDepthBufferSurface(true); + } + if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) { + texceptions.set(ZETA_TEXCEPTION_INDEX); + } + + texture_cache.GuardRenderTargets(false); + + return texceptions; +} + +bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachment) { + bool overlap = false; + for (auto& [view, layout] : sampled_views) { + if (!attachment.IsSameSurface(*view)) { + continue; + } + overlap = true; + *layout = vk::ImageLayout::eGeneral; + } + return overlap; +} + +std::tuple<vk::Framebuffer, vk::Extent2D> RasterizerVulkan::ConfigureFramebuffers( + vk::RenderPass renderpass) { + FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(), + std::numeric_limits<u32>::max()}; + + const auto MarkAsModifiedAndPush = [&](const View& view) { + if (view == nullptr) { + return false; + } + key.views.push_back(view->GetHandle()); + key.width = std::min(key.width, view->GetWidth()); + key.height = std::min(key.height, view->GetHeight()); + return true; + }; + + for (std::size_t index = 0; index < std::size(color_attachments); ++index) { + if (MarkAsModifiedAndPush(color_attachments[index])) { + texture_cache.MarkColorBufferInUse(index); + } + } + if (MarkAsModifiedAndPush(zeta_attachment)) { + texture_cache.MarkDepthBufferInUse(); + } + + const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key); + auto& framebuffer = fbentry->second; + if (is_cache_miss) { + const vk::FramebufferCreateInfo framebuffer_ci({}, key.renderpass, + static_cast<u32>(key.views.size()), + key.views.data(), key.width, key.height, 1); + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + framebuffer = dev.createFramebufferUnique(framebuffer_ci, nullptr, dld); + } + + return {*framebuffer, vk::Extent2D{key.width, key.height}}; +} + +RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineState& fixed_state, + BufferBindings& buffer_bindings, + bool is_indexed, + bool is_instanced) { + MICROPROFILE_SCOPE(Vulkan_Geometry); + + const auto& gpu = system.GPU().Maxwell3D(); + const auto& regs = gpu.regs; + + SetupVertexArrays(fixed_state.vertex_input, buffer_bindings); + + const u32 base_instance = regs.vb_base_instance; + const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1; + const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first; + const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count; + + DrawParameters params{base_instance, num_instances, base_vertex, num_vertices, is_indexed}; + SetupIndexBuffer(buffer_bindings, params, is_indexed); + + return params; +} + +void RasterizerVulkan::SetupShaderDescriptors( + const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) { + texture_cache.GuardSamplers(true); + + for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) { + // Skip VertexA stage + const auto& shader = shaders[stage + 1]; + if (!shader) { + continue; + } + const auto& entries = shader->GetEntries(); + SetupGraphicsConstBuffers(entries, stage); + SetupGraphicsGlobalBuffers(entries, stage); + SetupGraphicsTexelBuffers(entries, stage); + SetupGraphicsTextures(entries, stage); + SetupGraphicsImages(entries, stage); + } + texture_cache.GuardSamplers(false); +} + +void RasterizerVulkan::SetupImageTransitions( + Texceptions texceptions, const std::array<View, Maxwell::NumRenderTargets>& color_attachments, + const View& zeta_attachment) { + TransitionImages(sampled_views, vk::PipelineStageFlagBits::eAllGraphics, + vk::AccessFlagBits::eShaderRead); + TransitionImages(image_views, vk::PipelineStageFlagBits::eAllGraphics, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); + + for (std::size_t rt = 0; rt < std::size(color_attachments); ++rt) { + const auto color_attachment = color_attachments[rt]; + if (color_attachment == nullptr) { + continue; + } + const auto image_layout = + texceptions[rt] ? vk::ImageLayout::eGeneral : vk::ImageLayout::eColorAttachmentOptimal; + color_attachment->Transition( + image_layout, vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite); + } + + if (zeta_attachment != nullptr) { + const auto image_layout = texceptions[ZETA_TEXCEPTION_INDEX] + ? vk::ImageLayout::eGeneral + : vk::ImageLayout::eDepthStencilAttachmentOptimal; + zeta_attachment->Transition(image_layout, vk::PipelineStageFlagBits::eLateFragmentTests, + vk::AccessFlagBits::eDepthStencilAttachmentRead | + vk::AccessFlagBits::eDepthStencilAttachmentWrite); + } +} + +void RasterizerVulkan::UpdateDynamicStates() { + auto& gpu = system.GPU().Maxwell3D(); + UpdateViewportsState(gpu); + UpdateScissorsState(gpu); + UpdateDepthBias(gpu); + UpdateBlendConstants(gpu); + UpdateDepthBounds(gpu); + UpdateStencilFaces(gpu); +} + +void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, + BufferBindings& buffer_bindings) { + const auto& regs = system.GPU().Maxwell3D().regs; + + for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexAttributes); ++index) { + const auto& attrib = regs.vertex_attrib_format[index]; + if (!attrib.IsValid()) { + continue; + } + + const auto& buffer = regs.vertex_array[attrib.buffer]; + ASSERT(buffer.IsEnabled()); + + vertex_input.attributes[vertex_input.num_attributes++] = + FixedPipelineState::VertexAttribute(index, attrib.buffer, attrib.type, attrib.size, + attrib.offset); + } + + for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexArrays); ++index) { + const auto& vertex_array = regs.vertex_array[index]; + if (!vertex_array.IsEnabled()) { + continue; + } + + const GPUVAddr start{vertex_array.StartAddress()}; + const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; + + ASSERT(end > start); + const std::size_t size{end - start + 1}; + const auto [buffer, offset] = buffer_cache.UploadMemory(start, size); + + vertex_input.bindings[vertex_input.num_bindings++] = FixedPipelineState::VertexBinding( + index, vertex_array.stride, + regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0); + buffer_bindings.AddVertexBinding(buffer, offset); + } +} + +void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, + bool is_indexed) { + const auto& regs = system.GPU().Maxwell3D().regs; + switch (regs.draw.topology) { + case Maxwell::PrimitiveTopology::Quads: + if (params.is_indexed) { + UNIMPLEMENTED(); + } else { + const auto [buffer, offset] = + quad_array_pass.Assemble(params.num_vertices, params.base_vertex); + buffer_bindings.SetIndexBinding(&buffer, offset, vk::IndexType::eUint32); + params.base_vertex = 0; + params.num_vertices = params.num_vertices * 6 / 4; + params.is_indexed = true; + } + break; + default: { + if (!is_indexed) { + break; + } + const GPUVAddr gpu_addr = regs.index_array.IndexStart(); + auto [buffer, offset] = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize()); + + auto format = regs.index_array.format; + const bool is_uint8 = format == Maxwell::IndexFormat::UnsignedByte; + if (is_uint8 && !device.IsExtIndexTypeUint8Supported()) { + std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, *buffer, offset); + format = Maxwell::IndexFormat::UnsignedShort; + } + + buffer_bindings.SetIndexBinding(buffer, offset, MaxwellToVK::IndexFormat(device, format)); + break; + } + } +} + +void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) { + MICROPROFILE_SCOPE(Vulkan_ConstBuffers); + const auto& gpu = system.GPU().Maxwell3D(); + const auto& shader_stage = gpu.state.shader_stages[stage]; + for (const auto& entry : entries.const_buffers) { + SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]); + } +} + +void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) { + MICROPROFILE_SCOPE(Vulkan_GlobalBuffers); + auto& gpu{system.GPU()}; + const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]}; + + for (const auto& entry : entries.global_buffers) { + const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset(); + SetupGlobalBuffer(entry, addr); + } +} + +void RasterizerVulkan::SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage) { + MICROPROFILE_SCOPE(Vulkan_Textures); + const auto& gpu = system.GPU().Maxwell3D(); + for (const auto& entry : entries.texel_buffers) { + const auto image = GetTextureInfo(gpu, entry, stage).tic; + SetupTexelBuffer(image, entry); + } +} + +void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) { + MICROPROFILE_SCOPE(Vulkan_Textures); + const auto& gpu = system.GPU().Maxwell3D(); + for (const auto& entry : entries.samplers) { + const auto texture = GetTextureInfo(gpu, entry, stage); + SetupTexture(texture, entry); + } +} + +void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) { + MICROPROFILE_SCOPE(Vulkan_Images); + const auto& gpu = system.GPU().KeplerCompute(); + for (const auto& entry : entries.images) { + const auto tic = GetTextureInfo(gpu, entry, stage).tic; + SetupImage(tic, entry); + } +} + +void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) { + MICROPROFILE_SCOPE(Vulkan_ConstBuffers); + const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + for (const auto& entry : entries.const_buffers) { + const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; + const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); + Tegra::Engines::ConstBufferInfo buffer; + buffer.address = config.Address(); + buffer.size = config.size; + buffer.enabled = mask[entry.GetIndex()]; + SetupConstBuffer(entry, buffer); + } +} + +void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) { + MICROPROFILE_SCOPE(Vulkan_GlobalBuffers); + const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config}; + for (const auto& entry : entries.global_buffers) { + const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()}; + SetupGlobalBuffer(entry, addr); + } +} + +void RasterizerVulkan::SetupComputeTexelBuffers(const ShaderEntries& entries) { + MICROPROFILE_SCOPE(Vulkan_Textures); + const auto& gpu = system.GPU().KeplerCompute(); + for (const auto& entry : entries.texel_buffers) { + const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; + SetupTexelBuffer(image, entry); + } +} + +void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) { + MICROPROFILE_SCOPE(Vulkan_Textures); + const auto& gpu = system.GPU().KeplerCompute(); + for (const auto& entry : entries.samplers) { + const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex); + SetupTexture(texture, entry); + } +} + +void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) { + MICROPROFILE_SCOPE(Vulkan_Images); + const auto& gpu = system.GPU().KeplerCompute(); + for (const auto& entry : entries.images) { + const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic; + SetupImage(tic, entry); + } +} + +void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry, + const Tegra::Engines::ConstBufferInfo& buffer) { + // Align the size to avoid bad std140 interactions + const std::size_t size = + Common::AlignUp(CalculateConstBufferSize(entry, buffer), 4 * sizeof(float)); + ASSERT(size <= MaxConstbufferSize); + + const auto [buffer_handle, offset] = + buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment()); + + update_descriptor_queue.AddBuffer(buffer_handle, offset, size); +} + +void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) { + auto& memory_manager{system.GPU().MemoryManager()}; + const auto actual_addr = memory_manager.Read<u64>(address); + const auto size = memory_manager.Read<u32>(address + 8); + + if (size == 0) { + // Sometimes global memory pointers don't have a proper size. Upload a dummy entry because + // Vulkan doesn't like empty buffers. + constexpr std::size_t dummy_size = 4; + const auto buffer = buffer_cache.GetEmptyBuffer(dummy_size); + update_descriptor_queue.AddBuffer(buffer, 0, dummy_size); + return; + } + + const auto [buffer, offset] = buffer_cache.UploadMemory( + actual_addr, size, device.GetStorageBufferAlignment(), entry.IsWritten()); + update_descriptor_queue.AddBuffer(buffer, offset, size); +} + +void RasterizerVulkan::SetupTexelBuffer(const Tegra::Texture::TICEntry& tic, + const TexelBufferEntry& entry) { + const auto view = texture_cache.GetTextureSurface(tic, entry); + ASSERT(view->IsBufferView()); + + update_descriptor_queue.AddTexelBuffer(view->GetBufferView()); +} + +void RasterizerVulkan::SetupTexture(const Tegra::Texture::FullTextureInfo& texture, + const SamplerEntry& entry) { + auto view = texture_cache.GetTextureSurface(texture.tic, entry); + ASSERT(!view->IsBufferView()); + + const auto image_view = view->GetHandle(texture.tic.x_source, texture.tic.y_source, + texture.tic.z_source, texture.tic.w_source); + const auto sampler = sampler_cache.GetSampler(texture.tsc); + update_descriptor_queue.AddSampledImage(sampler, image_view); + + const auto image_layout = update_descriptor_queue.GetLastImageLayout(); + *image_layout = vk::ImageLayout::eShaderReadOnlyOptimal; + sampled_views.push_back(ImageView{std::move(view), image_layout}); +} + +void RasterizerVulkan::SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry) { + auto view = texture_cache.GetImageSurface(tic, entry); + + if (entry.IsWritten()) { + view->MarkAsModified(texture_cache.Tick()); + } + + UNIMPLEMENTED_IF(tic.IsBuffer()); + + const auto image_view = view->GetHandle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); + update_descriptor_queue.AddImage(image_view); + + const auto image_layout = update_descriptor_queue.GetLastImageLayout(); + *image_layout = vk::ImageLayout::eGeneral; + image_views.push_back(ImageView{std::move(view), image_layout}); +} + +void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.viewport_transform && scheduler.TouchViewports()) { + return; + } + gpu.dirty.viewport_transform = false; + const auto& regs = gpu.regs; + const std::array viewports{ + GetViewportState(device, regs, 0), GetViewportState(device, regs, 1), + GetViewportState(device, regs, 2), GetViewportState(device, regs, 3), + GetViewportState(device, regs, 4), GetViewportState(device, regs, 5), + GetViewportState(device, regs, 6), GetViewportState(device, regs, 7), + GetViewportState(device, regs, 8), GetViewportState(device, regs, 9), + GetViewportState(device, regs, 10), GetViewportState(device, regs, 11), + GetViewportState(device, regs, 12), GetViewportState(device, regs, 13), + GetViewportState(device, regs, 14), GetViewportState(device, regs, 15)}; + scheduler.Record([viewports](auto cmdbuf, auto& dld) { + cmdbuf.setViewport(0, static_cast<u32>(viewports.size()), viewports.data(), dld); + }); +} + +void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.scissor_test && scheduler.TouchScissors()) { + return; + } + gpu.dirty.scissor_test = false; + const auto& regs = gpu.regs; + const std::array scissors = { + GetScissorState(regs, 0), GetScissorState(regs, 1), GetScissorState(regs, 2), + GetScissorState(regs, 3), GetScissorState(regs, 4), GetScissorState(regs, 5), + GetScissorState(regs, 6), GetScissorState(regs, 7), GetScissorState(regs, 8), + GetScissorState(regs, 9), GetScissorState(regs, 10), GetScissorState(regs, 11), + GetScissorState(regs, 12), GetScissorState(regs, 13), GetScissorState(regs, 14), + GetScissorState(regs, 15)}; + scheduler.Record([scissors](auto cmdbuf, auto& dld) { + cmdbuf.setScissor(0, static_cast<u32>(scissors.size()), scissors.data(), dld); + }); +} + +void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.polygon_offset && scheduler.TouchDepthBias()) { + return; + } + gpu.dirty.polygon_offset = false; + const auto& regs = gpu.regs; + scheduler.Record([constant = regs.polygon_offset_units, clamp = regs.polygon_offset_clamp, + factor = regs.polygon_offset_factor](auto cmdbuf, auto& dld) { + cmdbuf.setDepthBias(constant, clamp, factor / 2.0f, dld); + }); +} + +void RasterizerVulkan::UpdateBlendConstants(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.blend_state && scheduler.TouchBlendConstants()) { + return; + } + gpu.dirty.blend_state = false; + const std::array blend_color = {gpu.regs.blend_color.r, gpu.regs.blend_color.g, + gpu.regs.blend_color.b, gpu.regs.blend_color.a}; + scheduler.Record([blend_color](auto cmdbuf, auto& dld) { + cmdbuf.setBlendConstants(blend_color.data(), dld); + }); +} + +void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.depth_bounds_values && scheduler.TouchDepthBounds()) { + return; + } + gpu.dirty.depth_bounds_values = false; + const auto& regs = gpu.regs; + scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]]( + auto cmdbuf, auto& dld) { cmdbuf.setDepthBounds(min, max, dld); }); +} + +void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D& gpu) { + if (!gpu.dirty.stencil_test && scheduler.TouchStencilValues()) { + return; + } + gpu.dirty.stencil_test = false; + const auto& regs = gpu.regs; + if (regs.stencil_two_side_enable) { + // Separate values per face + scheduler.Record( + [front_ref = regs.stencil_front_func_ref, front_write_mask = regs.stencil_front_mask, + front_test_mask = regs.stencil_front_func_mask, back_ref = regs.stencil_back_func_ref, + back_write_mask = regs.stencil_back_mask, + back_test_mask = regs.stencil_back_func_mask](auto cmdbuf, auto& dld) { + // Front face + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front_ref, dld); + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front_write_mask, dld); + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front_test_mask, dld); + + // Back face + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back_ref, dld); + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back_write_mask, dld); + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back_test_mask, dld); + }); + } else { + // Front face defines both faces + scheduler.Record([ref = regs.stencil_back_func_ref, write_mask = regs.stencil_back_mask, + test_mask = regs.stencil_back_func_mask](auto cmdbuf, auto& dld) { + cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, ref, dld); + cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, write_mask, dld); + cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, test_mask, dld); + }); + } +} + +std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const { + std::size_t size = CalculateVertexArraysSize(); + if (is_indexed) { + size = Common::AlignUp(size, 4) + CalculateIndexBufferSize(); + } + size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment()); + return size; +} + +std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const { + return Tegra::Engines::KeplerCompute::NumConstBuffers * + (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); +} + +std::size_t RasterizerVulkan::CalculateVertexArraysSize() const { + const auto& regs = system.GPU().Maxwell3D().regs; + + std::size_t size = 0; + for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { + // This implementation assumes that all attributes are used in the shader. + const GPUVAddr start{regs.vertex_array[index].StartAddress()}; + const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; + DEBUG_ASSERT(end > start); + + size += (end - start + 1) * regs.vertex_array[index].enable; + } + return size; +} + +std::size_t RasterizerVulkan::CalculateIndexBufferSize() const { + const auto& regs = system.GPU().Maxwell3D().regs; + return static_cast<std::size_t>(regs.index_array.count) * + static_cast<std::size_t>(regs.index_array.FormatSizeInBytes()); +} + +std::size_t RasterizerVulkan::CalculateConstBufferSize( + const ConstBufferEntry& entry, const Tegra::Engines::ConstBufferInfo& buffer) const { + if (entry.IsIndirect()) { + // Buffer is accessed indirectly, so upload the entire thing + return buffer.size; + } else { + // Buffer is accessed directly, upload just what we use + return entry.GetSize(); + } +} + +RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const { + using namespace VideoCore::Surface; + + const auto& regs = system.GPU().Maxwell3D().regs; + RenderPassParams renderpass_params; + + for (std::size_t rt = 0; rt < static_cast<std::size_t>(regs.rt_control.count); ++rt) { + const auto& rendertarget = regs.rt[rt]; + if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) + continue; + renderpass_params.color_attachments.push_back(RenderPassParams::ColorAttachment{ + static_cast<u32>(rt), PixelFormatFromRenderTargetFormat(rendertarget.format), + texceptions.test(rt)}); + } + + renderpass_params.has_zeta = regs.zeta_enable; + if (renderpass_params.has_zeta) { + renderpass_params.zeta_pixel_format = PixelFormatFromDepthFormat(regs.zeta.format); + renderpass_params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX]; + } + + return renderpass_params; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h new file mode 100644 index 000000000..7be71e734 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -0,0 +1,263 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <bitset> +#include <memory> +#include <utility> +#include <vector> + +#include <boost/container/static_vector.hpp> +#include <boost/functional/hash.hpp> + +#include "common/common_types.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_accelerated.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_sampler_cache.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Core { +class System; +} + +namespace Core::Frontend { +class EmuWindow; +} + +namespace Tegra::Engines { +class Maxwell3D; +} + +namespace Vulkan { + +struct VKScreenInfo; + +using ImageViewsPack = + boost::container::static_vector<vk::ImageView, Maxwell::NumRenderTargets + 1>; + +struct FramebufferCacheKey { + vk::RenderPass renderpass{}; + u32 width = 0; + u32 height = 0; + ImageViewsPack views; + + std::size_t Hash() const noexcept { + std::size_t hash = 0; + boost::hash_combine(hash, static_cast<VkRenderPass>(renderpass)); + for (const auto& view : views) { + boost::hash_combine(hash, static_cast<VkImageView>(view)); + } + boost::hash_combine(hash, width); + boost::hash_combine(hash, height); + return hash; + } + + bool operator==(const FramebufferCacheKey& rhs) const noexcept { + return std::tie(renderpass, views, width, height) == + std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height); + } +}; + +} // namespace Vulkan + +namespace std { + +template <> +struct hash<Vulkan::FramebufferCacheKey> { + std::size_t operator()(const Vulkan::FramebufferCacheKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace Vulkan { + +class BufferBindings; + +struct ImageView { + View view; + vk::ImageLayout* layout = nullptr; +}; + +class RasterizerVulkan : public VideoCore::RasterizerAccelerated { +public: + explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window, + VKScreenInfo& screen_info, const VKDevice& device, + VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + VKScheduler& scheduler); + ~RasterizerVulkan() override; + + bool DrawBatch(bool is_indexed) override; + bool DrawMultiBatch(bool is_indexed) override; + void Clear() override; + void DispatchCompute(GPUVAddr code_addr) override; + void FlushAll() override; + void FlushRegion(CacheAddr addr, u64 size) override; + void InvalidateRegion(CacheAddr addr, u64 size) override; + void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; + void FlushCommands() override; + void TickFrame() override; + bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) override; + bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, + u32 pixel_stride) override; + + /// Maximum supported size that a constbuffer can have in bytes. + static constexpr std::size_t MaxConstbufferSize = 0x10000; + static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0, + "The maximum size of a constbuffer must be a multiple of the size of GLvec4"); + +private: + struct DrawParameters { + void Draw(vk::CommandBuffer cmdbuf, const vk::DispatchLoaderDynamic& dld) const; + + u32 base_instance = 0; + u32 num_instances = 0; + u32 base_vertex = 0; + u32 num_vertices = 0; + bool is_indexed = 0; + }; + + using Texceptions = std::bitset<Maxwell::NumRenderTargets + 1>; + + static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8; + + void Draw(bool is_indexed, bool is_instanced); + + void FlushWork(); + + Texceptions UpdateAttachments(); + + std::tuple<vk::Framebuffer, vk::Extent2D> ConfigureFramebuffers(vk::RenderPass renderpass); + + /// Setups geometry buffers and state. + DrawParameters SetupGeometry(FixedPipelineState& fixed_state, BufferBindings& buffer_bindings, + bool is_indexed, bool is_instanced); + + /// Setup descriptors in the graphics pipeline. + void SetupShaderDescriptors(const std::array<Shader, Maxwell::MaxShaderProgram>& shaders); + + void SetupImageTransitions(Texceptions texceptions, + const std::array<View, Maxwell::NumRenderTargets>& color_attachments, + const View& zeta_attachment); + + void UpdateDynamicStates(); + + bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment); + + void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, + BufferBindings& buffer_bindings); + + void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed); + + /// Setup constant buffers in the graphics pipeline. + void SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage); + + /// Setup global buffers in the graphics pipeline. + void SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage); + + /// Setup texel buffers in the graphics pipeline. + void SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage); + + /// Setup textures in the graphics pipeline. + void SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage); + + /// Setup images in the graphics pipeline. + void SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage); + + /// Setup constant buffers in the compute pipeline. + void SetupComputeConstBuffers(const ShaderEntries& entries); + + /// Setup global buffers in the compute pipeline. + void SetupComputeGlobalBuffers(const ShaderEntries& entries); + + /// Setup texel buffers in the compute pipeline. + void SetupComputeTexelBuffers(const ShaderEntries& entries); + + /// Setup textures in the compute pipeline. + void SetupComputeTextures(const ShaderEntries& entries); + + /// Setup images in the compute pipeline. + void SetupComputeImages(const ShaderEntries& entries); + + void SetupConstBuffer(const ConstBufferEntry& entry, + const Tegra::Engines::ConstBufferInfo& buffer); + + void SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address); + + void SetupTexelBuffer(const Tegra::Texture::TICEntry& image, const TexelBufferEntry& entry); + + void SetupTexture(const Tegra::Texture::FullTextureInfo& texture, const SamplerEntry& entry); + + void SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry); + + void UpdateViewportsState(Tegra::Engines::Maxwell3D& gpu); + void UpdateScissorsState(Tegra::Engines::Maxwell3D& gpu); + void UpdateDepthBias(Tegra::Engines::Maxwell3D& gpu); + void UpdateBlendConstants(Tegra::Engines::Maxwell3D& gpu); + void UpdateDepthBounds(Tegra::Engines::Maxwell3D& gpu); + void UpdateStencilFaces(Tegra::Engines::Maxwell3D& gpu); + + std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const; + + std::size_t CalculateComputeStreamBufferSize() const; + + std::size_t CalculateVertexArraysSize() const; + + std::size_t CalculateIndexBufferSize() const; + + std::size_t CalculateConstBufferSize(const ConstBufferEntry& entry, + const Tegra::Engines::ConstBufferInfo& buffer) const; + + RenderPassParams GetRenderPassParams(Texceptions texceptions) const; + + Core::System& system; + Core::Frontend::EmuWindow& render_window; + VKScreenInfo& screen_info; + const VKDevice& device; + VKResourceManager& resource_manager; + VKMemoryManager& memory_manager; + VKScheduler& scheduler; + + VKStagingBufferPool staging_pool; + VKDescriptorPool descriptor_pool; + VKUpdateDescriptorQueue update_descriptor_queue; + QuadArrayPass quad_array_pass; + Uint8Pass uint8_pass; + + VKTextureCache texture_cache; + VKPipelineCache pipeline_cache; + VKBufferCache buffer_cache; + VKSamplerCache sampler_cache; + + std::array<View, Maxwell::NumRenderTargets> color_attachments; + View zeta_attachment; + + std::vector<ImageView> sampled_views; + std::vector<ImageView> image_views; + + u32 draw_counter = 0; + + // TODO(Rodrigo): Invalidate on image destruction + std::unordered_map<FramebufferCacheKey, UniqueFramebuffer> framebuffer_cache; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp new file mode 100644 index 000000000..93f5d7ba0 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -0,0 +1,100 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <vector> + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_renderpass_cache.h" + +namespace Vulkan { + +VKRenderPassCache::VKRenderPassCache(const VKDevice& device) : device{device} {} + +VKRenderPassCache::~VKRenderPassCache() = default; + +vk::RenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) { + const auto [pair, is_cache_miss] = cache.try_emplace(params); + auto& entry = pair->second; + if (is_cache_miss) { + entry = CreateRenderPass(params); + } + return *entry; +} + +UniqueRenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& params) const { + std::vector<vk::AttachmentDescription> descriptors; + std::vector<vk::AttachmentReference> color_references; + + for (std::size_t rt = 0; rt < params.color_attachments.size(); ++rt) { + const auto attachment = params.color_attachments[rt]; + const auto format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, attachment.pixel_format); + ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", + static_cast<u32>(attachment.pixel_format)); + + // TODO(Rodrigo): Add eMayAlias when it's needed. + const auto color_layout = attachment.is_texception + ? vk::ImageLayout::eGeneral + : vk::ImageLayout::eColorAttachmentOptimal; + descriptors.emplace_back(vk::AttachmentDescriptionFlagBits::eMayAlias, format.format, + vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, + vk::AttachmentStoreOp::eDontCare, color_layout, color_layout); + color_references.emplace_back(static_cast<u32>(rt), color_layout); + } + + vk::AttachmentReference zeta_attachment_ref; + if (params.has_zeta) { + const auto format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.zeta_pixel_format); + ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}", + static_cast<u32>(params.zeta_pixel_format)); + + const auto zeta_layout = params.zeta_texception + ? vk::ImageLayout::eGeneral + : vk::ImageLayout::eDepthStencilAttachmentOptimal; + descriptors.emplace_back(vk::AttachmentDescriptionFlags{}, format.format, + vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, zeta_layout, zeta_layout); + zeta_attachment_ref = + vk::AttachmentReference(static_cast<u32>(params.color_attachments.size()), zeta_layout); + } + + const vk::SubpassDescription subpass_description( + {}, vk::PipelineBindPoint::eGraphics, 0, nullptr, static_cast<u32>(color_references.size()), + color_references.data(), nullptr, params.has_zeta ? &zeta_attachment_ref : nullptr, 0, + nullptr); + + vk::AccessFlags access; + vk::PipelineStageFlags stage; + if (!color_references.empty()) { + access |= + vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite; + stage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + } + + if (params.has_zeta) { + access |= vk::AccessFlagBits::eDepthStencilAttachmentRead | + vk::AccessFlagBits::eDepthStencilAttachmentWrite; + stage |= vk::PipelineStageFlagBits::eLateFragmentTests; + } + + const vk::SubpassDependency subpass_dependency(VK_SUBPASS_EXTERNAL, 0, stage, stage, {}, access, + {}); + + const vk::RenderPassCreateInfo create_info({}, static_cast<u32>(descriptors.size()), + descriptors.data(), 1, &subpass_description, 1, + &subpass_dependency); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createRenderPassUnique(create_info, nullptr, dld); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h new file mode 100644 index 000000000..b49b2db48 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h @@ -0,0 +1,97 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <tuple> +#include <unordered_map> + +#include <boost/container/static_vector.hpp> +#include <boost/functional/hash.hpp> + +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/surface.h" + +namespace Vulkan { + +class VKDevice; + +// TODO(Rodrigo): Optimize this structure for faster hashing + +struct RenderPassParams { + struct ColorAttachment { + u32 index = 0; + VideoCore::Surface::PixelFormat pixel_format = VideoCore::Surface::PixelFormat::Invalid; + bool is_texception = false; + + std::size_t Hash() const noexcept { + return static_cast<std::size_t>(pixel_format) | + static_cast<std::size_t>(is_texception) << 6 | + static_cast<std::size_t>(index) << 7; + } + + bool operator==(const ColorAttachment& rhs) const noexcept { + return std::tie(index, pixel_format, is_texception) == + std::tie(rhs.index, rhs.pixel_format, rhs.is_texception); + } + }; + + boost::container::static_vector<ColorAttachment, + Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> + color_attachments{}; + // TODO(Rodrigo): Unify has_zeta into zeta_pixel_format and zeta_component_type. + VideoCore::Surface::PixelFormat zeta_pixel_format = VideoCore::Surface::PixelFormat::Invalid; + bool has_zeta = false; + bool zeta_texception = false; + + std::size_t Hash() const noexcept { + std::size_t hash = 0; + for (const auto& rt : color_attachments) { + boost::hash_combine(hash, rt.Hash()); + } + boost::hash_combine(hash, zeta_pixel_format); + boost::hash_combine(hash, has_zeta); + boost::hash_combine(hash, zeta_texception); + return hash; + } + + bool operator==(const RenderPassParams& rhs) const { + return std::tie(color_attachments, zeta_pixel_format, has_zeta, zeta_texception) == + std::tie(rhs.color_attachments, rhs.zeta_pixel_format, rhs.has_zeta, + rhs.zeta_texception); + } +}; + +} // namespace Vulkan + +namespace std { + +template <> +struct hash<Vulkan::RenderPassParams> { + std::size_t operator()(const Vulkan::RenderPassParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace Vulkan { + +class VKRenderPassCache final { +public: + explicit VKRenderPassCache(const VKDevice& device); + ~VKRenderPassCache(); + + vk::RenderPass GetRenderPass(const RenderPassParams& params); + +private: + UniqueRenderPass CreateRenderPass(const RenderPassParams& params) const; + + const VKDevice& device; + std::unordered_map<RenderPassParams, UniqueRenderPass> cache; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp index 1ce583f75..0a8ec8398 100644 --- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp @@ -46,9 +46,9 @@ UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) {}, MaxwellToVK::Sampler::Filter(tsc.mag_filter), MaxwellToVK::Sampler::Filter(tsc.min_filter), MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), - MaxwellToVK::Sampler::WrapMode(tsc.wrap_u, tsc.mag_filter), - MaxwellToVK::Sampler::WrapMode(tsc.wrap_v, tsc.mag_filter), - MaxwellToVK::Sampler::WrapMode(tsc.wrap_p, tsc.mag_filter), tsc.GetLodBias(), + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), tsc.GetLodBias(), has_anisotropy, max_anisotropy, tsc.depth_compare_enabled, MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), tsc.GetMinLod(), tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack), diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index a8baf91de..dd6d2ef03 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -954,6 +954,10 @@ private: Expression Visit(const Node& node) { if (const auto operation = std::get_if<OperationNode>(&*node)) { + if (const auto amend_index = operation->GetAmendIndex()) { + [[maybe_unused]] const Type type = Visit(ir.GetAmendNode(*amend_index)).type; + ASSERT(type == Type::Void); + } const auto operation_index = static_cast<std::size_t>(operation->GetCode()); const auto decompiler = operation_decompilers[operation_index]; if (decompiler == nullptr) { @@ -1142,6 +1146,10 @@ private: } if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { + if (const auto amend_index = conditional->GetAmendIndex()) { + [[maybe_unused]] const Type type = Visit(ir.GetAmendNode(*amend_index)).type; + ASSERT(type == Type::Void); + } // It's invalid to call conditional on nested nodes, use an operation instead const Id true_label = OpLabel(); const Id skip_label = OpLabel(); @@ -1788,6 +1796,19 @@ private: return {}; } + Expression UAtomicAdd(Operation operation) { + const auto& smem = std::get<SmemNode>(*operation[0]); + Id address = AsUint(Visit(smem.GetAddress())); + address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U)); + const Id pointer = OpAccessChain(t_smem_uint, shared_memory, address); + + const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device)); + const Id semantics = Constant(t_uint, 0U); + + const Id value = AsUint(Visit(operation[1])); + return {OpAtomicIAdd(t_uint, pointer, scope, semantics, value), Type::Uint}; + } + Expression Branch(Operation operation) { const auto& target = std::get<ImmediateNode>(*operation[0]); OpStore(jmp_to, Constant(t_uint, target.GetValue())); @@ -2365,6 +2386,8 @@ private: &SPIRVDecompiler::AtomicImageXor, &SPIRVDecompiler::AtomicImageExchange, + &SPIRVDecompiler::UAtomicAdd, + &SPIRVDecompiler::Branch, &SPIRVDecompiler::BranchIndirect, &SPIRVDecompiler::PushFlowStack, diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp new file mode 100644 index 000000000..b97c4cb3d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -0,0 +1,34 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> +#include <vector> +#include "common/alignment.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" + +namespace Vulkan { + +UniqueShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data) { + // Avoid undefined behavior by copying to a staging allocation + ASSERT(code_size % sizeof(u32) == 0); + const auto data = std::make_unique<u32[]>(code_size / sizeof(u32)); + std::memcpy(data.get(), code_data, code_size); + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + const vk::ShaderModuleCreateInfo shader_ci({}, code_size, data.get()); + vk::ShaderModule shader_module; + if (dev.createShaderModule(&shader_ci, nullptr, &shader_module, dld) != vk::Result::eSuccess) { + UNREACHABLE_MSG("Shader module failed to build!"); + } + + return UniqueShaderModule(shader_module, vk::ObjectDestroy(dev, nullptr, dld)); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h new file mode 100644 index 000000000..c06d65970 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_shader_util.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" + +namespace Vulkan { + +class VKDevice; + +UniqueShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data); + +} // namespace Vulkan 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 02310375f..4d9488f49 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -13,6 +13,7 @@ #include "video_core/renderer_vulkan/declarations.h" #include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" namespace Vulkan { diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 62f1427f5..d48d3b44c 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -3,86 +3,144 @@ // Refer to the license.txt file included. #include <algorithm> -#include <memory> #include <optional> +#include <tuple> #include <vector> +#include "common/alignment.h" #include "common/assert.h" #include "video_core/renderer_vulkan/declarations.h" #include "video_core/renderer_vulkan/vk_device.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" #include "video_core/renderer_vulkan/vk_resource_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_stream_buffer.h" namespace Vulkan { +namespace { + constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; -VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler, u64 size, vk::BufferUsageFlags usage, - vk::AccessFlags access, vk::PipelineStageFlags pipeline_stage) - : device{device}, scheduler{scheduler}, buffer_size{size}, access{access}, pipeline_stage{ - pipeline_stage} { - CreateBuffers(memory_manager, usage); - ReserveWatches(WATCHES_INITIAL_RESERVE); +constexpr u64 STREAM_BUFFER_SIZE = 256 * 1024 * 1024; + +std::optional<u32> FindMemoryType(const VKDevice& device, u32 filter, + vk::MemoryPropertyFlags wanted) { + const auto properties = device.GetPhysical().getMemoryProperties(device.GetDispatchLoader()); + for (u32 i = 0; i < properties.memoryTypeCount; i++) { + if (!(filter & (1 << i))) { + continue; + } + if ((properties.memoryTypes[i].propertyFlags & wanted) == wanted) { + return i; + } + } + return {}; +} + +} // Anonymous namespace + +VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler, + vk::BufferUsageFlags usage) + : device{device}, scheduler{scheduler} { + CreateBuffers(usage); + ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); + ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); } VKStreamBuffer::~VKStreamBuffer() = default; -std::tuple<u8*, u64, bool> VKStreamBuffer::Reserve(u64 size) { - ASSERT(size <= buffer_size); +std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) { + ASSERT(size <= STREAM_BUFFER_SIZE); mapped_size = size; - if (offset + size > buffer_size) { - // The buffer would overflow, save the amount of used buffers, signal an invalidation and - // reset the state. - invalidation_mark = used_watches; - used_watches = 0; + if (alignment > 0) { + offset = Common::AlignUp(offset, alignment); + } + + WaitPendingOperations(offset); + + bool invalidated = false; + if (offset + size > STREAM_BUFFER_SIZE) { + // The buffer would overflow, save the amount of used watches and reset the state. + invalidation_mark = current_watch_cursor; + current_watch_cursor = 0; offset = 0; + + // Swap watches and reset waiting cursors. + std::swap(previous_watches, current_watches); + wait_cursor = 0; + wait_bound = 0; + + // Ensure that we don't wait for uncommitted fences. + scheduler.Flush(); + + invalidated = true; } - return {mapped_pointer + offset, offset, invalidation_mark.has_value()}; + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + const auto pointer = reinterpret_cast<u8*>(dev.mapMemory(*memory, offset, size, {}, dld)); + return {pointer, offset, invalidated}; } -void VKStreamBuffer::Send(u64 size) { +void VKStreamBuffer::Unmap(u64 size) { ASSERT_MSG(size <= mapped_size, "Reserved size is too small"); - if (invalidation_mark) { - // TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish. - scheduler.Flush(); - std::for_each(watches.begin(), watches.begin() + *invalidation_mark, - [&](auto& resource) { resource->Wait(); }); - invalidation_mark = std::nullopt; - } + const auto dev = device.GetLogical(); + dev.unmapMemory(*memory, device.GetDispatchLoader()); + + offset += size; - if (used_watches + 1 >= watches.size()) { + if (current_watch_cursor + 1 >= current_watches.size()) { // Ensure that there are enough watches. - ReserveWatches(WATCHES_RESERVE_CHUNK); + ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); } - // Add a watch for this allocation. - watches[used_watches++]->Watch(scheduler.GetFence()); - - offset += size; + auto& watch = current_watches[current_watch_cursor++]; + watch.upper_bound = offset; + watch.fence.Watch(scheduler.GetFence()); } -void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) { - const vk::BufferCreateInfo buffer_ci({}, buffer_size, usage, vk::SharingMode::eExclusive, 0, - nullptr); - +void VKStreamBuffer::CreateBuffers(vk::BufferUsageFlags usage) { + const vk::BufferCreateInfo buffer_ci({}, STREAM_BUFFER_SIZE, usage, vk::SharingMode::eExclusive, + 0, nullptr); const auto dev = device.GetLogical(); const auto& dld = device.GetDispatchLoader(); buffer = dev.createBufferUnique(buffer_ci, nullptr, dld); - commit = memory_manager.Commit(*buffer, true); - mapped_pointer = commit->GetData(); + + const auto requirements = dev.getBufferMemoryRequirements(*buffer, dld); + // Prefer device local host visible allocations (this should hit AMD's pinned memory). + auto type = FindMemoryType(device, requirements.memoryTypeBits, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent | + vk::MemoryPropertyFlagBits::eDeviceLocal); + if (!type) { + // Otherwise search for a host visible allocation. + type = FindMemoryType(device, requirements.memoryTypeBits, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + ASSERT_MSG(type, "No host visible and coherent memory type found"); + } + const vk::MemoryAllocateInfo alloc_ci(requirements.size, *type); + memory = dev.allocateMemoryUnique(alloc_ci, nullptr, dld); + + dev.bindBufferMemory(*buffer, *memory, 0, dld); } -void VKStreamBuffer::ReserveWatches(std::size_t grow_size) { - const std::size_t previous_size = watches.size(); - watches.resize(previous_size + grow_size); - std::generate(watches.begin() + previous_size, watches.end(), - []() { return std::make_unique<VKFenceWatch>(); }); +void VKStreamBuffer::ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size) { + watches.resize(watches.size() + grow_size); +} + +void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { + if (!invalidation_mark) { + return; + } + while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) { + auto& watch = previous_watches[wait_cursor]; + wait_bound = watch.upper_bound; + watch.fence.Wait(); + ++wait_cursor; + } } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h index 842e54162..187c0c612 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.h +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h @@ -4,28 +4,24 @@ #pragma once -#include <memory> #include <optional> #include <tuple> #include <vector> #include "common/common_types.h" #include "video_core/renderer_vulkan/declarations.h" -#include "video_core/renderer_vulkan/vk_memory_manager.h" namespace Vulkan { class VKDevice; class VKFence; class VKFenceWatch; -class VKResourceManager; class VKScheduler; -class VKStreamBuffer { +class VKStreamBuffer final { public: - explicit VKStreamBuffer(const VKDevice& device, VKMemoryManager& memory_manager, - VKScheduler& scheduler, u64 size, vk::BufferUsageFlags usage, - vk::AccessFlags access, vk::PipelineStageFlags pipeline_stage); + explicit VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler, + vk::BufferUsageFlags usage); ~VKStreamBuffer(); /** @@ -34,39 +30,47 @@ public: * @returns A tuple in the following order: Raw memory pointer (with offset added), buffer * offset and a boolean that's true when buffer has been invalidated. */ - std::tuple<u8*, u64, bool> Reserve(u64 size); + std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment); /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. - void Send(u64 size); + void Unmap(u64 size); - vk::Buffer GetBuffer() const { + vk::Buffer GetHandle() const { return *buffer; } private: + struct Watch final { + VKFenceWatch fence; + u64 upper_bound{}; + }; + /// Creates Vulkan buffer handles committing the required the required memory. - void CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage); + void CreateBuffers(vk::BufferUsageFlags usage); /// Increases the amount of watches available. - void ReserveWatches(std::size_t grow_size); + void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size); + + void WaitPendingOperations(u64 requested_upper_bound); const VKDevice& device; ///< Vulkan device manager. VKScheduler& scheduler; ///< Command scheduler. - const u64 buffer_size; ///< Total size of the stream buffer. const vk::AccessFlags access; ///< Access usage of this stream buffer. const vk::PipelineStageFlags pipeline_stage; ///< Pipeline usage of this stream buffer. - UniqueBuffer buffer; ///< Mapped buffer. - VKMemoryCommit commit; ///< Memory commit. - u8* mapped_pointer{}; ///< Pointer to the host visible commit + UniqueBuffer buffer; ///< Mapped buffer. + UniqueDeviceMemory memory; ///< Memory allocation. u64 offset{}; ///< Buffer iterator. u64 mapped_size{}; ///< Size reserved for the current copy. - std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Total watches - std::size_t used_watches{}; ///< Count of watches, reset on invalidation. - std::optional<std::size_t> - invalidation_mark{}; ///< Number of watches used in the current invalidation. + std::vector<Watch> current_watches; ///< Watches recorded in the current iteration. + std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation. + std::optional<std::size_t> invalidation_mark; ///< Number of watches used in the previous cycle. + + std::vector<Watch> previous_watches; ///< Watches used in the previous iteration. + std::size_t wait_cursor{}; ///< Last watch being waited for completion. + u64 wait_bound{}; ///< Highest offset being watched for completion. }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index ebc68f030..f47b691a8 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -123,7 +123,7 @@ bool VKSwapchain::Present(vk::Semaphore render_semaphore, VKFence& fence) { ASSERT(fences[image_index] == nullptr); fences[image_index] = &fence; - frame_index = (frame_index + 1) % image_count; + frame_index = (frame_index + 1) % static_cast<u32>(image_count); return recreated; } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index a1e7938d2..2f3b2ccd5 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -40,19 +40,19 @@ public: return extent; } - u32 GetImageCount() const { + std::size_t GetImageCount() const { return image_count; } - u32 GetImageIndex() const { + std::size_t GetImageIndex() const { return image_index; } - vk::Image GetImageIndex(u32 index) const { + vk::Image GetImageIndex(std::size_t index) const { return images[index]; } - vk::ImageView GetImageViewIndex(u32 index) const { + vk::ImageView GetImageViewIndex(std::size_t index) const { return *image_views[index]; } @@ -77,7 +77,7 @@ private: UniqueSwapchainKHR swapchain; - u32 image_count{}; + std::size_t image_count{}; std::vector<vk::Image> images; std::vector<UniqueImageView> image_views; std::vector<UniqueFramebuffer> framebuffers; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp new file mode 100644 index 000000000..51b0d38a6 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -0,0 +1,475 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstring> +#include <memory> +#include <variant> +#include <vector> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/morton.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" +#include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/surface.h" +#include "video_core/textures/convert.h" + +namespace Vulkan { + +using VideoCore::MortonSwizzle; +using VideoCore::MortonSwizzleMode; + +using Tegra::Texture::SwizzleSource; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::SurfaceCompression; +using VideoCore::Surface::SurfaceTarget; + +namespace { + +vk::ImageType SurfaceTargetToImage(SurfaceTarget target) { + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture1DArray: + return vk::ImageType::e1D; + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + return vk::ImageType::e2D; + case SurfaceTarget::Texture3D: + return vk::ImageType::e3D; + } + UNREACHABLE_MSG("Unknown texture target={}", static_cast<u32>(target)); + return {}; +} + +vk::ImageAspectFlags PixelFormatToImageAspect(PixelFormat pixel_format) { + if (pixel_format < PixelFormat::MaxColorFormat) { + return vk::ImageAspectFlagBits::eColor; + } else if (pixel_format < PixelFormat::MaxDepthFormat) { + return vk::ImageAspectFlagBits::eDepth; + } else if (pixel_format < PixelFormat::MaxDepthStencilFormat) { + return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + } else { + UNREACHABLE_MSG("Invalid pixel format={}", static_cast<u32>(pixel_format)); + return vk::ImageAspectFlagBits::eColor; + } +} + +vk::ImageViewType GetImageViewType(SurfaceTarget target) { + switch (target) { + case SurfaceTarget::Texture1D: + return vk::ImageViewType::e1D; + case SurfaceTarget::Texture2D: + return vk::ImageViewType::e2D; + case SurfaceTarget::Texture3D: + return vk::ImageViewType::e3D; + case SurfaceTarget::Texture1DArray: + return vk::ImageViewType::e1DArray; + case SurfaceTarget::Texture2DArray: + return vk::ImageViewType::e2DArray; + case SurfaceTarget::TextureCubemap: + return vk::ImageViewType::eCube; + case SurfaceTarget::TextureCubeArray: + return vk::ImageViewType::eCubeArray; + case SurfaceTarget::TextureBuffer: + break; + } + UNREACHABLE(); + return {}; +} + +UniqueBuffer CreateBuffer(const VKDevice& device, const SurfaceParams& params) { + // TODO(Rodrigo): Move texture buffer creation to the buffer cache + const vk::BufferCreateInfo buffer_ci({}, params.GetHostSizeInBytes(), + vk::BufferUsageFlagBits::eUniformTexelBuffer | + vk::BufferUsageFlagBits::eTransferSrc | + vk::BufferUsageFlagBits::eTransferDst, + vk::SharingMode::eExclusive, 0, nullptr); + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + return dev.createBufferUnique(buffer_ci, nullptr, dld); +} + +vk::BufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device, + const SurfaceParams& params, + vk::Buffer buffer) { + ASSERT(params.IsBuffer()); + + const auto format = + MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format; + return vk::BufferViewCreateInfo({}, buffer, format, 0, params.GetHostSizeInBytes()); +} + +vk::ImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceParams& params) { + constexpr auto sample_count = vk::SampleCountFlagBits::e1; + constexpr auto tiling = vk::ImageTiling::eOptimal; + + ASSERT(!params.IsBuffer()); + + const auto [format, attachable, storage] = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.pixel_format); + + auto image_usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eTransferSrc; + if (attachable) { + image_usage |= params.IsPixelFormatZeta() ? vk::ImageUsageFlagBits::eDepthStencilAttachment + : vk::ImageUsageFlagBits::eColorAttachment; + } + if (storage) { + image_usage |= vk::ImageUsageFlagBits::eStorage; + } + + vk::ImageCreateFlags flags; + vk::Extent3D extent; + switch (params.target) { + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + flags |= vk::ImageCreateFlagBits::eCubeCompatible; + [[fallthrough]]; + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture2DArray: + extent = vk::Extent3D(params.width, params.height, 1); + break; + case SurfaceTarget::Texture3D: + extent = vk::Extent3D(params.width, params.height, params.depth); + break; + case SurfaceTarget::TextureBuffer: + UNREACHABLE(); + } + + return vk::ImageCreateInfo(flags, SurfaceTargetToImage(params.target), format, extent, + params.num_levels, static_cast<u32>(params.GetNumLayers()), + sample_count, tiling, image_usage, vk::SharingMode::eExclusive, 0, + nullptr, vk::ImageLayout::eUndefined); +} + +} // Anonymous namespace + +CachedSurface::CachedSurface(Core::System& system, const VKDevice& device, + VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + VKScheduler& scheduler, VKStagingBufferPool& staging_pool, + GPUVAddr gpu_addr, const SurfaceParams& params) + : SurfaceBase<View>{gpu_addr, params}, system{system}, device{device}, + resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler}, + staging_pool{staging_pool} { + if (params.IsBuffer()) { + buffer = CreateBuffer(device, params); + commit = memory_manager.Commit(*buffer, false); + + const auto buffer_view_ci = GenerateBufferViewCreateInfo(device, params, *buffer); + format = buffer_view_ci.format; + + const auto dev = device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + buffer_view = dev.createBufferViewUnique(buffer_view_ci, nullptr, dld); + } else { + const auto image_ci = GenerateImageCreateInfo(device, params); + format = image_ci.format; + + image.emplace(device, scheduler, image_ci, PixelFormatToImageAspect(params.pixel_format)); + commit = memory_manager.Commit(image->GetHandle(), false); + } + + // TODO(Rodrigo): Move this to a virtual function. + main_view = CreateViewInner( + ViewParams(params.target, 0, static_cast<u32>(params.GetNumLayers()), 0, params.num_levels), + true); +} + +CachedSurface::~CachedSurface() = default; + +void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) { + // To upload data we have to be outside of a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + if (params.IsBuffer()) { + UploadBuffer(staging_buffer); + } else { + UploadImage(staging_buffer); + } +} + +void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { + UNIMPLEMENTED_IF(params.IsBuffer()); + + if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5U) { + LOG_WARNING(Render_Vulkan, "A1B5G5R5 flushing is stubbed"); + } + + // We can't copy images to buffers inside a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + FullTransition(vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferRead, + vk::ImageLayout::eTransferSrcOptimal); + + const auto& buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); + // TODO(Rodrigo): Do this in a single copy + for (u32 level = 0; level < params.num_levels; ++level) { + scheduler.Record([image = image->GetHandle(), buffer = *buffer.handle, + copy = GetBufferImageCopy(level)](auto cmdbuf, auto& dld) { + cmdbuf.copyImageToBuffer(image, vk::ImageLayout::eTransferSrcOptimal, buffer, {copy}, + dld); + }); + } + scheduler.Finish(); + + // TODO(Rodrigo): Use an intern buffer for staging buffers and avoid this unnecessary memcpy. + std::memcpy(staging_buffer.data(), buffer.commit->Map(host_memory_size), host_memory_size); +} + +void CachedSurface::DecorateSurfaceName() { + // TODO(Rodrigo): Add name decorations +} + +View CachedSurface::CreateView(const ViewParams& params) { + return CreateViewInner(params, false); +} + +View CachedSurface::CreateViewInner(const ViewParams& params, bool is_proxy) { + // TODO(Rodrigo): Add name decorations + return views[params] = std::make_shared<CachedSurfaceView>(device, *this, params, is_proxy); +} + +void CachedSurface::UploadBuffer(const std::vector<u8>& staging_buffer) { + const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); + std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size); + + scheduler.Record([src_buffer = *src_buffer.handle, dst_buffer = *buffer, + size = params.GetHostSizeInBytes()](auto cmdbuf, auto& dld) { + const vk::BufferCopy copy(0, 0, size); + cmdbuf.copyBuffer(src_buffer, dst_buffer, {copy}, dld); + + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eVertexShader, {}, {}, + {vk::BufferMemoryBarrier(vk::AccessFlagBits::eTransferWrite, + vk::AccessFlagBits::eShaderRead, 0, 0, dst_buffer, 0, size)}, + {}, dld); + }); +} + +void CachedSurface::UploadImage(const std::vector<u8>& staging_buffer) { + const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true); + std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size); + + FullTransition(vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite, + vk::ImageLayout::eTransferDstOptimal); + + for (u32 level = 0; level < params.num_levels; ++level) { + vk::BufferImageCopy copy = GetBufferImageCopy(level); + const auto& dld = device.GetDispatchLoader(); + if (image->GetAspectMask() == + (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil)) { + vk::BufferImageCopy depth = copy; + vk::BufferImageCopy stencil = copy; + depth.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth; + stencil.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil; + scheduler.Record([buffer = *src_buffer.handle, image = image->GetHandle(), depth, + stencil](auto cmdbuf, auto& dld) { + cmdbuf.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, + {depth, stencil}, dld); + }); + } else { + scheduler.Record([buffer = *src_buffer.handle, image = image->GetHandle(), + copy](auto cmdbuf, auto& dld) { + cmdbuf.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, + {copy}, dld); + }); + } + } +} + +vk::BufferImageCopy CachedSurface::GetBufferImageCopy(u32 level) const { + const u32 vk_depth = params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1; + const auto compression_type = params.GetCompressionType(); + const std::size_t mip_offset = compression_type == SurfaceCompression::Converted + ? params.GetConvertedMipmapOffset(level) + : params.GetHostMipmapLevelOffset(level); + + return vk::BufferImageCopy( + mip_offset, 0, 0, + {image->GetAspectMask(), level, 0, static_cast<u32>(params.GetNumLayers())}, {0, 0, 0}, + {params.GetMipWidth(level), params.GetMipHeight(level), vk_depth}); +} + +vk::ImageSubresourceRange CachedSurface::GetImageSubresourceRange() const { + return {image->GetAspectMask(), 0, params.num_levels, 0, + static_cast<u32>(params.GetNumLayers())}; +} + +CachedSurfaceView::CachedSurfaceView(const VKDevice& device, CachedSurface& surface, + const ViewParams& params, bool is_proxy) + : VideoCommon::ViewBase{params}, params{surface.GetSurfaceParams()}, + image{surface.GetImageHandle()}, buffer_view{surface.GetBufferViewHandle()}, + aspect_mask{surface.GetAspectMask()}, device{device}, surface{surface}, + base_layer{params.base_layer}, num_layers{params.num_layers}, base_level{params.base_level}, + num_levels{params.num_levels}, image_view_type{image ? GetImageViewType(params.target) + : vk::ImageViewType{}} {} + +CachedSurfaceView::~CachedSurfaceView() = default; + +vk::ImageView CachedSurfaceView::GetHandle(SwizzleSource x_source, SwizzleSource y_source, + SwizzleSource z_source, SwizzleSource w_source) { + const u32 swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source); + if (last_image_view && last_swizzle == swizzle) { + return last_image_view; + } + last_swizzle = swizzle; + + const auto [entry, is_cache_miss] = view_cache.try_emplace(swizzle); + auto& image_view = entry->second; + if (!is_cache_miss) { + return last_image_view = *image_view; + } + + auto swizzle_x = MaxwellToVK::SwizzleSource(x_source); + auto swizzle_y = MaxwellToVK::SwizzleSource(y_source); + auto swizzle_z = MaxwellToVK::SwizzleSource(z_source); + auto swizzle_w = MaxwellToVK::SwizzleSource(w_source); + + if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5U) { + // A1B5G5R5 is implemented as A1R5G5B5, we have to change the swizzle here. + std::swap(swizzle_x, swizzle_z); + } + + // Games can sample depth or stencil values on textures. This is decided by the swizzle value on + // hardware. To emulate this on Vulkan we specify it in the aspect. + vk::ImageAspectFlags aspect = aspect_mask; + if (aspect == (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil)) { + UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G); + const bool is_first = x_source == SwizzleSource::R; + switch (params.pixel_format) { + case VideoCore::Surface::PixelFormat::Z24S8: + case VideoCore::Surface::PixelFormat::Z32FS8: + aspect = is_first ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eStencil; + break; + case VideoCore::Surface::PixelFormat::S8Z24: + aspect = is_first ? vk::ImageAspectFlagBits::eStencil : vk::ImageAspectFlagBits::eDepth; + break; + default: + aspect = vk::ImageAspectFlagBits::eDepth; + UNIMPLEMENTED(); + } + + // Vulkan doesn't seem to understand swizzling of a depth stencil image, use identity + swizzle_x = vk::ComponentSwizzle::eR; + swizzle_y = vk::ComponentSwizzle::eG; + swizzle_z = vk::ComponentSwizzle::eB; + swizzle_w = vk::ComponentSwizzle::eA; + } + + const vk::ImageViewCreateInfo image_view_ci( + {}, surface.GetImageHandle(), image_view_type, surface.GetImage().GetFormat(), + {swizzle_x, swizzle_y, swizzle_z, swizzle_w}, + {aspect, base_level, num_levels, base_layer, num_layers}); + + const auto dev = device.GetLogical(); + image_view = dev.createImageViewUnique(image_view_ci, nullptr, device.GetDispatchLoader()); + return last_image_view = *image_view; +} + +VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, + const VKDevice& device, VKResourceManager& resource_manager, + VKMemoryManager& memory_manager, VKScheduler& scheduler, + VKStagingBufferPool& staging_pool) + : TextureCache(system, rasterizer), device{device}, resource_manager{resource_manager}, + memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {} + +VKTextureCache::~VKTextureCache() = default; + +Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) { + return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager, + scheduler, staging_pool, gpu_addr, params); +} + +void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) { + const bool src_3d = src_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D; + const bool dst_3d = dst_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D; + UNIMPLEMENTED_IF(src_3d); + + // The texture cache handles depth in OpenGL terms, we have to handle it as subresource and + // dimension respectively. + const u32 dst_base_layer = dst_3d ? 0 : copy_params.dest_z; + const u32 dst_offset_z = dst_3d ? copy_params.dest_z : 0; + + const u32 extent_z = dst_3d ? copy_params.depth : 1; + const u32 num_layers = dst_3d ? 1 : copy_params.depth; + + // We can't copy inside a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + src_surface->Transition(copy_params.source_z, copy_params.depth, copy_params.source_level, 1, + vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferRead, + vk::ImageLayout::eTransferSrcOptimal); + dst_surface->Transition( + dst_base_layer, num_layers, copy_params.dest_level, 1, vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eTransferDstOptimal); + + const auto& dld{device.GetDispatchLoader()}; + const vk::ImageSubresourceLayers src_subresource( + src_surface->GetAspectMask(), copy_params.source_level, copy_params.source_z, num_layers); + const vk::ImageSubresourceLayers dst_subresource( + dst_surface->GetAspectMask(), copy_params.dest_level, dst_base_layer, num_layers); + const vk::Offset3D src_offset(copy_params.source_x, copy_params.source_y, 0); + const vk::Offset3D dst_offset(copy_params.dest_x, copy_params.dest_y, dst_offset_z); + const vk::Extent3D extent(copy_params.width, copy_params.height, extent_z); + const vk::ImageCopy copy(src_subresource, src_offset, dst_subresource, dst_offset, extent); + const vk::Image src_image = src_surface->GetImageHandle(); + const vk::Image dst_image = dst_surface->GetImageHandle(); + scheduler.Record([src_image, dst_image, copy](auto cmdbuf, auto& dld) { + cmdbuf.copyImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image, + vk::ImageLayout::eTransferDstOptimal, {copy}, dld); + }); +} + +void VKTextureCache::ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) { + // We can't blit inside a renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + + src_view->Transition(vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferRead); + dst_view->Transition(vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + + const auto& cfg = copy_config; + const auto src_top_left = vk::Offset3D(cfg.src_rect.left, cfg.src_rect.top, 0); + const auto src_bot_right = vk::Offset3D(cfg.src_rect.right, cfg.src_rect.bottom, 1); + const auto dst_top_left = vk::Offset3D(cfg.dst_rect.left, cfg.dst_rect.top, 0); + const auto dst_bot_right = vk::Offset3D(cfg.dst_rect.right, cfg.dst_rect.bottom, 1); + const vk::ImageBlit blit(src_view->GetImageSubresourceLayers(), {src_top_left, src_bot_right}, + dst_view->GetImageSubresourceLayers(), {dst_top_left, dst_bot_right}); + const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; + + const auto& dld{device.GetDispatchLoader()}; + scheduler.Record([src_image = src_view->GetImage(), dst_image = dst_view->GetImage(), blit, + is_linear](auto cmdbuf, auto& dld) { + cmdbuf.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image, + vk::ImageLayout::eTransferDstOptimal, {blit}, + is_linear ? vk::Filter::eLinear : vk::Filter::eNearest, dld); + }); +} + +void VKTextureCache::BufferCopy(Surface& src_surface, Surface& dst_surface) { + // Currently unimplemented. PBO copies should be dropped and we should use a render pass to + // convert from color to depth and viceversa. + LOG_WARNING(Render_Vulkan, "Unimplemented"); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h new file mode 100644 index 000000000..d3edbe80c --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -0,0 +1,239 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <unordered_map> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_cache.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_image.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/textures/decoders.h" + +namespace Core { +class System; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace Vulkan { + +class RasterizerVulkan; +class VKDevice; +class VKResourceManager; +class VKScheduler; +class VKStagingBufferPool; + +class CachedSurfaceView; +class CachedSurface; + +using Surface = std::shared_ptr<CachedSurface>; +using View = std::shared_ptr<CachedSurfaceView>; +using TextureCacheBase = VideoCommon::TextureCache<Surface, View>; + +using VideoCommon::SurfaceParams; +using VideoCommon::ViewParams; + +class CachedSurface final : public VideoCommon::SurfaceBase<View> { + friend CachedSurfaceView; + +public: + explicit CachedSurface(Core::System& system, const VKDevice& device, + VKResourceManager& resource_manager, VKMemoryManager& memory_manager, + VKScheduler& scheduler, VKStagingBufferPool& staging_pool, + GPUVAddr gpu_addr, const SurfaceParams& params); + ~CachedSurface(); + + void UploadTexture(const std::vector<u8>& staging_buffer) override; + void DownloadTexture(std::vector<u8>& staging_buffer) override; + + void FullTransition(vk::PipelineStageFlags new_stage_mask, vk::AccessFlags new_access, + vk::ImageLayout new_layout) { + image->Transition(0, static_cast<u32>(params.GetNumLayers()), 0, params.num_levels, + new_stage_mask, new_access, new_layout); + } + + void Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels, + vk::PipelineStageFlags new_stage_mask, vk::AccessFlags new_access, + vk::ImageLayout new_layout) { + image->Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask, + new_access, new_layout); + } + + VKImage& GetImage() { + return *image; + } + + const VKImage& GetImage() const { + return *image; + } + + vk::Image GetImageHandle() const { + return image->GetHandle(); + } + + vk::ImageAspectFlags GetAspectMask() const { + return image->GetAspectMask(); + } + + vk::BufferView GetBufferViewHandle() const { + return *buffer_view; + } + +protected: + void DecorateSurfaceName(); + + View CreateView(const ViewParams& params) override; + View CreateViewInner(const ViewParams& params, bool is_proxy); + +private: + void UploadBuffer(const std::vector<u8>& staging_buffer); + + void UploadImage(const std::vector<u8>& staging_buffer); + + vk::BufferImageCopy GetBufferImageCopy(u32 level) const; + + vk::ImageSubresourceRange GetImageSubresourceRange() const; + + Core::System& system; + const VKDevice& device; + VKResourceManager& resource_manager; + VKMemoryManager& memory_manager; + VKScheduler& scheduler; + VKStagingBufferPool& staging_pool; + + std::optional<VKImage> image; + UniqueBuffer buffer; + UniqueBufferView buffer_view; + VKMemoryCommit commit; + + vk::Format format; +}; + +class CachedSurfaceView final : public VideoCommon::ViewBase { +public: + explicit CachedSurfaceView(const VKDevice& device, CachedSurface& surface, + const ViewParams& params, bool is_proxy); + ~CachedSurfaceView(); + + vk::ImageView GetHandle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source); + + bool IsSameSurface(const CachedSurfaceView& rhs) const { + return &surface == &rhs.surface; + } + + vk::ImageView GetHandle() { + return GetHandle(Tegra::Texture::SwizzleSource::R, Tegra::Texture::SwizzleSource::G, + Tegra::Texture::SwizzleSource::B, Tegra::Texture::SwizzleSource::A); + } + + u32 GetWidth() const { + return params.GetMipWidth(base_level); + } + + u32 GetHeight() const { + return params.GetMipHeight(base_level); + } + + bool IsBufferView() const { + return buffer_view; + } + + vk::Image GetImage() const { + return image; + } + + vk::BufferView GetBufferView() const { + return buffer_view; + } + + vk::ImageSubresourceRange GetImageSubresourceRange() const { + return {aspect_mask, base_level, num_levels, base_layer, num_layers}; + } + + vk::ImageSubresourceLayers GetImageSubresourceLayers() const { + return {surface.GetAspectMask(), base_level, base_layer, num_layers}; + } + + void Transition(vk::ImageLayout new_layout, vk::PipelineStageFlags new_stage_mask, + vk::AccessFlags new_access) const { + surface.Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask, + new_access, new_layout); + } + + void MarkAsModified(u64 tick) { + surface.MarkAsModified(true, tick); + } + +private: + static u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source) { + return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) | + (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source); + } + + // Store a copy of these values to avoid double dereference when reading them + const SurfaceParams params; + const vk::Image image; + const vk::BufferView buffer_view; + const vk::ImageAspectFlags aspect_mask; + + const VKDevice& device; + CachedSurface& surface; + const u32 base_layer; + const u32 num_layers; + const u32 base_level; + const u32 num_levels; + const vk::ImageViewType image_view_type; + + vk::ImageView last_image_view; + u32 last_swizzle{}; + + std::unordered_map<u32, UniqueImageView> view_cache; +}; + +class VKTextureCache final : public TextureCacheBase { +public: + explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, + const VKDevice& device, VKResourceManager& resource_manager, + VKMemoryManager& memory_manager, VKScheduler& scheduler, + VKStagingBufferPool& staging_pool); + ~VKTextureCache(); + +private: + Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override; + + void ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) override; + + void ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) override; + + void BufferCopy(Surface& src_surface, Surface& dst_surface) override; + + const VKDevice& device; + VKResourceManager& resource_manager; + VKMemoryManager& memory_manager; + VKScheduler& scheduler; + VKStagingBufferPool& staging_pool; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp new file mode 100644 index 000000000..0e577b9ff --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -0,0 +1,57 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <variant> +#include <boost/container/static_vector.hpp> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" + +namespace Vulkan { + +VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler) + : device{device}, scheduler{scheduler} {} + +VKUpdateDescriptorQueue::~VKUpdateDescriptorQueue() = default; + +void VKUpdateDescriptorQueue::TickFrame() { + payload.clear(); +} + +void VKUpdateDescriptorQueue::Acquire() { + entries.clear(); +} + +void VKUpdateDescriptorQueue::Send(vk::DescriptorUpdateTemplate update_template, + vk::DescriptorSet set) { + if (payload.size() + entries.size() >= payload.max_size()) { + LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread"); + scheduler.WaitWorker(); + payload.clear(); + } + + const auto payload_start = payload.data() + payload.size(); + for (const auto& entry : entries) { + if (const auto image = std::get_if<vk::DescriptorImageInfo>(&entry)) { + payload.push_back(*image); + } else if (const auto buffer = std::get_if<Buffer>(&entry)) { + payload.emplace_back(*buffer->buffer, buffer->offset, buffer->size); + } else if (const auto texel = std::get_if<vk::BufferView>(&entry)) { + payload.push_back(*texel); + } else { + UNREACHABLE(); + } + } + + scheduler.Record([dev = device.GetLogical(), payload_start, set, + update_template]([[maybe_unused]] auto cmdbuf, auto& dld) { + dev.updateDescriptorSetWithTemplate(set, update_template, payload_start, dld); + }); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h new file mode 100644 index 000000000..8c825aa29 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -0,0 +1,86 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> +#include <variant> +#include <boost/container/static_vector.hpp> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" + +namespace Vulkan { + +class VKDevice; +class VKScheduler; + +class DescriptorUpdateEntry { +public: + explicit DescriptorUpdateEntry() : image{} {} + + DescriptorUpdateEntry(vk::DescriptorImageInfo image) : image{image} {} + + DescriptorUpdateEntry(vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size) + : buffer{buffer, offset, size} {} + + DescriptorUpdateEntry(vk::BufferView texel_buffer) : texel_buffer{texel_buffer} {} + +private: + union { + vk::DescriptorImageInfo image; + vk::DescriptorBufferInfo buffer; + vk::BufferView texel_buffer; + }; +}; + +class VKUpdateDescriptorQueue final { +public: + explicit VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler); + ~VKUpdateDescriptorQueue(); + + void TickFrame(); + + void Acquire(); + + void Send(vk::DescriptorUpdateTemplate update_template, vk::DescriptorSet set); + + void AddSampledImage(vk::Sampler sampler, vk::ImageView image_view) { + entries.emplace_back(vk::DescriptorImageInfo{sampler, image_view, {}}); + } + + void AddImage(vk::ImageView image_view) { + entries.emplace_back(vk::DescriptorImageInfo{{}, image_view, {}}); + } + + void AddBuffer(const vk::Buffer* buffer, u64 offset, std::size_t size) { + entries.push_back(Buffer{buffer, offset, size}); + } + + void AddTexelBuffer(vk::BufferView texel_buffer) { + entries.emplace_back(texel_buffer); + } + + vk::ImageLayout* GetLastImageLayout() { + return &std::get<vk::DescriptorImageInfo>(entries.back()).imageLayout; + } + +private: + struct Buffer { + const vk::Buffer* buffer{}; + u64 offset{}; + std::size_t size{}; + }; + using Variant = std::variant<vk::DescriptorImageInfo, Buffer, vk::BufferView>; + // Old gcc versions don't consider this trivially copyable. + // static_assert(std::is_trivially_copyable_v<Variant>); + + const VKDevice& device; + VKScheduler& scheduler; + + boost::container::static_vector<Variant, 0x400> entries; + boost::container::static_vector<DescriptorUpdateEntry, 0x10000> payload; +}; + +} // namespace Vulkan diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp index b427ac873..0229733b6 100644 --- a/src/video_core/shader/control_flow.cpp +++ b/src/video_core/shader/control_flow.cpp @@ -65,7 +65,7 @@ struct BlockInfo { struct CFGRebuildState { explicit CFGRebuildState(const ProgramCode& program_code, u32 start, ConstBufferLocker& locker) - : program_code{program_code}, start{start}, locker{locker} {} + : program_code{program_code}, locker{locker}, start{start} {} const ProgramCode& program_code; ConstBufferLocker& locker; diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp index c934d0719..7591a715f 100644 --- a/src/video_core/shader/decode/memory.cpp +++ b/src/video_core/shader/decode/memory.cpp @@ -6,6 +6,7 @@ #include <vector> #include <fmt/format.h> +#include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -15,6 +16,8 @@ namespace VideoCommon::Shader { +using Tegra::Shader::AtomicOp; +using Tegra::Shader::AtomicType; using Tegra::Shader::Attribute; using Tegra::Shader::Instruction; using Tegra::Shader::OpCode; @@ -22,34 +25,39 @@ using Tegra::Shader::Register; namespace { -u32 GetLdgMemorySize(Tegra::Shader::UniformType uniform_type) { +bool IsUnaligned(Tegra::Shader::UniformType uniform_type) { + return uniform_type == Tegra::Shader::UniformType::UnsignedByte || + uniform_type == Tegra::Shader::UniformType::UnsignedShort; +} + +u32 GetUnalignedMask(Tegra::Shader::UniformType uniform_type) { switch (uniform_type) { case Tegra::Shader::UniformType::UnsignedByte: - case Tegra::Shader::UniformType::Single: - return 1; - case Tegra::Shader::UniformType::Double: - return 2; - case Tegra::Shader::UniformType::Quad: - case Tegra::Shader::UniformType::UnsignedQuad: - return 4; + return 0b11; + case Tegra::Shader::UniformType::UnsignedShort: + return 0b10; default: - UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type)); - return 1; + UNREACHABLE(); + return 0; } } -u32 GetStgMemorySize(Tegra::Shader::UniformType uniform_type) { +u32 GetMemorySize(Tegra::Shader::UniformType uniform_type) { switch (uniform_type) { + case Tegra::Shader::UniformType::UnsignedByte: + return 8; + case Tegra::Shader::UniformType::UnsignedShort: + return 16; case Tegra::Shader::UniformType::Single: - return 1; + return 32; case Tegra::Shader::UniformType::Double: - return 2; + return 64; case Tegra::Shader::UniformType::Quad: case Tegra::Shader::UniformType::UnsignedQuad: - return 4; + return 128; default: UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type)); - return 1; + return 32; } } @@ -184,9 +192,10 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { }(); const auto [real_address_base, base_address, descriptor] = - TrackGlobalMemory(bb, instr, false); + TrackGlobalMemory(bb, instr, true, false); - const u32 count = GetLdgMemorySize(type); + const u32 size = GetMemorySize(type); + const u32 count = Common::AlignUp(size, 32) / 32; if (!real_address_base || !base_address) { // Tracking failed, load zeroes. for (u32 i = 0; i < count; ++i) { @@ -200,14 +209,15 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { const Node real_address = Operation(OperationCode::UAdd, real_address_base, it_offset); Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor); - if (type == Tegra::Shader::UniformType::UnsignedByte) { - // To handle unaligned loads get the byte used to dereferenced global memory - // and extract that byte from the loaded uint32. - Node byte = Operation(OperationCode::UBitwiseAnd, real_address, Immediate(3)); - byte = Operation(OperationCode::ULogicalShiftLeft, std::move(byte), Immediate(3)); + // To handle unaligned loads get the bytes used to dereference global memory and extract + // those bytes from the loaded u32. + if (IsUnaligned(type)) { + Node mask = Immediate(GetUnalignedMask(type)); + Node offset = Operation(OperationCode::UBitwiseAnd, real_address, std::move(mask)); + offset = Operation(OperationCode::ULogicalShiftLeft, offset, Immediate(3)); - gmem = Operation(OperationCode::UBitfieldExtract, std::move(gmem), std::move(byte), - Immediate(8)); + gmem = Operation(OperationCode::UBitfieldExtract, std::move(gmem), + std::move(offset), Immediate(size)); } SetTemporary(bb, i, gmem); @@ -295,23 +305,53 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } }(); + // For unaligned reads we have to read memory too. + const bool is_read = IsUnaligned(type); const auto [real_address_base, base_address, descriptor] = - TrackGlobalMemory(bb, instr, true); + TrackGlobalMemory(bb, instr, is_read, true); if (!real_address_base || !base_address) { // Tracking failed, skip the store. break; } - const u32 count = GetStgMemorySize(type); + const u32 size = GetMemorySize(type); + const u32 count = Common::AlignUp(size, 32) / 32; for (u32 i = 0; i < count; ++i) { const Node it_offset = Immediate(i * 4); const Node real_address = Operation(OperationCode::UAdd, real_address_base, it_offset); const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor); - const Node value = GetRegister(instr.gpr0.Value() + i); + Node value = GetRegister(instr.gpr0.Value() + i); + + if (IsUnaligned(type)) { + Node mask = Immediate(GetUnalignedMask(type)); + Node offset = Operation(OperationCode::UBitwiseAnd, real_address, std::move(mask)); + offset = Operation(OperationCode::ULogicalShiftLeft, offset, Immediate(3)); + + value = Operation(OperationCode::UBitfieldInsert, gmem, std::move(value), offset, + Immediate(size)); + } + bb.push_back(Operation(OperationCode::Assign, gmem, value)); } break; } + case OpCode::Id::ATOMS: { + UNIMPLEMENTED_IF_MSG(instr.atoms.operation != AtomicOp::Add, "operation={}", + static_cast<int>(instr.atoms.operation.Value())); + UNIMPLEMENTED_IF_MSG(instr.atoms.type != AtomicType::U32, "type={}", + static_cast<int>(instr.atoms.type.Value())); + + const s32 offset = instr.atoms.GetImmediateOffset(); + Node address = GetRegister(instr.gpr8); + address = Operation(OperationCode::IAdd, std::move(address), Immediate(offset)); + + Node memory = GetSharedMemory(std::move(address)); + Node data = GetRegister(instr.gpr20); + + Node value = Operation(OperationCode::UAtomicAdd, std::move(memory), std::move(data)); + SetRegister(bb, instr.gpr0, std::move(value)); + break; + } case OpCode::Id::AL2P: { // Ignore al2p.direction since we don't care about it. @@ -336,7 +376,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackGlobalMemory(NodeBlock& bb, Instruction instr, - bool is_write) { + bool is_read, bool is_write) { const auto addr_register{GetRegister(instr.gmem.gpr)}; const auto immediate_offset{static_cast<u32>(instr.gmem.offset)}; @@ -351,11 +391,8 @@ std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackGlobalMemory(NodeBlock& const GlobalMemoryBase descriptor{index, offset}; const auto& [entry, is_new] = used_global_memory.try_emplace(descriptor); auto& usage = entry->second; - if (is_write) { - usage.is_written = true; - } else { - usage.is_read = true; - } + usage.is_written |= is_write; + usage.is_read |= is_read; const auto real_address = Operation(OperationCode::UAdd, NO_PRECISE, Immediate(immediate_offset), addr_register); diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index cf99cc5be..0b567e39d 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp @@ -801,14 +801,10 @@ std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement( std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coord_count, bool is_tld4) { - const auto [coord_offsets, size, wrap_value, - diff_value] = [is_tld4]() -> std::tuple<std::array<u32, 3>, u32, s32, s32> { - if (is_tld4) { - return {{0, 8, 16}, 6, 32, 64}; - } else { - return {{0, 4, 8}, 4, 8, 16}; - } - }(); + const std::array coord_offsets = is_tld4 ? std::array{0U, 8U, 16U} : std::array{0U, 4U, 8U}; + const u32 size = is_tld4 ? 6 : 4; + const s32 wrap_value = is_tld4 ? 32 : 8; + const s32 diff_value = is_tld4 ? 64 : 16; const u32 mask = (1U << size) - 1; std::vector<Node> aoffi; @@ -821,7 +817,7 @@ std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coor LOG_WARNING(HW_GPU, "AOFFI constant folding failed, some hardware might have graphical issues"); for (std::size_t coord = 0; coord < coord_count; ++coord) { - const Node value = BitfieldExtract(aoffi_reg, coord_offsets.at(coord), size); + const Node value = BitfieldExtract(aoffi_reg, coord_offsets[coord], size); const Node condition = Operation(OperationCode::LogicalIGreaterEqual, value, Immediate(wrap_value)); const Node negative = Operation(OperationCode::IAdd, value, Immediate(-diff_value)); @@ -831,7 +827,7 @@ std::vector<Node> ShaderIR::GetAoffiCoordinates(Node aoffi_reg, std::size_t coor } for (std::size_t coord = 0; coord < coord_count; ++coord) { - s32 value = (*aoffi_immediate >> coord_offsets.at(coord)) & mask; + s32 value = (*aoffi_immediate >> coord_offsets[coord]) & mask; if (value >= wrap_value) { value -= diff_value; } diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 4d2f4d6a8..075c7d07c 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -162,6 +162,8 @@ enum class OperationCode { AtomicImageXor, /// (MetaImage, int[N] coords) -> void AtomicImageExchange, /// (MetaImage, int[N] coords) -> void + UAtomicAdd, /// (smem, uint) -> uint + Branch, /// (uint branch_target) -> void BranchIndirect, /// (uint branch_target) -> void PushFlowStack, /// (uint branch_target) -> void @@ -392,8 +394,30 @@ struct MetaImage { using Meta = std::variant<MetaArithmetic, MetaTexture, MetaImage, MetaStackClass, Tegra::Shader::HalfType>; +class AmendNode { +public: + std::optional<std::size_t> GetAmendIndex() const { + if (amend_index == amend_null_index) { + return std::nullopt; + } + return {amend_index}; + } + + void SetAmendIndex(std::size_t index) { + amend_index = index; + } + + void ClearAmend() { + amend_index = amend_null_index; + } + +private: + static constexpr std::size_t amend_null_index = 0xFFFFFFFFFFFFFFFFULL; + std::size_t amend_index{amend_null_index}; +}; + /// Holds any kind of operation that can be done in the IR -class OperationNode final { +class OperationNode final : public AmendNode { public: explicit OperationNode(OperationCode code) : OperationNode(code, Meta{}) {} @@ -433,7 +457,7 @@ private: }; /// Encloses inside any kind of node that returns a boolean conditionally-executed code -class ConditionalNode final { +class ConditionalNode final : public AmendNode { public: explicit ConditionalNode(Node condition, std::vector<Node>&& code) : condition{std::move(condition)}, code{std::move(code)} {} diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp index 1d9825c76..31eecb3f4 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -446,4 +446,10 @@ Node ShaderIR::BitfieldInsert(Node base, Node insert, u32 offset, u32 bits) { Immediate(bits)); } +std::size_t ShaderIR::DeclareAmend(Node new_amend) { + const std::size_t id = amend_code.size(); + amend_code.push_back(new_amend); + return id; +} + } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index baed06ccd..ba1db4c11 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -176,6 +176,10 @@ public: /// Returns a condition code evaluated from internal flags Node GetConditionCode(Tegra::Shader::ConditionCode cc) const; + const Node& GetAmendNode(std::size_t index) const { + return amend_code[index]; + } + private: friend class ASTDecoder; @@ -390,7 +394,10 @@ private: std::tuple<Node, Node, GlobalMemoryBase> TrackGlobalMemory(NodeBlock& bb, Tegra::Shader::Instruction instr, - bool is_write); + bool is_read, bool is_write); + + /// Register new amending code and obtain the reference id. + std::size_t DeclareAmend(Node new_amend); const ProgramCode& program_code; const u32 main_offset; @@ -406,6 +413,7 @@ private: std::map<u32, NodeBlock> basic_blocks; NodeBlock global_code; ASTManager program_manager{true, true}; + std::vector<Node> amend_code; std::set<u32> used_registers; std::set<Tegra::Shader::Pred> used_predicates; diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 271e67533..81fb9f633 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -95,7 +95,7 @@ constexpr std::array<Table, 74> DefinitionTable = {{ {TextureFormat::ZF32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::Z32F}, {TextureFormat::Z16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::Z16}, {TextureFormat::S8Z24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8Z24}, - {TextureFormat::ZF32_X24S8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::Z32FS8}, + {TextureFormat::ZF32_X24S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::Z32FS8}, {TextureFormat::DXT1, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1}, {TextureFormat::DXT1, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::DXT1_SRGB}, diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index 992b5c022..9256fd6d9 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -209,6 +209,11 @@ public: return target == VideoCore::Surface::SurfaceTarget::TextureBuffer; } + /// Returns the number of layers in the surface. + std::size_t GetNumLayers() const { + return is_layered ? depth : 1; + } + /// Returns the debug name of the texture for use in graphic debuggers. std::string TargetName() const; @@ -287,10 +292,6 @@ private: /// Returns the size of a layer std::size_t GetLayerSize(bool as_host_size, bool uncompressed) const; - std::size_t GetNumLayers() const { - return is_layered ? depth : 1; - } - /// Returns true if these parameters are from a layered surface. bool IsLayered() const; }; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 07a720494..7490fb718 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -215,18 +215,11 @@ void GRenderWindow::moveContext() { } void GRenderWindow::SwapBuffers() { - // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`, - // since we never call `doneCurrent` in this thread. - // However: - // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called - // since the last time `swapBuffers` was executed; - // - On macOS, if `makeCurrent` isn't called explicitly, resizing the buffer breaks. - context->makeCurrent(child); - context->swapBuffers(child); + if (!first_frame) { - emit FirstFrameDisplayed(); first_frame = true; + emit FirstFrameDisplayed(); } } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index f92a4b3c3..59918847a 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -10,6 +10,7 @@ #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" +#include "input_common/udp/client.h" #include "yuzu/configuration/config.h" #include "yuzu/uisettings.h" @@ -429,6 +430,16 @@ void Config::ReadControlValues() { QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) .toString() .toStdString(); + Settings::values.udp_input_address = + ReadSetting(QStringLiteral("udp_input_address"), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) + .toString() + .toStdString(); + Settings::values.udp_input_port = static_cast<u16>( + ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT) + .toInt()); + Settings::values.udp_pad_index = + static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); qt_config->endGroup(); } @@ -911,6 +922,12 @@ void Config::SaveControlValues() { QString::fromStdString(Settings::values.motion_device), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); + WriteSetting(QStringLiteral("udp_input_address"), + QString::fromStdString(Settings::values.udp_input_address), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); + WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, + InputCommon::CemuhookUDP::DEFAULT_PORT); + WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 25b2e1b05..8497eaa14 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -73,11 +73,11 @@ void ConfigureDialog::RetranslateUI() { Q_DECLARE_METATYPE(QList<QWidget*>); void ConfigureDialog::PopulateSelectionList() { - const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ + const std::array<std::pair<QString, QList<QWidget*>>, 5> items{ {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, - {tr("System"), - {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}}, + {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, {tr("Graphics"), {ui->graphicsTab}}, + {tr("Audio"), {ui->audioTab}}, {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, }; diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index daedbc33e..e43e84d39 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -21,10 +21,8 @@ constexpr std::array default_icon_sizes{ }; constexpr std::array row_text_names{ - QT_TR_NOOP("Filename"), - QT_TR_NOOP("Filetype"), - QT_TR_NOOP("Title ID"), - QT_TR_NOOP("Title Name"), + QT_TR_NOOP("Filename"), QT_TR_NOOP("Filetype"), QT_TR_NOOP("Title ID"), + QT_TR_NOOP("Title Name"), QT_TR_NOOP("None"), }; } // Anonymous namespace @@ -46,6 +44,12 @@ ConfigureGameList::ConfigureGameList(QWidget* parent) &ConfigureGameList::RequestGameListUpdate); connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureGameList::RequestGameListUpdate); + + // Update text ComboBoxes after user interaction. + connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated), + [=]() { ConfigureGameList::UpdateSecondRowComboBox(); }); + connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated), + [=]() { ConfigureGameList::UpdateFirstRowComboBox(); }); } ConfigureGameList::~ConfigureGameList() = default; @@ -68,10 +72,6 @@ void ConfigureGameList::SetConfiguration() { ui->show_add_ons->setChecked(UISettings::values.show_add_ons); ui->icon_size_combobox->setCurrentIndex( ui->icon_size_combobox->findData(UISettings::values.icon_size)); - ui->row_1_text_combobox->setCurrentIndex( - ui->row_1_text_combobox->findData(UISettings::values.row_1_text_id)); - ui->row_2_text_combobox->setCurrentIndex( - ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); } void ConfigureGameList::changeEvent(QEvent* event) { @@ -104,10 +104,43 @@ void ConfigureGameList::InitializeIconSizeComboBox() { } void ConfigureGameList::InitializeRowComboBoxes() { - for (std::size_t i = 0; i < row_text_names.size(); ++i) { - const QString row_text_name = QString::fromUtf8(row_text_names[i]); + UpdateFirstRowComboBox(true); + UpdateSecondRowComboBox(true); +} + +void ConfigureGameList::UpdateFirstRowComboBox(bool init) { + const int currentIndex = + init ? UISettings::values.row_1_text_id + : ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData()); + ui->row_1_text_combobox->clear(); + + for (std::size_t i = 0; i < row_text_names.size(); i++) { + const QString row_text_name = QString::fromUtf8(row_text_names[i]); ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); + } + + ui->row_1_text_combobox->setCurrentIndex(ui->row_1_text_combobox->findData(currentIndex)); + + ui->row_1_text_combobox->removeItem(4); // None + ui->row_1_text_combobox->removeItem( + ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData())); +} + +void ConfigureGameList::UpdateSecondRowComboBox(bool init) { + const int currentIndex = + init ? UISettings::values.row_2_text_id + : ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData()); + + ui->row_2_text_combobox->clear(); + + for (std::size_t i = 0; i < row_text_names.size(); ++i) { + const QString row_text_name = QString::fromUtf8(row_text_names[i]); ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); } + + ui->row_2_text_combobox->setCurrentIndex(ui->row_2_text_combobox->findData(currentIndex)); + + ui->row_2_text_combobox->removeItem( + ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData())); } diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index e11822919..ecd3fa174 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -31,5 +31,8 @@ private: void InitializeIconSizeComboBox(); void InitializeRowComboBoxes(); + void UpdateFirstRowComboBox(bool init = false); + void UpdateSecondRowComboBox(bool init = false); + std::unique_ptr<Ui::ConfigureGameList> ui; }; diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 3ea0b8d67..fa9052136 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -48,6 +48,7 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { } ui->hotkey_list->expandAll(); + ui->hotkey_list->resizeColumnToContents(0); } void ConfigureHotkeys::changeEvent(QEvent* event) { diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 1c2b37afd..7cde72d1b 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -108,11 +108,14 @@ public: }}; const auto& row1 = row_data.at(UISettings::values.row_1_text_id); - const auto& row2 = row_data.at(UISettings::values.row_2_text_id); + const int row2_id = UISettings::values.row_2_text_id; - if (row1.isEmpty() || row1 == row2) - return row2; - if (row2.isEmpty()) + if (row2_id == 4) // None + return row1; + + const auto& row2 = row_data.at(row2_id); + + if (row1 == row2) return row1; return QString(row1 + QStringLiteral("\n ") + row2); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b21fbf826..b5dd3e0d6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -526,19 +526,30 @@ void GMainWindow::InitializeHotkeys() { const QString main_window = QStringLiteral("Main Window"); const QString load_file = QStringLiteral("Load File"); + const QString load_amiibo = QStringLiteral("Load Amiibo"); const QString exit_yuzu = QStringLiteral("Exit yuzu"); + const QString restart_emulation = QStringLiteral("Restart Emulation"); const QString stop_emulation = QStringLiteral("Stop Emulation"); const QString toggle_filter_bar = QStringLiteral("Toggle Filter Bar"); const QString toggle_status_bar = QStringLiteral("Toggle Status Bar"); const QString fullscreen = QStringLiteral("Fullscreen"); + const QString capture_screenshot = QStringLiteral("Capture Screenshot"); ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file)); ui.action_Load_File->setShortcutContext( hotkey_registry.GetShortcutContext(main_window, load_file)); + ui.action_Load_Amiibo->setShortcut(hotkey_registry.GetKeySequence(main_window, load_amiibo)); + ui.action_Load_Amiibo->setShortcutContext( + hotkey_registry.GetShortcutContext(main_window, load_amiibo)); + ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence(main_window, exit_yuzu)); ui.action_Exit->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, exit_yuzu)); + ui.action_Restart->setShortcut(hotkey_registry.GetKeySequence(main_window, restart_emulation)); + ui.action_Restart->setShortcutContext( + hotkey_registry.GetShortcutContext(main_window, restart_emulation)); + ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence(main_window, stop_emulation)); ui.action_Stop->setShortcutContext( hotkey_registry.GetShortcutContext(main_window, stop_emulation)); @@ -553,6 +564,11 @@ void GMainWindow::InitializeHotkeys() { ui.action_Show_Status_Bar->setShortcutContext( hotkey_registry.GetShortcutContext(main_window, toggle_status_bar)); + ui.action_Capture_Screenshot->setShortcut( + hotkey_registry.GetKeySequence(main_window, capture_screenshot)); + ui.action_Capture_Screenshot->setShortcutContext( + hotkey_registry.GetShortcutContext(main_window, capture_screenshot)); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this), &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile); connect( diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 21f422500..a2c9e4547 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -15,7 +15,7 @@ </property> <property name="windowIcon"> <iconset> - <normaloff>src/pcafe/res/icon3_64x64.ico</normaloff>src/pcafe/res/icon3_64x64.ico</iconset> + <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset> </property> <property name="tabShape"> <enum>QTabWidget::Rounded</enum> @@ -98,6 +98,7 @@ <addaction name="action_Display_Dock_Widget_Headers"/> <addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Status_Bar"/> + <addaction name="separator"/> <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Tools"> diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index 43bad9678..738c4b2fc 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -7,10 +7,10 @@ namespace UISettings { const Themes themes{{ - {"Default", "default"}, + {"Light", "default"}, + {"Light Colorful", "colorful"}, {"Dark", "qdarkstyle"}, - {"Colorful", "colorful"}, - {"Colorful Dark", "colorful_dark"}, + {"Dark Colorful", "colorful_dark"}, }}; Values values = {}; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 1a812cb87..161583b54 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -12,6 +12,7 @@ #include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" #include "input_common/main.h" +#include "input_common/udp/client.h" #include "yuzu_cmd/config.h" #include "yuzu_cmd/default_ini.h" @@ -297,6 +298,10 @@ void Config::ReadValues() { sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); Settings::values.touchscreen.diameter_y = sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); + Settings::values.udp_input_address = + sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); + Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger( + "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); std::transform(keyboard_keys.begin(), keyboard_keys.end(), Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 8d18a4a5a..e829f8695 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -69,12 +69,29 @@ rstick= # - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: # - "update_period": update period in milliseconds (default to 100) # - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) +# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol motion_device= # for touch input, the following devices are available: # - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required +# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol +# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system touch_device= +# Most desktop operating systems do not expose a way to poll the motion state of the controllers +# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly +# from a controller device to the client program. Citra has a client that can connect and read +# from any cemuhook compatible motion program. + +# IPv4 address of the udp input server (Default "127.0.0.1") +udp_input_address= + +# Port of the udp input server. (Default 26760) +udp_input_port= + +# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) +udp_pad_index= + [Core] # Whether to use multi-core for CPU emulation # 0 (default): Disabled, 1: Enabled |