summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/common/CMakeLists.txt5
-rw-r--r--src/common/logging/backend.cpp2
-rw-r--r--src/common/logging/backend.h2
-rw-r--r--src/common/logging/log.h33
-rw-r--r--src/common/string_util.cpp22
-rw-r--r--src/common/telemetry.cpp15
-rw-r--r--src/common/thread.h9
-rw-r--r--src/common/x64/cpu_detect.cpp68
-rw-r--r--src/common/x64/cpu_detect.h31
-rw-r--r--src/core/CMakeLists.txt40
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp5
-rw-r--r--src/core/arm/exclusive_monitor.cpp14
-rw-r--r--src/core/arm/exclusive_monitor.h9
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp3
-rw-r--r--src/core/core.cpp75
-rw-r--r--src/core/core.h10
-rw-r--r--src/core/core_cpu.cpp127
-rw-r--r--src/core/core_cpu.h120
-rw-r--r--src/core/core_manager.cpp70
-rw-r--r--src/core/core_manager.h63
-rw-r--r--src/core/cpu_core_manager.cpp152
-rw-r--r--src/core/cpu_core_manager.h62
-rw-r--r--src/core/cpu_manager.cpp81
-rw-r--r--src/core/cpu_manager.h50
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp657
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.h14
-rw-r--r--src/core/gdbstub/gdbstub.cpp2
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp1
-rw-r--r--src/core/hle/kernel/kernel.cpp52
-rw-r--r--src/core/hle/kernel/kernel.h19
-rw-r--r--src/core/hle/kernel/physical_core.cpp51
-rw-r--r--src/core/hle/kernel/physical_core.h77
-rw-r--r--src/core/hle/kernel/physical_memory.h5
-rw-r--r--src/core/hle/kernel/process.cpp4
-rw-r--r--src/core/hle/kernel/scheduler.cpp1
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/kernel/thread.cpp3
-rw-r--r--src/core/hle/kernel/vm_manager.cpp37
-rw-r--r--src/core/hle/kernel/wait_object.cpp3
-rw-r--r--src/core/hle/service/acc/acc.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp14
-rw-r--r--src/core/hle/service/acc/profile_manager.h27
-rw-r--r--src/core/hle/service/friend/friend.cpp2
-rw-r--r--src/core/hle/service/mii/mii_manager.h156
-rw-r--r--src/core/hle/service/sockets/bsd.cpp48
-rw-r--r--src/core/hle/service/sockets/bsd.h4
-rw-r--r--src/core/hle/service/time/clock_types.h103
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h16
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_core.h17
-rw-r--r--src/core/hle/service/time/errors.h22
-rw-r--r--src/core/hle/service/time/interface.cpp19
-rw-r--r--src/core/hle/service/time/interface.h11
-rw-r--r--src/core/hle/service/time/local_system_clock_context_writer.h28
-rw-r--r--src/core/hle/service/time/network_system_clock_context_writer.h28
-rw-r--r--src/core/hle/service/time/standard_local_system_clock_core.h17
-rw-r--r--src/core/hle/service/time/standard_network_system_clock_core.h46
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.cpp26
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.h42
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.cpp77
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.h57
-rw-r--r--src/core/hle/service/time/steady_clock_core.h55
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.cpp55
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.h43
-rw-r--r--src/core/hle/service/time/system_clock_core.cpp72
-rw-r--r--src/core/hle/service/time/system_clock_core.h71
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.cpp24
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.h29
-rw-r--r--src/core/hle/service/time/time.cpp474
-rw-r--r--src/core/hle/service/time/time.h101
-rw-r--r--src/core/hle/service/time/time_manager.cpp137
-rw-r--r--src/core/hle/service/time/time_manager.h117
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp53
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h35
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp125
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h46
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp1039
-rw-r--r--src/core/hle/service/time/time_zone_manager.h54
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp170
-rw-r--r--src/core/hle/service/time/time_zone_service.h31
-rw-r--r--src/core/hle/service/time/time_zone_types.h87
-rw-r--r--src/core/loader/elf.cpp3
-rw-r--r--src/core/loader/kip.cpp5
-rw-r--r--src/core/loader/nso.cpp21
-rw-r--r--src/core/loader/nso.h2
-rw-r--r--src/core/memory.cpp11
-rw-r--r--src/core/memory.h16
-rw-r--r--src/core/settings.h3
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/main.cpp12
-rw-r--r--src/input_common/udp/client.cpp287
-rw-r--r--src/input_common/udp/client.h96
-rw-r--r--src/input_common/udp/protocol.cpp79
-rw-r--r--src/input_common/udp/protocol.h256
-rw-r--r--src/input_common/udp/udp.cpp96
-rw-r--r--src/input_common/udp/udp.h27
-rw-r--r--src/video_core/CMakeLists.txt9
-rw-r--r--src/video_core/engines/maxwell_3d.cpp1
-rw-r--r--src/video_core/engines/maxwell_3d.h14
-rw-r--r--src/video_core/engines/shader_bytecode.h67
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp42
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_state.h3
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp56
-rw-r--r--src/video_core/renderer_opengl/utils.cpp17
-rw-r--r--src/video_core/renderer_opengl/utils.h14
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp9
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h2
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h72
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp627
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h119
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp1141
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h252
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp98
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp475
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h239
-rw-r--r--src/video_core/shader/control_flow.cpp2
-rw-r--r--src/video_core/shader/decode/memory.cpp186
-rw-r--r--src/video_core/shader/decode/texture.cpp33
-rw-r--r--src/video_core/shader/node.h2
-rw-r--r--src/video_core/shader/shader_ir.h2
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp2
-rw-r--r--src/video_core/texture_cache/surface_base.cpp2
-rw-r--r--src/video_core/texture_cache/surface_params.h9
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure.ui6
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp12
-rw-r--r--src/yuzu/configuration/configure_general.cpp8
-rw-r--r--src/yuzu/configuration/configure_general.ui37
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp1
-rw-r--r--src/yuzu/configuration/configure_ui.cpp (renamed from src/yuzu/configuration/configure_gamelist.cpp)93
-rw-r--r--src/yuzu/configuration/configure_ui.h (renamed from src/yuzu/configuration/configure_gamelist.h)13
-rw-r--r--src/yuzu/configuration/configure_ui.ui (renamed from src/yuzu/configuration/configure_gamelist.ui)59
-rw-r--r--src/yuzu/game_list_p.h11
-rw-r--r--src/yuzu/main.cpp16
-rw-r--r--src/yuzu/main.ui3
-rw-r--r--src/yuzu/uisettings.cpp6
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h17
150 files changed, 8781 insertions, 1579 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/logging/backend.cpp b/src/common/logging/backend.cpp
index 8f2591d53..04bc3128f 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -120,7 +120,7 @@ private:
duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
entry.log_class = log_class;
entry.log_level = log_level;
- entry.filename = Common::TrimSourcePath(filename);
+ entry.filename = filename;
entry.line_num = line_nr;
entry.function = function;
entry.message = std::move(message);
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index fca0267a1..fc338c70d 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -23,7 +23,7 @@ struct Entry {
std::chrono::microseconds timestamp;
Class log_class;
Level log_level;
- std::string filename;
+ const char* filename;
unsigned int line_num;
std::string function;
std::string message;
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 259708116..13a4f1e30 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -9,6 +9,15 @@
namespace Log {
+// trims up to and including the last of ../, ..\, src/, src\ in a string
+constexpr const char* TrimSourcePath(std::string_view source) {
+ const auto rfind = [source](const std::string_view match) {
+ return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
+ };
+ auto idx = std::max({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
+ return source.data() + idx;
+}
+
/// Specifies the severity or level of detail of the log message.
enum class Level : u8 {
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
@@ -141,24 +150,24 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
#ifdef _DEBUG
#define LOG_TRACE(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
#else
#define LOG_TRACE(log_class, fmt, ...) (void(0))
#endif
#define LOG_DEBUG(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
#define LOG_INFO(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
#define LOG_WARNING(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
#define LOG_ERROR(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
#define LOG_CRITICAL(log_class, ...) \
- ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
- __func__, __VA_ARGS__)
+ ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, \
+ ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 959f278aa..84883a1d3 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -223,26 +223,4 @@ std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buff
return std::u16string(buffer.begin(), buffer.begin() + len);
}
-const char* TrimSourcePath(const char* path, const char* root) {
- const char* p = path;
-
- while (*p != '\0') {
- const char* next_slash = p;
- while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
- ++next_slash;
- }
-
- bool is_src = Common::ComparePartialString(p, next_slash, root);
- p = next_slash;
-
- if (*p != '\0') {
- ++p;
- }
- if (is_src) {
- path = p;
- }
- }
- return path;
-}
-
} // namespace Common
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..d342cafe0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -15,14 +15,14 @@ add_library(core STATIC
constants.h
core.cpp
core.h
- core_cpu.cpp
- core_cpu.h
+ core_manager.cpp
+ core_manager.h
core_timing.cpp
core_timing.h
core_timing_util.cpp
core_timing_util.h
- cpu_core_manager.cpp
- cpu_core_manager.h
+ cpu_manager.cpp
+ cpu_manager.h
crypto/aes_util.cpp
crypto/aes_util.h
crypto/encryption_layer.cpp
@@ -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
@@ -156,6 +158,8 @@ add_library(core STATIC
hle/kernel/mutex.h
hle/kernel/object.cpp
hle/kernel/object.h
+ hle/kernel/physical_core.cpp
+ hle/kernel/physical_core.h
hle/kernel/process.cpp
hle/kernel/process.h
hle/kernel/process_capability.cpp
@@ -461,12 +465,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 e825c0526..791640a3a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -10,11 +10,12 @@
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/core.h"
-#include "core/core_cpu.h"
+#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
@@ -87,7 +88,7 @@ public:
if (GDBStub::IsServerEnabled()) {
parent.jit->HaltExecution();
parent.SetPC(pc);
- Kernel::Thread* thread = Kernel::GetCurrentThread();
+ Kernel::Thread* const thread = parent.system.CurrentScheduler().GetCurrentThread();
parent.SaveContext(thread->GetContext());
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index abd59ff4b..94570e520 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -2,10 +2,24 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#endif
#include "core/arm/exclusive_monitor.h"
+#include "core/memory.h"
namespace Core {
ExclusiveMonitor::~ExclusiveMonitor() = default;
+std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
+ std::size_t num_cores) {
+#ifdef ARCHITECTURE_x86_64
+ return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
+#else
+ // TODO(merry): Passthrough exclusive monitor
+ return nullptr;
+#endif
+}
+
} // namespace Core
diff --git a/src/core/arm/exclusive_monitor.h b/src/core/arm/exclusive_monitor.h
index f59aca667..4ef418b90 100644
--- a/src/core/arm/exclusive_monitor.h
+++ b/src/core/arm/exclusive_monitor.h
@@ -4,8 +4,14 @@
#pragma once
+#include <memory>
+
#include "common/common_types.h"
+namespace Memory {
+class Memory;
+}
+
namespace Core {
class ExclusiveMonitor {
@@ -22,4 +28,7 @@ public:
virtual bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) = 0;
};
+std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
+ std::size_t num_cores);
+
} // namespace Core
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index 48182c99a..f99ad5802 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -9,6 +9,7 @@
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/svc.h"
namespace Core {
@@ -177,7 +178,7 @@ void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
}
- Kernel::Thread* thread = Kernel::GetCurrentThread();
+ Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread();
SaveContext(thread->GetContext());
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d697b80ef..c53d122be 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -11,9 +11,9 @@
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_cpu.h"
+#include "core/core_manager.h"
#include "core/core_timing.h"
-#include "core/cpu_core_manager.h"
+#include "core/cpu_manager.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/mode.h"
@@ -28,6 +28,7 @@
#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
@@ -113,16 +114,25 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
- cpu_core_manager{system}, reporter{system}, applet_manager{system} {}
+ cpu_manager{system}, reporter{system}, applet_manager{system} {}
- Cpu& CurrentCpuCore() {
- return cpu_core_manager.GetCurrentCore();
+ CoreManager& CurrentCoreManager() {
+ return cpu_manager.GetCurrentCoreManager();
+ }
+
+ Kernel::PhysicalCore& CurrentPhysicalCore() {
+ const auto index = cpu_manager.GetActiveCoreIndex();
+ return kernel.PhysicalCore(index);
+ }
+
+ Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
+ return kernel.PhysicalCore(index);
}
ResultStatus RunLoop(bool tight_loop) {
status = ResultStatus::Success;
- cpu_core_manager.RunLoop(tight_loop);
+ cpu_manager.RunLoop(tight_loop);
return status;
}
@@ -131,8 +141,8 @@ struct System::Impl {
LOG_DEBUG(HW_Memory, "initialized OK");
core_timing.Initialize();
- cpu_core_manager.Initialize();
kernel.Initialize();
+ cpu_manager.Initialize();
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
@@ -205,7 +215,6 @@ struct System::Impl {
// Main process has been loaded and been made current.
// Begin GPU and CPU execution.
gpu_core->Start();
- cpu_core_manager.StartThreads();
// Initialize cheat engine
if (cheat_engine) {
@@ -272,7 +281,7 @@ struct System::Impl {
gpu_core.reset();
// Close all CPU/threading state
- cpu_core_manager.Shutdown();
+ cpu_manager.Shutdown();
// Shutdown kernel and core timing
kernel.Shutdown();
@@ -342,7 +351,7 @@ struct System::Impl {
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
Memory::Memory memory;
- CpuCoreManager cpu_core_manager;
+ CpuManager cpu_manager;
bool is_powered_on = false;
bool exit_lock = false;
@@ -377,12 +386,12 @@ struct System::Impl {
System::System() : impl{std::make_unique<Impl>(*this)} {}
System::~System() = default;
-Cpu& System::CurrentCpuCore() {
- return impl->CurrentCpuCore();
+CoreManager& System::CurrentCoreManager() {
+ return impl->CurrentCoreManager();
}
-const Cpu& System::CurrentCpuCore() const {
- return impl->CurrentCpuCore();
+const CoreManager& System::CurrentCoreManager() const {
+ return impl->CurrentCoreManager();
}
System::ResultStatus System::RunLoop(bool tight_loop) {
@@ -394,7 +403,7 @@ System::ResultStatus System::SingleStep() {
}
void System::InvalidateCpuInstructionCaches() {
- impl->cpu_core_manager.InvalidateAllInstructionCaches();
+ impl->kernel.InvalidateAllInstructionCaches();
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
@@ -406,13 +415,11 @@ bool System::IsPoweredOn() const {
}
void System::PrepareReschedule() {
- CurrentCpuCore().PrepareReschedule();
+ impl->CurrentPhysicalCore().Stop();
}
void System::PrepareReschedule(const u32 core_index) {
- if (core_index < GlobalScheduler().CpuCoresCount()) {
- CpuCore(core_index).PrepareReschedule();
- }
+ impl->kernel.PrepareReschedule(core_index);
}
PerfStatsResults System::GetAndResetPerfStats() {
@@ -428,31 +435,31 @@ const TelemetrySession& System::TelemetrySession() const {
}
ARM_Interface& System::CurrentArmInterface() {
- return CurrentCpuCore().ArmInterface();
+ return impl->CurrentPhysicalCore().ArmInterface();
}
const ARM_Interface& System::CurrentArmInterface() const {
- return CurrentCpuCore().ArmInterface();
+ return impl->CurrentPhysicalCore().ArmInterface();
}
std::size_t System::CurrentCoreIndex() const {
- return CurrentCpuCore().CoreIndex();
+ return impl->cpu_manager.GetActiveCoreIndex();
}
Kernel::Scheduler& System::CurrentScheduler() {
- return CurrentCpuCore().Scheduler();
+ return impl->CurrentPhysicalCore().Scheduler();
}
const Kernel::Scheduler& System::CurrentScheduler() const {
- return CurrentCpuCore().Scheduler();
+ return impl->CurrentPhysicalCore().Scheduler();
}
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
- return CpuCore(core_index).Scheduler();
+ return impl->GetPhysicalCore(core_index).Scheduler();
}
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
- return CpuCore(core_index).Scheduler();
+ return impl->GetPhysicalCore(core_index).Scheduler();
}
/// Gets the global scheduler
@@ -474,28 +481,28 @@ const Kernel::Process* System::CurrentProcess() const {
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
- return CpuCore(core_index).ArmInterface();
+ return impl->GetPhysicalCore(core_index).ArmInterface();
}
const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
- return CpuCore(core_index).ArmInterface();
+ return impl->GetPhysicalCore(core_index).ArmInterface();
}
-Cpu& System::CpuCore(std::size_t core_index) {
- return impl->cpu_core_manager.GetCore(core_index);
+CoreManager& System::GetCoreManager(std::size_t core_index) {
+ return impl->cpu_manager.GetCoreManager(core_index);
}
-const Cpu& System::CpuCore(std::size_t core_index) const {
+const CoreManager& System::GetCoreManager(std::size_t core_index) const {
ASSERT(core_index < NUM_CPU_CORES);
- return impl->cpu_core_manager.GetCore(core_index);
+ return impl->cpu_manager.GetCoreManager(core_index);
}
ExclusiveMonitor& System::Monitor() {
- return impl->cpu_core_manager.GetExclusiveMonitor();
+ return impl->kernel.GetExclusiveMonitor();
}
const ExclusiveMonitor& System::Monitor() const {
- return impl->cpu_core_manager.GetExclusiveMonitor();
+ return impl->kernel.GetExclusiveMonitor();
}
Memory::Memory& System::Memory() {
diff --git a/src/core/core.h b/src/core/core.h
index e240c5c58..e69d68fcf 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -93,7 +93,7 @@ class Memory;
namespace Core {
class ARM_Interface;
-class Cpu;
+class CoreManager;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
@@ -218,10 +218,10 @@ public:
const ARM_Interface& ArmInterface(std::size_t core_index) const;
/// Gets a CPU interface to the CPU core with the specified index
- Cpu& CpuCore(std::size_t core_index);
+ CoreManager& GetCoreManager(std::size_t core_index);
/// Gets a CPU interface to the CPU core with the specified index
- const Cpu& CpuCore(std::size_t core_index) const;
+ const CoreManager& GetCoreManager(std::size_t core_index) const;
/// Gets a reference to the exclusive monitor
ExclusiveMonitor& Monitor();
@@ -364,10 +364,10 @@ private:
System();
/// Returns the currently running CPU core
- Cpu& CurrentCpuCore();
+ CoreManager& CurrentCoreManager();
/// Returns the currently running CPU core
- const Cpu& CurrentCpuCore() const;
+ const CoreManager& CurrentCoreManager() const;
/**
* Initialize the emulated system.
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
deleted file mode 100644
index 630cd4feb..000000000
--- a/src/core/core_cpu.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <condition_variable>
-#include <mutex>
-
-#include "common/logging/log.h"
-#ifdef ARCHITECTURE_x86_64
-#include "core/arm/dynarmic/arm_dynarmic.h"
-#endif
-#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-#include "core/core.h"
-#include "core/core_cpu.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/lock.h"
-#include "core/settings.h"
-
-namespace Core {
-
-void CpuBarrier::NotifyEnd() {
- std::unique_lock lock{mutex};
- end = true;
- condition.notify_all();
-}
-
-bool CpuBarrier::Rendezvous() {
- if (!Settings::values.use_multi_core) {
- // Meaningless when running in single-core mode
- return true;
- }
-
- if (!end) {
- std::unique_lock lock{mutex};
-
- --cores_waiting;
- if (!cores_waiting) {
- cores_waiting = NUM_CPU_CORES;
- condition.notify_all();
- return true;
- }
-
- condition.wait(lock);
- return true;
- }
-
- return false;
-}
-
-Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
- std::size_t core_index)
- : cpu_barrier{cpu_barrier}, global_scheduler{system.GlobalScheduler()},
- core_timing{system.CoreTiming()}, core_index{core_index} {
-#ifdef ARCHITECTURE_x86_64
- arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
-#else
- arm_interface = std::make_unique<ARM_Unicorn>(system);
- LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
-#endif
-
- scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
-}
-
-Cpu::~Cpu() = default;
-
-std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(
- [[maybe_unused]] Memory::Memory& memory, [[maybe_unused]] std::size_t num_cores) {
-#ifdef ARCHITECTURE_x86_64
- return std::make_unique<DynarmicExclusiveMonitor>(memory, num_cores);
-#else
- // TODO(merry): Passthrough exclusive monitor
- return nullptr;
-#endif
-}
-
-void Cpu::RunLoop(bool tight_loop) {
- // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
- if (!cpu_barrier.Rendezvous()) {
- // If rendezvous failed, session has been killed
- return;
- }
-
- Reschedule();
-
- // If we don't have a currently active thread then don't execute instructions,
- // instead advance to the next event and try to yield to the next thread
- if (Kernel::GetCurrentThread() == nullptr) {
- LOG_TRACE(Core, "Core-{} idling", core_index);
- core_timing.Idle();
- } else {
- if (tight_loop) {
- arm_interface->Run();
- } else {
- arm_interface->Step();
- }
- // We are stopping a run, exclusive state must be cleared
- arm_interface->ClearExclusiveState();
- }
- core_timing.Advance();
-
- Reschedule();
-}
-
-void Cpu::SingleStep() {
- return RunLoop(false);
-}
-
-void Cpu::PrepareReschedule() {
- arm_interface->PrepareReschedule();
-}
-
-void Cpu::Reschedule() {
- // Lock the global kernel mutex when we manipulate the HLE state
- std::lock_guard lock(HLE::g_hle_lock);
-
- global_scheduler.SelectThread(core_index);
- scheduler->TryDoContextSwitch();
-}
-
-void Cpu::Shutdown() {
- scheduler->Shutdown();
-}
-
-} // namespace Core
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
deleted file mode 100644
index 78f5021a2..000000000
--- a/src/core/core_cpu.h
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <atomic>
-#include <condition_variable>
-#include <cstddef>
-#include <memory>
-#include <mutex>
-#include "common/common_types.h"
-
-namespace Kernel {
-class GlobalScheduler;
-class Scheduler;
-} // namespace Kernel
-
-namespace Core {
-class System;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-}
-
-namespace Memory {
-class Memory;
-}
-
-namespace Core {
-
-class ARM_Interface;
-class ExclusiveMonitor;
-
-constexpr unsigned NUM_CPU_CORES{4};
-
-class CpuBarrier {
-public:
- bool IsAlive() const {
- return !end;
- }
-
- void NotifyEnd();
-
- bool Rendezvous();
-
-private:
- unsigned cores_waiting{NUM_CPU_CORES};
- std::mutex mutex;
- std::condition_variable condition;
- std::atomic<bool> end{};
-};
-
-class Cpu {
-public:
- Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
- std::size_t core_index);
- ~Cpu();
-
- void RunLoop(bool tight_loop = true);
-
- void SingleStep();
-
- void PrepareReschedule();
-
- ARM_Interface& ArmInterface() {
- return *arm_interface;
- }
-
- const ARM_Interface& ArmInterface() const {
- return *arm_interface;
- }
-
- Kernel::Scheduler& Scheduler() {
- return *scheduler;
- }
-
- const Kernel::Scheduler& Scheduler() const {
- return *scheduler;
- }
-
- bool IsMainCore() const {
- return core_index == 0;
- }
-
- std::size_t CoreIndex() const {
- return core_index;
- }
-
- void Shutdown();
-
- /**
- * Creates an exclusive monitor to handle exclusive reads/writes.
- *
- * @param memory The current memory subsystem that the monitor may wish
- * to keep track of.
- *
- * @param num_cores The number of cores to assume about the CPU.
- *
- * @returns The constructed exclusive monitor instance, or nullptr if the current
- * CPU backend is unable to use an exclusive monitor.
- */
- static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
- std::size_t num_cores);
-
-private:
- void Reschedule();
-
- std::unique_ptr<ARM_Interface> arm_interface;
- CpuBarrier& cpu_barrier;
- Kernel::GlobalScheduler& global_scheduler;
- std::unique_ptr<Kernel::Scheduler> scheduler;
- Timing::CoreTiming& core_timing;
-
- std::atomic<bool> reschedule_pending = false;
- std::size_t core_index;
-};
-
-} // namespace Core
diff --git a/src/core/core_manager.cpp b/src/core/core_manager.cpp
new file mode 100644
index 000000000..8eacf92dd
--- /dev/null
+++ b/src/core/core_manager.cpp
@@ -0,0 +1,70 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <condition_variable>
+#include <mutex>
+
+#include "common/logging/log.h"
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#endif
+#include "core/arm/exclusive_monitor.h"
+#include "core/arm/unicorn/arm_unicorn.h"
+#include "core/core.h"
+#include "core/core_manager.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
+#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/lock.h"
+#include "core/settings.h"
+
+namespace Core {
+
+CoreManager::CoreManager(System& system, std::size_t core_index)
+ : global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
+ core_index)},
+ core_timing{system.CoreTiming()}, core_index{core_index} {}
+
+CoreManager::~CoreManager() = default;
+
+void CoreManager::RunLoop(bool tight_loop) {
+ Reschedule();
+
+ // If we don't have a currently active thread then don't execute instructions,
+ // instead advance to the next event and try to yield to the next thread
+ if (Kernel::GetCurrentThread() == nullptr) {
+ LOG_TRACE(Core, "Core-{} idling", core_index);
+ core_timing.Idle();
+ } else {
+ if (tight_loop) {
+ physical_core.Run();
+ } else {
+ physical_core.Step();
+ }
+ }
+ core_timing.Advance();
+
+ Reschedule();
+}
+
+void CoreManager::SingleStep() {
+ return RunLoop(false);
+}
+
+void CoreManager::PrepareReschedule() {
+ physical_core.Stop();
+}
+
+void CoreManager::Reschedule() {
+ // Lock the global kernel mutex when we manipulate the HLE state
+ std::lock_guard lock(HLE::g_hle_lock);
+
+ global_scheduler.SelectThread(core_index);
+
+ physical_core.Scheduler().TryDoContextSwitch();
+}
+
+} // namespace Core
diff --git a/src/core/core_manager.h b/src/core/core_manager.h
new file mode 100644
index 000000000..b14e723d7
--- /dev/null
+++ b/src/core/core_manager.h
@@ -0,0 +1,63 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <cstddef>
+#include <memory>
+#include "common/common_types.h"
+
+namespace Kernel {
+class GlobalScheduler;
+class PhysicalCore;
+} // namespace Kernel
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+class CoreTiming;
+}
+
+namespace Memory {
+class Memory;
+}
+
+namespace Core {
+
+constexpr unsigned NUM_CPU_CORES{4};
+
+class CoreManager {
+public:
+ CoreManager(System& system, std::size_t core_index);
+ ~CoreManager();
+
+ void RunLoop(bool tight_loop = true);
+
+ void SingleStep();
+
+ void PrepareReschedule();
+
+ bool IsMainCore() const {
+ return core_index == 0;
+ }
+
+ std::size_t CoreIndex() const {
+ return core_index;
+ }
+
+private:
+ void Reschedule();
+
+ Kernel::GlobalScheduler& global_scheduler;
+ Kernel::PhysicalCore& physical_core;
+ Timing::CoreTiming& core_timing;
+
+ std::atomic<bool> reschedule_pending = false;
+ std::size_t core_index;
+};
+
+} // namespace Core
diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp
deleted file mode 100644
index f04a34133..000000000
--- a/src/core/cpu_core_manager.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "core/arm/exclusive_monitor.h"
-#include "core/core.h"
-#include "core/core_cpu.h"
-#include "core/core_timing.h"
-#include "core/cpu_core_manager.h"
-#include "core/gdbstub/gdbstub.h"
-#include "core/settings.h"
-
-namespace Core {
-namespace {
-void RunCpuCore(const System& system, Cpu& cpu_state) {
- while (system.IsPoweredOn()) {
- cpu_state.RunLoop(true);
- }
-}
-} // Anonymous namespace
-
-CpuCoreManager::CpuCoreManager(System& system) : system{system} {}
-CpuCoreManager::~CpuCoreManager() = default;
-
-void CpuCoreManager::Initialize() {
- barrier = std::make_unique<CpuBarrier>();
- exclusive_monitor = Cpu::MakeExclusiveMonitor(system.Memory(), cores.size());
-
- for (std::size_t index = 0; index < cores.size(); ++index) {
- cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
- }
-}
-
-void CpuCoreManager::StartThreads() {
- // Create threads for CPU cores 1-3, and build thread_to_cpu map
- // CPU core 0 is run on the main thread
- thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
- if (!Settings::values.use_multi_core) {
- return;
- }
-
- for (std::size_t index = 0; index < core_threads.size(); ++index) {
- core_threads[index] = std::make_unique<std::thread>(RunCpuCore, std::cref(system),
- std::ref(*cores[index + 1]));
- thread_to_cpu[core_threads[index]->get_id()] = cores[index + 1].get();
- }
-}
-
-void CpuCoreManager::Shutdown() {
- barrier->NotifyEnd();
- if (Settings::values.use_multi_core) {
- for (auto& thread : core_threads) {
- thread->join();
- thread.reset();
- }
- }
-
- thread_to_cpu.clear();
- for (auto& cpu_core : cores) {
- cpu_core->Shutdown();
- cpu_core.reset();
- }
-
- exclusive_monitor.reset();
- barrier.reset();
-}
-
-Cpu& CpuCoreManager::GetCore(std::size_t index) {
- return *cores.at(index);
-}
-
-const Cpu& CpuCoreManager::GetCore(std::size_t index) const {
- return *cores.at(index);
-}
-
-ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() {
- return *exclusive_monitor;
-}
-
-const ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() const {
- return *exclusive_monitor;
-}
-
-Cpu& CpuCoreManager::GetCurrentCore() {
- if (Settings::values.use_multi_core) {
- const auto& search = thread_to_cpu.find(std::this_thread::get_id());
- ASSERT(search != thread_to_cpu.end());
- ASSERT(search->second);
- return *search->second;
- }
-
- // Otherwise, use single-threaded mode active_core variable
- return *cores[active_core];
-}
-
-const Cpu& CpuCoreManager::GetCurrentCore() const {
- if (Settings::values.use_multi_core) {
- const auto& search = thread_to_cpu.find(std::this_thread::get_id());
- ASSERT(search != thread_to_cpu.end());
- ASSERT(search->second);
- return *search->second;
- }
-
- // Otherwise, use single-threaded mode active_core variable
- return *cores[active_core];
-}
-
-void CpuCoreManager::RunLoop(bool tight_loop) {
- // Update thread_to_cpu in case Core 0 is run from a different host thread
- thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
-
- if (GDBStub::IsServerEnabled()) {
- GDBStub::HandlePacket();
-
- // If the loop is halted and we want to step, use a tiny (1) number of instructions to
- // execute. Otherwise, get out of the loop function.
- if (GDBStub::GetCpuHaltFlag()) {
- if (GDBStub::GetCpuStepFlag()) {
- tight_loop = false;
- } else {
- return;
- }
- }
- }
-
- auto& core_timing = system.CoreTiming();
- core_timing.ResetRun();
- bool keep_running{};
- do {
- keep_running = false;
- for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
- core_timing.SwitchContext(active_core);
- if (core_timing.CanCurrentContextRun()) {
- cores[active_core]->RunLoop(tight_loop);
- }
- keep_running |= core_timing.CanCurrentContextRun();
- }
- } while (keep_running);
-
- if (GDBStub::IsServerEnabled()) {
- GDBStub::SetCpuStepFlag(false);
- }
-}
-
-void CpuCoreManager::InvalidateAllInstructionCaches() {
- for (auto& cpu : cores) {
- cpu->ArmInterface().ClearInstructionCache();
- }
-}
-
-} // namespace Core
diff --git a/src/core/cpu_core_manager.h b/src/core/cpu_core_manager.h
deleted file mode 100644
index 2cbbf8216..000000000
--- a/src/core/cpu_core_manager.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <map>
-#include <memory>
-#include <thread>
-
-namespace Core {
-
-class Cpu;
-class CpuBarrier;
-class ExclusiveMonitor;
-class System;
-
-class CpuCoreManager {
-public:
- explicit CpuCoreManager(System& system);
- CpuCoreManager(const CpuCoreManager&) = delete;
- CpuCoreManager(CpuCoreManager&&) = delete;
-
- ~CpuCoreManager();
-
- CpuCoreManager& operator=(const CpuCoreManager&) = delete;
- CpuCoreManager& operator=(CpuCoreManager&&) = delete;
-
- void Initialize();
- void StartThreads();
- void Shutdown();
-
- Cpu& GetCore(std::size_t index);
- const Cpu& GetCore(std::size_t index) const;
-
- Cpu& GetCurrentCore();
- const Cpu& GetCurrentCore() const;
-
- ExclusiveMonitor& GetExclusiveMonitor();
- const ExclusiveMonitor& GetExclusiveMonitor() const;
-
- void RunLoop(bool tight_loop);
-
- void InvalidateAllInstructionCaches();
-
-private:
- static constexpr std::size_t NUM_CPU_CORES = 4;
-
- std::unique_ptr<ExclusiveMonitor> exclusive_monitor;
- std::unique_ptr<CpuBarrier> barrier;
- std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cores;
- std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> core_threads;
- std::size_t active_core{}; ///< Active core, only used in single thread mode
-
- /// Map of guest threads to CPU cores
- std::map<std::thread::id, Cpu*> thread_to_cpu;
-
- System& system;
-};
-
-} // namespace Core
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
new file mode 100644
index 000000000..70ddbdcca
--- /dev/null
+++ b/src/core/cpu_manager.cpp
@@ -0,0 +1,81 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/arm/exclusive_monitor.h"
+#include "core/core.h"
+#include "core/core_manager.h"
+#include "core/core_timing.h"
+#include "core/cpu_manager.h"
+#include "core/gdbstub/gdbstub.h"
+
+namespace Core {
+
+CpuManager::CpuManager(System& system) : system{system} {}
+CpuManager::~CpuManager() = default;
+
+void CpuManager::Initialize() {
+ for (std::size_t index = 0; index < core_managers.size(); ++index) {
+ core_managers[index] = std::make_unique<CoreManager>(system, index);
+ }
+}
+
+void CpuManager::Shutdown() {
+ for (auto& cpu_core : core_managers) {
+ cpu_core.reset();
+ }
+}
+
+CoreManager& CpuManager::GetCoreManager(std::size_t index) {
+ return *core_managers.at(index);
+}
+
+const CoreManager& CpuManager::GetCoreManager(std::size_t index) const {
+ return *core_managers.at(index);
+}
+
+CoreManager& CpuManager::GetCurrentCoreManager() {
+ // Otherwise, use single-threaded mode active_core variable
+ return *core_managers[active_core];
+}
+
+const CoreManager& CpuManager::GetCurrentCoreManager() const {
+ // Otherwise, use single-threaded mode active_core variable
+ return *core_managers[active_core];
+}
+
+void CpuManager::RunLoop(bool tight_loop) {
+ if (GDBStub::IsServerEnabled()) {
+ GDBStub::HandlePacket();
+
+ // If the loop is halted and we want to step, use a tiny (1) number of instructions to
+ // execute. Otherwise, get out of the loop function.
+ if (GDBStub::GetCpuHaltFlag()) {
+ if (GDBStub::GetCpuStepFlag()) {
+ tight_loop = false;
+ } else {
+ return;
+ }
+ }
+ }
+
+ auto& core_timing = system.CoreTiming();
+ core_timing.ResetRun();
+ bool keep_running{};
+ do {
+ keep_running = false;
+ for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
+ core_timing.SwitchContext(active_core);
+ if (core_timing.CanCurrentContextRun()) {
+ core_managers[active_core]->RunLoop(tight_loop);
+ }
+ keep_running |= core_timing.CanCurrentContextRun();
+ }
+ } while (keep_running);
+
+ if (GDBStub::IsServerEnabled()) {
+ GDBStub::SetCpuStepFlag(false);
+ }
+}
+
+} // namespace Core
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
new file mode 100644
index 000000000..feb619e1b
--- /dev/null
+++ b/src/core/cpu_manager.h
@@ -0,0 +1,50 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+
+namespace Core {
+
+class CoreManager;
+class System;
+
+class CpuManager {
+public:
+ explicit CpuManager(System& system);
+ CpuManager(const CpuManager&) = delete;
+ CpuManager(CpuManager&&) = delete;
+
+ ~CpuManager();
+
+ CpuManager& operator=(const CpuManager&) = delete;
+ CpuManager& operator=(CpuManager&&) = delete;
+
+ void Initialize();
+ void Shutdown();
+
+ CoreManager& GetCoreManager(std::size_t index);
+ const CoreManager& GetCoreManager(std::size_t index) const;
+
+ CoreManager& GetCurrentCoreManager();
+ const CoreManager& GetCurrentCoreManager() const;
+
+ std::size_t GetActiveCoreIndex() const {
+ return active_core;
+ }
+
+ void RunLoop(bool tight_loop);
+
+private:
+ static constexpr std::size_t NUM_CPU_CORES = 4;
+
+ std::array<std::unique_ptr<CoreManager>, NUM_CPU_CORES> core_managers;
+ std::size_t active_core{}; ///< Active core, only used in single thread mode
+
+ System& system;
+};
+
+} // namespace Core
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/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 37cb28848..67e95999d 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -35,7 +35,7 @@
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/core_cpu.h"
+#include "core/core_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index db189c8e3..2ea3dcb61 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -8,7 +8,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
-#include "core/core_cpu.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/scheduler.h"
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 1d0783bd3..edd4c4259 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -3,13 +3,15 @@
// Refer to the license.txt file included.
#include <atomic>
+#include <functional>
#include <memory>
#include <mutex>
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
-
+#include "core/arm/arm_interface.h"
+#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
@@ -17,6 +19,7 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
@@ -98,6 +101,7 @@ struct KernelCore::Impl {
void Initialize(KernelCore& kernel) {
Shutdown();
+ InitializePhysicalCores();
InitializeSystemResourceLimit(kernel);
InitializeThreads();
InitializePreemption();
@@ -121,6 +125,21 @@ struct KernelCore::Impl {
global_scheduler.Shutdown();
named_ports.clear();
+
+ for (auto& core : cores) {
+ core.Shutdown();
+ }
+ cores.clear();
+
+ exclusive_monitor.reset();
+ }
+
+ void InitializePhysicalCores() {
+ exclusive_monitor =
+ Core::MakeExclusiveMonitor(system.Memory(), global_scheduler.CpuCoresCount());
+ for (std::size_t i = 0; i < global_scheduler.CpuCoresCount(); i++) {
+ cores.emplace_back(system, i, *exclusive_monitor);
+ }
}
// Creates the default system resource limit
@@ -186,6 +205,9 @@ struct KernelCore::Impl {
/// the ConnectToPort SVC.
NamedPortTable named_ports;
+ std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
+ std::vector<Kernel::PhysicalCore> cores;
+
// System context
Core::System& system;
};
@@ -240,6 +262,34 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
return impl->global_scheduler;
}
+Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
+ return impl->cores[id];
+}
+
+const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
+ return impl->cores[id];
+}
+
+Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
+ return *impl->exclusive_monitor;
+}
+
+const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
+ return *impl->exclusive_monitor;
+}
+
+void KernelCore::InvalidateAllInstructionCaches() {
+ for (std::size_t i = 0; i < impl->global_scheduler.CpuCoresCount(); i++) {
+ PhysicalCore(i).ArmInterface().ClearInstructionCache();
+ }
+}
+
+void KernelCore::PrepareReschedule(std::size_t id) {
+ if (id < impl->global_scheduler.CpuCoresCount()) {
+ impl->cores[id].Stop();
+ }
+}
+
void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
impl->named_ports.emplace(std::move(name), std::move(port));
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 3bf0068ed..fccffaf3a 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -11,8 +11,9 @@
#include "core/hle/kernel/object.h"
namespace Core {
+class ExclusiveMonitor;
class System;
-}
+} // namespace Core
namespace Core::Timing {
class CoreTiming;
@@ -25,6 +26,7 @@ class AddressArbiter;
class ClientPort;
class GlobalScheduler;
class HandleTable;
+class PhysicalCore;
class Process;
class ResourceLimit;
class Thread;
@@ -84,6 +86,21 @@ public:
/// Gets the sole instance of the global scheduler
const Kernel::GlobalScheduler& GlobalScheduler() const;
+ /// Gets the an instance of the respective physical CPU core.
+ Kernel::PhysicalCore& PhysicalCore(std::size_t id);
+
+ /// Gets the an instance of the respective physical CPU core.
+ const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
+
+ /// Stops execution of 'id' core, in order to reschedule a new thread.
+ void PrepareReschedule(std::size_t id);
+
+ Core::ExclusiveMonitor& GetExclusiveMonitor();
+
+ const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
+
+ void InvalidateAllInstructionCaches();
+
/// Adds a port to the named port table
void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
new file mode 100644
index 000000000..9303dd273
--- /dev/null
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -0,0 +1,51 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/arm/arm_interface.h"
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#endif
+#include "core/arm/exclusive_monitor.h"
+#include "core/arm/unicorn/arm_unicorn.h"
+#include "core/core.h"
+#include "core/hle/kernel/physical_core.h"
+#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/thread.h"
+
+namespace Kernel {
+
+PhysicalCore::PhysicalCore(Core::System& system, std::size_t id,
+ Core::ExclusiveMonitor& exclusive_monitor)
+ : core_index{id} {
+#ifdef ARCHITECTURE_x86_64
+ arm_interface = std::make_unique<Core::ARM_Dynarmic>(system, exclusive_monitor, core_index);
+#else
+ arm_interface = std::make_shared<Core::ARM_Unicorn>(system);
+ LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
+#endif
+
+ scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
+}
+
+PhysicalCore::~PhysicalCore() = default;
+
+void PhysicalCore::Run() {
+ arm_interface->Run();
+ arm_interface->ClearExclusiveState();
+}
+
+void PhysicalCore::Step() {
+ arm_interface->Step();
+}
+
+void PhysicalCore::Stop() {
+ arm_interface->PrepareReschedule();
+}
+
+void PhysicalCore::Shutdown() {
+ scheduler->Shutdown();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
new file mode 100644
index 000000000..4c32c0f1b
--- /dev/null
+++ b/src/core/hle/kernel/physical_core.h
@@ -0,0 +1,77 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+
+namespace Kernel {
+class Scheduler;
+} // namespace Kernel
+
+namespace Core {
+class ARM_Interface;
+class ExclusiveMonitor;
+class System;
+} // namespace Core
+
+namespace Kernel {
+
+class PhysicalCore {
+public:
+ PhysicalCore(Core::System& system, std::size_t id, Core::ExclusiveMonitor& exclusive_monitor);
+ ~PhysicalCore();
+
+ PhysicalCore(const PhysicalCore&) = delete;
+ PhysicalCore& operator=(const PhysicalCore&) = delete;
+
+ PhysicalCore(PhysicalCore&&) = default;
+ PhysicalCore& operator=(PhysicalCore&&) = default;
+
+ /// Execute current jit state
+ void Run();
+ /// Execute a single instruction in current jit.
+ void Step();
+ /// Stop JIT execution/exit
+ void Stop();
+
+ // Shutdown this physical core.
+ void Shutdown();
+
+ Core::ARM_Interface& ArmInterface() {
+ return *arm_interface;
+ }
+
+ const Core::ARM_Interface& ArmInterface() const {
+ return *arm_interface;
+ }
+
+ bool IsMainCore() const {
+ return core_index == 0;
+ }
+
+ bool IsSystemCore() const {
+ return core_index == 3;
+ }
+
+ std::size_t CoreIndex() const {
+ return core_index;
+ }
+
+ Kernel::Scheduler& Scheduler() {
+ return *scheduler;
+ }
+
+ const Kernel::Scheduler& Scheduler() const {
+ return *scheduler;
+ }
+
+private:
+ std::size_t core_index;
+ std::unique_ptr<Core::ARM_Interface> arm_interface;
+ std::unique_ptr<Kernel::Scheduler> scheduler;
+};
+
+} // namespace Kernel
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/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index d36fcd7d9..eb196a690 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -14,7 +14,6 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index dbcdb0b88..1d99bf7a2 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -15,7 +15,7 @@
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
-#include "core/core_cpu.h"
+#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/address_arbiter.h"
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index e84e5ce0d..e965b5b04 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -13,7 +13,6 @@
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/errors.h"
@@ -356,7 +355,7 @@ void Thread::SetActivity(ThreadActivity value) {
// Set status if not waiting
if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
SetStatus(ThreadStatus::Paused);
- Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
+ kernel.PrepareReschedule(processor_id);
}
} else if (status == ThreadStatus::Paused) {
// Ready to reschedule
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/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 745f2c4e8..a0c806e8f 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -7,7 +7,6 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/core_cpu.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
@@ -96,7 +95,7 @@ void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
}
if (resume) {
thread->ResumeFromWait();
- Core::System::GetInstance().PrepareReschedule(thread->GetProcessorID());
+ kernel.PrepareReschedule(thread->GetProcessorID());
}
}
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/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 884ad173b..f67fab2f9 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -42,6 +42,26 @@ void BSD::Socket(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
+void BSD::Select(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Bind(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
void BSD::Connect(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
@@ -52,6 +72,26 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
+void BSD::Listen(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
@@ -80,7 +120,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{2, &BSD::Socket, "Socket"},
{3, nullptr, "SocketExempt"},
{4, nullptr, "Open"},
- {5, nullptr, "Select"},
+ {5, &BSD::Select, "Select"},
{6, nullptr, "Poll"},
{7, nullptr, "Sysctl"},
{8, nullptr, "Recv"},
@@ -88,15 +128,15 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{10, nullptr, "Send"},
{11, &BSD::SendTo, "SendTo"},
{12, nullptr, "Accept"},
- {13, nullptr, "Bind"},
+ {13, &BSD::Bind, "Bind"},
{14, &BSD::Connect, "Connect"},
{15, nullptr, "GetPeerName"},
{16, nullptr, "GetSockName"},
{17, nullptr, "GetSockOpt"},
- {18, nullptr, "Listen"},
+ {18, &BSD::Listen, "Listen"},
{19, nullptr, "Ioctl"},
{20, nullptr, "Fcntl"},
- {21, nullptr, "SetSockOpt"},
+ {21, &BSD::SetSockOpt, "SetSockOpt"},
{22, nullptr, "Shutdown"},
{23, nullptr, "ShutdownAllSockets"},
{24, nullptr, "Write"},
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 0fe0e65c6..3098e3baf 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -18,7 +18,11 @@ private:
void RegisterClient(Kernel::HLERequestContext& ctx);
void StartMonitoring(Kernel::HLERequestContext& ctx);
void Socket(Kernel::HLERequestContext& ctx);
+ void Select(Kernel::HLERequestContext& ctx);
+ void Bind(Kernel::HLERequestContext& ctx);
void Connect(Kernel::HLERequestContext& ctx);
+ void Listen(Kernel::HLERequestContext& ctx);
+ void SetSockOpt(Kernel::HLERequestContext& ctx);
void SendTo(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
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/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 3c2a29d9b..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);
@@ -601,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 142852082..ccfed4f2e 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -153,6 +153,9 @@ 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
@@ -171,6 +174,7 @@ if (ENABLE_VULKAN)
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
@@ -190,8 +194,11 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_stream_buffer.h
renderer_vulkan/vk_swapchain.cpp
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)
+ 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..f443ec0fe 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -215,6 +215,40 @@ 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 GlobalAtomicOp : u64 {
+ Add = 0,
+ Min = 1,
+ Max = 2,
+ Inc = 3,
+ Dec = 4,
+ And = 5,
+ Or = 6,
+ Xor = 7,
+ Exch = 8,
+ SafeAdd = 10,
+};
+
+enum class GlobalAtomicType : u64 {
+ U32 = 0,
+ S32 = 1,
+ U64 = 2,
+ F32_FTZ_RN = 3,
+ F16x2_FTZ_RN = 4,
+ S64 = 5,
+};
+
enum class UniformType : u64 {
UnsignedByte = 0,
SignedByte = 1,
@@ -236,6 +270,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 +980,22 @@ union Instruction {
} stg;
union {
+ BitField<52, 4, GlobalAtomicOp> operation;
+ BitField<49, 3, GlobalAtomicType> type;
+ BitField<28, 20, s64> offset;
+ } atom;
+
+ 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 +1716,11 @@ 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
+ ATOM, // Atomic operation on 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 +2023,8 @@ 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("11101101--------", Id::ATOM, Type::Memory, "ATOM"),
+ 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/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 f9f7a97b5..a1ac3d7a9 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1019,7 +1019,6 @@ private:
}
return {{"gl_ViewportIndex", Type::Int}};
case 3:
- UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
return {{"gl_PointSize", Type::Float}};
}
return {};
@@ -1856,6 +1855,13 @@ private:
Type::Uint};
}
+ template <const std::string_view& opname, Type type>
+ Expression Atomic(Operation operation) {
+ return {fmt::format("atomic{}({}, {})", opname, Visit(operation[0]).GetCode(),
+ Visit(operation[1]).As(type)),
+ type};
+ }
+
Expression Branch(Operation operation) {
const auto target = std::get_if<ImmediateNode>(&*operation[0]);
UNIMPLEMENTED_IF(!target);
@@ -2194,6 +2200,8 @@ private:
&GLSLDecompiler::AtomicImage<Func::Xor>,
&GLSLDecompiler::AtomicImage<Func::Exchange>,
+ &GLSLDecompiler::Atomic<Func::Add, Type::Uint>,
+
&GLSLDecompiler::Branch,
&GLSLDecompiler::BranchIndirect,
&GLSLDecompiler::PushFlowStack,
@@ -2313,7 +2321,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);
@@ -2321,7 +2329,7 @@ public:
}
void operator()(const ExprOr& expr) {
- inner += "( ";
+ inner += '(';
std::visit(*this, *expr.operand1);
inner += " || ";
std::visit(*this, *expr.operand2);
@@ -2339,28 +2347,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) {
@@ -2372,8 +2359,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 {
@@ -2381,8 +2367,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..d4b81cd87 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
@@ -176,6 +176,19 @@ GLint GetSwizzleSource(SwizzleSource source) {
return GL_NONE;
}
+GLenum GetComponent(PixelFormat format, bool is_first) {
+ switch (format) {
+ case PixelFormat::Z24S8:
+ case PixelFormat::Z32FS8:
+ return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
+ case PixelFormat::S8Z24:
+ return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
+ default:
+ UNREACHABLE();
+ return GL_DEPTH_COMPONENT;
+ }
+}
+
void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
if (params.IsBuffer()) {
return;
@@ -184,7 +197,7 @@ void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, params.num_levels - 1);
+ glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(params.num_levels - 1));
if (params.num_levels == 1) {
glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f);
}
@@ -253,14 +266,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);
}
}
}
@@ -418,11 +429,21 @@ void CachedSurfaceView::ApplySwizzle(SwizzleSource x_source, SwizzleSource y_sou
if (new_swizzle == swizzle)
return;
swizzle = new_swizzle;
- const std::array<GLint, 4> gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source),
- GetSwizzleSource(z_source),
- GetSwizzleSource(w_source)};
+ const std::array gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source),
+ GetSwizzleSource(z_source), GetSwizzleSource(w_source)};
const GLuint handle = GetTexture();
- glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
+ const PixelFormat format = surface.GetSurfaceParams().pixel_format;
+ switch (format) {
+ case PixelFormat::Z24S8:
+ case PixelFormat::Z32FS8:
+ case PixelFormat::S8Z24:
+ glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
+ GetComponent(format, x_source == SwizzleSource::R));
+ break;
+ default:
+ glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
+ break;
+ }
}
OGLTextureView CachedSurfaceView::CreateTextureView() const {
@@ -531,8 +552,11 @@ void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view,
const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect;
const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear;
- glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left,
- dst_rect.top, dst_rect.right, dst_rect.bottom, buffers,
+ glBlitFramebuffer(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.top),
+ static_cast<GLint>(src_rect.right), static_cast<GLint>(src_rect.bottom),
+ static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.top),
+ static_cast<GLint>(dst_rect.right), static_cast<GLint>(dst_rect.bottom),
+ buffers,
is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST);
}
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/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_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 2e0536bf6..b155dfb49 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -191,8 +191,7 @@ UniquePipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& render
const vk::PipelineRasterizationStateCreateInfo rasterizer_ci(
{}, rs.depth_clamp_enable, false, vk::PolygonMode::eFill,
rs.cull_enable ? MaxwellToVK::CullFace(rs.cull_face) : vk::CullModeFlagBits::eNone,
- rs.cull_enable ? MaxwellToVK::FrontFace(rs.front_face) : vk::FrontFace::eCounterClockwise,
- rs.depth_bias_enable, 0.0f, 0.0f, 0.0f, 1.0f);
+ 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);
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 48e23d4cd..7ddf7d3ee 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -325,9 +325,6 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
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;
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
index fc324952b..7be71e734 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -4,10 +4,260 @@
#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 RasterizerVulkan : public VideoCore::RasterizerInterface {};
+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_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 8fe852ce8..1ab22251e 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -542,11 +542,10 @@ private:
return;
}
- for (u32 rt = 0; rt < static_cast<u32>(frag_colors.size()); ++rt) {
- if (!specialization.enabled_rendertargets[rt]) {
+ for (u32 rt = 0; rt < static_cast<u32>(std::size(frag_colors)); ++rt) {
+ if (!IsRenderTargetEnabled(rt)) {
continue;
}
-
const Id id = AddGlobalVariable(OpVariable(t_out_float4, spv::StorageClass::Output));
Name(id, fmt::format("frag_color{}", rt));
Decorate(id, spv::Decoration::Location, rt);
@@ -852,6 +851,15 @@ private:
return binding;
}
+ bool IsRenderTargetEnabled(u32 rt) const {
+ for (u32 component = 0; component < 4; ++component) {
+ if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
bool IsInputAttributeArray() const {
return stage == ShaderType::TesselationControl || stage == ShaderType::TesselationEval ||
stage == ShaderType::Geometry;
@@ -1115,15 +1123,7 @@ private:
}
if (const auto gmem = std::get_if<GmemNode>(&*node)) {
- const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor());
- const Id real = AsUint(Visit(gmem->GetRealAddress()));
- const Id base = AsUint(Visit(gmem->GetBaseAddress()));
-
- Id offset = OpISub(t_uint, real, base);
- offset = OpUDiv(t_uint, offset, Constant(t_uint, 4U));
- return {OpLoad(t_float,
- OpAccessChain(t_gmem_float, gmem_buffer, Constant(t_uint, 0U), offset)),
- Type::Float};
+ return {OpLoad(t_uint, GetGlobalMemoryPointer(*gmem)), Type::Uint};
}
if (const auto lmem = std::get_if<LmemNode>(&*node)) {
@@ -1134,10 +1134,7 @@ private:
}
if (const auto smem = std::get_if<SmemNode>(&*node)) {
- 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);
- return {OpLoad(t_uint, pointer), Type::Uint};
+ return {OpLoad(t_uint, GetSharedMemoryPointer(*smem)), Type::Uint};
}
if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
@@ -1331,20 +1328,10 @@ private:
target = {OpAccessChain(t_prv_float, local_memory, address), Type::Float};
} else if (const auto smem = std::get_if<SmemNode>(&*dest)) {
- ASSERT(stage == ShaderType::Compute);
- Id address = AsUint(Visit(smem->GetAddress()));
- address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
- target = {OpAccessChain(t_smem_uint, shared_memory, address), Type::Uint};
+ target = {GetSharedMemoryPointer(*smem), Type::Uint};
} else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
- const Id real = AsUint(Visit(gmem->GetRealAddress()));
- const Id base = AsUint(Visit(gmem->GetBaseAddress()));
- const Id diff = OpISub(t_uint, real, base);
- const Id offset = OpShiftRightLogical(t_uint, diff, Constant(t_uint, 2));
-
- const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor());
- target = {OpAccessChain(t_gmem_float, gmem_buffer, Constant(t_uint, 0), offset),
- Type::Float};
+ target = {GetGlobalMemoryPointer(*gmem), Type::Uint};
} else {
UNIMPLEMENTED();
@@ -1796,6 +1783,24 @@ private:
return {};
}
+ Expression AtomicAdd(Operation operation) {
+ Id pointer;
+ if (const auto smem = std::get_if<SmemNode>(&*operation[0])) {
+ pointer = GetSharedMemoryPointer(*smem);
+ } else if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) {
+ pointer = GetGlobalMemoryPointer(*gmem);
+ } else {
+ UNREACHABLE();
+ return {Constant(t_uint, 0), Type::Uint};
+ }
+
+ 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()));
@@ -1876,19 +1881,14 @@ private:
// rendertargets/components are skipped in the register assignment.
u32 current_reg = 0;
for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
- if (!specialization.enabled_rendertargets[rt]) {
- // Skip rendertargets that are not enabled
- continue;
- }
// TODO(Subv): Figure out how dual-source blending is configured in the Switch.
for (u32 component = 0; component < 4; ++component) {
- const Id pointer = AccessElement(t_out_float, frag_colors.at(rt), component);
- if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
- OpStore(pointer, SafeGetRegister(current_reg));
- ++current_reg;
- } else {
- OpStore(pointer, component == 3 ? v_float_one : v_float_zero);
+ if (!header.ps.IsColorComponentOutputEnabled(rt, component)) {
+ continue;
}
+ const Id pointer = AccessElement(t_out_float, frag_colors[rt], component);
+ OpStore(pointer, SafeGetRegister(current_reg));
+ ++current_reg;
}
}
if (header.ps.omap.depth) {
@@ -2227,6 +2227,22 @@ private:
return {};
}
+ Id GetGlobalMemoryPointer(const GmemNode& gmem) {
+ const Id real = AsUint(Visit(gmem.GetRealAddress()));
+ const Id base = AsUint(Visit(gmem.GetBaseAddress()));
+ const Id diff = OpISub(t_uint, real, base);
+ const Id offset = OpShiftRightLogical(t_uint, diff, Constant(t_uint, 2));
+ const Id buffer = global_buffers.at(gmem.GetDescriptor());
+ return OpAccessChain(t_gmem_uint, buffer, Constant(t_uint, 0), offset);
+ }
+
+ Id GetSharedMemoryPointer(const SmemNode& smem) {
+ ASSERT(stage == ShaderType::Compute);
+ Id address = AsUint(Visit(smem.GetAddress()));
+ address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
+ return OpAccessChain(t_smem_uint, shared_memory, address);
+ }
+
static constexpr std::array operation_decompilers = {
&SPIRVDecompiler::Assign,
@@ -2373,6 +2389,8 @@ private:
&SPIRVDecompiler::AtomicImageXor,
&SPIRVDecompiler::AtomicImageExchange,
+ &SPIRVDecompiler::AtomicAdd,
+
&SPIRVDecompiler::Branch,
&SPIRVDecompiler::BranchIndirect,
&SPIRVDecompiler::PushFlowStack,
@@ -2467,9 +2485,9 @@ private:
Id t_smem_uint{};
- const Id t_gmem_float = TypePointer(spv::StorageClass::StorageBuffer, t_float);
+ const Id t_gmem_uint = TypePointer(spv::StorageClass::StorageBuffer, t_uint);
const Id t_gmem_array =
- Name(Decorate(TypeRuntimeArray(t_float), spv::Decoration::ArrayStride, 4U), "GmemArray");
+ Name(Decorate(TypeRuntimeArray(t_uint), spv::Decoration::ArrayStride, 4U), "GmemArray");
const Id t_gmem_struct = MemberDecorate(
Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct);
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
index 10794be1c..f5dc14d9e 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -102,9 +102,6 @@ struct Specialization final {
Maxwell::TessellationSpacing spacing{};
bool clockwise{};
} tessellation;
-
- // Fragment specific
- std::bitset<8> enabled_rendertargets;
};
// Old gcc versions don't consider this trivially copyable.
// static_assert(std::is_trivially_copyable_v<Specialization>);
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_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/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..b5fbc4d58 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,44 +16,75 @@
namespace VideoCommon::Shader {
+using Tegra::Shader::AtomicOp;
+using Tegra::Shader::AtomicType;
using Tegra::Shader::Attribute;
+using Tegra::Shader::GlobalAtomicOp;
+using Tegra::Shader::GlobalAtomicType;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
using Tegra::Shader::Register;
+using Tegra::Shader::StoreType;
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;
}
}
+Node ExtractUnaligned(Node value, Node address, u32 mask, u32 size) {
+ Node offset = Operation(OperationCode::UBitwiseAnd, address, Immediate(mask));
+ offset = Operation(OperationCode::ULogicalShiftLeft, std::move(offset), Immediate(3));
+ return Operation(OperationCode::UBitfieldExtract, std::move(value), std::move(offset),
+ Immediate(size));
+}
+
+Node InsertUnaligned(Node dest, Node value, Node address, u32 mask, u32 size) {
+ Node offset = Operation(OperationCode::UBitwiseAnd, std::move(address), Immediate(mask));
+ offset = Operation(OperationCode::ULogicalShiftLeft, std::move(offset), Immediate(3));
+ return Operation(OperationCode::UBitfieldInsert, std::move(dest), std::move(value),
+ std::move(offset), Immediate(size));
+}
+
+Node Sign16Extend(Node value) {
+ Node sign = Operation(OperationCode::UBitwiseAnd, value, Immediate(1U << 15));
+ Node is_sign = Operation(OperationCode::LogicalUEqual, std::move(sign), Immediate(1U << 15));
+ Node extend = Operation(OperationCode::Select, is_sign, Immediate(0xFFFF0000), Immediate(0));
+ return Operation(OperationCode::UBitwiseOr, std::move(value), std::move(extend));
+}
+
} // Anonymous namespace
u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
@@ -128,26 +160,31 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown));
[[fallthrough]];
case OpCode::Id::LD_S: {
- const auto GetMemory = [&](s32 offset) {
+ const auto GetAddress = [&](s32 offset) {
ASSERT(offset % 4 == 0);
const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset);
- const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8),
- immediate_offset);
- return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(address)
- : GetLocalMemory(address);
+ return Operation(OperationCode::IAdd, GetRegister(instr.gpr8), immediate_offset);
+ };
+ const auto GetMemory = [&](s32 offset) {
+ return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(GetAddress(offset))
+ : GetLocalMemory(GetAddress(offset));
};
switch (instr.ldst_sl.type.Value()) {
- case Tegra::Shader::StoreType::Bits32:
- case Tegra::Shader::StoreType::Bits64:
- case Tegra::Shader::StoreType::Bits128: {
- const u32 count = [&]() {
+ case StoreType::Signed16:
+ SetRegister(bb, instr.gpr0,
+ Sign16Extend(ExtractUnaligned(GetMemory(0), GetAddress(0), 0b10, 16)));
+ break;
+ case StoreType::Bits32:
+ case StoreType::Bits64:
+ case StoreType::Bits128: {
+ const u32 count = [&] {
switch (instr.ldst_sl.type.Value()) {
- case Tegra::Shader::StoreType::Bits32:
+ case StoreType::Bits32:
return 1;
- case Tegra::Shader::StoreType::Bits64:
+ case StoreType::Bits64:
return 2;
- case Tegra::Shader::StoreType::Bits128:
+ case StoreType::Bits128:
return 4;
default:
UNREACHABLE();
@@ -184,9 +221,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 +238,10 @@ 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));
-
- gmem = Operation(OperationCode::UBitfieldExtract, std::move(gmem), std::move(byte),
- Immediate(8));
+ // To handle unaligned loads get the bytes used to dereference global memory and extract
+ // those bytes from the loaded u32.
+ if (IsUnaligned(type)) {
+ gmem = ExtractUnaligned(gmem, real_address, GetUnalignedMask(type), size);
}
SetTemporary(bb, i, gmem);
@@ -259,21 +293,28 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate);
};
- const auto set_memory = opcode->get().GetId() == OpCode::Id::ST_L
- ? &ShaderIR::SetLocalMemory
- : &ShaderIR::SetSharedMemory;
+ const bool is_local = opcode->get().GetId() == OpCode::Id::ST_L;
+ const auto set_memory = is_local ? &ShaderIR::SetLocalMemory : &ShaderIR::SetSharedMemory;
+ const auto get_memory = is_local ? &ShaderIR::GetLocalMemory : &ShaderIR::GetSharedMemory;
switch (instr.ldst_sl.type.Value()) {
- case Tegra::Shader::StoreType::Bits128:
+ case StoreType::Bits128:
(this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3));
(this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2));
[[fallthrough]];
- case Tegra::Shader::StoreType::Bits64:
+ case StoreType::Bits64:
(this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1));
[[fallthrough]];
- case Tegra::Shader::StoreType::Bits32:
+ case StoreType::Bits32:
(this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0));
break;
+ case StoreType::Signed16: {
+ Node address = GetAddress(0);
+ Node memory = (this->*get_memory)(address);
+ (this->*set_memory)(
+ bb, address, InsertUnaligned(memory, GetRegister(instr.gpr0), address, 0b10, 16));
+ break;
+ }
default:
UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(),
static_cast<u32>(instr.ldst_sl.type.Value()));
@@ -295,23 +336,67 @@ 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)) {
+ const u32 mask = GetUnalignedMask(type);
+ value = InsertUnaligned(gmem, std::move(value), real_address, mask, size);
+ }
+
bb.push_back(Operation(OperationCode::Assign, gmem, value));
}
break;
}
+ case OpCode::Id::ATOM: {
+ UNIMPLEMENTED_IF_MSG(instr.atom.operation != GlobalAtomicOp::Add, "operation={}",
+ static_cast<int>(instr.atom.operation.Value()));
+ UNIMPLEMENTED_IF_MSG(instr.atom.type != GlobalAtomicType::S32, "type={}",
+ static_cast<int>(instr.atom.type.Value()));
+
+ const auto [real_address, base_address, descriptor] =
+ TrackGlobalMemory(bb, instr, true, true);
+ if (!real_address || !base_address) {
+ // Tracking failed, skip atomic.
+ break;
+ }
+
+ Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
+ Node value = Operation(OperationCode::AtomicAdd, std::move(gmem), GetRegister(instr.gpr20));
+ SetRegister(bb, instr.gpr0, std::move(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::AtomicAdd, 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 +421,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 +436,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 4b14cdf58..0b567e39d 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -161,16 +161,16 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
case OpCode::Id::TXD: {
UNIMPLEMENTED_IF_MSG(instr.txd.UsesMiscMode(TextureMiscMode::AOFFI),
"AOFFI is not implemented");
- UNIMPLEMENTED_IF_MSG(instr.txd.is_array != 0, "TXD Array is not implemented");
+ const bool is_array = instr.txd.is_array != 0;
u64 base_reg = instr.gpr8.Value();
const auto derivate_reg = instr.gpr20.Value();
const auto texture_type = instr.txd.texture_type.Value();
const auto coord_count = GetCoordCount(texture_type);
- const Sampler* sampler = is_bindless
- ? GetBindlessSampler(base_reg, {{texture_type, false, false}})
- : GetSampler(instr.sampler, {{texture_type, false, false}});
+ const Sampler* sampler =
+ is_bindless ? GetBindlessSampler(base_reg, {{texture_type, is_array, false}})
+ : GetSampler(instr.sampler, {{texture_type, is_array, false}});
Node4 values;
if (sampler == nullptr) {
for (u32 element = 0; element < values.size(); ++element) {
@@ -179,6 +179,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
WriteTexInstructionFloat(bb, instr, values);
break;
}
+
if (is_bindless) {
base_reg++;
}
@@ -192,8 +193,14 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
derivates.push_back(GetRegister(derivate_reg + derivate + 1));
}
+ Node array_node = {};
+ if (is_array) {
+ const Node info_reg = GetRegister(base_reg + coord_count);
+ array_node = BitfieldExtract(info_reg, 0, 16);
+ }
+
for (u32 element = 0; element < values.size(); ++element) {
- MetaTexture meta{*sampler, {}, {}, {}, {}, derivates, {}, {}, {}, element};
+ MetaTexture meta{*sampler, array_node, {}, {}, {}, derivates, {}, {}, {}, element};
values[element] = Operation(OperationCode::TextureGradient, std::move(meta), coords);
}
@@ -794,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;
@@ -814,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));
@@ -824,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 4e155542a..9af1f0228 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
+ AtomicAdd, /// (memory, {u}int) -> {u}int
+
Branch, /// (uint branch_target) -> void
BranchIndirect, /// (uint branch_target) -> void
PushFlowStack, /// (uint branch_target) -> void
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index aacd0a0da..ba1db4c11 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -394,7 +394,7 @@ 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);
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_base.cpp b/src/video_core/texture_cache/surface_base.cpp
index 829268b4c..84469b7ba 100644
--- a/src/video_core/texture_cache/surface_base.cpp
+++ b/src/video_core/texture_cache/surface_base.cpp
@@ -135,7 +135,7 @@ std::vector<CopyParams> SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& i
for (u32 level = 0; level < mipmaps; level++) {
const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level);
const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level);
- result.emplace_back(width, height, layer, level);
+ result.emplace_back(0, 0, layer, 0, 0, layer, level, level, width, height, 1);
}
}
return result;
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/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 11ae1e66e..a3fb91d29 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -36,9 +36,6 @@ add_executable(yuzu
configuration/configure_filesystem.cpp
configuration/configure_filesystem.h
configuration/configure_filesystem.ui
- configuration/configure_gamelist.cpp
- configuration/configure_gamelist.h
- configuration/configure_gamelist.ui
configuration/configure_general.cpp
configuration/configure_general.h
configuration/configure_general.ui
@@ -75,6 +72,9 @@ add_executable(yuzu
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_touchscreen_advanced.ui
+ configuration/configure_ui.cpp
+ configuration/configure_ui.h
+ configuration/configure_ui.ui
configuration/configure_web.cpp
configuration/configure_web.h
configuration/configure_web.ui
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.ui b/src/yuzu/configuration/configure.ui
index 372427ae2..67b990f1a 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -48,7 +48,7 @@
<string>General</string>
</attribute>
</widget>
- <widget class="ConfigureGameList" name="gameListTab">
+ <widget class="ConfigureUi" name="uiTab">
<attribute name="title">
<string>Game List</string>
</attribute>
@@ -166,9 +166,9 @@
<container>1</container>
</customwidget>
<customwidget>
- <class>ConfigureGameList</class>
+ <class>ConfigureUi</class>
<extends>QWidget</extends>
- <header>configuration/configure_gamelist.h</header>
+ <header>configuration/configure_ui.h</header>
<container>1</container>
</customwidget>
<customwidget>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 25b2e1b05..db3b19352 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -34,7 +34,7 @@ void ConfigureDialog::SetConfiguration() {}
void ConfigureDialog::ApplyConfiguration() {
ui->generalTab->ApplyConfiguration();
- ui->gameListTab->ApplyConfiguration();
+ ui->uiTab->ApplyConfiguration();
ui->systemTab->ApplyConfiguration();
ui->profileManagerTab->ApplyConfiguration();
ui->filesystemTab->applyConfiguration();
@@ -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{
- {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
- {tr("System"),
- {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}},
+ const std::array<std::pair<QString, QList<QWidget*>>, 5> items{
+ {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}},
+ {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
{tr("Graphics"), {ui->graphicsTab}},
+ {tr("Audio"), {ui->audioTab}},
{tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
};
@@ -108,7 +108,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{ui->audioTab, tr("Audio")},
{ui->debugTab, tr("Debug")},
{ui->webTab, tr("Web")},
- {ui->gameListTab, tr("Game List")},
+ {ui->uiTab, tr("UI")},
{ui->filesystemTab, tr("Filesystem")},
{ui->serviceTab, tr("Services")},
};
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 34e1d7fea..5ef927114 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -15,11 +15,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->setupUi(this);
- for (const auto& theme : UISettings::themes) {
- ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
- QString::fromUtf8(theme.second));
- }
-
SetConfiguration();
connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
@@ -30,7 +25,6 @@ ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
- ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
@@ -41,8 +35,6 @@ void ConfigureGeneral::SetConfiguration() {
void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
- UISettings::values.theme =
- ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 26b3486ff..857119bb3 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -65,39 +65,12 @@
</property>
</widget>
</item>
- <item>
- <widget class="QCheckBox" name="toggle_background_pause">
- <property name="text">
- <string>Pause emulation when in background</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="theme_group_box">
- <property name="title">
- <string>Theme</string>
- </property>
- <layout class="QHBoxLayout" name="theme_qhbox_layout">
- <item>
- <layout class="QVBoxLayout" name="theme_qvbox_layout">
<item>
- <layout class="QHBoxLayout" name="theme_qhbox_layout_2">
- <item>
- <widget class="QLabel" name="theme_label">
- <property name="text">
- <string>Theme:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="theme_combobox"/>
- </item>
- </layout>
+ <widget class="QCheckBox" name="toggle_background_pause">
+ <property name="text">
+ <string>Pause emulation when in background</string>
+ </property>
+ </widget>
</item>
</layout>
</item>
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/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_ui.cpp
index daedbc33e..c4a84cc67 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -7,8 +7,8 @@
#include "common/common_types.h"
#include "core/settings.h"
-#include "ui_configure_gamelist.h"
-#include "yuzu/configuration/configure_gamelist.h"
+#include "ui_configure_ui.h"
+#include "yuzu/configuration/configure_ui.h"
#include "yuzu/uisettings.h"
namespace {
@@ -21,36 +21,45 @@ 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
-ConfigureGameList::ConfigureGameList(QWidget* parent)
- : QWidget(parent), ui(new Ui::ConfigureGameList) {
+ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
ui->setupUi(this);
+ for (const auto& theme : UISettings::themes) {
+ ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
+ QString::fromUtf8(theme.second));
+ }
+
InitializeIconSizeComboBox();
InitializeRowComboBoxes();
SetConfiguration();
// Force game list reload if any of the relevant settings are changed.
- connect(ui->show_unknown, &QCheckBox::stateChanged, this,
- &ConfigureGameList::RequestGameListUpdate);
+ connect(ui->show_unknown, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureGameList::RequestGameListUpdate);
+ &ConfigureUi::RequestGameListUpdate);
connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureGameList::RequestGameListUpdate);
+ &ConfigureUi::RequestGameListUpdate);
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureGameList::RequestGameListUpdate);
+ &ConfigureUi::RequestGameListUpdate);
+
+ // Update text ComboBoxes after user interaction.
+ connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
+ [=]() { ConfigureUi::UpdateSecondRowComboBox(); });
+ connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
+ [=]() { ConfigureUi::UpdateFirstRowComboBox(); });
}
-ConfigureGameList::~ConfigureGameList() = default;
+ConfigureUi::~ConfigureUi() = default;
-void ConfigureGameList::ApplyConfiguration() {
+void ConfigureUi::ApplyConfiguration() {
+ UISettings::values.theme =
+ ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
UISettings::values.show_unknown = ui->show_unknown->isChecked();
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
@@ -59,22 +68,19 @@ void ConfigureGameList::ApplyConfiguration() {
Settings::Apply();
}
-void ConfigureGameList::RequestGameListUpdate() {
+void ConfigureUi::RequestGameListUpdate() {
UISettings::values.is_game_list_reload_pending.exchange(true);
}
-void ConfigureGameList::SetConfiguration() {
+void ConfigureUi::SetConfiguration() {
+ ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->show_unknown->setChecked(UISettings::values.show_unknown);
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) {
+void ConfigureUi::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
@@ -82,7 +88,7 @@ void ConfigureGameList::changeEvent(QEvent* event) {
QWidget::changeEvent(event);
}
-void ConfigureGameList::RetranslateUI() {
+void ConfigureUi::RetranslateUI() {
ui->retranslateUi(this);
for (int i = 0; i < ui->icon_size_combobox->count(); i++) {
@@ -97,17 +103,50 @@ void ConfigureGameList::RetranslateUI() {
}
}
-void ConfigureGameList::InitializeIconSizeComboBox() {
+void ConfigureUi::InitializeIconSizeComboBox() {
for (const auto& size : default_icon_sizes) {
ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
}
}
-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]);
+void ConfigureUi::InitializeRowComboBoxes() {
+ UpdateFirstRowComboBox(true);
+ UpdateSecondRowComboBox(true);
+}
+
+void ConfigureUi::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 ConfigureUi::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_ui.h
index e11822919..d471afe99 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -8,15 +8,15 @@
#include <QWidget>
namespace Ui {
-class ConfigureGameList;
+class ConfigureUi;
}
-class ConfigureGameList : public QWidget {
+class ConfigureUi : public QWidget {
Q_OBJECT
public:
- explicit ConfigureGameList(QWidget* parent = nullptr);
- ~ConfigureGameList() override;
+ explicit ConfigureUi(QWidget* parent = nullptr);
+ ~ConfigureUi() override;
void ApplyConfiguration();
@@ -31,5 +31,8 @@ private:
void InitializeIconSizeComboBox();
void InitializeRowComboBoxes();
- std::unique_ptr<Ui::ConfigureGameList> ui;
+ void UpdateFirstRowComboBox(bool init = false);
+ void UpdateSecondRowComboBox(bool init = false);
+
+ std::unique_ptr<Ui::ConfigureUi> ui;
};
diff --git a/src/yuzu/configuration/configure_gamelist.ui b/src/yuzu/configuration/configure_ui.ui
index 7a69377e7..aa36bd112 100644
--- a/src/yuzu/configuration/configure_gamelist.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ConfigureGameList</class>
- <widget class="QWidget" name="ConfigureGameList">
+ <class>ConfigureUi</class>
+ <widget class="QWidget" name="ConfigureUi">
<property name="geometry">
<rect>
<x>0</x>
@@ -21,7 +21,34 @@
<property name="title">
<string>General</string>
</property>
- <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="theme_label">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="theme_combobox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="GameListGroupBox">
+ <property name="title">
+ <string>Game List</string>
+ </property>
+ <layout class="QHBoxLayout" name="GameListHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
@@ -38,19 +65,6 @@
</property>
</widget>
</item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="IconSizeGroupBox">
- <property name="title">
- <string>Icon Size</string>
- </property>
- <layout class="QHBoxLayout" name="icon_size_qhbox_layout">
- <item>
- <layout class="QVBoxLayout" name="icon_size_qvbox_layout">
<item>
<layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
<item>
@@ -65,19 +79,6 @@
</item>
</layout>
</item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="RowGroupBox">
- <property name="title">
- <string>Row Text</string>
- </property>
- <layout class="QHBoxLayout" name="RowHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="RowVerticalLayout">
<item>
<layout class="QHBoxLayout" name="row_1_qhbox_layout">
<item>
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