diff options
Diffstat (limited to 'src')
348 files changed, 17452 insertions, 4775 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 26cf9480b..581c46b52 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -17,85 +17,78 @@ if ($ENV{CI}) string(SUBSTRING ${WORD} 1 -1 REMAINDER) string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) # this leaves a trailing space on the last word, but we actually want that - # because of how its styled in the title bar. + # because of how it's styled in the title bar. set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ") endforeach() endif() endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY) -set(SRCS - break_points.cpp - file_util.cpp - hash.cpp - logging/filter.cpp - logging/text_formatter.cpp - logging/backend.cpp - memory_util.cpp - microprofile.cpp - misc.cpp - param_package.cpp - scm_rev.cpp - string_util.cpp - telemetry.cpp - thread.cpp - timer.cpp - ) - -set(HEADERS - alignment.h - assert.h - bit_field.h - bit_set.h - break_points.h - chunk_file.h - code_block.h - color.h - common_funcs.h - common_paths.h - common_types.h - file_util.h - hash.h - linear_disk_cache.h - logging/text_formatter.h - logging/filter.h - logging/log.h - logging/backend.h - math_util.h - memory_util.h - microprofile.h - microprofileui.h - param_package.h - platform.h - quaternion.h - scm_rev.h - scope_exit.h - string_util.h - swap.h - synchronized_wrapper.h - telemetry.h - thread.h - thread_queue_list.h - threadsafe_queue.h - timer.h - vector_math.h - ) +add_library(common STATIC + alignment.h + assert.h + bit_field.h + bit_set.h + break_points.cpp + break_points.h + chunk_file.h + code_block.h + color.h + common_funcs.h + common_paths.h + common_types.h + file_util.cpp + file_util.h + hash.cpp + hash.h + linear_disk_cache.h + logging/backend.cpp + logging/backend.h + logging/filter.cpp + logging/filter.h + logging/log.h + logging/text_formatter.cpp + logging/text_formatter.h + math_util.h + memory_util.cpp + memory_util.h + microprofile.cpp + microprofile.h + microprofileui.h + misc.cpp + param_package.cpp + param_package.h + quaternion.h + scm_rev.cpp + scm_rev.h + scope_exit.h + string_util.cpp + string_util.h + swap.h + synchronized_wrapper.h + telemetry.cpp + telemetry.h + thread.cpp + thread.h + thread_queue_list.h + threadsafe_queue.h + timer.cpp + timer.h + vector_math.h +) if(ARCHITECTURE_x86_64) - set(SRCS ${SRCS} + target_sources(common + PRIVATE x64/cpu_detect.cpp - ) - - set(HEADERS ${HEADERS} x64/cpu_detect.h x64/xbyak_abi.h x64/xbyak_util.h - ) + ) endif() -create_directory_groups(${SRCS} ${HEADERS}) +create_target_directory_groups(common) -add_library(common STATIC ${SRCS} ${HEADERS}) target_link_libraries(common PUBLIC Boost::boost microprofile) if (ARCHITECTURE_x86_64) target_link_libraries(common PRIVATE xbyak) diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 9c2e6b28c..84e3cbe58 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -236,7 +236,7 @@ public: IntTy m_val; }; -} // Common +} // namespace Common typedef Common::BitSet<u8> BitSet8; typedef Common::BitSet<u16> BitSet16; diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h index 5145a3657..972ef9039 100644 --- a/src/common/chunk_file.h +++ b/src/common/chunk_file.h @@ -607,8 +607,9 @@ public: u32 cookie = arbitraryNumber; Do(cookie); if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { - LOG_ERROR(Common, "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). " - "Aborting savestate load...", + LOG_ERROR(Common, + "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). " + "Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); SetError(ERROR_FAILURE); } diff --git a/src/common/color.h b/src/common/color.h index 4ebd4f3d0..24a445dac 100644 --- a/src/common/color.h +++ b/src/common/color.h @@ -256,4 +256,4 @@ inline void EncodeX24S8(u8 stencil, u8* bytes) { bytes[3] = stencil; } -} // namespace +} // namespace Color diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 5ab036b34..4e1d702f7 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -873,20 +873,19 @@ bool IOFile::Flush() { } bool IOFile::Resize(u64 size) { - if (!IsOpen() || - 0 != + if (!IsOpen() || 0 != #ifdef _WIN32 - // ector: _chsize sucks, not 64-bit safe - // F|RES: changed to _chsize_s. i think it is 64-bit safe - _chsize_s(_fileno(m_file), size) + // ector: _chsize sucks, not 64-bit safe + // F|RES: changed to _chsize_s. i think it is 64-bit safe + _chsize_s(_fileno(m_file), size) #else - // TODO: handle 64bit and growing - ftruncate(fileno(m_file), size) + // TODO: handle 64bit and growing + ftruncate(fileno(m_file), size) #endif - ) + ) m_good = false; return m_good; } -} // namespace +} // namespace FileUtil diff --git a/src/common/file_util.h b/src/common/file_util.h index 94adfcd7e..143f099eb 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path); // Set the current directory to given directory bool SetCurrentDir(const std::string& directory); -// Returns a pointer to a string with a Citra data dir in the user's home +// Returns a pointer to a string with a yuzu data dir in the user's home // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = ""); @@ -253,7 +253,7 @@ private: bool m_good = true; }; -} // namespace +} // namespace FileUtil // To deal with Windows being dumb at unicode: template <typename T> diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 13f915a01..a763f4abf 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -32,17 +32,33 @@ namespace Log { CLS(Kernel) \ SUB(Kernel, SVC) \ CLS(Service) \ - SUB(Service, SM) \ + SUB(Service, ACC) \ + SUB(Service, Audio) \ + SUB(Service, AM) \ + SUB(Service, AOC) \ + SUB(Service, APM) \ + SUB(Service, Fatal) \ + SUB(Service, Friend) \ SUB(Service, FS) \ - SUB(Service, GSP) \ - SUB(Service, CFG) \ - SUB(Service, DSP) \ SUB(Service, HID) \ + SUB(Service, LM) \ + SUB(Service, NFP) \ + SUB(Service, NIFM) \ + SUB(Service, NS) \ + SUB(Service, NVDRV) \ + SUB(Service, PCTL) \ + SUB(Service, SET) \ + SUB(Service, SM) \ + SUB(Service, SPL) \ + SUB(Service, SSL) \ + SUB(Service, Time) \ + SUB(Service, VI) \ CLS(HW) \ SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ SUB(HW, AES) \ + CLS(IPC) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ @@ -91,8 +107,8 @@ const char* GetLevelName(Level log_level) { Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, const char* format, va_list args) { - using std::chrono::steady_clock; using std::chrono::duration_cast; + using std::chrono::steady_clock; static steady_clock::time_point time_origin = steady_clock::now(); @@ -131,4 +147,4 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned PrintColoredMessage(entry); } -} +} // namespace Log diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index c4fe2acbf..70744e3e5 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -47,4 +47,4 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign const char* function, const char* format, va_list args); void SetFilter(Filter* filter); -} +} // namespace Log diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 12e5bb45d..733247b51 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -94,4 +94,4 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin, bool Filter::CheckMessage(Class log_class, Level level) const { return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]); } -} +} // namespace Log diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index b51df61de..16fa72642 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -50,4 +50,4 @@ public: private: std::array<Level, (size_t)Class::Count> class_levels; }; -} +} // namespace Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 35b5af3cb..7f079b20f 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -49,17 +49,33 @@ enum class Class : ClassType { Kernel_SVC, ///< Kernel system calls Service, ///< HLE implementation of system services. Each major service /// should have its own subclass. - Service_SM, ///< The SRV (Service Directory) implementation - Service_FS, ///< The FS (Filesystem) service implementation - Service_GSP, ///< The GSP (GPU control) service - Service_CFG, ///< The CFG (Configuration) service - Service_DSP, ///< The DSP (DSP control) service + Service_ACC, ///< The ACC (Accounts) service + Service_AM, ///< The AM (Applet manager) service + Service_AOC, ///< The AOC (AddOn Content) service + Service_APM, ///< The APM (Performance) service + Service_Audio, ///< The Audio (Audio control) service + Service_Fatal, ///< The Fatal service + Service_Friend, ///< The friend service + Service_FS, ///< The FS (Filesystem) service Service_HID, ///< The HID (Human interface device) service + Service_LM, ///< The LM (Logger) service + Service_NFP, ///< The NFP service + Service_NIFM, ///< The NIFM (Network interface) service + Service_NS, ///< The NS services + Service_NVDRV, ///< The NVDRV (Nvidia driver) service + Service_PCTL, ///< The PCTL (Parental control) service + Service_SET, ///< The SET (Settings) service + Service_SM, ///< The SM (Service manager) service + Service_SPL, ///< The SPL service + Service_SSL, ///< The SSL service + Service_Time, ///< The time service + Service_VI, ///< The VI (Video interface) service HW, ///< Low-level hardware emulation HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation HW_AES, ///< AES engine emulation + IPC, ///< IPC interface Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend @@ -70,7 +86,7 @@ enum class Class : ClassType { Loader, ///< ROM loader Input, ///< Input emulation Network, ///< Network emulation - WebService, ///< Interface to Citra Web Services + WebService, ///< Interface to yuzu Web Services Count ///< Total number of logging classes }; diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index f71e748d1..e7e46c76b 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -129,4 +129,4 @@ void PrintColoredMessage(const Entry& entry) { #undef ESC #endif } -} +} // namespace Log diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 749268310..66e86d9ec 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -28,4 +28,4 @@ void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); void PrintMessage(const Entry& entry); /// Prints the same message as `PrintMessage`, but colored acoording to the severity level. void PrintColoredMessage(const Entry& entry); -} +} // namespace Log diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index c19729b21..759ad02ca 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -40,11 +40,12 @@ void* AllocateExecutableMemory(size_t size, bool low) { if (low && (!map_hint)) map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */ #endif - void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE + void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANON | MAP_PRIVATE #if defined(ARCHITECTURE_X64) && defined(MAP_32BIT) - | (low ? MAP_32BIT : 0) + | (low ? MAP_32BIT : 0) #endif - , + , -1, 0); #endif /* defined(_WIN32) */ diff --git a/src/common/platform.h b/src/common/platform.h deleted file mode 100644 index c62fb7c8f..000000000 --- a/src/common/platform.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (C) 2005-2012 Gekko Emulator - * - * @file platform.h - * @author ShizZy <shizzy247@gmail.com> - * @date 2012-02-11 - * @brief Platform detection macros for portable compilation - * - * @section LICENSE - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details at - * http://www.gnu.org/copyleft/gpl.html - * - * Official project repository can be found at: - * http://code.google.com/p/gekko-gc-emu/ - */ - -#pragma once - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Platform detection - -#if defined(ARCHITECTURE_x86_64) || defined(__aarch64__) -#define EMU_ARCH_BITS 64 -#elif defined(__i386) || defined(_M_IX86) || defined(__arm__) || defined(_M_ARM) -#define EMU_ARCH_BITS 32 -#endif diff --git a/src/common/quaternion.h b/src/common/quaternion.h index 77f626bcb..ea39298c1 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -46,4 +46,4 @@ inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float ang return {axis * std::sin(angle / 2), std::cos(angle / 2)}; } -} // namspace Math +} // namespace Math diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index 18aaa1735..db0f4a947 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -12,4 +12,4 @@ extern const char g_scm_desc[]; extern const char g_build_name[]; extern const char g_build_date[]; -} // namespace +} // namespace Common diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h index 072ab285d..baf1f1c9e 100644 --- a/src/common/scope_exit.h +++ b/src/common/scope_exit.h @@ -22,7 +22,7 @@ template <typename Func> ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); } -} +} // namespace detail /** * This macro allows you to conveniently specify a block of code that will run on scope exit. Handy diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 6959915fa..e9a2a6b00 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -202,7 +202,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ #ifdef _WIN32 ":" #endif - ); + ); if (std::string::npos == dir_end) dir_end = 0; else @@ -462,4 +462,4 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l return std::string(buffer, len); } -} +} // namespace Common diff --git a/src/common/string_util.h b/src/common/string_util.h index 259360aec..ceb8df48e 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -134,4 +134,4 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { * NUL-terminated then the string ends at max_len characters. */ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); -} +} // namespace Common diff --git a/src/common/telemetry.h b/src/common/telemetry.h index dd6bbd759..3694c76f2 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -53,10 +53,10 @@ template <typename T> class Field : public FieldInterface { public: Field(FieldType type, std::string name, const T& value) - : type(type), name(std::move(name)), value(value) {} + : name(std::move(name)), type(type), value(value) {} Field(FieldType type, std::string name, T&& value) - : type(type), name(std::move(name)), value(std::move(value)) {} + : name(std::move(name)), type(type), value(std::move(value)) {} Field(const Field& other) : Field(other.type, other.name, other.value) {} diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index edd0e4a3f..38a450d69 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -158,4 +158,4 @@ private: std::array<Queue, NUM_QUEUES> queues; }; -} // namespace +} // namespace Common diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 62f17fbb5..2dfcd39c8 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -54,7 +54,7 @@ static CPUCaps Detect() { caps.num_cores = std::thread::hardware_concurrency(); // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support - // Citra at all anyway + // yuzu at all anyway int cpu_id[4]; memset(caps.brand_string, 0, sizeof(caps.brand_string)); diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index 6090d93e1..fd3fbdd4b 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -60,20 +60,41 @@ const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ // GPRs - Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::r8, Xbyak::util::r9, Xbyak::util::r10, + Xbyak::util::rcx, + Xbyak::util::rdx, + Xbyak::util::r8, + Xbyak::util::r9, + Xbyak::util::r10, Xbyak::util::r11, // XMMs - Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, + Xbyak::util::xmm0, + Xbyak::util::xmm1, + Xbyak::util::xmm2, + Xbyak::util::xmm3, + Xbyak::util::xmm4, Xbyak::util::xmm5, }); const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ // GPRs - Xbyak::util::rbx, Xbyak::util::rsi, Xbyak::util::rdi, Xbyak::util::rbp, Xbyak::util::r12, - Xbyak::util::r13, Xbyak::util::r14, Xbyak::util::r15, + Xbyak::util::rbx, + Xbyak::util::rsi, + Xbyak::util::rdi, + Xbyak::util::rbp, + Xbyak::util::r12, + Xbyak::util::r13, + Xbyak::util::r14, + Xbyak::util::r15, // XMMs - Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, Xbyak::util::xmm10, - Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, Xbyak::util::xmm14, + Xbyak::util::xmm6, + Xbyak::util::xmm7, + Xbyak::util::xmm8, + Xbyak::util::xmm9, + Xbyak::util::xmm10, + Xbyak::util::xmm11, + Xbyak::util::xmm12, + Xbyak::util::xmm13, + Xbyak::util::xmm14, Xbyak::util::xmm15, }); @@ -90,18 +111,40 @@ const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ // GPRs - Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::rdi, Xbyak::util::rsi, Xbyak::util::r8, - Xbyak::util::r9, Xbyak::util::r10, Xbyak::util::r11, + Xbyak::util::rcx, + Xbyak::util::rdx, + Xbyak::util::rdi, + Xbyak::util::rsi, + Xbyak::util::r8, + Xbyak::util::r9, + Xbyak::util::r10, + Xbyak::util::r11, // XMMs - Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, - Xbyak::util::xmm5, Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, - Xbyak::util::xmm10, Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, - Xbyak::util::xmm14, Xbyak::util::xmm15, + Xbyak::util::xmm0, + Xbyak::util::xmm1, + Xbyak::util::xmm2, + Xbyak::util::xmm3, + Xbyak::util::xmm4, + Xbyak::util::xmm5, + Xbyak::util::xmm6, + Xbyak::util::xmm7, + Xbyak::util::xmm8, + Xbyak::util::xmm9, + Xbyak::util::xmm10, + Xbyak::util::xmm11, + Xbyak::util::xmm12, + Xbyak::util::xmm13, + Xbyak::util::xmm14, + Xbyak::util::xmm15, }); const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ // GPRs - Xbyak::util::rbx, Xbyak::util::rbp, Xbyak::util::r12, Xbyak::util::r13, Xbyak::util::r14, + Xbyak::util::rbx, + Xbyak::util::rbp, + Xbyak::util::r12, + Xbyak::util::r13, + Xbyak::util::r14, Xbyak::util::r15, }); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 20e41038b..6f8104516 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,175 +1,270 @@ -set(SRCS - arm/dynarmic/arm_dynarmic.cpp - arm/unicorn/arm_unicorn.cpp - core.cpp - core_timing.cpp - file_sys/archive_backend.cpp - file_sys/disk_archive.cpp - file_sys/ivfc_archive.cpp - file_sys/path_parser.cpp - file_sys/savedata_archive.cpp - file_sys/title_metadata.cpp - frontend/emu_window.cpp - frontend/framebuffer_layout.cpp - gdbstub/gdbstub.cpp - hle/config_mem.cpp - hle/kernel/address_arbiter.cpp - hle/kernel/client_port.cpp - hle/kernel/client_session.cpp - hle/kernel/condition_variable.cpp - hle/kernel/domain.cpp - hle/kernel/event.cpp - hle/kernel/handle_table.cpp - hle/kernel/hle_ipc.cpp - hle/kernel/kernel.cpp - hle/kernel/memory.cpp - hle/kernel/mutex.cpp - hle/kernel/object_address_table.cpp - hle/kernel/process.cpp - hle/kernel/resource_limit.cpp - hle/kernel/server_port.cpp - hle/kernel/server_session.cpp - hle/kernel/shared_memory.cpp - hle/kernel/svc.cpp - hle/kernel/thread.cpp - hle/kernel/timer.cpp - hle/kernel/vm_manager.cpp - hle/kernel/wait_object.cpp - hle/lock.cpp - hle/romfs.cpp - hle/service/acc/acc.cpp - hle/service/acc/acc_u0.cpp - hle/service/am/am.cpp - hle/service/am/applet_oe.cpp - hle/service/aoc/aoc_u.cpp - hle/service/apm/apm.cpp - hle/service/audio/audio.cpp - hle/service/audio/audout_u.cpp - hle/service/hid/hid.cpp - hle/service/lm/lm.cpp - hle/service/nvdrv/devices/nvdisp_disp0.cpp - hle/service/nvdrv/devices/nvhost_as_gpu.cpp - hle/service/nvdrv/devices/nvmap.cpp - hle/service/nvdrv/interface.cpp - hle/service/nvdrv/nvdrv.cpp - hle/service/pctl/pctl.cpp - hle/service/pctl/pctl_a.cpp - hle/service/service.cpp - hle/service/sm/controller.cpp - hle/service/sm/sm.cpp - hle/service/time/time.cpp - hle/service/time/time_s.cpp - hle/service/vi/vi.cpp - hle/service/vi/vi_m.cpp - hle/shared_page.cpp - hw/hw.cpp - hw/lcd.cpp - loader/elf.cpp - loader/linker.cpp - loader/loader.cpp - loader/nro.cpp - loader/nso.cpp - tracer/recorder.cpp - memory.cpp - perf_stats.cpp - settings.cpp - telemetry_session.cpp - ) +add_library(core STATIC + arm/arm_interface.h + arm/unicorn/arm_unicorn.cpp + arm/unicorn/arm_unicorn.h + core.cpp + core.h + core_timing.cpp + core_timing.h + file_sys/directory.h + file_sys/disk_filesystem.cpp + file_sys/disk_filesystem.h + file_sys/errors.h + file_sys/filesystem.cpp + file_sys/filesystem.h + file_sys/path_parser.cpp + file_sys/path_parser.h + file_sys/program_metadata.cpp + file_sys/program_metadata.h + file_sys/romfs_factory.cpp + file_sys/romfs_factory.h + file_sys/romfs_filesystem.cpp + file_sys/romfs_filesystem.h + file_sys/savedata_factory.cpp + file_sys/savedata_factory.h + file_sys/sdmc_factory.cpp + file_sys/sdmc_factory.h + file_sys/storage.h + frontend/emu_window.cpp + frontend/emu_window.h + frontend/framebuffer_layout.cpp + frontend/framebuffer_layout.h + frontend/input.h + gdbstub/gdbstub.cpp + gdbstub/gdbstub.h + hle/config_mem.cpp + hle/config_mem.h + hle/ipc.h + hle/ipc_helpers.h + hle/kernel/client_port.cpp + hle/kernel/client_port.h + hle/kernel/client_session.cpp + hle/kernel/client_session.h + hle/kernel/condition_variable.cpp + hle/kernel/condition_variable.h + hle/kernel/errors.h + hle/kernel/event.cpp + hle/kernel/event.h + hle/kernel/handle_table.cpp + hle/kernel/handle_table.h + hle/kernel/hle_ipc.cpp + hle/kernel/hle_ipc.h + hle/kernel/kernel.cpp + hle/kernel/kernel.h + hle/kernel/memory.cpp + hle/kernel/memory.h + hle/kernel/mutex.cpp + hle/kernel/mutex.h + hle/kernel/object_address_table.cpp + hle/kernel/object_address_table.h + hle/kernel/process.cpp + hle/kernel/process.h + hle/kernel/resource_limit.cpp + hle/kernel/resource_limit.h + hle/kernel/scheduler.cpp + hle/kernel/scheduler.h + hle/kernel/server_port.cpp + hle/kernel/server_port.h + hle/kernel/server_session.cpp + hle/kernel/server_session.h + hle/kernel/session.h + hle/kernel/shared_memory.cpp + hle/kernel/shared_memory.h + hle/kernel/svc.cpp + hle/kernel/svc.h + hle/kernel/svc_wrap.h + hle/kernel/thread.cpp + hle/kernel/thread.h + hle/kernel/timer.cpp + hle/kernel/timer.h + hle/kernel/vm_manager.cpp + hle/kernel/vm_manager.h + hle/kernel/wait_object.cpp + hle/kernel/wait_object.h + hle/lock.cpp + hle/lock.h + hle/result.h + hle/romfs.cpp + hle/romfs.h + hle/service/acc/acc.cpp + hle/service/acc/acc.h + hle/service/acc/acc_u0.cpp + hle/service/acc/acc_u0.h + hle/service/am/am.cpp + hle/service/am/am.h + hle/service/am/applet_ae.cpp + hle/service/am/applet_ae.h + hle/service/am/applet_oe.cpp + hle/service/am/applet_oe.h + hle/service/aoc/aoc_u.cpp + hle/service/aoc/aoc_u.h + hle/service/apm/apm.cpp + hle/service/apm/apm.h + hle/service/apm/interface.cpp + hle/service/apm/interface.h + hle/service/audio/audio.cpp + hle/service/audio/audio.h + hle/service/audio/audin_u.cpp + hle/service/audio/audin_u.h + hle/service/audio/audout_u.cpp + hle/service/audio/audout_u.h + hle/service/audio/audrec_u.cpp + hle/service/audio/audrec_u.h + hle/service/audio/audren_u.cpp + hle/service/audio/audren_u.h + hle/service/audio/audren_u.cpp + hle/service/audio/audren_u.h + hle/service/audio/codecctl.cpp + hle/service/audio/codecctl.h + hle/service/fatal/fatal.cpp + hle/service/fatal/fatal.h + hle/service/fatal/fatal_p.cpp + hle/service/fatal/fatal_p.h + hle/service/fatal/fatal_u.cpp + hle/service/fatal/fatal_u.h + hle/service/filesystem/filesystem.cpp + hle/service/filesystem/filesystem.h + hle/service/filesystem/fsp_srv.cpp + hle/service/filesystem/fsp_srv.h + hle/service/friend/friend.cpp + hle/service/friend/friend.h + hle/service/friend/friend_a.cpp + hle/service/friend/friend_a.h + hle/service/hid/hid.cpp + hle/service/hid/hid.h + hle/service/lm/lm.cpp + hle/service/lm/lm.h + hle/service/nifm/nifm.cpp + hle/service/nifm/nifm.h + hle/service/nifm/nifm_a.cpp + hle/service/nifm/nifm_a.h + hle/service/nifm/nifm_s.cpp + hle/service/nifm/nifm_s.h + hle/service/nifm/nifm_u.cpp + hle/service/nifm/nifm_u.h + hle/service/nfp/nfp.cpp + hle/service/nfp/nfp.h + hle/service/nfp/nfp_user.cpp + hle/service/nfp/nfp_user.h + hle/service/ns/ns.cpp + hle/service/ns/ns.h + hle/service/ns/pl_u.cpp + hle/service/ns/pl_u.h + hle/service/nvdrv/devices/nvdevice.h + hle/service/nvdrv/devices/nvdisp_disp0.cpp + hle/service/nvdrv/devices/nvdisp_disp0.h + hle/service/nvdrv/devices/nvhost_as_gpu.cpp + hle/service/nvdrv/devices/nvhost_as_gpu.h + hle/service/nvdrv/devices/nvhost_ctrl.cpp + hle/service/nvdrv/devices/nvhost_ctrl.h + hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp + hle/service/nvdrv/devices/nvhost_ctrl_gpu.h + hle/service/nvdrv/devices/nvhost_gpu.cpp + hle/service/nvdrv/devices/nvhost_gpu.h + hle/service/nvdrv/devices/nvmap.cpp + hle/service/nvdrv/devices/nvmap.h + hle/service/nvdrv/interface.cpp + hle/service/nvdrv/interface.h + hle/service/nvdrv/nvdrv.cpp + hle/service/nvdrv/nvdrv.h + hle/service/nvdrv/nvmemp.cpp + hle/service/nvdrv/nvmemp.h + hle/service/nvflinger/buffer_queue.cpp + hle/service/nvflinger/buffer_queue.h + hle/service/nvflinger/nvflinger.cpp + hle/service/nvflinger/nvflinger.h + hle/service/pctl/pctl.cpp + hle/service/pctl/pctl.h + hle/service/pctl/pctl_a.cpp + hle/service/pctl/pctl_a.h + hle/service/service.cpp + hle/service/service.h + hle/service/set/set.cpp + hle/service/set/set.h + hle/service/set/set_cal.cpp + hle/service/set/set_cal.h + hle/service/set/set_fd.cpp + hle/service/set/set_fd.h + hle/service/set/set_sys.cpp + hle/service/set/set_sys.h + hle/service/set/settings.cpp + hle/service/set/settings.h + hle/service/sm/controller.cpp + hle/service/sm/controller.h + hle/service/sm/sm.cpp + hle/service/sm/sm.h + hle/service/sockets/bsd.cpp + hle/service/sockets/bsd.h + hle/service/sockets/nsd.cpp + hle/service/sockets/nsd.h + hle/service/sockets/sfdnsres.cpp + hle/service/sockets/sfdnsres.h + hle/service/sockets/sockets.cpp + hle/service/sockets/sockets.h + hle/service/spl/csrng.cpp + hle/service/spl/csrng.h + hle/service/spl/module.cpp + hle/service/spl/module.h + hle/service/spl/spl.cpp + hle/service/spl/spl.h + hle/service/ssl/ssl.cpp + hle/service/ssl/ssl.h + hle/service/time/time.cpp + hle/service/time/time.h + hle/service/time/time_s.cpp + hle/service/time/time_s.h + hle/service/time/time_u.cpp + hle/service/time/time_u.h + hle/service/vi/vi.cpp + hle/service/vi/vi.h + hle/service/vi/vi_m.cpp + hle/service/vi/vi_m.h + hle/service/vi/vi_s.cpp + hle/service/vi/vi_s.h + hle/service/vi/vi_u.cpp + hle/service/vi/vi_u.h + hle/shared_page.cpp + hle/shared_page.h + hw/hw.cpp + hw/hw.h + hw/lcd.cpp + hw/lcd.h + loader/deconstructed_rom_directory.cpp + loader/deconstructed_rom_directory.h + loader/elf.cpp + loader/elf.h + loader/linker.cpp + loader/linker.h + loader/loader.cpp + loader/loader.h + loader/nro.cpp + loader/nro.h + loader/nso.cpp + loader/nso.h + memory.cpp + memory.h + memory_hook.h + memory_setup.h + perf_stats.cpp + perf_stats.h + settings.cpp + settings.h + telemetry_session.cpp + telemetry_session.h + tracer/citrace.h + tracer/recorder.cpp + tracer/recorder.h +) -set(HEADERS - arm/arm_interface.h - arm/dynarmic/arm_dynarmic.h - arm/unicorn/arm_unicorn.h - core.h - core_timing.h - file_sys/archive_backend.h - file_sys/directory_backend.h - file_sys/disk_archive.h - file_sys/errors.h - file_sys/file_backend.h - file_sys/ivfc_archive.h - file_sys/path_parser.h - file_sys/savedata_archive.h - frontend/emu_window.h - frontend/framebuffer_layout.h - frontend/input.h - gdbstub/gdbstub.h - hle/config_mem.h - hle/ipc.h - hle/ipc_helpers.h - hle/kernel/address_arbiter.h - hle/kernel/client_port.h - hle/kernel/client_session.h - hle/kernel/condition_variable.h - hle/kernel/domain.h - hle/kernel/errors.h - hle/kernel/event.h - hle/kernel/handle_table.h - hle/kernel/hle_ipc.h - hle/kernel/kernel.h - hle/kernel/memory.h - hle/kernel/mutex.h - hle/kernel/object_address_table.h - hle/kernel/process.h - hle/kernel/resource_limit.h - hle/kernel/server_port.h - hle/kernel/server_session.h - hle/kernel/session.h - hle/kernel/shared_memory.h - hle/kernel/sync_object.h - hle/kernel/svc.h - hle/kernel/svc_wrap.h - hle/kernel/thread.h - hle/kernel/timer.h - hle/kernel/vm_manager.h - hle/kernel/wait_object.h - hle/lock.h - hle/result.h - hle/romfs.h - hle/service/acc/acc.h - hle/service/acc/acc_u0.h - hle/service/am/am.h - hle/service/am/applet_oe.h - hle/service/aoc/aoc_u.h - hle/service/apm/apm.h - hle/service/audio/audio.h - hle/service/audio/audout_u.h - hle/service/hid/hid.h - hle/service/lm/lm.h - hle/service/nvdrv/devices/nvdevice.h - hle/service/nvdrv/devices/nvdisp_disp0.h - hle/service/nvdrv/devices/nvhost_as_gpu.h - hle/service/nvdrv/devices/nvmap.h - hle/service/nvdrv/interface.h - hle/service/nvdrv/nvdrv.h - hle/service/pctl/pctl.h - hle/service/pctl/pctl_a.h - hle/service/service.h - hle/service/sm/controller.h - hle/service/sm/sm.h - hle/service/time/time.h - hle/service/time/time_s.h - hle/service/vi/vi.h - hle/service/vi/vi_m.h - hle/shared_page.h - hw/hw.h - hw/lcd.h - loader/elf.h - loader/linker.h - loader/loader.h - loader/nro.h - loader/nso.h - tracer/recorder.h - tracer/citrace.h - memory.h - memory_setup.h - mmio.h - perf_stats.h - settings.h - telemetry_session.h - ) +create_target_directory_groups(core) -create_directory_groups(${SRCS} ${HEADERS}) -add_library(core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(core PUBLIC common PRIVATE dynarmic video_core) +target_link_libraries(core PUBLIC common PRIVATE video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn) + +if (ARCHITECTURE_x86_64) + target_sources(core PRIVATE + arm/dynarmic/arm_dynarmic.cpp + arm/dynarmic/arm_dynarmic.h + ) + target_link_libraries(core PRIVATE dynarmic) +endif() diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 5ae60214e..32ff3c345 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -25,22 +25,18 @@ public: VAddr tls_address; }; - /** - * Runs the CPU for the given number of instructions - * @param num_instructions Number of instructions to run - */ - void Run(int num_instructions) { - ExecuteInstructions(num_instructions); - this->num_instructions += num_instructions; - } + /// Runs the CPU until an event happens + virtual void Run() = 0; /// Step CPU by one instruction - void Step() { - Run(1); - } + virtual void Step() = 0; + /// Maps a backing memory region for the CPU virtual void MapBackingMemory(VAddr address, size_t size, u8* memory, - Kernel::VMAPermission perms) {} + Kernel::VMAPermission perms) = 0; + + /// Unmaps a region of memory that was previously mapped using MapBackingMemory + virtual void UnmapMemory(VAddr address, size_t size) = 0; /// Clear all instruction cache virtual void ClearInstructionCache() = 0; @@ -122,19 +118,4 @@ public: /// Prepare core for thread reschedule (if needed to correctly handle state) virtual void PrepareReschedule() = 0; - - /// Getter for num_instructions - u64 GetNumInstructions() const { - return num_instructions; - } - -protected: - /** - * Executes the given number of instructions - * @param num_instructions Number of instructions to executes - */ - virtual void ExecuteInstructions(int num_instructions) = 0; - -private: - u64 num_instructions = 0; ///< Number of instructions executed }; diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 2ad48dcc7..6afad0e0c 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -6,11 +6,16 @@ #include <memory> #include <dynarmic/A64/a64.h> #include <dynarmic/A64/config.h> +#include "common/logging/log.h" #include "core/arm/dynarmic/arm_dynarmic.h" +#include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/memory.h" #include "core/hle/kernel/svc.h" #include "core/memory.h" +using Vector = Dynarmic::A64::Vector; + class ARM_Dynarmic_Callbacks : public Dynarmic::A64::UserCallbacks { public: explicit ARM_Dynarmic_Callbacks(ARM_Dynarmic& parent) : parent(parent) {} @@ -28,6 +33,9 @@ public: u64 MemoryRead64(u64 vaddr) override { return Memory::Read64(vaddr); } + Vector MemoryRead128(u64 vaddr) override { + return {Memory::Read64(vaddr), Memory::Read64(vaddr + 8)}; + } void MemoryWrite8(u64 vaddr, u8 value) override { Memory::Write8(vaddr, value); @@ -41,49 +49,91 @@ public: void MemoryWrite64(u64 vaddr, u64 value) override { Memory::Write64(vaddr, value); } + void MemoryWrite128(u64 vaddr, Vector value) override { + Memory::Write64(vaddr, value[0]); + Memory::Write64(vaddr + 8, value[1]); + } void InterpreterFallback(u64 pc, size_t num_instructions) override { + LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)", + pc, num_instructions, MemoryReadCode(pc)); + ARM_Interface::ThreadContext ctx; parent.SaveContext(ctx); parent.inner_unicorn.LoadContext(ctx); - parent.inner_unicorn.ExecuteInstructions(num_instructions); + parent.inner_unicorn.ExecuteInstructions(static_cast<int>(num_instructions)); parent.inner_unicorn.SaveContext(ctx); parent.LoadContext(ctx); num_interpreted_instructions += num_instructions; } - void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override { - ASSERT_MSG(false, "ExceptionRaised(%" PRIx64 ")", pc); + void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { + switch (exception) { + case Dynarmic::A64::Exception::WaitForInterrupt: + case Dynarmic::A64::Exception::WaitForEvent: + case Dynarmic::A64::Exception::SendEvent: + case Dynarmic::A64::Exception::SendEventLocal: + case Dynarmic::A64::Exception::Yield: + return; + default: + ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")", + static_cast<size_t>(exception), pc); + } } void CallSVC(u32 swi) override { - printf("svc %x\n", swi); Kernel::CallSVC(swi); } void AddTicks(u64 ticks) override { - if (ticks > ticks_remaining) { - ticks_remaining = 0; - return; - } - ticks -= ticks_remaining; + CoreTiming::AddTicks(ticks - num_interpreted_instructions); + num_interpreted_instructions = 0; } u64 GetTicksRemaining() override { - return ticks_remaining; + return std::max(CoreTiming::GetDowncount(), 0); + } + u64 GetCNTPCT() override { + return CoreTiming::GetTicks(); } ARM_Dynarmic& parent; - size_t ticks_remaining = 0; size_t num_interpreted_instructions = 0; - u64 tpidrr0_el0 = 0; + u64 tpidrro_el0 = 0; + u64 tpidr_el0 = 0; }; +std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_Callbacks>& cb) { + const auto page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data(); + + Dynarmic::A64::UserConfig config; + config.callbacks = cb.get(); + config.tpidrro_el0 = &cb->tpidrro_el0; + config.tpidr_el0 = &cb->tpidr_el0; + config.dczid_el0 = 4; + config.ctr_el0 = 0x8444c004; + config.page_table = reinterpret_cast<void**>(page_table); + config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS; + config.silently_mirror_page_table = false; + + return std::make_unique<Dynarmic::A64::Jit>(config); +} + +void ARM_Dynarmic::Run() { + ASSERT(Memory::GetCurrentPageTable() == current_page_table); + + jit->Run(); +} + +void ARM_Dynarmic::Step() { + cb->InterpreterFallback(jit->GetPC(), 1); +} + ARM_Dynarmic::ARM_Dynarmic() - : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), - jit(Dynarmic::A64::UserConfig{cb.get()}) { + : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), jit(MakeJit(cb)) { ARM_Interface::ThreadContext ctx; inner_unicorn.SaveContext(ctx); LoadContext(ctx); + PageTableChanged(); } ARM_Dynarmic::~ARM_Dynarmic() = default; @@ -93,28 +143,32 @@ void ARM_Dynarmic::MapBackingMemory(u64 address, size_t size, u8* memory, inner_unicorn.MapBackingMemory(address, size, memory, perms); } +void ARM_Dynarmic::UnmapMemory(u64 address, size_t size) { + inner_unicorn.UnmapMemory(address, size); +} + void ARM_Dynarmic::SetPC(u64 pc) { - jit.SetPC(pc); + jit->SetPC(pc); } u64 ARM_Dynarmic::GetPC() const { - return jit.GetPC(); + return jit->GetPC(); } u64 ARM_Dynarmic::GetReg(int index) const { - return jit.GetRegister(index); + return jit->GetRegister(index); } void ARM_Dynarmic::SetReg(int index, u64 value) { - jit.SetRegister(index, value); + jit->SetRegister(index, value); } u128 ARM_Dynarmic::GetExtReg(int index) const { - return jit.GetVector(index); + return jit->GetVector(index); } void ARM_Dynarmic::SetExtReg(int index, u128 value) { - jit.SetVector(index, value); + jit->SetVector(index, value); } u32 ARM_Dynarmic::GetVFPReg(int /*index*/) const { @@ -127,58 +181,52 @@ void ARM_Dynarmic::SetVFPReg(int /*index*/, u32 /*value*/) { } u32 ARM_Dynarmic::GetCPSR() const { - return jit.GetPstate(); + return jit->GetPstate(); } void ARM_Dynarmic::SetCPSR(u32 cpsr) { - jit.SetPstate(cpsr); + jit->SetPstate(cpsr); } u64 ARM_Dynarmic::GetTlsAddress() const { - return cb->tpidrr0_el0; + return cb->tpidrro_el0; } void ARM_Dynarmic::SetTlsAddress(u64 address) { - cb->tpidrr0_el0 = address; -} - -void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { - cb->ticks_remaining = num_instructions; - jit.Run(); - CoreTiming::AddTicks(num_instructions - cb->num_interpreted_instructions); - cb->num_interpreted_instructions = 0; + cb->tpidrro_el0 = address; } void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) { - ctx.cpu_registers = jit.GetRegisters(); - ctx.sp = jit.GetSP(); - ctx.pc = jit.GetPC(); - ctx.cpsr = jit.GetPstate(); - ctx.fpu_registers = jit.GetVectors(); - ctx.fpscr = jit.GetFpcr(); - ctx.tls_address = cb->tpidrr0_el0; + ctx.cpu_registers = jit->GetRegisters(); + ctx.sp = jit->GetSP(); + ctx.pc = jit->GetPC(); + ctx.cpsr = jit->GetPstate(); + ctx.fpu_registers = jit->GetVectors(); + ctx.fpscr = jit->GetFpcr(); + ctx.tls_address = cb->tpidrro_el0; } void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) { - jit.SetRegisters(ctx.cpu_registers); - jit.SetSP(ctx.sp); - jit.SetPC(ctx.pc); - jit.SetPstate(ctx.cpsr); - jit.SetVectors(ctx.fpu_registers); - jit.SetFpcr(ctx.fpscr); - cb->tpidrr0_el0 = ctx.tls_address; + jit->SetRegisters(ctx.cpu_registers); + jit->SetSP(ctx.sp); + jit->SetPC(ctx.pc); + jit->SetPstate(static_cast<u32>(ctx.cpsr)); + jit->SetVectors(ctx.fpu_registers); + jit->SetFpcr(static_cast<u32>(ctx.fpscr)); + cb->tpidrro_el0 = ctx.tls_address; } void ARM_Dynarmic::PrepareReschedule() { - if (jit.IsExecuting()) { - jit.HaltExecution(); + if (jit->IsExecuting()) { + jit->HaltExecution(); } } void ARM_Dynarmic::ClearInstructionCache() { - jit.ClearCache(); + jit->ClearCache(); } void ARM_Dynarmic::PageTableChanged() { - UNIMPLEMENTED(); + jit = MakeJit(cb); + current_page_table = Memory::GetCurrentPageTable(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 43dc56d74..128669d01 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -19,7 +19,7 @@ public: void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override; - + void UnmapMemory(u64 address, size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; @@ -29,6 +29,8 @@ public: u32 GetVFPReg(int index) const override; void SetVFPReg(int index, u32 value) override; u32 GetCPSR() const override; + void Run() override; + void Step() override; void SetCPSR(u32 cpsr) override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; @@ -37,7 +39,6 @@ public: void LoadContext(const ThreadContext& ctx) override; void PrepareReschedule() override; - void ExecuteInstructions(int num_instructions) override; void ClearInstructionCache() override; void PageTableChanged() override; @@ -45,6 +46,8 @@ public: private: friend class ARM_Dynarmic_Callbacks; std::unique_ptr<ARM_Dynarmic_Callbacks> cb; - Dynarmic::A64::Jit jit; + std::unique_ptr<Dynarmic::A64::Jit> jit; ARM_Unicorn inner_unicorn; + + Memory::PageTable* current_page_table = nullptr; }; diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp index fd64eab39..b0cdc2403 100644 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ b/src/core/arm/unicorn/arm_unicorn.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <unicorn/arm64.h> #include "common/assert.h" #include "common/microprofile.h" @@ -52,7 +53,8 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si void* user_data) { ARM_Interface::ThreadContext ctx{}; Core::CPU().SaveContext(ctx); - ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%llx", addr); + ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%lx, pc=0x%lx, lr=0x%lx", addr, + ctx.pc, ctx.cpu_registers[30]); return {}; } @@ -76,6 +78,10 @@ void ARM_Unicorn::MapBackingMemory(VAddr address, size_t size, u8* memory, CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory)); } +void ARM_Unicorn::UnmapMemory(VAddr address, size_t size) { + CHECKED(uc_mem_unmap(uc, address, size)); +} + void ARM_Unicorn::SetPC(u64 pc) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); } @@ -148,6 +154,14 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); } +void ARM_Unicorn::Run() { + ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0)); +} + +void ARM_Unicorn::Step() { + ExecuteInstructions(1); +} + MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); void ARM_Unicorn::ExecuteInstructions(int num_instructions) { diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h index c9a561dec..b99b58e4c 100644 --- a/src/core/arm/unicorn/arm_unicorn.h +++ b/src/core/arm/unicorn/arm_unicorn.h @@ -14,6 +14,7 @@ public: ~ARM_Unicorn(); void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override; + void UnmapMemory(VAddr address, size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; @@ -29,7 +30,9 @@ public: void SaveContext(ThreadContext& ctx) override; void LoadContext(const ThreadContext& ctx) override; void PrepareReschedule() override; - void ExecuteInstructions(int num_instructions) override; + void ExecuteInstructions(int num_instructions); + void Run() override; + void Step() override; void ClearInstructionCache() override; void PageTableChanged() override{}; diff --git a/src/core/core.cpp b/src/core/core.cpp index ae1eb2430..11654d4da 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -5,7 +5,9 @@ #include <memory> #include <utility> #include "common/logging/log.h" +#ifdef ARCHITECTURE_x86_64 #include "core/arm/dynarmic/arm_dynarmic.h" +#endif #include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" #include "core/core_timing.h" @@ -24,7 +26,7 @@ namespace Core { /*static*/ System System::s_instance; -System::ResultStatus System::RunLoop(int tight_loop) { +System::ResultStatus System::RunLoop(bool tight_loop) { status = ResultStatus::Success; if (!cpu_core) { return ResultStatus::ErrorNotInitialized; @@ -38,7 +40,7 @@ System::ResultStatus System::RunLoop(int tight_loop) { if (GDBStub::GetCpuHaltFlag()) { if (GDBStub::GetCpuStepFlag()) { GDBStub::SetCpuStepFlag(false); - tight_loop = 1; + tight_loop = false; } else { return ResultStatus::Success; } @@ -54,7 +56,11 @@ System::ResultStatus System::RunLoop(int tight_loop) { PrepareReschedule(); } else { CoreTiming::Advance(); - cpu_core->Run(tight_loop); + if (tight_loop) { + cpu_core->Run(); + } else { + cpu_core->Step(); + } } HW::Update(); @@ -64,7 +70,7 @@ System::ResultStatus System::RunLoop(int tight_loop) { } System::ResultStatus System::SingleStep() { - return RunLoop(1); + return RunLoop(false); } System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) { @@ -80,7 +86,6 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file if (system_mode.second != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!", static_cast<int>(system_mode.second)); - System::Shutdown(); switch (system_mode.second) { case Loader::ResultStatus::ErrorEncrypted: @@ -94,14 +99,15 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file ResultStatus init_result{Init(emu_window, system_mode.first.get())}; if (init_result != ResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", init_result); + LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", + static_cast<int>(init_result)); System::Shutdown(); return init_result; } - const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)}; + const Loader::ResultStatus load_result{app_loader->Load(current_process)}; if (Loader::ResultStatus::Success != load_result) { - LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result); + LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", static_cast<int>(load_result)); System::Shutdown(); switch (load_result) { @@ -132,27 +138,34 @@ void System::Reschedule() { } reschedule_pending = false; - Kernel::Reschedule(); + Core::System::GetInstance().Scheduler().Reschedule(); } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(HW_Memory, "initialized OK"); - switch (Settings::values.cpu_core) { - case Settings::CpuCore::Unicorn: - cpu_core = std::make_unique<ARM_Unicorn>(); - break; - case Settings::CpuCore::Dynarmic: - default: - cpu_core = std::make_unique<ARM_Dynarmic>(); - break; + CoreTiming::Init(); + + current_process = Kernel::Process::Create("main"); + + if (Settings::values.use_cpu_jit) { +#ifdef ARCHITECTURE_x86_64 + cpu_core = std::make_shared<ARM_Dynarmic>(); +#else + cpu_core = std::make_shared<ARM_Unicorn>(); + LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); +#endif + } else { + cpu_core = std::make_shared<ARM_Unicorn>(); } + gpu_core = std::make_unique<Tegra::GPU>(); + telemetry_session = std::make_unique<Core::TelemetrySession>(); - CoreTiming::Init(); HW::Init(); Kernel::Init(system_mode); + scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get()); Service::Init(); GDBStub::Init(); @@ -180,15 +193,18 @@ void System::Shutdown() { perf_results.frametime * 1000.0); // Shutdown emulation session - GDBStub::Shutdown(); VideoCore::Shutdown(); + GDBStub::Shutdown(); Service::Shutdown(); + scheduler = nullptr; Kernel::Shutdown(); HW::Shutdown(); - CoreTiming::Shutdown(); + telemetry_session = nullptr; + gpu_core = nullptr; cpu_core = nullptr; + CoreTiming::Shutdown(); + app_loader = nullptr; - telemetry_session = nullptr; LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index a9a035a1b..ade456cfc 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -7,10 +7,14 @@ #include <memory> #include <string> #include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/scheduler.h" #include "core/loader/loader.h" #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/gpu.h" class EmuWindow; class ARM_Interface; @@ -50,14 +54,14 @@ public: * is not required to do a full dispatch with each instruction. NOTE: the number of instructions * requested is not guaranteed to run, as this will be interrupted preemptively if a hardware * update is requested (e.g. on a thread switch). - * @param tight_loop Number of instructions to execute. - * @return Result status, indicating whethor or not the operation succeeded. + * @param tight_loop If false, the CPU single-steps. + * @return Result status, indicating whether or not the operation succeeded. */ - ResultStatus RunLoop(int tight_loop = 100000); + ResultStatus RunLoop(bool tight_loop = true); /** * Step the CPU one instruction - * @return Result status, indicating whethor or not the operation succeeded. + * @return Result status, indicating whether or not the operation succeeded. */ ResultStatus SingleStep(); @@ -102,6 +106,18 @@ public: return *cpu_core; } + Tegra::GPU& GPU() { + return *gpu_core; + } + + Kernel::Scheduler& Scheduler() { + return *scheduler; + } + + Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { + return current_process; + } + PerfStats perf_stats; FrameLimiter frame_limiter; @@ -120,6 +136,14 @@ public: return *app_loader; } + void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { + debug_context = std::move(context); + } + + std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const { + return debug_context; + } + private: /** * Initialize the emulated system. @@ -135,8 +159,13 @@ private: /// AppLoader used to load the current executing application std::unique_ptr<Loader::AppLoader> app_loader; - ///< ARM11 CPU core - std::unique_ptr<ARM_Interface> cpu_core; + std::shared_ptr<ARM_Interface> cpu_core; + std::unique_ptr<Kernel::Scheduler> scheduler; + std::unique_ptr<Tegra::GPU> gpu_core; + + std::shared_ptr<Tegra::DebugContext> debug_context; + + Kernel::SharedPtr<Kernel::Process> current_process; /// When true, signals that a reschedule should happen bool reschedule_pending{}; @@ -158,4 +187,8 @@ inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } +inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { + return System::GetInstance().CurrentProcess(); +} + } // namespace Core diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a0656f0a8..9e1bf2d0e 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -122,7 +122,7 @@ u64 GetTicks() { } void AddTicks(u64 ticks) { - downcount -= ticks; + downcount -= static_cast<int>(ticks); } u64 GetIdleTicks() { @@ -208,7 +208,7 @@ void Advance() { Event evt = std::move(event_queue.front()); std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<Event>()); event_queue.pop_back(); - evt.type->callback(evt.userdata, global_timer - evt.time); + evt.type->callback(evt.userdata, static_cast<int>(global_timer - evt.time)); } is_global_timer_sane = false; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h new file mode 100644 index 000000000..c7639795e --- /dev/null +++ b/src/core/file_sys/directory.h @@ -0,0 +1,55 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +// Structure of a directory entry, from +// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry +const size_t FILENAME_LENGTH = 0x300; +struct Entry { + char filename[FILENAME_LENGTH]; + INSERT_PADDING_BYTES(4); + EntryType type; + INSERT_PADDING_BYTES(3); + u64 file_size; +}; +static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!"); +static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry."); +static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry."); + +class DirectoryBackend : NonCopyable { +public: + DirectoryBackend() {} + virtual ~DirectoryBackend() {} + + /** + * List files contained in the directory + * @param count Number of entries to return at once in entries + * @param entries Buffer to read data into + * @return Number of entries listed + */ + virtual u64 Read(const u64 count, Entry* entries) = 0; + + /// Returns the number of entries still left to read. + virtual u64 GetEntryCount() const = 0; + + /** + * Close the directory + * @return true if the directory closed correctly + */ + virtual bool Close() const = 0; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h deleted file mode 100644 index 0c93f2074..000000000 --- a/src/core/file_sys/directory_backend.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <cstddef> -#include "common/common_types.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format -const size_t FILENAME_LENGTH = 0x20C / 2; -struct Entry { - char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated) - std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated) - char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD) - std::array<char, 4> - extension; // 8.3 file extension (set to spaces for directories, null-terminated) - char unknown2; // unknown (always 0x01) - char unknown3; // unknown (0x00 or 0x08) - char is_directory; // directory flag - char is_hidden; // hidden flag - char is_archive; // archive flag - char is_read_only; // read-only flag - u64 file_size; // file size (for files only) -}; -static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!"); -static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry."); -static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry."); -static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry."); -static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry."); - -class DirectoryBackend : NonCopyable { -public: - DirectoryBackend() {} - virtual ~DirectoryBackend() {} - - /** - * List files contained in the directory - * @param count Number of entries to return at once in entries - * @param entries Buffer to read data into - * @return Number of entries listed - */ - virtual u32 Read(const u32 count, Entry* entries) = 0; - - /** - * Close the directory - * @return true if the directory closed correctly - */ - virtual bool Close() const = 0; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp deleted file mode 100644 index 98d80aabc..000000000 --- a/src/core/file_sys/disk_archive.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <cstdio> -#include <memory> -#include "common/common_types.h" -#include "common/file_util.h" -#include "common/logging/log.h" -#include "core/file_sys/disk_archive.h" -#include "core/file_sys/errors.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { - if (!mode.read_flag) - return ERROR_INVALID_OPEN_FLAGS; - - file->Seek(offset, SEEK_SET); - return MakeResult<size_t>(file->ReadBytes(buffer, length)); -} - -ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush, - const u8* buffer) const { - if (!mode.write_flag) - return ERROR_INVALID_OPEN_FLAGS; - - file->Seek(offset, SEEK_SET); - size_t written = file->WriteBytes(buffer, length); - if (flush) - file->Flush(); - return MakeResult<size_t>(written); -} - -u64 DiskFile::GetSize() const { - return file->GetSize(); -} - -bool DiskFile::SetSize(const u64 size) const { - file->Resize(size); - file->Flush(); - return true; -} - -bool DiskFile::Close() const { - return file->Close(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -DiskDirectory::DiskDirectory(const std::string& path) : directory() { - unsigned size = FileUtil::ScanDirectoryTree(path, directory); - directory.size = size; - directory.isDirectory = true; - children_iterator = directory.children.begin(); -} - -u32 DiskDirectory::Read(const u32 count, Entry* entries) { - u32 entries_read = 0; - - while (entries_read < count && children_iterator != directory.children.cend()) { - const FileUtil::FSTEntry& file = *children_iterator; - const std::string& filename = file.virtualName; - Entry& entry = entries[entries_read]; - - LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, - file.isDirectory); - - // TODO(Link Mauve): use a proper conversion to UTF-16. - for (size_t j = 0; j < FILENAME_LENGTH; ++j) { - entry.filename[j] = filename[j]; - if (!filename[j]) - break; - } - - FileUtil::SplitFilename83(filename, entry.short_name, entry.extension); - - entry.is_directory = file.isDirectory; - entry.is_hidden = (filename[0] == '.'); - entry.is_read_only = 0; - entry.file_size = file.size; - - // We emulate a SD card where the archive bit has never been cleared, as it would be on - // most user SD cards. - // Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a - // file bit. - entry.is_archive = !file.isDirectory; - - ++entries_read; - ++children_iterator; - } - return entries_read; -} - -} // namespace FileSys diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h deleted file mode 100644 index eb9166df6..000000000 --- a/src/core/file_sys/disk_archive.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <memory> -#include <string> -#include <vector> -#include "common/common_types.h" -#include "common/file_util.h" -#include "core/file_sys/archive_backend.h" -#include "core/file_sys/directory_backend.h" -#include "core/file_sys/file_backend.h" -#include "core/hle/result.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -class DiskFile : public FileBackend { -public: - DiskFile(FileUtil::IOFile&& file_, const Mode& mode_) - : file(new FileUtil::IOFile(std::move(file_))) { - mode.hex = mode_.hex; - } - - ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; - ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; - u64 GetSize() const override; - bool SetSize(u64 size) const override; - bool Close() const override; - - void Flush() const override { - file->Flush(); - } - -protected: - Mode mode; - std::unique_ptr<FileUtil::IOFile> file; -}; - -class DiskDirectory : public DirectoryBackend { -public: - DiskDirectory(const std::string& path); - - ~DiskDirectory() override { - Close(); - } - - u32 Read(const u32 count, Entry* entries) override; - - bool Close() const override { - return true; - } - -protected: - u32 total_entries_in_directory; - FileUtil::FSTEntry directory; - - // We need to remember the last entry we returned, so a subsequent call to Read will continue - // from the next one. This iterator will always point to the next unread entry. - std::vector<FileUtil::FSTEntry>::iterator children_iterator; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp new file mode 100644 index 000000000..4235f3935 --- /dev/null +++ b/src/core/file_sys/disk_filesystem.cpp @@ -0,0 +1,228 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/errors.h" + +namespace FileSys { + +static std::string ModeFlagsToString(Mode mode) { + std::string mode_str; + u32 mode_flags = static_cast<u32>(mode); + + // Calculate the correct open mode for the file. + if ((mode_flags & static_cast<u32>(Mode::Read)) && + (mode_flags & static_cast<u32>(Mode::Write))) { + if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a+"; + else + mode_str = "r+"; + } else { + if (mode_flags & static_cast<u32>(Mode::Read)) + mode_str = "r"; + else if (mode_flags & static_cast<u32>(Mode::Append)) + mode_str = "a"; + else if (mode_flags & static_cast<u32>(Mode::Write)) + mode_str = "w"; + } + + mode_str += "b"; + + return mode_str; +} + +std::string Disk_FileSystem::GetName() const { + return "Disk"; +} + +ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path, + Mode mode) const { + + // Calculate the correct open mode for the file. + std::string mode_str = ModeFlagsToString(mode); + + std::string full_path = base_directory + path; + auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str()); + + if (!file->IsOpen()) { + return ERROR_PATH_NOT_FOUND; + } + + return MakeResult<std::unique_ptr<StorageBackend>>( + std::make_unique<Disk_Storage>(std::move(file))); +} + +ResultCode Disk_FileSystem::DeleteFile(const Path& path) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(bunnei): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + std::string full_path = base_directory + path; + if (size == 0) { + FileUtil::CreateEmptyFile(full_path); + return RESULT_SUCCESS; + } + + FileUtil::IOFile file(full_path, "wb"); + // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) + // We do this by seeking to the right size, then writing a single null byte. + if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { + return RESULT_SUCCESS; + } + + LOG_ERROR(Service_FS, "Too large file"); + // TODO(Subv): Find out the correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const { + // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox. + std::string full_path = base_directory + path; + + if (FileUtil::CreateDir(full_path)) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory( + const std::string& path) const { + + std::string full_path = base_directory + path; + + if (!FileUtil::IsDirectory(full_path)) { + // TODO(Subv): Find the correct error code for this. + return ResultCode(-1); + } + + auto directory = std::make_unique<Disk_Directory>(full_path); + return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); +} + +u64 Disk_FileSystem::GetFreeSpaceSize() const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + return 0; +} + +ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const { + std::string full_path = base_directory + path; + if (!FileUtil::Exists(full_path)) { + return ERROR_PATH_NOT_FOUND; + } + + if (FileUtil::IsDirectory(full_path)) + return MakeResult(EntryType::Directory); + + return MakeResult(EntryType::File); +} + +ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { + LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length); + file->Seek(offset, SEEK_SET); + return MakeResult<size_t>(file->ReadBytes(buffer, length)); +} + +ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush, + const u8* buffer) const { + LOG_WARNING(Service_FS, "(STUBBED) called"); + file->Seek(offset, SEEK_SET); + size_t written = file->WriteBytes(buffer, length); + if (flush) { + file->Flush(); + } + return MakeResult<size_t>(written); +} + +u64 Disk_Storage::GetSize() const { + return file->GetSize(); +} + +bool Disk_Storage::SetSize(const u64 size) const { + file->Resize(size); + file->Flush(); + return true; +} + +Disk_Directory::Disk_Directory(const std::string& path) : directory() { + unsigned size = FileUtil::ScanDirectoryTree(path, directory); + directory.size = size; + directory.isDirectory = true; + children_iterator = directory.children.begin(); +} + +u64 Disk_Directory::Read(const u64 count, Entry* entries) { + u64 entries_read = 0; + + while (entries_read < count && children_iterator != directory.children.cend()) { + const FileUtil::FSTEntry& file = *children_iterator; + const std::string& filename = file.virtualName; + Entry& entry = entries[entries_read]; + + LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, + file.isDirectory); + + // TODO(Link Mauve): use a proper conversion to UTF-16. + for (size_t j = 0; j < FILENAME_LENGTH; ++j) { + entry.filename[j] = filename[j]; + if (!filename[j]) + break; + } + + if (file.isDirectory) { + entry.file_size = 0; + entry.type = EntryType::Directory; + } else { + entry.file_size = file.size; + entry.type = EntryType::File; + } + + ++entries_read; + ++children_iterator; + } + return entries_read; +} + +u64 Disk_Directory::GetEntryCount() const { + // We convert the children iterator into a const_iterator to allow template argument deduction + // in std::distance. + std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator; + return std::distance(current, directory.children.end()); +} + +} // namespace FileSys diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h new file mode 100644 index 000000000..742d7db1a --- /dev/null +++ b/src/core/file_sys/disk_filesystem.h @@ -0,0 +1,85 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> +#include <string> +#include "common/common_types.h" +#include "common/file_util.h" +#include "core/file_sys/directory.h" +#include "core/file_sys/filesystem.h" +#include "core/file_sys/storage.h" +#include "core/hle/result.h" + +namespace FileSys { + +class Disk_FileSystem : public FileSystemBackend { +public: + explicit Disk_FileSystem(std::string base_directory) + : base_directory(std::move(base_directory)) {} + + std::string GetName() const override; + + ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const override; + ResultCode DeleteFile(const Path& path) const override; + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; + ResultCode CreateFile(const std::string& path, u64 size) const override; + ResultCode CreateDirectory(const std::string& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; + u64 GetFreeSpaceSize() const override; + ResultVal<EntryType> GetEntryType(const std::string& path) const override; + +protected: + std::string base_directory; +}; + +class Disk_Storage : public StorageBackend { +public: + Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {} + + ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; + ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; + u64 GetSize() const override; + bool SetSize(u64 size) const override; + bool Close() const override { + return false; + } + void Flush() const override {} + +private: + std::shared_ptr<FileUtil::IOFile> file; +}; + +class Disk_Directory : public DirectoryBackend { +public: + Disk_Directory(const std::string& path); + + ~Disk_Directory() override { + Close(); + } + + u64 Read(const u64 count, Entry* entries) override; + u64 GetEntryCount() const override; + + bool Close() const override { + return true; + } + +protected: + u32 total_entries_in_directory; + FileUtil::FSTEntry directory; + + // We need to remember the last entry we returned, so a subsequent call to Read will continue + // from the next one. This iterator will always point to the next unread entry. + std::vector<FileUtil::FSTEntry>::iterator children_iterator; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index be3224ef8..0ed7d2a0c 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -10,36 +10,17 @@ namespace FileSys { namespace ErrCodes { enum { - RomFSNotFound = 100, - ArchiveNotMounted = 101, - FileNotFound = 112, - PathNotFound = 113, - GameCardNotInserted = 141, - NotFound = 120, - FileAlreadyExists = 180, - DirectoryAlreadyExists = 185, - AlreadyExists = 190, - InvalidOpenFlags = 230, - DirectoryNotEmpty = 240, - NotAFile = 250, - NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive - ExeFSSectionNotFound = 567, - CommandNotAllowed = 630, - InvalidReadFlag = 700, - InvalidPath = 702, - WriteBeyondEnd = 705, - UnsupportedOpenFlags = 760, - IncorrectExeFSReadSize = 761, - UnexpectedFileOrDirectory = 770, + NotFound = 1, }; } +constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrorModule::FS, ErrCodes::NotFound); + // TODO(bunnei): Replace these with correct errors for Switch OS constexpr ResultCode ERROR_INVALID_PATH(ResultCode(-1)); constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ResultCode(-1)); constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(ResultCode(-1)); constexpr ResultCode ERROR_FILE_NOT_FOUND(ResultCode(-1)); -constexpr ResultCode ERROR_PATH_NOT_FOUND(ResultCode(-1)); constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ResultCode(-1)); constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ResultCode(-1)); constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(ResultCode(-1)); diff --git a/src/core/file_sys/archive_backend.cpp b/src/core/file_sys/filesystem.cpp index 87a240d7a..82fdb3c46 100644 --- a/src/core/file_sys/archive_backend.cpp +++ b/src/core/file_sys/filesystem.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,7 +7,7 @@ #include <sstream> #include "common/logging/log.h" #include "common/string_util.h" -#include "core/file_sys/archive_backend.h" +#include "core/file_sys/filesystem.h" #include "core/memory.h" namespace FileSys { @@ -119,4 +119,4 @@ std::vector<u8> Path::AsBinary() const { return {}; } } -} +} // namespace FileSys diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/filesystem.h index 58f6c150c..399427ca2 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/filesystem.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -15,7 +15,7 @@ namespace FileSys { -class FileBackend; +class StorageBackend; class DirectoryBackend; // Path string type @@ -27,11 +27,15 @@ enum LowPathType : u32 { Wchar = 4, }; -union Mode { - u32 hex; - BitField<0, 1, u32> read_flag; - BitField<1, 1, u32> write_flag; - BitField<2, 1, u32> create_flag; +enum EntryType : u8 { + Directory = 0, + File = 1, +}; + +enum class Mode : u32 { + Read = 1, + Write = 2, + Append = 4, }; class Path { @@ -71,9 +75,9 @@ struct ArchiveFormatInfo { }; static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD"); -class ArchiveBackend : NonCopyable { +class FileSystemBackend : NonCopyable { public: - virtual ~ArchiveBackend() {} + virtual ~FileSystemBackend() {} /** * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) @@ -81,13 +85,12 @@ public: virtual std::string GetName() const = 0; /** - * Open a file specified by its path, using the specified mode - * @param path Path relative to the archive - * @param mode Mode to open the file with - * @return Opened file, or error code + * Create a file specified by its path + * @param path Path relative to the Archive + * @param size The size of the new file, filled with zeroes + * @return Result of the operation */ - virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, - const Mode& mode) const = 0; + virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0; /** * Delete a file specified by its path @@ -97,12 +100,11 @@ public: virtual ResultCode DeleteFile(const Path& path) const = 0; /** - * Rename a File specified by its path - * @param src_path Source path relative to the archive - * @param dest_path Destination path relative to the archive + * Create a directory specified by its path + * @param path Path relative to the archive * @return Result of the operation */ - virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; + virtual ResultCode CreateDirectory(const std::string& path) const = 0; /** * Delete a directory specified by its path @@ -119,19 +121,12 @@ public: virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0; /** - * Create a file specified by its path - * @param path Path relative to the Archive - * @param size The size of the new file, filled with zeroes - * @return Result of the operation - */ - virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; - - /** - * Create a directory specified by its path - * @param path Path relative to the archive + * Rename a File specified by its path + * @param src_path Source path relative to the archive + * @param dest_path Destination path relative to the archive * @return Result of the operation */ - virtual ResultCode CreateDirectory(const Path& path) const = 0; + virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; /** * Rename a Directory specified by its path @@ -142,22 +137,38 @@ public: virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; /** + * Open a file specified by its path, using the specified mode + * @param path Path relative to the archive + * @param mode Mode to open the file with + * @return Opened file, or error code + */ + virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const = 0; + + /** * Open a directory specified by its path * @param path Path relative to the archive * @return Opened directory, or error code */ - virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; + virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const = 0; /** * Get the free space * @return The number of free bytes in the archive */ - virtual u64 GetFreeBytes() const = 0; + virtual u64 GetFreeSpaceSize() const = 0; + + /** + * Get the type of the specified path + * @return The type of the specified path or error code + */ + virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0; }; -class ArchiveFactory : NonCopyable { +class FileSystemFactory : NonCopyable { public: - virtual ~ArchiveFactory() {} + virtual ~FileSystemFactory() {} /** * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) @@ -169,15 +180,14 @@ public: * @param path Path to the archive * @return An ArchiveBackend corresponding operating specified archive path. */ - virtual ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) = 0; + virtual ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) = 0; /** * Deletes the archive contents and then re-creates the base folder * @param path Path to the archive - * @param format_info Format information for the new archive * @return ResultCode of the operation, 0 on success */ - virtual ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) = 0; + virtual ResultCode Format(const Path& path) = 0; /** * Retrieves the format info about the archive with the specified path diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp deleted file mode 100644 index b3c3f2c6f..000000000 --- a/src/core/file_sys/ivfc_archive.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstring> -#include <memory> -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/file_sys/ivfc_archive.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -std::string IVFCArchive::GetName() const { - return "IVFC"; -} - -ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, - const Mode& mode) const { - return MakeResult<std::unique_ptr<FileBackend>>( - std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); -} - -ResultCode IVFCArchive::DeleteFile(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a file from an IVFC archive (%s).", - GetName().c_str()); - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", - GetName().c_str()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::DeleteDirectory(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", - GetName().c_str()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", - GetName().c_str()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { - LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive (%s).", - GetName().c_str()); - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::CreateDirectory(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", - GetName().c_str()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", - GetName().c_str()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const { - return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>()); -} - -u64 IVFCArchive::GetFreeBytes() const { - LOG_WARNING(Service_FS, "Attempted to get the free space in an IVFC archive"); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -ResultVal<size_t> IVFCFile::Read(const u64 offset, const size_t length, u8* buffer) const { - LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length); - romfs_file->Seek(data_offset + offset, SEEK_SET); - size_t read_length = (size_t)std::min((u64)length, data_size - offset); - - return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length)); -} - -ResultVal<size_t> IVFCFile::Write(const u64 offset, const size_t length, const bool flush, - const u8* buffer) const { - LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); - // TODO(Subv): Find error code - return MakeResult<size_t>(0); -} - -u64 IVFCFile::GetSize() const { - return data_size; -} - -bool IVFCFile::SetSize(const u64 size) const { - LOG_ERROR(Service_FS, "Attempted to set the size of an IVFC file"); - return false; -} - -} // namespace FileSys diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h index b9f52f65d..184f59d55 100644 --- a/src/core/file_sys/path_parser.h +++ b/src/core/file_sys/path_parser.h @@ -6,7 +6,7 @@ #include <string> #include <vector> -#include "core/file_sys/archive_backend.h" +#include "core/file_sys/filesystem.h" namespace FileSys { diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp new file mode 100644 index 000000000..a6dcebcc3 --- /dev/null +++ b/src/core/file_sys/program_metadata.cpp @@ -0,0 +1,114 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/file_sys/program_metadata.h" +#include "core/loader/loader.h" + +namespace FileSys { + +Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) { + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + std::vector<u8> file_data(file.GetSize()); + + if (!file.ReadBytes(file_data.data(), file_data.size())) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(file_data); + if (result != Loader::ResultStatus::Success) + LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str()); + + return result; +} + +Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8> file_data, size_t offset) { + size_t total_size = static_cast<size_t>(file_data.size() - offset); + if (total_size < sizeof(Header)) + return Loader::ResultStatus::Error; + + size_t header_offset = offset; + memcpy(&npdm_header, &file_data[offset], sizeof(Header)); + + size_t aci_offset = header_offset + npdm_header.aci_offset; + size_t acid_offset = header_offset + npdm_header.acid_offset; + memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader)); + memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader)); + + size_t fac_offset = acid_offset + acid_header.fac_offset; + size_t fah_offset = aci_offset + aci_header.fah_offset; + memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl)); + memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader)); + + return Loader::ResultStatus::Success; +} + +bool ProgramMetadata::Is64BitProgram() const { + return npdm_header.has_64_bit_instructions; +} + +ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const { + return npdm_header.address_space_type; +} + +u8 ProgramMetadata::GetMainThreadPriority() const { + return npdm_header.main_thread_priority; +} + +u8 ProgramMetadata::GetMainThreadCore() const { + return npdm_header.main_thread_cpu; +} + +u32 ProgramMetadata::GetMainThreadStackSize() const { + return npdm_header.main_stack_size; +} + +u64 ProgramMetadata::GetTitleID() const { + return aci_header.title_id; +} + +u64 ProgramMetadata::GetFilesystemPermissions() const { + return aci_file_access.permissions; +} + +void ProgramMetadata::Print() const { + LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data()); + LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority); + LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu); + LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size); + LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category); + LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags); + LOG_DEBUG(Service_FS, " > 64-bit instructions: %s", + npdm_header.has_64_bit_instructions ? "YES" : "NO"); + + auto address_space = "Unknown"; + switch (npdm_header.address_space_type) { + case ProgramAddressSpaceType::Is64Bit: + address_space = "64-bit"; + break; + case ProgramAddressSpaceType::Is32Bit: + address_space = "32-bit"; + break; + } + + LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space); + + // Begin ACID printing (potential perms, signed) + LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data()); + LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags); + LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO"); + LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min); + LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max); + LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions); + + // Begin ACI0 printing (actual perms, unsigned) + LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data()); + LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id); + LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions); +} +} // namespace FileSys diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h new file mode 100644 index 000000000..b80a08485 --- /dev/null +++ b/src/core/file_sys/program_metadata.h @@ -0,0 +1,154 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { +enum class ResultStatus; +} + +namespace FileSys { + +enum class ProgramAddressSpaceType : u8 { + Is64Bit = 1, + Is32Bit = 2, +}; + +enum class ProgramFilePermission : u64 { + MountContent = 1ULL << 0, + SaveDataBackup = 1ULL << 5, + SdCard = 1ULL << 21, + Calibration = 1ULL << 34, + Bit62 = 1ULL << 62, + Everything = 1ULL << 63, +}; + +/** + * Helper which implements an interface to parse Program Description Metadata (NPDM) + * Data can either be loaded from a file path or with data and an offset into it. + */ +class ProgramMetadata { +public: + Loader::ResultStatus Load(const std::string& file_path); + Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0); + + bool Is64BitProgram() const; + ProgramAddressSpaceType GetAddressSpaceType() const; + u8 GetMainThreadPriority() const; + u8 GetMainThreadCore() const; + u32 GetMainThreadStackSize() const; + u64 GetTitleID() const; + u64 GetFilesystemPermissions() const; + + void Print() const; + +private: + struct Header { + std::array<char, 4> magic; + std::array<u8, 8> reserved; + union { + u8 flags; + + BitField<0, 1, u8> has_64_bit_instructions; + BitField<1, 3, ProgramAddressSpaceType> address_space_type; + BitField<4, 4, u8> reserved_2; + }; + u8 reserved_3; + u8 main_thread_priority; + u8 main_thread_cpu; + std::array<u8, 8> reserved_4; + u32_le process_category; + u32_le main_stack_size; + std::array<u8, 0x10> application_name; + std::array<u8, 0x40> reserved_5; + u32_le aci_offset; + u32_le aci_size; + u32_le acid_offset; + u32_le acid_size; + }; + + static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); + + struct AcidHeader { + std::array<u8, 0x100> signature; + std::array<u8, 0x100> nca_modulus; + std::array<char, 4> magic; + u32_le nca_size; + std::array<u8, 0x4> reserved; + union { + u32 flags; + + BitField<0, 1, u32> is_retail; + BitField<1, 31, u32> flags_unk; + }; + u64_le title_id_min; + u64_le title_id_max; + u32_le fac_offset; + u32_le fac_size; + u32_le sac_offset; + u32_le sac_size; + u32_le kac_offset; + u32_le kac_size; + INSERT_PADDING_BYTES(0x8); + }; + + static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong"); + + struct AciHeader { + std::array<char, 4> magic; + std::array<u8, 0xC> reserved; + u64_le title_id; + INSERT_PADDING_BYTES(0x8); + u32_le fah_offset; + u32_le fah_size; + u32_le sac_offset; + u32_le sac_size; + u32_le kac_offset; + u32_le kac_size; + INSERT_PADDING_BYTES(0x8); + }; + + static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong"); + +#pragma pack(push, 1) + + struct FileAccessControl { + u8 version; + INSERT_PADDING_BYTES(3); + u64_le permissions; + std::array<u8, 0x20> unknown; + }; + + static_assert(sizeof(FileAccessControl) == 0x2C, "FS access control structure size is wrong"); + + struct FileAccessHeader { + u8 version; + INSERT_PADDING_BYTES(3); + u64_le permissions; + u32_le unk_offset; + u32_le unk_size; + u32_le unk_offset_2; + u32_le unk_size_2; + }; + + static_assert(sizeof(FileAccessHeader) == 0x1C, "FS access header structure size is wrong"); + +#pragma pack(pop) + + Header npdm_header; + AciHeader aci_header; + AcidHeader acid_header; + + FileAccessControl acid_file_access; + FileAccessHeader aci_file_access; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp new file mode 100644 index 000000000..b21427948 --- /dev/null +++ b/src/core/file_sys/romfs_factory.cpp @@ -0,0 +1,38 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/file_sys/romfs_factory.h" +#include "core/file_sys/romfs_filesystem.h" + +namespace FileSys { + +RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) { + // Load the RomFS from the app + if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { + LOG_ERROR(Service_FS, "Unable to read RomFS!"); + } +} + +ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& path) { + auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode RomFS_Factory::Format(const Path& path) { + LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const { + LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +} // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h new file mode 100644 index 000000000..e0698e642 --- /dev/null +++ b/src/core/file_sys/romfs_factory.h @@ -0,0 +1,35 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include <vector> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" +#include "core/loader/loader.h" + +namespace FileSys { + +/// File system interface to the RomFS archive +class RomFS_Factory final : public FileSystemFactory { +public: + explicit RomFS_Factory(Loader::AppLoader& app_loader); + + std::string GetName() const override { + return "ArchiveFactory_RomFS"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::shared_ptr<FileUtil::IOFile> romfs_file; + u64 data_offset; + u64 data_size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp new file mode 100644 index 000000000..0c6cc3157 --- /dev/null +++ b/src/core/file_sys/romfs_filesystem.cpp @@ -0,0 +1,113 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/file_sys/romfs_filesystem.h" + +namespace FileSys { + +std::string RomFS_FileSystem::GetName() const { + return "RomFS"; +} + +ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path, + Mode mode) const { + return MakeResult<std::unique_ptr<StorageBackend>>( + std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size)); +} + +ResultCode RomFS_FileSystem::DeleteFile(const Path& path) const { + LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive (%s).", + GetName().c_str()); + // TODO(bunnei): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const { + LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).", + GetName().c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const { + LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).", + GetName().c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const { + LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).", + GetName().c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const { + LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive (%s).", + GetName().c_str()); + // TODO(bunnei): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const { + LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).", + GetName().c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { + LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).", + GetName().c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory( + const std::string& path) const { + LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive"); + return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>()); +} + +u64 RomFS_FileSystem::GetFreeSpaceSize() const { + LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive"); + return 0; +} + +ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const { + LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path %s).", path.c_str()); + // TODO(wwylele): Use correct error code + return ResultCode(-1); +} + +ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { + LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length); + romfs_file->Seek(data_offset + offset, SEEK_SET); + size_t read_length = (size_t)std::min((u64)length, data_size - offset); + + return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length)); +} + +ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush, + const u8* buffer) const { + LOG_ERROR(Service_FS, "Attempted to write to ROMFS file"); + // TODO(Subv): Find error code + return MakeResult<size_t>(0); +} + +u64 RomFS_Storage::GetSize() const { + return data_size; +} + +bool RomFS_Storage::SetSize(const u64 size) const { + LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file"); + return false; +} + +} // namespace FileSys diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/romfs_filesystem.h index e6fbdfb1f..3f94c04d0 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/romfs_filesystem.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,39 +10,38 @@ #include <vector> #include "common/common_types.h" #include "common/file_util.h" -#include "core/file_sys/archive_backend.h" -#include "core/file_sys/directory_backend.h" -#include "core/file_sys/file_backend.h" +#include "core/file_sys/directory.h" +#include "core/file_sys/filesystem.h" +#include "core/file_sys/storage.h" #include "core/hle/result.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - namespace FileSys { /** - * Helper which implements an interface to deal with IVFC images used in some archives - * This should be subclassed by concrete archive types, which will provide the - * input data (load the raw IVFC archive) and override any required methods + * Helper which implements an interface to deal with Switch .istorage ROMFS images used in some + * archives This should be subclassed by concrete archive types, which will provide the input data + * (load the raw ROMFS archive) and override any required methods */ -class IVFCArchive : public ArchiveBackend { +class RomFS_FileSystem : public FileSystemBackend { public: - IVFCArchive(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) + RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) : romfs_file(file), data_offset(offset), data_size(size) {} std::string GetName() const override; - ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path, + Mode mode) const override; ResultCode DeleteFile(const Path& path) const override; ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; ResultCode DeleteDirectory(const Path& path) const override; ResultCode DeleteDirectoryRecursively(const Path& path) const override; - ResultCode CreateFile(const Path& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; + ResultCode CreateFile(const std::string& path, u64 size) const override; + ResultCode CreateDirectory(const std::string& path) const override; ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; - u64 GetFreeBytes() const override; + ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory( + const std::string& path) const override; + u64 GetFreeSpaceSize() const override; + ResultVal<EntryType> GetEntryType(const std::string& path) const override; protected: std::shared_ptr<FileUtil::IOFile> romfs_file; @@ -50,9 +49,9 @@ protected: u64 data_size; }; -class IVFCFile : public FileBackend { +class RomFS_Storage : public StorageBackend { public: - IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) + RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) : romfs_file(file), data_offset(offset), data_size(size) {} ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; @@ -70,9 +69,12 @@ private: u64 data_size; }; -class IVFCDirectory : public DirectoryBackend { +class ROMFSDirectory : public DirectoryBackend { public: - u32 Read(const u32 count, Entry* entries) override { + u64 Read(const u64 count, Entry* entries) override { + return 0; + } + u64 GetEntryCount() const override { return 0; } bool Close() const override { diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp deleted file mode 100644 index d7b012f6e..000000000 --- a/src/core/file_sys/savedata_archive.cpp +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/file_util.h" -#include "core/file_sys/disk_archive.h" -#include "core/file_sys/errors.h" -#include "core/file_sys/path_parser.h" -#include "core/file_sys/savedata_archive.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path, - const Mode& mode) const { - LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); - - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - if (mode.hex == 0) { - LOG_ERROR(Service_FS, "Empty open mode"); - return ERROR_UNSUPPORTED_OPEN_FLAGS; - } - - if (mode.create_flag && !mode.write_flag) { - LOG_ERROR(Service_FS, "Create flag set but write flag not set"); - return ERROR_UNSUPPORTED_OPEN_FLAGS; - } - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::PathNotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - case PathParser::DirectoryFound: - LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); - return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; - case PathParser::NotFound: - if (!mode.create_flag) { - LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", - full_path.c_str()); - return ERROR_FILE_NOT_FOUND; - } else { - // Create the file - FileUtil::CreateEmptyFile(full_path); - } - break; - case PathParser::FileFound: - break; // Expected 'success' case - } - - FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); - if (!file.IsOpen()) { - LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); - return ERROR_FILE_NOT_FOUND; - } - - auto disk_file = std::make_unique<DiskFile>(std::move(file), mode); - return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); -} - -ResultCode SaveDataArchive::DeleteFile(const Path& path) const { - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::PathNotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - case PathParser::DirectoryFound: - case PathParser::NotFound: - LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::FileFound: - break; // Expected 'success' case - } - - if (FileUtil::Delete(full_path)) { - return RESULT_SUCCESS; - } - - LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); - return ERROR_FILE_NOT_FOUND; -} - -ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - const PathParser path_parser_src(src_path); - - // TODO: Verify these return codes with HW - if (!path_parser_src.IsValid()) { - LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const PathParser path_parser_dest(dest_path); - - if (!path_parser_dest.IsValid()) { - LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto src_path_full = path_parser_src.BuildHostPath(mount_point); - const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); - - if (FileUtil::Rename(src_path_full, dest_path_full)) { - return RESULT_SUCCESS; - } - - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -template <typename T> -static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, - T deleter) { - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - if (path_parser.IsRootDirectory()) - return ERROR_DIRECTORY_NOT_EMPTY; - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::PathNotFound: - case PathParser::NotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - case PathParser::FileFound: - LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); - return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; - case PathParser::DirectoryFound: - break; // Expected 'success' case - } - - if (deleter(full_path)) { - return RESULT_SUCCESS; - } - - LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); - return ERROR_DIRECTORY_NOT_EMPTY; -} - -ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const { - return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); -} - -ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { - return DeleteDirectoryHelper( - path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); -} - -ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::PathNotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); - return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; - case PathParser::DirectoryFound: - case PathParser::FileFound: - LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); - return ERROR_FILE_ALREADY_EXISTS; - case PathParser::NotFound: - break; // Expected 'success' case - } - - if (size == 0) { - FileUtil::CreateEmptyFile(full_path); - return RESULT_SUCCESS; - } - - FileUtil::IOFile file(full_path, "wb"); - // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) - // We do this by seeking to the right size, then writing a single null byte. - if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { - return RESULT_SUCCESS; - } - - LOG_ERROR(Service_FS, "Too large file"); - - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::PathNotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); - return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; - case PathParser::DirectoryFound: - case PathParser::FileFound: - LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); - return ERROR_DIRECTORY_ALREADY_EXISTS; - case PathParser::NotFound: - break; // Expected 'success' case - } - - if (FileUtil::CreateDir(mount_point + path.AsString())) { - return RESULT_SUCCESS; - } - - LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); - - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - const PathParser path_parser_src(src_path); - - // TODO: Verify these return codes with HW - if (!path_parser_src.IsValid()) { - LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const PathParser path_parser_dest(dest_path); - - if (!path_parser_dest.IsValid()) { - LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto src_path_full = path_parser_src.BuildHostPath(mount_point); - const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); - - if (FileUtil::Rename(src_path_full, dest_path_full)) { - return RESULT_SUCCESS; - } - - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( - const Path& path) const { - const PathParser path_parser(path); - - if (!path_parser.IsValid()) { - LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); - return ERROR_INVALID_PATH; - } - - const auto full_path = path_parser.BuildHostPath(mount_point); - - switch (path_parser.GetHostStatus(mount_point)) { - case PathParser::InvalidMountPoint: - LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); - return ERROR_FILE_NOT_FOUND; - case PathParser::PathNotFound: - case PathParser::NotFound: - LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); - return ERROR_PATH_NOT_FOUND; - case PathParser::FileInPath: - case PathParser::FileFound: - LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); - return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; - case PathParser::DirectoryFound: - break; // Expected 'success' case - } - - auto directory = std::make_unique<DiskDirectory>(full_path); - return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); -} - -u64 SaveDataArchive::GetFreeBytes() const { - // TODO: Stubbed to return 1GiB - return 1024 * 1024 * 1024; -} - -} // namespace FileSys diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h deleted file mode 100644 index 176d35710..000000000 --- a/src/core/file_sys/savedata_archive.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include "core/file_sys/archive_backend.h" -#include "core/file_sys/directory_backend.h" -#include "core/file_sys/file_backend.h" -#include "core/hle/result.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -/// Archive backend for general save data archive type (SaveData and SystemSaveData) -class SaveDataArchive : public ArchiveBackend { -public: - explicit SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {} - - std::string GetName() const override { - return "SaveDataArchive: " + mount_point; - } - - ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, - const Mode& mode) const override; - ResultCode DeleteFile(const Path& path) const override; - ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; - ResultCode DeleteDirectory(const Path& path) const override; - ResultCode DeleteDirectoryRecursively(const Path& path) const override; - ResultCode CreateFile(const Path& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; - ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; - u64 GetFreeBytes() const override; - -protected: - std::string mount_point; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp new file mode 100644 index 000000000..14868fed2 --- /dev/null +++ b/src/core/file_sys/savedata_factory.cpp @@ -0,0 +1,57 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/savedata_factory.h" +#include "core/hle/kernel/process.h" + +namespace FileSys { + +SaveData_Factory::SaveData_Factory(std::string nand_directory) + : nand_directory(std::move(nand_directory)) {} + +ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) { + std::string save_directory = GetFullPath(); + // Return an error if the save data doesn't actually exist. + if (!FileUtil::IsDirectory(save_directory)) { + // TODO(Subv): Find out correct error code. + return ResultCode(-1); + } + + auto archive = std::make_unique<Disk_FileSystem>(save_directory); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode SaveData_Factory::Format(const Path& path) { + LOG_WARNING(Service_FS, "Format archive %s", GetName().c_str()); + // Create the save data directory. + if (!FileUtil::CreateFullPath(GetFullPath())) { + // TODO(Subv): Find the correct error code. + return ResultCode(-1); + } + + return RESULT_SUCCESS; +} + +ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const { + LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +std::string SaveData_Factory::GetFullPath() const { + u64 title_id = Core::CurrentProcess()->program_id; + // TODO(Subv): Somehow obtain this value. + u32 user = 0; + return Common::StringFromFormat("%ssave/%016" PRIX64 "/%08X/", nand_directory.c_str(), title_id, + user); +} + +} // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h new file mode 100644 index 000000000..73a42aab6 --- /dev/null +++ b/src/core/file_sys/savedata_factory.h @@ -0,0 +1,33 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" + +namespace FileSys { + +/// File system interface to the SaveData archive +class SaveData_Factory final : public FileSystemFactory { +public: + explicit SaveData_Factory(std::string nand_directory); + + std::string GetName() const override { + return "SaveData_Factory"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::string nand_directory; + + std::string GetFullPath() const; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp new file mode 100644 index 000000000..00e80d2a7 --- /dev/null +++ b/src/core/file_sys/sdmc_factory.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include <memory> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/disk_filesystem.h" +#include "core/file_sys/sdmc_factory.h" + +namespace FileSys { + +SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {} + +ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) { + // Create the SD Card directory if it doesn't already exist. + if (!FileUtil::IsDirectory(sd_directory)) { + FileUtil::CreateFullPath(sd_directory); + } + + auto archive = std::make_unique<Disk_FileSystem>(sd_directory); + return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); +} + +ResultCode SDMC_Factory::Format(const Path& path) { + LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str()); + // TODO(Subv): Find the right error code for this + return ResultCode(-1); +} + +ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const { + LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + // TODO(bunnei): Find the right error code for this + return ResultCode(-1); +} + +} // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h new file mode 100644 index 000000000..93becda25 --- /dev/null +++ b/src/core/file_sys/sdmc_factory.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include "common/common_types.h" +#include "core/file_sys/filesystem.h" +#include "core/hle/result.h" + +namespace FileSys { + +/// File system interface to the SDCard archive +class SDMC_Factory final : public FileSystemFactory { +public: + explicit SDMC_Factory(std::string sd_directory); + + std::string GetName() const override { + return "SDMC_Factory"; + } + ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; + ResultCode Format(const Path& path) override; + ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; + +private: + std::string sd_directory; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/storage.h index 5e7c2bab4..2a6811831 100644 --- a/src/core/file_sys/file_backend.h +++ b/src/core/file_sys/storage.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -8,15 +8,12 @@ #include "common/common_types.h" #include "core/hle/result.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - namespace FileSys { -class FileBackend : NonCopyable { +class StorageBackend : NonCopyable { public: - FileBackend() {} - virtual ~FileBackend() {} + StorageBackend() {} + virtual ~StorageBackend() {} /** * Read data from the file @@ -39,10 +36,9 @@ public: const u8* buffer) const = 0; /** - * Get the size of the file in bytes - * @return Size of the file in bytes + * Flushes the file */ - virtual u64 GetSize() const = 0; + virtual void Flush() const = 0; /** * Set the size of the file in bytes @@ -52,15 +48,16 @@ public: virtual bool SetSize(u64 size) const = 0; /** - * Close the file - * @return true if the file closed correctly + * Get the size of the file in bytes + * @return Size of the file in bytes */ - virtual bool Close() const = 0; + virtual u64 GetSize() const = 0; /** - * Flushes the file + * Close the file + * @return true if the file closed correctly */ - virtual void Flush() const = 0; + virtual bool Close() const = 0; }; } // namespace FileSys diff --git a/src/core/file_sys/title_metadata.cpp b/src/core/file_sys/title_metadata.cpp deleted file mode 100644 index e29ba6064..000000000 --- a/src/core/file_sys/title_metadata.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cinttypes> -#include "common/alignment.h" -#include "common/file_util.h" -#include "common/logging/log.h" -#include "core/file_sys/title_metadata.h" -#include "core/loader/loader.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -static u32 GetSignatureSize(u32 signature_type) { - switch (signature_type) { - case Rsa4096Sha1: - case Rsa4096Sha256: - return 0x200; - - case Rsa2048Sha1: - case Rsa2048Sha256: - return 0x100; - - case EllipticSha1: - case EcdsaSha256: - return 0x3C; - } -} - -Loader::ResultStatus TitleMetadata::Load() { - FileUtil::IOFile file(filepath, "rb"); - if (!file.IsOpen()) - return Loader::ResultStatus::Error; - - if (!file.ReadBytes(&signature_type, sizeof(u32_be))) - return Loader::ResultStatus::Error; - - // Signature lengths are variable, and the body follows the signature - u32 signature_size = GetSignatureSize(signature_type); - - tmd_signature.resize(signature_size); - if (!file.ReadBytes(&tmd_signature[0], signature_size)) - return Loader::ResultStatus::Error; - - // The TMD body start position is rounded to the nearest 0x40 after the signature - size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); - file.Seek(body_start, SEEK_SET); - - // Read our TMD body, then load the amount of ContentChunks specified - if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) - return Loader::ResultStatus::Error; - - for (u16 i = 0; i < tmd_body.content_count; i++) { - ContentChunk chunk; - if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) { - tmd_chunks.push_back(chunk); - } else { - LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!", - filepath.c_str(), i); - return Loader::ResultStatus::ErrorInvalidFormat; - } - } - - return Loader::ResultStatus::Success; -} - -Loader::ResultStatus TitleMetadata::Save() { - UNIMPLEMENTED(); - return Loader::ResultStatus::Success; -} - -u64 TitleMetadata::GetTitleID() const { - return tmd_body.title_id; -} - -u32 TitleMetadata::GetTitleType() const { - return tmd_body.title_type; -} - -u16 TitleMetadata::GetTitleVersion() const { - return tmd_body.title_version; -} - -u64 TitleMetadata::GetSystemVersion() const { - return tmd_body.system_version; -} - -size_t TitleMetadata::GetContentCount() const { - return tmd_chunks.size(); -} - -u32 TitleMetadata::GetBootContentID() const { - return tmd_chunks[TMDContentIndex::Main].id; -} - -u32 TitleMetadata::GetManualContentID() const { - return tmd_chunks[TMDContentIndex::Manual].id; -} - -u32 TitleMetadata::GetDLPContentID() const { - return tmd_chunks[TMDContentIndex::DLP].id; -} - -void TitleMetadata::SetTitleID(u64 title_id) { - tmd_body.title_id = title_id; -} - -void TitleMetadata::SetTitleType(u32 type) { - tmd_body.title_type = type; -} - -void TitleMetadata::SetTitleVersion(u16 version) { - tmd_body.title_version = version; -} - -void TitleMetadata::SetSystemVersion(u64 version) { - tmd_body.system_version = version; -} - -void TitleMetadata::AddContentChunk(const ContentChunk& chunk) { - tmd_chunks.push_back(chunk); -} - -void TitleMetadata::Print() const { - LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(), - static_cast<u32>(tmd_body.content_count)); - - // Content info describes ranges of content chunks - LOG_DEBUG(Service_FS, "Content info:"); - for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { - if (tmd_body.contentinfo[i].command_count == 0) - break; - - LOG_DEBUG(Service_FS, " Index %04X, Command Count %04X", - static_cast<u32>(tmd_body.contentinfo[i].index), - static_cast<u32>(tmd_body.contentinfo[i].command_count)); - } - - // For each content info, print their content chunk range - for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { - u16 index = static_cast<u16>(tmd_body.contentinfo[i].index); - u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count); - - if (count == 0) - continue; - - LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i); - for (u16 j = index; j < index + count; j++) { - // Don't attempt to print content we don't have - if (j > tmd_body.content_count) - break; - - const ContentChunk& chunk = tmd_chunks[j]; - LOG_DEBUG(Service_FS, " ID %08X, Index %04X, Type %04x, Size %016" PRIX64, - static_cast<u32>(chunk.id), static_cast<u32>(chunk.index), - static_cast<u32>(chunk.type), static_cast<u64>(chunk.size)); - } - } -} -} // namespace FileSys diff --git a/src/core/file_sys/title_metadata.h b/src/core/file_sys/title_metadata.h deleted file mode 100644 index 1fc157bf3..000000000 --- a/src/core/file_sys/title_metadata.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <vector> -#include "common/common_types.h" -#include "common/swap.h" - -namespace Loader { -enum class ResultStatus; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -enum TMDSignatureType : u32 { - Rsa4096Sha1 = 0x10000, - Rsa2048Sha1 = 0x10001, - EllipticSha1 = 0x10002, - Rsa4096Sha256 = 0x10003, - Rsa2048Sha256 = 0x10004, - EcdsaSha256 = 0x10005 -}; - -enum TMDContentTypeFlag : u16 { - Encrypted = 1 << 1, - Disc = 1 << 2, - CFM = 1 << 3, - Optional = 1 << 14, - Shared = 1 << 15 -}; - -/** - * Helper which implements an interface to read and write Title Metadata (TMD) files. - * If a file path is provided and the file exists, it can be parsed and used, otherwise - * it must be created. The TMD file can then be interpreted, modified and/or saved. - */ -class TitleMetadata { -public: - struct ContentChunk { - u32_be id; - u16_be index; - u16_be type; - u64_be size; - std::array<u8, 0x20> hash; - }; - - static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong"); - - struct ContentInfo { - u16_be index; - u16_be command_count; - std::array<u8, 0x20> hash; - }; - - static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong"); - -#pragma pack(push, 1) - - struct Body { - std::array<u8, 0x40> issuer; - u8 version; - u8 ca_crl_version; - u8 signer_crl_version; - u8 reserved; - u64_be system_version; - u64_be title_id; - u32_be title_type; - u16_be group_id; - u32_be savedata_size; - u32_be srl_private_savedata_size; - std::array<u8, 4> reserved_2; - u8 srl_flag; - std::array<u8, 0x31> reserved_3; - u32_be access_rights; - u16_be title_version; - u16_be content_count; - u16_be boot_content; - std::array<u8, 2> reserved_4; - std::array<u8, 0x20> contentinfo_hash; - std::array<ContentInfo, 64> contentinfo; - }; - - static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong"); - -#pragma pack(pop) - - explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {} - Loader::ResultStatus Load(); - Loader::ResultStatus Save(); - - u64 GetTitleID() const; - u32 GetTitleType() const; - u16 GetTitleVersion() const; - u64 GetSystemVersion() const; - size_t GetContentCount() const; - u32 GetBootContentID() const; - u32 GetManualContentID() const; - u32 GetDLPContentID() const; - - void SetTitleID(u64 title_id); - void SetTitleType(u32 type); - void SetTitleVersion(u16 version); - void SetSystemVersion(u64 version); - void AddContentChunk(const ContentChunk& chunk); - - void Print() const; - -private: - enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 }; - - Body tmd_body; - u32_be signature_type; - std::vector<u8> tmd_signature; - std::vector<ContentChunk> tmd_chunks; - - std::string filepath; -}; - -} // namespace FileSys diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index dedbd4bdf..e4f337a0a 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -6,6 +6,7 @@ #include <algorithm> #include <atomic> +#include <cinttypes> #include <climits> #include <csignal> #include <cstdarg> @@ -57,9 +58,10 @@ const u32 SIGTERM = 15; const u32 MSG_WAITALL = 8; #endif -const u32 R15_REGISTER = 15; -const u32 CPSR_REGISTER = 25; -const u32 FPSCR_REGISTER = 58; +const u32 X30_REGISTER = 30; +const u32 SP_REGISTER = 31; +const u32 PC_REGISTER = 32; +const u32 CPSR_REGISTER = 33; // For sample XML files see the GDB source /gdb/features // GDB also wants the l character at the start @@ -68,48 +70,62 @@ static const char* target_xml = R"(l<?xml version="1.0"?> <!DOCTYPE target SYSTEM "gdb-target.dtd"> <target version="1.0"> - <feature name="org.gnu.gdb.arm.core"> - <reg name="r0" bitsize="32"/> - <reg name="r1" bitsize="32"/> - <reg name="r2" bitsize="32"/> - <reg name="r3" bitsize="32"/> - <reg name="r4" bitsize="32"/> - <reg name="r5" bitsize="32"/> - <reg name="r6" bitsize="32"/> - <reg name="r7" bitsize="32"/> - <reg name="r8" bitsize="32"/> - <reg name="r9" bitsize="32"/> - <reg name="r10" bitsize="32"/> - <reg name="r11" bitsize="32"/> - <reg name="r12" bitsize="32"/> - <reg name="sp" bitsize="32" type="data_ptr"/> - <reg name="lr" bitsize="32"/> - <reg name="pc" bitsize="32" type="code_ptr"/> - - <!-- The CPSR is register 25, rather than register 16, because - the FPA registers historically were placed between the PC - and the CPSR in the "g" packet. --> - - <reg name="cpsr" bitsize="32" regnum="25"/> - </feature> - <feature name="org.gnu.gdb.arm.vfp"> - <reg name="d0" bitsize="64" type="float"/> - <reg name="d1" bitsize="64" type="float"/> - <reg name="d2" bitsize="64" type="float"/> - <reg name="d3" bitsize="64" type="float"/> - <reg name="d4" bitsize="64" type="float"/> - <reg name="d5" bitsize="64" type="float"/> - <reg name="d6" bitsize="64" type="float"/> - <reg name="d7" bitsize="64" type="float"/> - <reg name="d8" bitsize="64" type="float"/> - <reg name="d9" bitsize="64" type="float"/> - <reg name="d10" bitsize="64" type="float"/> - <reg name="d11" bitsize="64" type="float"/> - <reg name="d12" bitsize="64" type="float"/> - <reg name="d13" bitsize="64" type="float"/> - <reg name="d14" bitsize="64" type="float"/> - <reg name="d15" bitsize="64" type="float"/> - <reg name="fpscr" bitsize="32" type="int" group="float"/> + <feature name="org.gnu.gdb.aarch64.core"> + <reg name="x0" bitsize="64"/> + <reg name="x1" bitsize="64"/> + <reg name="x2" bitsize="64"/> + <reg name="x3" bitsize="64"/> + <reg name="x4" bitsize="64"/> + <reg name="x5" bitsize="64"/> + <reg name="x6" bitsize="64"/> + <reg name="x7" bitsize="64"/> + <reg name="x8" bitsize="64"/> + <reg name="x9" bitsize="64"/> + <reg name="x10" bitsize="64"/> + <reg name="x11" bitsize="64"/> + <reg name="x12" bitsize="64"/> + <reg name="x13" bitsize="64"/> + <reg name="x14" bitsize="64"/> + <reg name="x15" bitsize="64"/> + <reg name="x16" bitsize="64"/> + <reg name="x17" bitsize="64"/> + <reg name="x18" bitsize="64"/> + <reg name="x19" bitsize="64"/> + <reg name="x20" bitsize="64"/> + <reg name="x21" bitsize="64"/> + <reg name="x22" bitsize="64"/> + <reg name="x23" bitsize="64"/> + <reg name="x24" bitsize="64"/> + <reg name="x25" bitsize="64"/> + <reg name="x26" bitsize="64"/> + <reg name="x27" bitsize="64"/> + <reg name="x28" bitsize="64"/> + <reg name="x29" bitsize="64"/> + <reg name="x30" bitsize="64"/> + <reg name="sp" bitsize="64" type="data_ptr"/> + + <reg name="pc" bitsize="64" type="code_ptr"/> + + <flags id="cpsr_flags" size="4"> + <field name="SP" start="0" end="0"/> + <field name="" start="1" end="1"/> + <field name="EL" start="2" end="3"/> + <field name="nRW" start="4" end="4"/> + <field name="" start="5" end="5"/> + <field name="F" start="6" end="6"/> + <field name="I" start="7" end="7"/> + <field name="A" start="8" end="8"/> + <field name="D" start="9" end="9"/> + + <field name="IL" start="20" end="20"/> + <field name="SS" start="21" end="21"/> + + <field name="V" start="28" end="28"/> + <field name="C" start="29" end="29"/> + <field name="Z" start="30" end="30"/> + <field name="N" start="31" end="31"/> + </flags> + <reg name="cpsr" bitsize="32" type="cpsr_flags"/> </feature> </target> )"; @@ -143,12 +159,12 @@ WSADATA InitData; struct Breakpoint { bool active; PAddr addr; - u32 len; + u64 len; }; -static std::map<u32, Breakpoint> breakpoints_execute; -static std::map<u32, Breakpoint> breakpoints_read; -static std::map<u32, Breakpoint> breakpoints_write; +static std::map<u64, Breakpoint> breakpoints_execute; +static std::map<u64, Breakpoint> breakpoints_read; +static std::map<u64, Breakpoint> breakpoints_write; /** * Turns hex string character into the equivalent byte. @@ -183,11 +199,11 @@ static u8 NibbleToHex(u8 n) { } /** -* Converts input hex string characters into an array of equivalent of u8 bytes. -* -* @param src Pointer to array of output hex string characters. -* @param len Length of src array. -*/ + * Converts input hex string characters into an array of equivalent of u8 bytes. + * + * @param src Pointer to array of output hex string characters. + * @param len Length of src array. + */ static u32 HexToInt(const u8* src, size_t len) { u32 output = 0; while (len-- > 0) { @@ -198,6 +214,21 @@ static u32 HexToInt(const u8* src, size_t len) { } /** + * Converts input hex string characters into an array of equivalent of u8 bytes. + * + * @param src Pointer to array of output hex string characters. + * @param len Length of src array. + */ +static u64 HexToLong(const u8* src, size_t len) { + u64 output = 0; + while (len-- > 0) { + output = (output << 4) | HexCharToValue(src[0]); + src++; + } + return output; +} + +/** * Converts input array of u8 bytes into their equivalent hex string characters. * * @param dest Pointer to buffer to store output hex string characters. @@ -234,8 +265,21 @@ static void GdbHexToMem(u8* dest, const u8* src, size_t len) { */ static void IntToGdbHex(u8* dest, u32 v) { for (int i = 0; i < 8; i += 2) { - dest[i + 1] = NibbleToHex(v >> (4 * i)); - dest[i] = NibbleToHex(v >> (4 * (i + 1))); + dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i))); + dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1)))); + } +} + +/** + * Convert a u64 into a gdb-formatted hex string. + * + * @param dest Pointer to buffer to store output hex string characters. + * @param v Value to convert. + */ +static void LongToGdbHex(u8* dest, u64 v) { + for (int i = 0; i < 16; i += 2) { + dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i))); + dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1)))); } } @@ -255,6 +299,22 @@ static u32 GdbHexToInt(const u8* src) { return output; } +/** + * Convert a gdb-formatted hex string into a u64. + * + * @param src Pointer to hex string. + */ +static u64 GdbHexToLong(const u8* src) { + u64 output = 0; + + for (int i = 0; i < 16; i += 2) { + output = (output << 4) | HexCharToValue(src[15 - i - 1]); + output = (output << 4) | HexCharToValue(src[15 - i]); + } + + return output; +} + /// Read a byte from the gdb client. static u8 ReadByte() { u8 c; @@ -277,7 +337,7 @@ static u8 CalculateChecksum(const u8* buffer, size_t length) { * * @param type Type of breakpoint list. */ -static std::map<u32, Breakpoint>& GetBreakpointList(BreakpointType type) { +static std::map<u64, Breakpoint>& GetBreakpointList(BreakpointType type) { switch (type) { case BreakpointType::Execute: return breakpoints_execute; @@ -297,19 +357,20 @@ static std::map<u32, Breakpoint>& GetBreakpointList(BreakpointType type) { * @param addr Address of breakpoint. */ static void RemoveBreakpoint(BreakpointType type, PAddr addr) { - std::map<u32, Breakpoint>& p = GetBreakpointList(type); + std::map<u64, Breakpoint>& p = GetBreakpointList(type); - auto bp = p.find(addr); + auto bp = p.find(static_cast<u64>(addr)); if (bp != p.end()) { - LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: %08x bytes at %08x of type %d\n", - bp->second.len, bp->second.addr, type); - p.erase(addr); + LOG_DEBUG(Debug_GDBStub, + "gdb: removed a breakpoint: %016" PRIx64 " bytes at %016" PRIx64 " of type %d\n", + bp->second.len, bp->second.addr, static_cast<int>(type)); + p.erase(static_cast<u64>(addr)); } } BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, BreakpointType type) { - std::map<u32, Breakpoint>& p = GetBreakpointList(type); - auto next_breakpoint = p.lower_bound(addr); + std::map<u64, Breakpoint>& p = GetBreakpointList(type); + auto next_breakpoint = p.lower_bound(static_cast<u64>(addr)); BreakpointAddress breakpoint; if (next_breakpoint != p.end()) { @@ -328,11 +389,11 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) { return false; } - std::map<u32, Breakpoint>& p = GetBreakpointList(type); + std::map<u64, Breakpoint>& p = GetBreakpointList(type); - auto bp = p.find(addr); + auto bp = p.find(static_cast<u64>(addr)); if (bp != p.end()) { - u32 len = bp->second.len; + u64 len = bp->second.len; // IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints // no matter if it's a 4-byte or 2-byte instruction. When you execute a @@ -348,8 +409,9 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) { if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { LOG_DEBUG(Debug_GDBStub, - "Found breakpoint type %d @ %08x, range: %08x - %08x (%d bytes)\n", type, - addr, bp->second.addr, bp->second.addr + len, len); + "Found breakpoint type %d @ %016" PRIx64 ", range: %016" PRIx64 + " - %016" PRIx64 " (%" PRIx64 " bytes)\n", + static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len); return true; } } @@ -419,7 +481,7 @@ static void HandleQuery() { SendReply("T0"); } else if (strncmp(query, "Supported", strlen("Supported")) == 0) { // PacketSize needs to be large enough for target xml - SendReply("PacketSize=800;qXfer:features:read+"); + SendReply("PacketSize=2000;qXfer:features:read+"); } else if (strncmp(query, "Xfer:features:read:target.xml:", strlen("Xfer:features:read:target.xml:")) == 0) { SendReply(target_xml); @@ -450,9 +512,7 @@ static void SendSignal(u32 signal) { latest_signal = signal; - std::string buffer = - Common::StringFromFormat("T%02x%02x:%08x;%02x:%08x;", latest_signal, 15, - htonl(Core::CPU().GetPC()), 13, htonl(Core::CPU().GetReg(13))); + std::string buffer = Common::StringFromFormat("T%02x", latest_signal); LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str()); SendReply(buffer.c_str()); } @@ -538,16 +598,12 @@ static void ReadRegister() { id |= HexCharToValue(command_buffer[2]); } - if (id <= R15_REGISTER) { - IntToGdbHex(reply, Core::CPU().GetReg(id)); + if (id <= SP_REGISTER) { + LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id))); + } else if (id == PC_REGISTER) { + LongToGdbHex(reply, Core::CPU().GetPC()); } else if (id == CPSR_REGISTER) { IntToGdbHex(reply, Core::CPU().GetCPSR()); - } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) { - IntToGdbHex(reply, Core::CPU().GetVFPReg( - id - CPSR_REGISTER - - 1)); // VFP registers should start at 26, so one after CSPR_REGISTER - } else if (id == FPSCR_REGISTER) { - UNIMPLEMENTED(); } else { return SendReply("E01"); } @@ -562,21 +618,19 @@ static void ReadRegisters() { u8* bufptr = buffer; - for (int reg = 0; reg <= R15_REGISTER; reg++) { - IntToGdbHex(bufptr + reg * CHAR_BIT, Core::CPU().GetReg(reg)); + for (int reg = 0; reg <= SP_REGISTER; reg++) { + LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg)); } - bufptr += (16 * CHAR_BIT); + bufptr += (32 * 16); - IntToGdbHex(bufptr, Core::CPU().GetCPSR()); + LongToGdbHex(bufptr, Core::CPU().GetPC()); - bufptr += CHAR_BIT; + bufptr += 16; - for (int reg = 0; reg <= 31; reg++) { - IntToGdbHex(bufptr + reg * CHAR_BIT, Core::CPU().GetVFPReg(reg)); - } + IntToGdbHex(bufptr, Core::CPU().GetCPSR()); - bufptr += (32 * CHAR_BIT); + bufptr += 8; SendReply(reinterpret_cast<char*>(buffer)); } @@ -592,14 +646,12 @@ static void WriteRegister() { id |= HexCharToValue(command_buffer[2]); } - if (id <= R15_REGISTER) { - Core::CPU().SetReg(id, GdbHexToInt(buffer_ptr)); + if (id <= SP_REGISTER) { + Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr)); + } else if (id == PC_REGISTER) { + Core::CPU().SetPC(GdbHexToLong(buffer_ptr)); } else if (id == CPSR_REGISTER) { Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr)); - } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) { - Core::CPU().SetVFPReg(id - CPSR_REGISTER - 1, GdbHexToInt(buffer_ptr)); - } else if (id == FPSCR_REGISTER) { - UNIMPLEMENTED(); } else { return SendReply("E01"); } @@ -614,20 +666,14 @@ static void WriteRegisters() { if (command_buffer[0] != 'G') return SendReply("E01"); - for (int i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) { - if (reg <= R15_REGISTER) { - Core::CPU().SetReg(reg, GdbHexToInt(buffer_ptr + i * CHAR_BIT)); + for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) { + if (reg <= SP_REGISTER) { + Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16)); + } else if (reg == PC_REGISTER) { + Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16)); } else if (reg == CPSR_REGISTER) { - Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * CHAR_BIT)); - } else if (reg == CPSR_REGISTER - 1) { - // Dummy FPA register, ignore - } else if (reg < CPSR_REGISTER) { - // Dummy FPA registers, ignore - i += 2; - } else if (reg > CPSR_REGISTER && reg < FPSCR_REGISTER) { - Core::CPU().SetVFPReg(reg - CPSR_REGISTER - 1, GdbHexToInt(buffer_ptr + i * CHAR_BIT)); - i++; // Skip padding - } else if (reg == FPSCR_REGISTER) { + Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16)); + } else { UNIMPLEMENTED(); } } @@ -641,13 +687,13 @@ static void ReadMemory() { auto start_offset = command_buffer + 1; auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); + VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); + u64 len = + HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset)); - LOG_DEBUG(Debug_GDBStub, "gdb: addr: %08x len: %08x\n", addr, len); + LOG_DEBUG(Debug_GDBStub, "gdb: addr: %016lx len: %016lx\n", addr, len); if (len * 2 > sizeof(reply)) { SendReply("E01"); @@ -669,11 +715,11 @@ static void ReadMemory() { static void WriteMemory() { auto start_offset = command_buffer + 1; auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); + VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); start_offset = addr_pos + 1; auto len_pos = std::find(start_offset, command_buffer + command_length, ':'); - u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset)); + u64 len = HexToLong(start_offset, static_cast<u64>(len_pos - start_offset)); if (!Memory::IsValidVirtualAddress(addr)) { return SendReply("E00"); @@ -726,8 +772,8 @@ static void Continue() { * @param addr Address of breakpoint. * @param len Length of breakpoint. */ -static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) { - std::map<u32, Breakpoint>& p = GetBreakpointList(type); +static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) { + std::map<u64, Breakpoint>& p = GetBreakpointList(type); Breakpoint breakpoint; breakpoint.active = true; @@ -735,8 +781,8 @@ static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) { breakpoint.len = len; p.insert({addr, breakpoint}); - LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %08x bytes at %08x\n", type, breakpoint.len, - breakpoint.addr); + LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %016" PRIx64 " bytes at %016" PRIx64 "\n", + static_cast<int>(type), breakpoint.len, breakpoint.addr); return true; } @@ -766,11 +812,11 @@ static void AddBreakpoint() { auto start_offset = command_buffer + 3; auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); + PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); + u64 len = + HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset)); if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints @@ -815,7 +861,7 @@ static void RemoveBreakpoint() { auto start_offset = command_buffer + 3; auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); + PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset)); if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints @@ -905,7 +951,7 @@ void ToggleServer(bool status) { server_enabled = status; // Start server - if (!IsConnected() && Core::System().GetInstance().IsPoweredOn()) { + if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) { Init(); } } else { @@ -1034,4 +1080,4 @@ bool GetCpuStepFlag() { void SetCpuStepFlag(bool is_step) { step_loop = is_step; } -}; +}; // namespace GDBStub diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h index 8f12c6a1d..201fca095 100644 --- a/src/core/gdbstub/gdbstub.h +++ b/src/core/gdbstub/gdbstub.h @@ -91,4 +91,4 @@ bool GetCpuStepFlag(); * @param is_step */ void SetCpuStepFlag(bool is_step); -} +} // namespace GDBStub diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp index e386ccdc6..038af7909 100644 --- a/src/core/hle/config_mem.cpp +++ b/src/core/hle/config_mem.cpp @@ -28,4 +28,4 @@ void Init() { config_mem.firm_ctr_sdk_ver = 0x0000F297; } -} // namespace +} // namespace ConfigMem diff --git a/src/core/hle/config_mem.h b/src/core/hle/config_mem.h index 42fa6d789..1840d1760 100644 --- a/src/core/hle/config_mem.h +++ b/src/core/hle/config_mem.h @@ -53,4 +53,4 @@ extern ConfigMemDef config_mem; void Init(); -} // namespace +} // namespace ConfigMem diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 88ba105e5..a6602e12c 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -91,6 +91,10 @@ struct BufferDescriptorX { address |= static_cast<VAddr>(address_bits_36_38) << 36; return address; } + + u64 Size() const { + return static_cast<u64>(size); + } }; static_assert(sizeof(BufferDescriptorX) == 8, "BufferDescriptorX size is incorrect"); @@ -133,6 +137,10 @@ struct BufferDescriptorC { address |= static_cast<VAddr>(address_bits_32_47) << 32; return address; } + + u64 Size() const { + return static_cast<u64>(size); + } }; static_assert(sizeof(BufferDescriptorC) == 8, "BufferDescriptorC size is incorrect"); @@ -143,6 +151,11 @@ struct DataPayloadHeader { static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect"); struct DomainMessageHeader { + enum class CommandType : u32_le { + SendMessage = 1, + CloseVirtualHandle = 2, + }; + union { // Used when responding to an IPC request, Server -> Client. struct { @@ -153,7 +166,7 @@ struct DomainMessageHeader { // Used when performing an IPC request, Client -> Server. struct { union { - BitField<0, 8, u32_le> command; + BitField<0, 8, CommandType> command; BitField<16, 16, u32_le> size; }; u32_le object_id; diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 25530a3c8..3f87c4297 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -11,7 +11,6 @@ #include "core/hle/ipc.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/domain.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" @@ -31,11 +30,6 @@ public: RequestHelperBase(Kernel::HLERequestContext& context) : context(&context), cmdbuf(context.CommandBuffer()) {} - void ValidateHeader() { - // DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)", - // header.raw); - } - void Skip(unsigned size_in_words, bool set_to_null) { if (set_to_null) memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); @@ -54,16 +48,36 @@ public: unsigned GetCurrentOffset() const { return static_cast<unsigned>(index); } + + void SetCurrentOffset(unsigned offset) { + index = static_cast<ptrdiff_t>(offset); + } }; -class RequestBuilder : public RequestHelperBase { +class ResponseBuilder : public RequestHelperBase { public: - RequestBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {} + ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {} + + u32 normal_params_size{}; + u32 num_handles_to_copy{}; + u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent + std::ptrdiff_t datapayload_index{}; + + /// Flags used for customizing the behavior of ResponseBuilder + enum class Flags : u32 { + None = 0, + /// Uses move handles to move objects in the response, even when in a domain. This is + /// required when PushMoveObjects is used. + AlwaysMoveHandles = 1, + }; + + ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size, + u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0, + Flags flags = Flags::None) + + : RequestHelperBase(context), normal_params_size(normal_params_size), + num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) { - RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size, - u32 num_handles_to_copy = 0, u32 num_handles_to_move = 0, - u32 num_domain_objects = 0) - : RequestHelperBase(context) { memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); context.ClearIncomingObjects(); @@ -73,12 +87,19 @@ public: // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory // padding. u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; - if (context.IsDomain()) { - raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects; + + u32 num_handles_to_move{}; + u32 num_domain_objects{}; + const bool always_move_handles{ + (static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0}; + if (!context.Session()->IsDomain() || always_move_handles) { + num_handles_to_move = num_objects_to_move; } else { - // If we're not in a domain, turn the domain object parameters into move handles. - num_handles_to_move += num_domain_objects; - num_domain_objects = 0; + num_domain_objects = num_objects_to_move; + } + + if (context.Session()->IsDomain()) { + raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects; } header.data_size.Assign(raw_data_size); @@ -97,7 +118,8 @@ public: AlignWithPadding(); - if (context.IsDomain()) { + const bool request_has_domain_header{context.GetDomainMessageHeader() != nullptr}; + if (context.Session()->IsDomain() && request_has_domain_header) { IPC::DomainMessageHeader domain_header{}; domain_header.num_objects = num_domain_objects; PushRaw(domain_header); @@ -106,23 +128,43 @@ public: IPC::DataPayloadHeader data_payload_header{}; data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); PushRaw(data_payload_header); + + datapayload_index = index; } - template <class T, class... Args> - void PushIpcInterface(Args&&... args) { - auto iface = std::make_shared<T>(std::forward<Args>(args)...); - if (context->IsDomain()) { + template <class T> + void PushIpcInterface(std::shared_ptr<T> iface) { + if (context->Session()->IsDomain()) { context->AddDomainObject(std::move(iface)); } else { - auto port = iface->CreatePort(); - auto session = port->Connect(); - ASSERT(session.Succeeded()); - context->AddMoveObject(std::move(session).Unwrap()); + auto sessions = Kernel::ServerSession::CreateSessionPair(iface->GetServiceName()); + auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions); + auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions); + iface->ClientConnected(server); + context->AddMoveObject(std::move(client)); } } + template <class T, class... Args> + void PushIpcInterface(Args&&... args) { + PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...)); + } + + void ValidateHeader() { + const size_t num_domain_objects = context->NumDomainObjects(); + const size_t num_move_objects = context->NumMoveObjects(); + ASSERT_MSG(!num_domain_objects || !num_move_objects, + "cannot move normal handles and domain objects"); + ASSERT_MSG((index - datapayload_index) == normal_params_size, + "normal_params_size value is incorrect"); + ASSERT_MSG((num_domain_objects + num_move_objects) == num_objects_to_move, + "num_objects_to_move value is incorrect"); + ASSERT_MSG(context->NumCopyObjects() == num_handles_to_copy, + "num_handles_to_copy value is incorrect"); + } + // Validate on destruction, as there shouldn't be any case where we don't want it - ~RequestBuilder() { + ~ResponseBuilder() { ValidateHeader(); } @@ -150,52 +192,52 @@ public: /// Push /// template <> -inline void RequestBuilder::Push(u32 value) { +inline void ResponseBuilder::Push(u32 value) { cmdbuf[index++] = value; } template <typename T> -void RequestBuilder::PushRaw(const T& value) { +void ResponseBuilder::PushRaw(const T& value) { std::memcpy(cmdbuf + index, &value, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } template <> -inline void RequestBuilder::Push(ResultCode value) { +inline void ResponseBuilder::Push(ResultCode value) { // Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded. Push(value.raw); Push<u32>(0); } template <> -inline void RequestBuilder::Push(u8 value) { +inline void ResponseBuilder::Push(u8 value) { PushRaw(value); } template <> -inline void RequestBuilder::Push(u16 value) { +inline void ResponseBuilder::Push(u16 value) { PushRaw(value); } template <> -inline void RequestBuilder::Push(u64 value) { +inline void ResponseBuilder::Push(u64 value) { Push(static_cast<u32>(value)); Push(static_cast<u32>(value >> 32)); } template <> -inline void RequestBuilder::Push(bool value) { +inline void ResponseBuilder::Push(bool value) { Push(static_cast<u8>(value)); } template <typename First, typename... Other> -void RequestBuilder::Push(const First& first_value, const Other&... other_values) { +void ResponseBuilder::Push(const First& first_value, const Other&... other_values) { Push(first_value); Push(other_values...); } template <typename... O> -inline void RequestBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) { +inline void ResponseBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) { auto objects = {pointers...}; for (auto& object : objects) { context->AddCopyObject(std::move(object)); @@ -203,7 +245,7 @@ inline void RequestBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) { } template <typename... O> -inline void RequestBuilder::PushMoveObjects(Kernel::SharedPtr<O>... pointers) { +inline void ResponseBuilder::PushMoveObjects(Kernel::SharedPtr<O>... pointers) { auto objects = {pointers...}; for (auto& object : objects) { context->AddMoveObject(std::move(object)); @@ -222,15 +264,10 @@ public: Skip(CommandIdSize, false); } - RequestBuilder MakeBuilder(u32 normal_params_size, u32 num_handles_to_copy, - u32 num_handles_to_move, u32 num_domain_objects, - bool validate_header = true) { - if (validate_header) { - ValidateHeader(); - } - - return {*context, normal_params_size, num_handles_to_copy, num_handles_to_move, - num_domain_objects}; + ResponseBuilder MakeBuilder(u32 normal_params_size, u32 num_handles_to_copy, + u32 num_handles_to_move, + ResponseBuilder::Flags flags = ResponseBuilder::Flags::None) { + return {*context, normal_params_size, num_handles_to_copy, num_handles_to_move, flags}; } template <typename T> @@ -301,6 +338,11 @@ inline u64 RequestParser::Pop() { } template <> +inline s64 RequestParser::Pop() { + return static_cast<s64>(Pop<u64>()); +} + +template <> inline bool RequestParser::Pop() { return Pop<u8>() != 0; } diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp deleted file mode 100644 index 776d342f0..000000000 --- a/src/core/hle/kernel/address_arbiter.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/hle/kernel/address_arbiter.h" -#include "core/hle/kernel/errors.h" -#include "core/hle/kernel/thread.h" -#include "core/memory.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Kernel namespace - -namespace Kernel { - -AddressArbiter::AddressArbiter() {} -AddressArbiter::~AddressArbiter() {} - -SharedPtr<AddressArbiter> AddressArbiter::Create(std::string name) { - SharedPtr<AddressArbiter> address_arbiter(new AddressArbiter); - - address_arbiter->name = std::move(name); - - return address_arbiter; -} - -ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, s32 value, - u64 nanoseconds) { - switch (type) { - - // Signal thread(s) waiting for arbitrate address... - case ArbitrationType::Signal: - // Negative value means resume all threads - if (value < 0) { - ArbitrateAllThreads(address); - } else { - // Resume first N threads - for (int i = 0; i < value; i++) - ArbitrateHighestPriorityThread(address); - } - break; - - // Wait current thread (acquire the arbiter)... - case ArbitrationType::WaitIfLessThan: - if ((s32)Memory::Read32(address) < value) { - Kernel::WaitCurrentThread_ArbitrateAddress(address); - } - break; - case ArbitrationType::WaitIfLessThanWithTimeout: - if ((s32)Memory::Read32(address) < value) { - Kernel::WaitCurrentThread_ArbitrateAddress(address); - GetCurrentThread()->WakeAfterDelay(nanoseconds); - } - break; - case ArbitrationType::DecrementAndWaitIfLessThan: { - s32 memory_value = Memory::Read32(address); - if (memory_value < value) { - // Only change the memory value if the thread should wait - Memory::Write32(address, (s32)memory_value - 1); - Kernel::WaitCurrentThread_ArbitrateAddress(address); - } - break; - } - case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: { - s32 memory_value = Memory::Read32(address); - if (memory_value < value) { - // Only change the memory value if the thread should wait - Memory::Write32(address, (s32)memory_value - 1); - Kernel::WaitCurrentThread_ArbitrateAddress(address); - GetCurrentThread()->WakeAfterDelay(nanoseconds); - } - break; - } - - default: - LOG_ERROR(Kernel, "unknown type=%d", type); - return ERR_INVALID_ENUM_VALUE_FND; - } - - // The calls that use a timeout seem to always return a Timeout error even if they did not put - // the thread to sleep - if (type == ArbitrationType::WaitIfLessThanWithTimeout || - type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) { - - return RESULT_TIMEOUT; - } - return RESULT_SUCCESS; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h deleted file mode 100644 index 1d24401b1..000000000 --- a/src/core/hle/kernel/address_arbiter.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/result.h" - -// Address arbiters are an underlying kernel synchronization object that can be created/used via -// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR -// applications use them as an underlying mechanism to implement thread-safe barriers, events, and -// semphores. - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Kernel namespace - -namespace Kernel { - -enum class ArbitrationType : u32 { - Signal, - WaitIfLessThan, - DecrementAndWaitIfLessThan, - WaitIfLessThanWithTimeout, - DecrementAndWaitIfLessThanWithTimeout, -}; - -class AddressArbiter final : public Object { -public: - /** - * Creates an address arbiter. - * - * @param name Optional name used for debugging. - * @returns The created AddressArbiter. - */ - static SharedPtr<AddressArbiter> Create(std::string name = "Unknown"); - - std::string GetTypeName() const override { - return "Arbiter"; - } - std::string GetName() const override { - return name; - } - - static const HandleType HANDLE_TYPE = HandleType::AddressArbiter; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - std::string name; ///< Name of address arbiter object (optional) - - ResultCode ArbitrateAddress(ArbitrationType type, VAddr address, s32 value, u64 nanoseconds); - -private: - AddressArbiter(); - ~AddressArbiter() override; -}; - -} // namespace FileSys diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index ce5d94e99..fb2b6f7a3 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -39,4 +39,4 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { return MakeResult(std::get<SharedPtr<ClientSession>>(sessions)); } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index 8f7d6ac44..a829aeb6d 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -47,4 +47,4 @@ private: ~ClientPort() override; }; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 646a5cc64..72773d8b1 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -48,4 +48,4 @@ ResultCode ClientSession::SendSyncRequest(SharedPtr<Thread> thread) { return server->HandleSyncRequest(std::move(thread)); } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index 671174ec4..2258f95bc 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -7,7 +7,7 @@ #include <memory> #include <string> #include "common/common_types.h" -#include "core/hle/kernel/sync_object.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/result.h" namespace Kernel { @@ -16,7 +16,7 @@ class ServerSession; class Session; class Thread; -class ClientSession final : public SyncObject { +class ClientSession final : public Object { public: friend class ServerSession; @@ -33,7 +33,7 @@ public: return HANDLE_TYPE; } - ResultCode SendSyncRequest(SharedPtr<Thread> thread) override; + ResultCode SendSyncRequest(SharedPtr<Thread> thread); std::string name; ///< Name of client port (optional) @@ -45,4 +45,4 @@ private: ~ClientSession() override; }; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/condition_variable.cpp b/src/core/hle/kernel/condition_variable.cpp index 5942eae61..a786d7f74 100644 --- a/src/core/hle/kernel/condition_variable.cpp +++ b/src/core/hle/kernel/condition_variable.cpp @@ -15,13 +15,12 @@ ConditionVariable::ConditionVariable() {} ConditionVariable::~ConditionVariable() {} ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr, - VAddr mutex_addr, std::string name) { SharedPtr<ConditionVariable> condition_variable(new ConditionVariable); condition_variable->name = std::move(name); condition_variable->guest_addr = guest_addr; - condition_variable->mutex_addr = mutex_addr; + condition_variable->mutex_addr = 0; // Condition variables are referenced by guest address, so track this in the kernel g_object_address_table.Insert(guest_addr, condition_variable); @@ -43,7 +42,7 @@ void ConditionVariable::Acquire(Thread* thread) { ResultCode ConditionVariable::Release(s32 target) { if (target == -1) { // When -1, wake up all waiting threads - SetAvailableCount(GetWaitingThreads().size()); + SetAvailableCount(static_cast<s32>(GetWaitingThreads().size())); WakeupAllWaitingThreads(); } else { // Otherwise, wake up just a single thread diff --git a/src/core/hle/kernel/condition_variable.h b/src/core/hle/kernel/condition_variable.h index 0610a284f..1c9f06769 100644 --- a/src/core/hle/kernel/condition_variable.h +++ b/src/core/hle/kernel/condition_variable.h @@ -4,8 +4,8 @@ #pragma once -#include <queue> #include <string> +#include <queue> #include "common/common_types.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/wait_object.h" @@ -19,12 +19,10 @@ public: * Creates a condition variable. * @param guest_addr Address of the object tracking the condition variable in guest memory. If * specified, this condition variable will update the guest object when its state changes. - * @param mutex_addr Optional address of a guest mutex associated with this condition variable, - * used by the OS for implementing events. * @param name Optional name of condition variable. * @return The created condition variable. */ - static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr, VAddr mutex_addr = 0, + static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr, std::string name = "Unknown"); std::string GetTypeName() const override { diff --git a/src/core/hle/kernel/domain.cpp b/src/core/hle/kernel/domain.cpp deleted file mode 100644 index 7ebb4b9c7..000000000 --- a/src/core/hle/kernel/domain.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/domain.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/session.h" -#include "core/hle/kernel/thread.h" - -namespace Kernel { - -ResultVal<SharedPtr<Domain>> Domain::Create(std::string name) { - SharedPtr<Domain> domain(new Domain); - domain->name = std::move(name); - return MakeResult(std::move(domain)); -} - -ResultVal<SharedPtr<Domain>> Domain::CreateFromSession(const Session& session) { - auto res = Create(session.port->GetName() + "_Domain"); - auto& domain = res.Unwrap(); - domain->request_handlers.push_back(std::move(session.server->hle_handler)); - Kernel::g_handle_table.ConvertSessionToDomain(session, domain); - return res; -} - -ResultCode Domain::SendSyncRequest(SharedPtr<Thread> thread) { - Kernel::HLERequestContext context(this); - u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress()); - context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, - Kernel::g_handle_table); - - auto& domain_message_header = context.GetDomainMessageHeader(); - if (domain_message_header) { - // If there is a DomainMessageHeader, then this is CommandType "Request" - const u32 object_id{context.GetDomainMessageHeader()->object_id}; - return request_handlers[object_id - 1]->HandleSyncRequest(context); - } - return request_handlers.front()->HandleSyncRequest(context); -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/domain.h b/src/core/hle/kernel/domain.h deleted file mode 100644 index 3fec3b0b2..000000000 --- a/src/core/hle/kernel/domain.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <string> -#include <vector> -#include "core/hle/kernel/sync_object.h" -#include "core/hle/result.h" - -namespace Kernel { - -class Session; -class SessionRequestHandler; - -class Domain final : public SyncObject { -public: - std::string GetTypeName() const override { - return "Domain"; - } - - static const HandleType HANDLE_TYPE = HandleType::Domain; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - static ResultVal<SharedPtr<Domain>> CreateFromSession(const Session& server); - - ResultCode SendSyncRequest(SharedPtr<Thread> thread) override; - - /// The name of this domain (optional) - std::string name; - - std::vector<std::shared_ptr<SessionRequestHandler>> request_handlers; - -private: - Domain() = default; - ~Domain() override = default; - - static ResultVal<SharedPtr<Domain>> Create(std::string name = "Unknown"); -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 23f9df0d6..9cae2369f 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -52,4 +52,4 @@ void Event::WakeupAllWaitingThreads() { signaled = false; } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index cc41abb85..e5c924a75 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -49,4 +49,4 @@ private: ~Event() override; }; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index 12506e64c..822449cd5 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -5,12 +5,11 @@ #include <utility> #include "common/assert.h" #include "common/logging/log.h" -#include "core/hle/kernel/client_session.h" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" namespace Kernel { @@ -55,14 +54,6 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) { return Create(std::move(object)); } -void HandleTable::ConvertSessionToDomain(const Session& session, SharedPtr<Object> domain) { - for (auto& object : objects) { - if (DynamicObjectCast<ClientSession>(object) == session.client) { - object = domain; - } - } -} - ResultCode HandleTable::Close(Handle handle) { if (!IsValid(handle)) return ERR_INVALID_HANDLE; @@ -87,7 +78,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { return GetCurrentThread(); } else if (handle == CurrentProcess) { - return g_current_process; + return Core::CurrentProcess(); } if (!IsValid(handle)) { @@ -104,4 +95,4 @@ void HandleTable::Clear() { next_free_slot = 0; } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h index dba5573a8..ba968c666 100644 --- a/src/core/hle/kernel/handle_table.h +++ b/src/core/hle/kernel/handle_table.h @@ -17,8 +17,6 @@ enum KernelHandle : Handle { CurrentProcess = 0xFFFF8001, }; -class Session; - /** * This class allows the creation of Handles, which are references to objects that can be tested * for validity and looked up. Here they are used to pass references to kernel objects to/from the @@ -62,11 +60,6 @@ public: ResultVal<Handle> Duplicate(Handle handle); /** - * Convert all handles of the specified Session to the specified Domain. - */ - void ConvertSessionToDomain(const Session& session, SharedPtr<Object> domain); - - /** * Closes a handle, removing it from the table and decreasing the object's ref-count. * @return `RESULT_SUCCESS` or one of the following errors: * - `ERR_INVALID_HANDLE`: an invalid handle was passed in. @@ -130,4 +123,4 @@ private: extern HandleTable g_handle_table; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index afa09b404..bef4f15f5 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -7,12 +7,13 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/domain.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_session.h" +#include "core/memory.h" namespace Kernel { @@ -26,8 +27,30 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s boost::range::remove_erase(connected_sessions, server_session); } -HLERequestContext::HLERequestContext(SharedPtr<Kernel::Domain> domain) : domain(std::move(domain)) { - cmd_buf[0] = 0; +SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, + const std::string& reason, u64 timeout, + WakeupCallback&& callback) { + + // Put the client thread to sleep until the wait event is signaled or the timeout expires. + thread->wakeup_callback = + [context = *this, callback](ThreadWakeupReason reason, SharedPtr<Thread> thread, + SharedPtr<WaitObject> object, size_t index) mutable -> bool { + ASSERT(thread->status == THREADSTATUS_WAIT_HLE_EVENT); + callback(thread, context, reason); + context.WriteToOutgoingCommandBuffer(*thread); + return true; + }; + + auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); + thread->status = THREADSTATUS_WAIT_HLE_EVENT; + thread->wait_objects = {event}; + event->AddWaitingThread(thread); + + if (timeout > 0) { + thread->WakeAfterDelay(timeout); + } + + return event; } HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session) @@ -39,7 +62,7 @@ HLERequestContext::~HLERequestContext() = default; void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { IPC::RequestParser rp(src_cmdbuf); - command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); + command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); if (command_header->type == IPC::CommandType::Close) { // Close does not populate the rest of the IPC header @@ -49,7 +72,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { // If handle descriptor is present, add size of it if (command_header->enable_handle_descriptor) { handle_descriptor_header = - std::make_unique<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>()); + std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>()); if (handle_descriptor_header->send_current_pid) { rp.Skip(2, false); } @@ -81,26 +104,34 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { for (unsigned i = 0; i < command_header->num_buf_w_descriptors; ++i) { buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); } - if (command_header->buf_c_descriptor_flags != - IPC::CommandHeader::BufferDescriptorCFlag::Disabled) { - if (command_header->buf_c_descriptor_flags != - IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { - UNIMPLEMENTED(); - } - } + + buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size; // Padding to align to 16 bytes rp.AlignWithPadding(); - if (IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) { + if (Session()->IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) { // If this is an incoming message, only CommandType "Request" has a domain header - // All outgoing domain messages have the domain header - domain_message_header = - std::make_unique<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>()); + // All outgoing domain messages have the domain header, if only incoming has it + if (incoming || domain_message_header) { + domain_message_header = + std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>()); + } else { + if (Session()->IsDomain()) + LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!"); + } } data_payload_header = - std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); + std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>()); + + data_payload_offset = rp.GetCurrentOffset(); + + if (domain_message_header && domain_message_header->command == + IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) { + // CloseVirtualHandle command does not have SFC* or any data + return; + } if (incoming) { ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I')); @@ -108,7 +139,31 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O')); } - data_payload_offset = rp.GetCurrentOffset(); + rp.SetCurrentOffset(buffer_c_offset); + + // For Inline buffers, the response data is written directly to buffer_c_offset + // and in this case we don't have any BufferDescriptorC on the request. + if (command_header->buf_c_descriptor_flags > + IPC::CommandHeader::BufferDescriptorCFlag::InlineDescriptor) { + if (command_header->buf_c_descriptor_flags == + IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { + buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); + } else { + unsigned num_buf_c_descriptors = + static_cast<unsigned>(command_header->buf_c_descriptor_flags.Value()) - 2; + + // This is used to detect possible underflows, in case something is broken + // with the two ifs above and the flags value is == 0 || == 1. + ASSERT(num_buf_c_descriptors < 14); + + for (unsigned i = 0; i < num_buf_c_descriptors; ++i) { + buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); + } + } + } + + rp.SetCurrentOffset(data_payload_offset); + command = rp.Pop<u32_le>(); rp.Skip(1, false); // The command is actually an u64, but we don't use the high part. } @@ -131,8 +186,11 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb return RESULT_SUCCESS; } -ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, - HandleTable& dst_table) { +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) { + std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; + Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), + dst_cmdbuf.size() * sizeof(u32)); + // The header was already built in the internal command buffer. Attempt to parse it to verify // the integrity and then copy it over to the target command buffer. ParseCommandBuffer(cmd_buf.data(), false); @@ -143,7 +201,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P if (domain_message_header) size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); - std::copy_n(cmd_buf.begin(), size, dst_cmdbuf); + std::copy_n(cmd_buf.begin(), size, dst_cmdbuf.data()); if (command_header->enable_handle_descriptor) { ASSERT_MSG(!move_objects.empty() || !copy_objects.empty(), @@ -173,19 +231,121 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P // TODO(Subv): Translate the X/A/B/W buffers. - if (IsDomain()) { + if (Session()->IsDomain() && domain_message_header) { ASSERT(domain_message_header->num_objects == domain_objects.size()); // Write the domain objects to the command buffer, these go after the raw untranslated data. // TODO(Subv): This completely ignores C buffers. size_t domain_offset = size - domain_message_header->num_objects; - auto& request_handlers = domain->request_handlers; + auto& request_handlers = server_session->domain_request_handlers; for (auto& object : domain_objects) { request_handlers.emplace_back(object); - dst_cmdbuf[domain_offset++] = request_handlers.size(); + dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size()); } } + + // Copy the translated command buffer back into the thread's command buffer area. + Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), + dst_cmdbuf.size() * sizeof(u32)); + return RESULT_SUCCESS; } +std::vector<u8> HLERequestContext::ReadBuffer() const { + std::vector<u8> buffer; + const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()}; + + if (is_buffer_a) { + buffer.resize(BufferDescriptorA()[0].Size()); + Memory::ReadBlock(BufferDescriptorA()[0].Address(), buffer.data(), buffer.size()); + } else { + buffer.resize(BufferDescriptorX()[0].Size()); + Memory::ReadBlock(BufferDescriptorX()[0].Address(), buffer.data(), buffer.size()); + } + + return buffer; +} + +size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const { + const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()}; + const size_t buffer_size{GetWriteBufferSize()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size (%016zx) is greater than buffer_size (%016zx)", size, buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } + + if (is_buffer_b) { + Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size); + } else { + Memory::WriteBlock(BufferDescriptorC()[0].Address(), buffer, size); + } + + return size; +} + +size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer) const { + return WriteBuffer(buffer.data(), buffer.size()); +} + +size_t HLERequestContext::GetReadBufferSize() const { + const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()}; + return is_buffer_a ? BufferDescriptorA()[0].Size() : BufferDescriptorX()[0].Size(); +} + +size_t HLERequestContext::GetWriteBufferSize() const { + const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()}; + return is_buffer_b ? BufferDescriptorB()[0].Size() : BufferDescriptorC()[0].Size(); +} + +std::string HLERequestContext::Description() const { + if (!command_header) { + return "No command header available"; + } + std::ostringstream s; + s << "IPC::CommandHeader: Type:" << static_cast<u32>(command_header->type.Value()); + s << ", X(Pointer):" << command_header->num_buf_x_descriptors; + if (command_header->num_buf_x_descriptors) { + s << '['; + for (u64 i = 0; i < command_header->num_buf_x_descriptors; ++i) { + s << "0x" << std::hex << BufferDescriptorX()[i].Size(); + if (i < command_header->num_buf_x_descriptors - 1) + s << ", "; + } + s << ']'; + } + s << ", A(Send):" << command_header->num_buf_a_descriptors; + if (command_header->num_buf_a_descriptors) { + s << '['; + for (u64 i = 0; i < command_header->num_buf_a_descriptors; ++i) { + s << "0x" << std::hex << BufferDescriptorA()[i].Size(); + if (i < command_header->num_buf_a_descriptors - 1) + s << ", "; + } + s << ']'; + } + s << ", B(Receive):" << command_header->num_buf_b_descriptors; + if (command_header->num_buf_b_descriptors) { + s << '['; + for (u64 i = 0; i < command_header->num_buf_b_descriptors; ++i) { + s << "0x" << std::hex << BufferDescriptorB()[i].Size(); + if (i < command_header->num_buf_b_descriptors - 1) + s << ", "; + } + s << ']'; + } + s << ", C(ReceiveList):" << BufferDescriptorC().size(); + if (!BufferDescriptorC().empty()) { + s << '['; + for (u64 i = 0; i < BufferDescriptorC().size(); ++i) { + s << "0x" << std::hex << BufferDescriptorC()[i].Size(); + if (i < BufferDescriptorC().size() - 1) + s << ", "; + } + s << ']'; + } + s << ", data_size:" << command_header->data_size.Value(); + + return s.str(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 6dceb766d..8b35da4c9 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -6,6 +6,7 @@ #include <array> #include <memory> +#include <string> #include <vector> #include <boost/container/small_vector.hpp> #include "common/common_types.h" @@ -13,6 +14,7 @@ #include "core/hle/ipc.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/thread.h" namespace Service { class ServiceFrameworkBase; @@ -24,6 +26,7 @@ class Domain; class HandleTable; class HLERequestContext; class Process; +class Event; /** * Interface implemented by HLE Session handlers. @@ -86,7 +89,6 @@ protected: */ class HLERequestContext { public: - HLERequestContext(SharedPtr<Kernel::Domain> domain); HLERequestContext(SharedPtr<Kernel::ServerSession> session); ~HLERequestContext(); @@ -96,28 +98,38 @@ public: } /** - * Returns the domain through which this request was made. - */ - const SharedPtr<Kernel::Domain>& Domain() const { - return domain; - } - - /** * Returns the session through which this request was made. This can be used as a map key to * access per-client data on services. */ - const SharedPtr<Kernel::ServerSession>& ServerSession() const { + const SharedPtr<Kernel::ServerSession>& Session() const { return server_session; } + using WakeupCallback = std::function<void(SharedPtr<Thread> thread, HLERequestContext& context, + ThreadWakeupReason reason)>; + + /** + * Puts the specified guest thread to sleep until the returned event is signaled or until the + * specified timeout expires. + * @param thread Thread to be put to sleep. + * @param reason Reason for pausing the thread, to be used for debugging purposes. + * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback + * invoked with a Timeout reason. + * @param callback Callback to be invoked when the thread is resumed. This callback must write + * the entire command response once again, regardless of the state of it before this function + * was called. + * @returns Event that when signaled will resume the thread and call the callback function. + */ + SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason, + u64 timeout, WakeupCallback&& callback); + void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); /// Populates this context with data from the requesting process/thread. ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, HandleTable& src_table); /// Writes data from this context back to the requesting process/thread. - ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, - HandleTable& dst_table); + ResultCode WriteToOutgoingCommandBuffer(Thread& thread); u32_le GetCommand() const { return command; @@ -143,14 +155,29 @@ public: return buffer_b_desciptors; } - const std::unique_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const { - return domain_message_header; + const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const { + return buffer_c_desciptors; } - bool IsDomain() const { - return domain != nullptr; + const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const { + return domain_message_header; } + /// Helper function to read a buffer using the appropriate buffer descriptor + std::vector<u8> ReadBuffer() const; + + /// Helper function to write a buffer using the appropriate buffer descriptor + size_t WriteBuffer(const void* buffer, size_t size) const; + + /// Helper function to write a buffer using the appropriate buffer descriptor + size_t WriteBuffer(const std::vector<u8>& buffer) const; + + /// Helper function to get the size of the input buffer + size_t GetReadBufferSize() const; + + /// Helper function to get the size of the output buffer + size_t GetWriteBufferSize() const; + template <typename T> SharedPtr<T> GetCopyObject(size_t index) { ASSERT(index < copy_objects.size()); @@ -183,25 +210,40 @@ public: domain_objects.clear(); } + size_t NumMoveObjects() const { + return move_objects.size(); + } + + size_t NumCopyObjects() const { + return copy_objects.size(); + } + + size_t NumDomainObjects() const { + return domain_objects.size(); + } + + std::string Description() const; + private: std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; - SharedPtr<Kernel::Domain> domain; SharedPtr<Kernel::ServerSession> server_session; // TODO(yuriks): Check common usage of this and optimize size accordingly boost::container::small_vector<SharedPtr<Object>, 8> move_objects; boost::container::small_vector<SharedPtr<Object>, 8> copy_objects; boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects; - std::unique_ptr<IPC::CommandHeader> command_header; - std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; - std::unique_ptr<IPC::DataPayloadHeader> data_payload_header; - std::unique_ptr<IPC::DomainMessageHeader> domain_message_header; + std::shared_ptr<IPC::CommandHeader> command_header; + std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header; + std::shared_ptr<IPC::DataPayloadHeader> data_payload_header; + std::shared_ptr<IPC::DomainMessageHeader> domain_message_header; std::vector<IPC::BufferDescriptorX> buffer_x_desciptors; std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors; std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors; std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors; + std::vector<IPC::BufferDescriptorC> buffer_c_desciptors; unsigned data_payload_offset{}; + unsigned buffer_c_offset{}; u32_le command{}; }; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index b0c3f4ae1..b325b879b 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -41,7 +41,6 @@ void Shutdown() { g_object_address_table.Clear(); Kernel::ThreadingShutdown(); - g_current_process = nullptr; Kernel::TimersShutdown(); Kernel::ResourceLimitsShutdown(); diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 4d9549e45..053bf4e17 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -31,11 +31,6 @@ enum class HandleType : u32 { ServerPort, ClientSession, ServerSession, - Domain, -}; - -enum { - DEFAULT_STACK_SIZE = 0x10000, }; enum class ResetType { @@ -84,27 +79,12 @@ public: case HandleType::CodeSet: case HandleType::ClientPort: case HandleType::ClientSession: - case HandleType::Domain: return false; } UNREACHABLE(); } - /** - * Check if svcSendSyncRequest can be called on the object - * @return True svcSendSyncRequest can be called on the object, otherwise false - */ - bool IsSyncable() const { - switch (GetHandleType()) { - case HandleType::ClientSession: - case HandleType::Domain: - return true; - } - - UNREACHABLE(); - } - public: static unsigned int next_object_id; diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 4e86eb918..0b9dc700c 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -70,6 +70,7 @@ ResultCode Mutex::Release(Thread* thread) { holding_thread->held_mutexes.erase(this); holding_thread->UpdatePriority(); SetHoldingThread(nullptr); + SetHasWaiters(!GetWaitingThreads().empty()); WakeupAllWaitingThreads(); Core::System::GetInstance().PrepareReschedule(); diff --git a/src/core/hle/kernel/object_address_table.cpp b/src/core/hle/kernel/object_address_table.cpp index 434c16add..cd286f85d 100644 --- a/src/core/hle/kernel/object_address_table.cpp +++ b/src/core/hle/kernel/object_address_table.cpp @@ -10,12 +10,12 @@ namespace Kernel { ObjectAddressTable g_object_address_table; void ObjectAddressTable::Insert(VAddr addr, SharedPtr<Object> obj) { - ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%llx", addr); + ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%lx", addr); objects[addr] = obj; } void ObjectAddressTable::Close(VAddr addr) { - ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%llx", addr); + ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%lx", addr); objects.erase(addr); } diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 8e74059ea..2cffec198 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -20,12 +20,9 @@ namespace Kernel { // Lists all processes that exist in the current session. static std::vector<SharedPtr<Process>> process_list; -SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) { +SharedPtr<CodeSet> CodeSet::Create(std::string name) { SharedPtr<CodeSet> codeset(new CodeSet); - codeset->name = std::move(name); - codeset->program_id = program_id; - return codeset; } @@ -41,6 +38,7 @@ SharedPtr<Process> Process::Create(std::string&& name) { process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); process->status = ProcessStatus::Created; + process->program_id = 0; process_list.push_back(process); return process; @@ -119,11 +117,13 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { } void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { - // Allocate and map stack + // Allocate and map the main thread stack + // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part + // of the user address space. vm_manager - .MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, + .MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size, std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, - MemoryState::Heap) + MemoryState::Mapped) .Unwrap(); misc_memory_used += stack_size; memory_region->used += stack_size; @@ -155,9 +155,9 @@ void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { }; // Map CodeSet segments - MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::Code); - MapSegment(module_->rodata, VMAPermission::Read, MemoryState::Static); - MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::Static); + MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::CodeStatic); + MapSegment(module_->rodata, VMAPermission::Read, MemoryState::CodeMutable); + MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::CodeMutable); } VAddr Process::GetLinearHeapAreaAddress() const { @@ -184,6 +184,8 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per // Initialize heap heap_memory = std::make_shared<std::vector<u8>>(); heap_start = heap_end = target; + } else { + vm_manager.UnmapRange(heap_start, heap_end - heap_start); } // If necessary, expand backing vector to cover new heap extents. @@ -203,7 +205,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per size, MemoryState::Heap)); vm_manager.Reprotect(vma, perms); - heap_used += size; + heap_used = size; memory_region->used += size; return MakeResult<VAddr>(heap_end - size); @@ -290,7 +292,7 @@ ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { CASCADE_RESULT(auto new_vma, vm_manager.MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, - vma->second.meminfo_state)); + MemoryState::Mapped)); // Protect mirror with permissions from old region vm_manager.Reprotect(new_vma, vma->second.permissions); // Remove permissions from old region @@ -321,5 +323,4 @@ SharedPtr<Process> GetProcessById(u32 process_id) { return *itr; } -SharedPtr<Process> g_current_process; } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index add98472f..68e77a4d1 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -56,7 +56,7 @@ class ResourceLimit; struct MemoryRegionInfo; struct CodeSet final : public Object { - static SharedPtr<CodeSet> Create(std::string name, u64 program_id); + static SharedPtr<CodeSet> Create(std::string name); std::string GetTypeName() const override { return "CodeSet"; @@ -72,8 +72,6 @@ struct CodeSet final : public Object { /// Name of the process std::string name; - /// Title ID corresponding to the process - u64 program_id; std::shared_ptr<std::vector<u8>> memory; @@ -113,6 +111,9 @@ public: static u32 next_process_id; + /// Title ID corresponding to the process + u64 program_id; + /// Resource limit descriptor for this process SharedPtr<ResourceLimit> resource_limit; @@ -202,5 +203,4 @@ void ClearProcessList(); /// Retrieves a process from the current list of processes. SharedPtr<Process> GetProcessById(u32 process_id); -extern SharedPtr<Process> g_current_process; } // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 517dc47a8..0149a3ed6 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -151,4 +151,4 @@ void ResourceLimitsInit() { void ResourceLimitsShutdown() {} -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 42874eb8d..1a0ca11f1 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h @@ -123,4 +123,4 @@ void ResourceLimitsInit(); // Destroys the resource limits void ResourceLimitsShutdown(); -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp new file mode 100644 index 000000000..921f27efb --- /dev/null +++ b/src/core/hle/kernel/scheduler.cpp @@ -0,0 +1,135 @@ +// Copyright 2018 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/hle/kernel/process.h" +#include "core/hle/kernel/scheduler.h" + +namespace Kernel { + +Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {} + +Scheduler::~Scheduler() { + for (auto& thread : thread_list) { + thread->Stop(); + } +} + +bool Scheduler::HaveReadyThreads() { + return ready_queue.get_first() != nullptr; +} + +Thread* Scheduler::GetCurrentThread() const { + return current_thread.get(); +} + +Thread* Scheduler::PopNextReadyThread() { + Thread* next = nullptr; + Thread* thread = GetCurrentThread(); + + if (thread && thread->status == THREADSTATUS_RUNNING) { + // We have to do better than the current thread. + // This call returns null when that's not possible. + next = ready_queue.pop_first_better(thread->current_priority); + if (!next) { + // Otherwise just keep going with the current thread + next = thread; + } + } else { + next = ready_queue.pop_first(); + } + + return next; +} + +void Scheduler::SwitchContext(Thread* new_thread) { + Thread* previous_thread = GetCurrentThread(); + + // Save context for previous thread + if (previous_thread) { + previous_thread->last_running_ticks = CoreTiming::GetTicks(); + cpu_core->SaveContext(previous_thread->context); + + if (previous_thread->status == THREADSTATUS_RUNNING) { + // This is only the case when a reschedule is triggered without the current thread + // yielding execution (i.e. an event triggered, system core time-sliced, etc) + ready_queue.push_front(previous_thread->current_priority, previous_thread); + previous_thread->status = THREADSTATUS_READY; + } + } + + // Load context of new thread + if (new_thread) { + ASSERT_MSG(new_thread->status == THREADSTATUS_READY, + "Thread must be ready to become running."); + + // Cancel any outstanding wakeup events for this thread + new_thread->CancelWakeupTimer(); + + auto previous_process = Core::CurrentProcess(); + + current_thread = new_thread; + + ready_queue.remove(new_thread->current_priority, new_thread); + new_thread->status = THREADSTATUS_RUNNING; + + if (previous_process != current_thread->owner_process) { + Core::CurrentProcess() = current_thread->owner_process; + SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); + } + + cpu_core->LoadContext(new_thread->context); + cpu_core->SetTlsAddress(new_thread->GetTLSAddress()); + } else { + current_thread = nullptr; + // Note: We do not reset the current process and current page table when idling because + // technically we haven't changed processes, our threads are just paused. + } +} + +void Scheduler::Reschedule() { + Thread* cur = GetCurrentThread(); + Thread* next = PopNextReadyThread(); + + if (cur && next) { + LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); + } else if (cur) { + LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId()); + } else if (next) { + LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId()); + } + + SwitchContext(next); +} + +void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) { + thread_list.push_back(thread); + ready_queue.prepare(priority); +} + +void Scheduler::RemoveThread(Thread* thread) { + thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), + thread_list.end()); +} + +void Scheduler::ScheduleThread(Thread* thread, u32 priority) { + ASSERT(thread->status == THREADSTATUS_READY); + ready_queue.push_back(priority, thread); +} + +void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { + ASSERT(thread->status == THREADSTATUS_READY); + ready_queue.remove(priority, thread); +} + +void Scheduler::SetThreadPriority(Thread* thread, u32 priority) { + // If thread was ready, adjust queues + if (thread->status == THREADSTATUS_READY) + ready_queue.move(thread, thread->current_priority, priority); + else + ready_queue.prepare(priority); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h new file mode 100644 index 000000000..27d0247d6 --- /dev/null +++ b/src/core/hle/kernel/scheduler.h @@ -0,0 +1,73 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "common/thread_queue_list.h" +#include "core/arm/arm_interface.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class Scheduler final { +public: + explicit Scheduler(ARM_Interface* cpu_core); + ~Scheduler(); + + /// Returns whether there are any threads that are ready to run. + bool HaveReadyThreads(); + + /// Reschedules to the next available thread (call after current thread is suspended) + void Reschedule(); + + /// Gets the current running thread + Thread* GetCurrentThread() const; + + /// Adds a new thread to the scheduler + void AddThread(SharedPtr<Thread> thread, u32 priority); + + /// Removes a thread from the scheduler + void RemoveThread(Thread* thread); + + /// Schedules a thread that has become "ready" + void ScheduleThread(Thread* thread, u32 priority); + + /// Unschedules a thread that was already scheduled + void UnscheduleThread(Thread* thread, u32 priority); + + /// Sets the priority of a thread in the scheduler + void SetThreadPriority(Thread* thread, u32 priority); + + /// Returns a list of all threads managed by the scheduler + const std::vector<SharedPtr<Thread>>& GetThreadList() const { + return thread_list; + } + +private: + /** + * Pops and returns the next thread from the thread queue + * @return A pointer to the next ready thread + */ + Thread* PopNextReadyThread(); + + /** + * Switches the CPU's active thread context to that of the specified thread + * @param new_thread The thread to switch to + */ + void SwitchContext(Thread* new_thread); + + /// Lists all thread ids that aren't deleted/etc. + std::vector<SharedPtr<Thread>> thread_list; + + /// Lists only ready thread ids. + Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue; + + SharedPtr<Thread> current_thread = nullptr; + + ARM_Interface* cpu_core; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 49a9cdfa3..0b7061403 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -50,4 +50,4 @@ std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortP return std::make_tuple(std::move(server_port), std::move(client_port)); } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index 6fe7c7f2f..9ef4ecc35 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -72,4 +72,4 @@ private: ~ServerPort() override; }; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 09d02a691..33397d84f 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -4,6 +4,8 @@ #include <tuple> +#include "core/core.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" #include "core/hle/kernel/handle_table.h" @@ -56,34 +58,91 @@ void ServerSession::Acquire(Thread* thread) { pending_requesting_threads.pop_back(); } +ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { + auto& domain_message_header = context.GetDomainMessageHeader(); + if (domain_message_header) { + // If there is a DomainMessageHeader, then this is CommandType "Request" + const u32 object_id{context.GetDomainMessageHeader()->object_id}; + switch (domain_message_header->command) { + case IPC::DomainMessageHeader::CommandType::SendMessage: + return domain_request_handlers[object_id - 1]->HandleSyncRequest(context); + + case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { + LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id); + + domain_request_handlers[object_id - 1] = nullptr; + + IPC::ResponseBuilder rb{context, 2}; + rb.Push(RESULT_SUCCESS); + return RESULT_SUCCESS; + } + } + + LOG_CRITICAL(IPC, "Unknown domain command=%d", + static_cast<int>(domain_message_header->command.Value())); + ASSERT(false); + } + + return RESULT_SUCCESS; +} + ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { // The ServerSession received a sync request, this means that there's new data available // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or // similar. - // If this ServerSession has an associated HLE handler, forward the request to it. - ResultCode result{RESULT_SUCCESS}; - if (hle_handler != nullptr) { - // Attempt to translate the incoming request's command buffer. - ResultCode translate_result = TranslateHLERequest(this); - if (translate_result.IsError()) - return translate_result; - - Kernel::HLERequestContext context(this); - u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress()); - context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, - Kernel::g_handle_table); - + Kernel::HLERequestContext context(this); + u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress()); + context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(), + Kernel::g_handle_table); + + ResultCode result = RESULT_SUCCESS; + // If the session has been converted to a domain, handle the domain request + if (IsDomain() && context.GetDomainMessageHeader()) { + result = HandleDomainSyncRequest(context); + // If there is no domain header, the regular session handler is used + } else if (hle_handler != nullptr) { + // If this ServerSession has an associated HLE handler, forward the request to it. result = hle_handler->HandleSyncRequest(context); - } else { - // Add the thread to the list of threads that have issued a sync request with this - // server. - pending_requesting_threads.push_back(std::move(thread)); + } + + if (thread->status == THREADSTATUS_RUNNING) { + // Put the thread to sleep until the server replies, it will be awoken in + // svcReplyAndReceive for LLE servers. + thread->status = THREADSTATUS_WAIT_IPC; + + if (hle_handler != nullptr) { + // For HLE services, we put the request threads to sleep for a short duration to + // simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for + // other reasons like an async callback. The IPC overhead is needed to prevent + // starvation when a thread only does sync requests to HLE services while a + // lower-priority thread is waiting to run. + + // This delay was approximated in a homebrew application by measuring the average time + // it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC + // request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have + // a high variance and vary between models. + static constexpr u64 IPCDelayNanoseconds = 39000; + thread->WakeAfterDelay(IPCDelayNanoseconds); + } else { + // Add the thread to the list of threads that have issued a sync request with this + // server. + pending_requesting_threads.push_back(std::move(thread)); + } } // If this ServerSession does not have an HLE implementation, just wake up the threads waiting // on it. WakeupAllWaitingThreads(); + + // Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the + // end of the command such that only commands following this one are handled as domains + if (convert_to_domain) { + ASSERT_MSG(domain_request_handlers.empty(), "already a domain"); + domain_request_handlers = {hle_handler}; + convert_to_domain = false; + } + return result; } @@ -103,9 +162,4 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& n return std::make_tuple(std::move(server_session), std::move(client_session)); } - -ResultCode TranslateHLERequest(ServerSession* server_session) { - // TODO(Subv): Implement this function once multiple concurrent processes are supported. - return RESULT_SUCCESS; -} } // namespace Kernel diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index f4360ddf3..2da807042 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -21,6 +21,7 @@ class ServerSession; class Session; class SessionRequestHandler; class Thread; +class HLERequestContext; /** * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS @@ -79,7 +80,10 @@ public: std::string name; ///< The name of this session (optional) std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint. std::shared_ptr<SessionRequestHandler> - hle_handler; ///< This session's HLE request handler (optional) + hle_handler; ///< This session's HLE request handler (applicable when not a domain) + + /// This is the list of domain request handlers (after conversion to a domain) + std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; /// List of threads that are pending a response after a sync request. This list is processed in /// a LIFO manner, thus, the last request will be dispatched first. @@ -91,6 +95,16 @@ public: /// TODO(Subv): Find a better name for this. SharedPtr<Thread> currently_handling; + /// Returns true if the session has been converted to a domain, otherwise False + bool IsDomain() const { + return !domain_request_handlers.empty(); + } + + /// Converts the session to a domain at the end of the current command + void ConvertToDomain() { + convert_to_domain = true; + } + private: ServerSession(); ~ServerSession() override; @@ -102,15 +116,13 @@ private: * @return The created server session */ static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown"); + + /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an + /// object handle. + ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context); + + /// When set to True, converts the session to a domain at the end of the command + bool convert_to_domain{}; }; -/** - * Performs command buffer translation for an HLE IPC request. - * The command buffer from the ServerSession thread's TLS is copied into a - * buffer and all descriptors in the buffer are processed. - * TODO(Subv): Implement this function, currently we do not support multiple processes running at - * once, but once that is implemented we'll need to properly translate all descriptors - * in the command buffer. - */ -ResultCode TranslateHLERequest(ServerSession* server_session); -} +} // namespace Kernel diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp index 8a2a7e3fd..642914744 100644 --- a/src/core/hle/kernel/session.cpp +++ b/src/core/hle/kernel/session.cpp @@ -9,4 +9,4 @@ namespace Kernel { Session::Session() {} Session::~Session() {} -} +} // namespace Kernel diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h index 2cf319e99..e69b034a7 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/kernel/session.h @@ -24,4 +24,4 @@ public: ServerSession* server = nullptr; ///< The server endpoint of the session. SharedPtr<ClientPort> port; ///< The port that this session is associated with (optional). }; -} +} // namespace Kernel diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 7279366ec..88230bdd9 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -4,6 +4,7 @@ #include <cstring> #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/shared_memory.h" @@ -14,7 +15,7 @@ namespace Kernel { SharedMemory::SharedMemory() {} SharedMemory::~SharedMemory() {} -SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size, +SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) { @@ -51,8 +52,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u } // Refresh the address mappings for the current process. - if (Kernel::g_current_process != nullptr) { - Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); + if (Core::CurrentProcess() != nullptr) { + Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); } } else { auto& vm_manager = shared_memory->owner_process->vm_manager; @@ -106,14 +107,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi // Error out if the requested permissions don't match what the creator process allows. if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match", - GetObjectId(), address, name.c_str()); - return ERR_INVALID_COMBINATION; - } - - // Heap-backed memory blocks can not be mapped with other_permissions = DontCare - if (base_address != 0 && other_permissions == MemoryPermission::DontCare) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match", + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match", GetObjectId(), address, name.c_str()); return ERR_INVALID_COMBINATION; } @@ -121,23 +115,18 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi // Error out if the provided permissions are not compatible with what the creator process needs. if (other_permissions != MemoryPermission::DontCare && static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match", + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match", GetObjectId(), address, name.c_str()); return ERR_WRONG_PERMISSION; } - // TODO(Subv): Check for the Shared Device Mem flag in the creator process. - /*if (was_created_with_shared_device_mem && address != 0) { - return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - }*/ - // TODO(Subv): The same process that created a SharedMemory object // can not map it in its own address space unless it was created with addr=0, result 0xD900182C. if (address != 0) { - if (address < Memory::HEAP_VADDR) { - LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, invalid address", + // TODO(shinyquagsire23): Check for virtual/mappable memory here too? + if (address >= Memory::HEAP_VADDR && address < Memory::HEAP_VADDR_END) { + LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, invalid address", GetObjectId(), address, name.c_str()); return ERR_INVALID_ADDRESS; } @@ -154,10 +143,9 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi auto result = target_process->vm_manager.MapMemoryBlock( target_address, backing_block, backing_block_offset, size, MemoryState::Shared); if (result.Failed()) { - LOG_ERROR( - Kernel, - "cannot map id=%u, target_address=0x%llx name=%s, error mapping to virtual memory", - GetObjectId(), target_address, name.c_str()); + LOG_ERROR(Kernel, + "cannot map id=%u, target_address=0x%lx name=%s, error mapping to virtual memory", + GetObjectId(), target_address, name.c_str()); return result.Code(); } diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 93a6f2182..86f818e90 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -39,7 +39,7 @@ public: * linear heap. * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u32 size, + static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, VAddr address = 0, MemoryRegion region = MemoryRegion::BASE, @@ -98,10 +98,10 @@ public: ResultCode Unmap(Process* target_process, VAddr address); /** - * Gets a pointer to the shared memory block - * @param offset Offset from the start of the shared memory block to get pointer - * @return Pointer to the shared memory block from the specified offset - */ + * Gets a pointer to the shared memory block + * @param offset Offset from the start of the shared memory block to get pointer + * @return Pointer to the shared memory block from the specified offset + */ u8* GetPointer(u32 offset = 0); /// Process that created this shared memory block. @@ -116,7 +116,7 @@ public: /// Offset into the backing block for this shared memory. size_t backing_block_offset; /// Size of the memory block. Page-aligned. - u32 size; + u64 size; /// Permission restrictions applied to the process which created the block. MemoryPermission permissions; /// Permission restrictions applied to other processes mapping the block. @@ -129,4 +129,4 @@ private: ~SharedMemory() override; }; -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 088058ebc..171bbd956 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -3,10 +3,12 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cinttypes> #include "common/logging/log.h" #include "common/microprofile.h" #include "common/string_util.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" @@ -20,7 +22,6 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_wrap.h" -#include "core/hle/kernel/sync_object.h" #include "core/hle/kernel/thread.h" #include "core/hle/lock.h" #include "core/hle/result.h" @@ -31,14 +32,14 @@ namespace Kernel { /// Set the process heap to a given Size. It can both extend and shrink the heap. static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) { LOG_TRACE(Kernel_SVC, "called, heap_size=0x%llx", heap_size); - auto& process = *g_current_process; + auto& process = *Core::CurrentProcess(); CASCADE_RESULT(*heap_addr, process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite)); return RESULT_SUCCESS; } static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%llx", addr); + LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%lx", addr); return RESULT_SUCCESS; } @@ -46,18 +47,18 @@ static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr, src_addr, size); - return g_current_process->MirrorMemory(dst_addr, src_addr, size); + return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size); } /// Unmaps a region that was previously mapped with svcMapMemory static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr, src_addr, size); - return g_current_process->UnmapMemory(dst_addr, src_addr, size); + return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out -static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) { +static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) { if (!Memory::IsValidVirtualAddress(port_name_address)) return ERR_NOT_FOUND; @@ -87,7 +88,7 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) { /// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Handle handle) { - SharedPtr<SyncObject> session = g_handle_table.Get<SyncObject>(handle); + SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle); if (!session) { LOG_ERROR(Kernel_SVC, "called with invalid handle=0x%08X", handle); return ERR_INVALID_HANDLE; @@ -253,8 +254,8 @@ static ResultCode CancelSynchronization(Handle thread_handle) { } /// Attempts to locks a mutex, creating it if it does not already exist -static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr, - Handle requesting_thread_handle) { +static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, + Handle requesting_thread_handle) { LOG_TRACE(Kernel_SVC, "called holding_thread_handle=0x%08X, mutex_addr=0x%llx, " "requesting_current_thread_handle=0x%08X", @@ -264,6 +265,7 @@ static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr, SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle); ASSERT(requesting_thread); + ASSERT(requesting_thread == GetCurrentThread()); SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr); if (!mutex) { @@ -278,7 +280,7 @@ static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr, } /// Unlock a mutex -static ResultCode UnlockMutex(VAddr mutex_addr) { +static ResultCode ArbitrateUnlock(VAddr mutex_addr) { LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr); SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr); @@ -305,23 +307,23 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) LOG_TRACE(Kernel_SVC, "called info_id=0x%X, info_sub_id=0x%X, handle=0x%08X", info_id, info_sub_id, handle); - auto& vm_manager = g_current_process->vm_manager; + auto& vm_manager = Core::CurrentProcess()->vm_manager; switch (static_cast<GetInfoType>(info_id)) { case GetInfoType::AllowedCpuIdBitmask: - *result = g_current_process->allowed_processor_mask; + *result = Core::CurrentProcess()->allowed_processor_mask; break; case GetInfoType::AllowedThreadPrioBitmask: - *result = g_current_process->allowed_thread_priority_mask; + *result = Core::CurrentProcess()->allowed_thread_priority_mask; break; case GetInfoType::MapRegionBaseAddr: - *result = vm_manager.GetAddressSpaceBaseAddr(); + *result = Memory::MAP_REGION_VADDR; break; case GetInfoType::MapRegionSize: - *result = vm_manager.GetAddressSpaceSize(); + *result = Memory::MAP_REGION_SIZE; break; case GetInfoType::HeapRegionBaseAddr: - *result = vm_manager.GetNewMapRegionBaseAddr() + vm_manager.GetNewMapRegionSize(); + *result = Memory::HEAP_VADDR; break; case GetInfoType::HeapRegionSize: *result = Memory::HEAP_SIZE; @@ -332,6 +334,9 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) case GetInfoType::TotalHeapUsage: *result = vm_manager.GetTotalHeapUsage(); break; + case GetInfoType::IsCurrentProcessBeingDebugged: + *result = 0; + break; case GetInfoType::RandomEntropy: *result = 0; break; @@ -342,13 +347,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) *result = vm_manager.GetAddressSpaceSize(); break; case GetInfoType::NewMapRegionBaseAddr: - *result = vm_manager.GetNewMapRegionBaseAddr(); + *result = Memory::NEW_MAP_REGION_VADDR; break; case GetInfoType::NewMapRegionSize: - *result = vm_manager.GetNewMapRegionSize(); + *result = Memory::NEW_MAP_REGION_SIZE; break; case GetInfoType::IsVirtualAddressMemoryEnabled: - *result = g_current_process->is_virtual_address_memory_enabled; + *result = Core::CurrentProcess()->is_virtual_address_memory_enabled; break; case GetInfoType::TitleId: LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0"); @@ -388,7 +393,7 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { // Note: The kernel uses the current process's resource limit instead of // the one from the thread owner's resource limit. - SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; + SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit; if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { return ERR_NOT_AUTHORIZED; } @@ -416,8 +421,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s "called, shared_memory_handle=0x%08X, addr=0x%llx, size=0x%llx, permissions=0x%08X", shared_memory_handle, addr, size, permissions); - SharedPtr<SharedMemory> shared_memory = - Kernel::g_handle_table.Get<SharedMemory>(shared_memory_handle); + SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { return ERR_INVALID_HANDLE; } @@ -432,7 +436,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s case MemoryPermission::WriteExecute: case MemoryPermission::ReadWriteExecute: case MemoryPermission::DontCare: - return shared_memory->Map(Kernel::g_current_process.get(), addr, permissions_type, + return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, MemoryPermission::DontCare); default: LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions); @@ -441,6 +445,16 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return RESULT_SUCCESS; } +static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { + LOG_WARNING(Kernel_SVC, + "called, shared_memory_handle=0x%08X, addr=0x%" PRIx64 ", size=0x%" PRIx64 "", + shared_memory_handle, addr, size); + + SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle); + + return shared_memory->Unmap(Core::CurrentProcess().get(), addr); +} + /// Query process memory static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/, Handle process_handle, u64 addr) { @@ -450,11 +464,11 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i } auto vma = process->vm_manager.FindVMA(addr); memory_info->attributes = 0; - if (vma == g_current_process->vm_manager.vma_map.end()) { + if (vma == Core::CurrentProcess()->vm_manager.vma_map.end()) { memory_info->base_address = 0; memory_info->permission = static_cast<u32>(VMAPermission::None); memory_info->size = 0; - memory_info->type = static_cast<u32>(MemoryState::Free); + memory_info->type = static_cast<u32>(MemoryState::Unmapped); } else { memory_info->base_address = vma->second.base; memory_info->permission = static_cast<u32>(vma->second.permissions); @@ -474,16 +488,17 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd /// Exits the current process static void ExitProcess() { - LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id); + LOG_INFO(Kernel_SVC, "Process %u exiting", Core::CurrentProcess()->process_id); - ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited"); + ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running, + "Process has already exited"); - g_current_process->status = ProcessStatus::Exited; + Core::CurrentProcess()->status = ProcessStatus::Exited; // Stop all the process threads that are currently waiting for objects. - auto& thread_list = GetThreadList(); + auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList(); for (auto& thread : thread_list) { - if (thread->owner_process != g_current_process) + if (thread->owner_process != Core::CurrentProcess()) continue; if (thread == GetCurrentThread()) @@ -512,14 +527,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V return ERR_OUT_OF_RANGE; } - SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; + SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit; if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { return ERR_NOT_AUTHORIZED; } if (processor_id == THREADPROCESSORID_DEFAULT) { // Set the target CPU to the one specified in the process' exheader. - processor_id = g_current_process->ideal_processor; + processor_id = Core::CurrentProcess()->ideal_processor; ASSERT(processor_id != THREADPROCESSORID_DEFAULT); } @@ -541,7 +556,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(name, entry_point, priority, arg, processor_id, stack_top, - g_current_process)); + Core::CurrentProcess())); CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread)); *out_handle = thread->guest_handle; @@ -583,7 +598,7 @@ static void SleepThread(s64 nanoseconds) { // Don't attempt to yield execution if there are no available threads to run, // this way we avoid a useless reschedule to the idle thread. - if (nanoseconds == 0 && !HaveReadyThreads()) + if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads()) return; // Sleep current thread and check for next thread to schedule @@ -613,20 +628,29 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr); } - ASSERT(mutex->GetOwnerHandle() == thread_handle); - SharedPtr<ConditionVariable> condition_variable = g_object_address_table.Get<ConditionVariable>(condition_variable_addr); if (!condition_variable) { // Create a new condition_variable for the specified address if one does not already exist - condition_variable = - ConditionVariable::Create(condition_variable_addr, mutex_addr).Unwrap(); + condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap(); condition_variable->name = Common::StringFromFormat("condition-variable-%llx", condition_variable_addr); } - ASSERT(condition_variable->GetAvailableCount() == 0); - ASSERT(condition_variable->mutex_addr == mutex_addr); + if (condition_variable->mutex_addr) { + // Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify + // everything is correct + ASSERT(condition_variable->mutex_addr == mutex_addr); + } else { + // Previously created the ConditionVariable using SignalProcessWideKey, set the mutex + // associated with it + condition_variable->mutex_addr = mutex_addr; + } + + if (mutex->GetOwnerHandle()) { + // Release the mutex if the current thread is holding it + mutex->Release(thread.get()); + } auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason, SharedPtr<Thread> thread, @@ -668,8 +692,6 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var CASCADE_CODE( WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback)); - mutex->Release(thread.get()); - return RESULT_SUCCESS; } @@ -728,14 +750,45 @@ static ResultCode ResetSignal(Handle handle) { /// Creates a TransferMemory object static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%llx, size=0x%llx, perms=%08X", addr, size, + LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%lx, size=0x%lx, perms=%08X", addr, size, permissions); *handle = 0; return RESULT_SUCCESS; } -static ResultCode SetThreadCoreMask(u64, u64, u64) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called"); +static ResultCode GetThreadCoreMask(Handle handle, u32* mask, u64* unknown) { + LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X", handle); + *mask = 0x0; + *unknown = 0xf; + return RESULT_SUCCESS; +} + +static ResultCode SetThreadCoreMask(Handle handle, u32 mask, u64 unknown) { + LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, mask=0x%08X, unknown=0x%lx", handle, + mask, unknown); + return RESULT_SUCCESS; +} + +static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions, + u32 remote_permissions) { + LOG_TRACE(Kernel_SVC, "called, size=0x%llx, localPerms=0x%08x, remotePerms=0x%08x", size, + local_permissions, remote_permissions); + auto sharedMemHandle = + SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size, + static_cast<MemoryPermission>(local_permissions), + static_cast<MemoryPermission>(remote_permissions)); + + CASCADE_RESULT(*handle, g_handle_table.Create(sharedMemHandle)); + return RESULT_SUCCESS; +} + +static ResultCode ClearEvent(Handle handle) { + LOG_TRACE(Kernel_SVC, "called, event=0xX", handle); + + SharedPtr<Event> evt = g_handle_table.Get<Event>(handle); + if (evt == nullptr) + return ERR_INVALID_HANDLE; + evt->Clear(); return RESULT_SUCCESS; } @@ -764,24 +817,24 @@ static const FunctionDef SVC_Table[] = { {0x0B, SvcWrap<SleepThread>, "SleepThread"}, {0x0C, SvcWrap<GetThreadPriority>, "GetThreadPriority"}, {0x0D, SvcWrap<SetThreadPriority>, "SetThreadPriority"}, - {0x0E, nullptr, "GetThreadCoreMask"}, + {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"}, {0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"}, {0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"}, {0x11, nullptr, "SignalEvent"}, - {0x12, nullptr, "ClearEvent"}, + {0x12, SvcWrap<ClearEvent>, "ClearEvent"}, {0x13, SvcWrap<MapSharedMemory>, "MapSharedMemory"}, - {0x14, nullptr, "UnmapSharedMemory"}, + {0x14, SvcWrap<UnmapSharedMemory>, "UnmapSharedMemory"}, {0x15, SvcWrap<CreateTransferMemory>, "CreateTransferMemory"}, {0x16, SvcWrap<CloseHandle>, "CloseHandle"}, {0x17, SvcWrap<ResetSignal>, "ResetSignal"}, {0x18, SvcWrap<WaitSynchronization>, "WaitSynchronization"}, {0x19, SvcWrap<CancelSynchronization>, "CancelSynchronization"}, - {0x1A, SvcWrap<LockMutex>, "LockMutex"}, - {0x1B, SvcWrap<UnlockMutex>, "UnlockMutex"}, + {0x1A, SvcWrap<ArbitrateLock>, "ArbitrateLock"}, + {0x1B, SvcWrap<ArbitrateUnlock>, "ArbitrateUnlock"}, {0x1C, SvcWrap<WaitProcessWideKeyAtomic>, "WaitProcessWideKeyAtomic"}, {0x1D, SvcWrap<SignalProcessWideKey>, "SignalProcessWideKey"}, {0x1E, SvcWrap<GetSystemTick>, "GetSystemTick"}, - {0x1F, SvcWrap<ConnectToPort>, "ConnectToPort"}, + {0x1F, SvcWrap<ConnectToNamedPort>, "ConnectToNamedPort"}, {0x20, nullptr, "SendSyncRequestLight"}, {0x21, SvcWrap<SendSyncRequest>, "SendSyncRequest"}, {0x22, nullptr, "SendSyncRequestWithUserBuffer"}, @@ -825,12 +878,12 @@ static const FunctionDef SVC_Table[] = { {0x48, nullptr, "Unknown"}, {0x49, nullptr, "Unknown"}, {0x4A, nullptr, "Unknown"}, - {0x4B, nullptr, "Unknown"}, - {0x4C, nullptr, "Unknown"}, + {0x4B, nullptr, "CreateJitMemory"}, + {0x4C, nullptr, "MapJitMemory"}, {0x4D, nullptr, "SleepSystem"}, {0x4E, nullptr, "ReadWriteRegister"}, {0x4F, nullptr, "SetProcessActivity"}, - {0x50, nullptr, "CreateSharedMemory"}, + {0x50, SvcWrap<CreateSharedMemory>, "CreateSharedMemory"}, {0x51, nullptr, "MapTransferMemory"}, {0x52, nullptr, "UnmapTransferMemory"}, {0x53, nullptr, "CreateInterruptEvent"}, diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 42cc41da3..bc471d01e 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -14,7 +14,11 @@ struct MemoryInfo { u32 type; u32 attributes; u32 permission; + u32 device_refcount; + u32 ipc_refcount; + INSERT_PADDING_WORDS(1); }; +static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size."); struct PageInfo { u64 flags; diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index fd7054bbd..5da4f5269 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -70,6 +70,21 @@ void SvcWrap() { FuncReturn(retval); } +template <ResultCode func(u32, u32, u64)> +void SvcWrap() { + FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw); +} + +template <ResultCode func(u32, u32*, u64*)> +void SvcWrap() { + u32 param_1 = 0; + u64 param_2 = 0; + ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); + Core::CPU().SetReg(1, param_1); + Core::CPU().SetReg(2, param_2); + FuncReturn(retval.raw); +} + template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( @@ -91,6 +106,11 @@ void SvcWrap() { FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2), (u32)PARAM(3)).raw); } +template <ResultCode func(u32, u64, u64)> +void SvcWrap() { + FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2)).raw); +} + template <ResultCode func(u32*, u64, u64, s64)> void SvcWrap() { u32 param_1 = 0; @@ -145,6 +165,15 @@ void SvcWrap() { FuncReturn(retval); } +template <ResultCode func(Handle*, u64, u32, u32)> +void SvcWrap() { + u32 param_1 = 0; + u32 retval = + func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; + Core::CPU().SetReg(1, param_1); + FuncReturn(retval); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Function wrappers that return type u32 diff --git a/src/core/hle/kernel/sync_object.h b/src/core/hle/kernel/sync_object.h deleted file mode 100644 index f2befa2ea..000000000 --- a/src/core/hle/kernel/sync_object.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <boost/smart_ptr/intrusive_ptr.hpp> -#include "core/hle/kernel/kernel.h" -#include "core/hle/result.h" - -namespace Kernel { - -class Thread; - -/// Class that represents a Kernel object that svcSendSyncRequest can be called on -class SyncObject : public Object { -public: - /** - * Handle a sync request from the emulated application. - * @param thread Thread that initiated the request. - * @returns ResultCode from the operation. - */ - virtual ResultCode SendSyncRequest(SharedPtr<Thread> thread) = 0; -}; - -// Specialization of DynamicObjectCast for SyncObjects -template <> -inline SharedPtr<SyncObject> DynamicObjectCast<SyncObject>(SharedPtr<Object> object) { - if (object != nullptr && object->IsSyncable()) { - return boost::static_pointer_cast<SyncObject>(std::move(object)); - } - return nullptr; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 111c496b9..f3a8aa4aa 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cinttypes> #include <list> #include <vector> #include "common/assert.h" @@ -40,14 +41,6 @@ void Thread::Acquire(Thread* thread) { // us to simply use a pool index or similar. static Kernel::HandleTable wakeup_callback_handle_table; -// Lists all thread ids that aren't deleted/etc. -static std::vector<SharedPtr<Thread>> thread_list; - -// Lists only ready thread ids. -static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue; - -static SharedPtr<Thread> current_thread; - // The first available thread id at startup static u32 next_thread_id; @@ -62,20 +55,6 @@ inline static u32 const NewThreadId() { Thread::Thread() {} Thread::~Thread() {} -Thread* GetCurrentThread() { - return current_thread.get(); -} - -/** - * Check if the specified thread is waiting on the specified address to be arbitrated - * @param thread The thread to test - * @param wait_address The address to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { - return thread->status == THREADSTATUS_WAIT_ARB && wait_address == thread->wait_address; -} - void Thread::Stop() { // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); @@ -85,7 +64,7 @@ void Thread::Stop() { // Clean up thread from ready queue // This is only needed when the thread is termintated forcefully (SVC TerminateProcess) if (status == THREADSTATUS_READY) { - ready_queue.remove(current_priority, this); + Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority); } status = THREADSTATUS_DEAD; @@ -105,113 +84,7 @@ void Thread::Stop() { u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u64 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; - Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); -} - -Thread* ArbitrateHighestPriorityThread(u32 address) { - Thread* highest_priority_thread = nullptr; - u32 priority = THREADPRIO_LOWEST; - - // Iterate through threads, find highest priority thread that is waiting to be arbitrated... - for (auto& thread : thread_list) { - if (!CheckWait_AddressArbiter(thread.get(), address)) - continue; - - if (thread == nullptr) - continue; - - if (thread->current_priority <= priority) { - highest_priority_thread = thread.get(); - priority = thread->current_priority; - } - } - - // If a thread was arbitrated, resume it - if (nullptr != highest_priority_thread) { - highest_priority_thread->ResumeFromWait(); - } - - return highest_priority_thread; -} - -void ArbitrateAllThreads(u32 address) { - // Resume all threads found to be waiting on the address - for (auto& thread : thread_list) { - if (CheckWait_AddressArbiter(thread.get(), address)) - thread->ResumeFromWait(); - } -} - -/** - * Switches the CPU's active thread context to that of the specified thread - * @param new_thread The thread to switch to - */ -static void SwitchContext(Thread* new_thread) { - Thread* previous_thread = GetCurrentThread(); - - // Save context for previous thread - if (previous_thread) { - previous_thread->last_running_ticks = CoreTiming::GetTicks(); - Core::CPU().SaveContext(previous_thread->context); - - if (previous_thread->status == THREADSTATUS_RUNNING) { - // This is only the case when a reschedule is triggered without the current thread - // yielding execution (i.e. an event triggered, system core time-sliced, etc) - ready_queue.push_front(previous_thread->current_priority, previous_thread); - previous_thread->status = THREADSTATUS_READY; - } - } - - // Load context of new thread - if (new_thread) { - ASSERT_MSG(new_thread->status == THREADSTATUS_READY, - "Thread must be ready to become running."); - - // Cancel any outstanding wakeup events for this thread - CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); - - auto previous_process = Kernel::g_current_process; - - current_thread = new_thread; - - ready_queue.remove(new_thread->current_priority, new_thread); - new_thread->status = THREADSTATUS_RUNNING; - - if (previous_process != current_thread->owner_process) { - Kernel::g_current_process = current_thread->owner_process; - SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); - } - - Core::CPU().LoadContext(new_thread->context); - Core::CPU().SetTlsAddress(new_thread->GetTLSAddress()); - } else { - current_thread = nullptr; - // Note: We do not reset the current process and current page table when idling because - // technically we haven't changed processes, our threads are just paused. - } -} - -/** - * Pops and returns the next thread from the thread queue - * @return A pointer to the next ready thread - */ -static Thread* PopNextReadyThread() { - Thread* next; - Thread* thread = GetCurrentThread(); - - if (thread && thread->status == THREADSTATUS_RUNNING) { - // We have to do better than the current thread. - // This call returns null when that's not possible. - next = ready_queue.pop_first_better(thread->current_priority); - if (!next) { - // Otherwise just keep going with the current thread - next = thread; - } - } else { - next = ready_queue.pop_first(); - } - - return next; + Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot); } void WaitCurrentThread_Sleep() { @@ -219,17 +92,10 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { - Thread* thread = GetCurrentThread(); - thread->wait_address = wait_address; - thread->status = THREADSTATUS_WAIT_ARB; -} - void ExitCurrentThread() { Thread* thread = GetCurrentThread(); thread->Stop(); - thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), - thread_list.end()); + Core::System::GetInstance().Scheduler().RemoveThread(thread); } /** @@ -247,7 +113,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { bool resume = true; if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || + thread->status == THREADSTATUS_WAIT_HLE_EVENT) { // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -281,8 +148,9 @@ void Thread::ResumeFromWait() { switch (status) { case THREADSTATUS_WAIT_SYNCH_ALL: case THREADSTATUS_WAIT_SYNCH_ANY: - case THREADSTATUS_WAIT_ARB: + case THREADSTATUS_WAIT_HLE_EVENT: case THREADSTATUS_WAIT_SLEEP: + case THREADSTATUS_WAIT_IPC: break; case THREADSTATUS_READY: @@ -306,32 +174,12 @@ void Thread::ResumeFromWait() { wakeup_callback = nullptr; - ready_queue.push_back(current_priority, this); status = THREADSTATUS_READY; + Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority); Core::System::GetInstance().PrepareReschedule(); } /** - * Prints the thread queue for debugging purposes - */ -static void DebugThreadQueue() { - Thread* thread = GetCurrentThread(); - if (!thread) { - LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD"); - } else { - LOG_DEBUG(Kernel, "0x%02X %u (current)", thread->current_priority, - GetCurrentThread()->GetObjectId()); - } - - for (auto& t : thread_list) { - u32 priority = ready_queue.contains(t.get()); - if (priority != -1) { - LOG_DEBUG(Kernel, "0x%02X %u", priority, t->GetObjectId()); - } - } -} - -/** * Finds a free location for the TLS section of a thread. * @param tls_slots The TLS page array of the thread's owner process. * Returns a tuple of (page, slot, alloc_needed) where: @@ -379,7 +227,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, SharedPtr<Process> owner_process) { // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { - LOG_ERROR(Kernel_SVC, "Invalid thread priority: %d", priority); + LOG_ERROR(Kernel_SVC, "Invalid thread priority: %u", priority); return ERR_OUT_OF_RANGE; } @@ -391,15 +239,14 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, // TODO(yuriks): Other checks, returning 0xD9001BEA if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { - LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); + LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %016" PRIx64, name.c_str(), entry_point); // TODO (bunnei): Find the correct error code to use here return ResultCode(-1); } SharedPtr<Thread> thread(new Thread); - thread_list.push_back(thread); - ready_queue.prepare(priority); + Core::System::GetInstance().Scheduler().AddThread(thread, priority); thread->thread_id = NewThreadId(); thread->status = THREADSTATUS_DORMANT; @@ -452,7 +299,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, // TODO(Subv): Find the correct MemoryState for this region. vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, linheap_memory, offset, Memory::PAGE_SIZE, - MemoryState::ThreadLocalStorage); + MemoryState::ThreadLocal); } // Mark the slot as used @@ -470,12 +317,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, void Thread::SetPriority(u32 priority) { ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, "Invalid priority value."); - // If thread was ready, adjust queues - if (status == THREADSTATUS_READY) - ready_queue.move(this, current_priority, priority); - else - ready_queue.prepare(priority); - + Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); nominal_priority = current_priority = priority; } @@ -489,22 +331,18 @@ void Thread::UpdatePriority() { } void Thread::BoostPriority(u32 priority) { - // If thread was ready, adjust queues - if (status == THREADSTATUS_READY) - ready_queue.move(this, current_priority, priority); - else - ready_queue.prepare(priority); + Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); current_priority = priority; } SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority, SharedPtr<Process> owner_process) { // Setup page table so we can write to memory - SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); // Initialize new "main" thread auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, - Memory::HEAP_VADDR_END, owner_process); + Memory::STACK_AREA_VADDR_END, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); @@ -519,25 +357,6 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority, return thread; } -bool HaveReadyThreads() { - return ready_queue.get_first() != nullptr; -} - -void Reschedule() { - Thread* cur = GetCurrentThread(); - Thread* next = PopNextReadyThread(); - - if (cur && next) { - LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); - } else if (cur) { - LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId()); - } else if (next) { - LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId()); - } - - SwitchContext(next); -} - void Thread::SetWaitSynchronizationResult(ResultCode result) { context.cpu_registers[0] = result.raw; } @@ -560,25 +379,20 @@ VAddr Thread::GetCommandBufferAddress() const { //////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * Gets the current thread + */ +Thread* GetCurrentThread() { + return Core::System::GetInstance().Scheduler().GetCurrentThread(); +} + void ThreadingInit() { ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback); - - current_thread = nullptr; next_thread_id = 1; } void ThreadingShutdown() { - current_thread = nullptr; - - for (auto& t : thread_list) { - t->Stop(); - } - thread_list.clear(); - ready_queue.clear(); -} - -const std::vector<SharedPtr<Thread>>& GetThreadList() { - return thread_list; + Kernel::ClearProcessList(); } } // namespace Kernel diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 0a1ada27d..dbf47e269 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -38,8 +38,9 @@ enum ThreadProcessorId : s32 { enum ThreadStatus { THREADSTATUS_RUNNING, ///< Currently running THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_HLE_EVENT, ///< Waiting for hle event to finish THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC + THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true THREADSTATUS_DORMANT, ///< Created but not yet made ready @@ -249,28 +250,6 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority, SharedPtr<Process> owner_process); /** - * Returns whether there are any threads that are ready to run. - */ -bool HaveReadyThreads(); - -/** - * Reschedules to the next available thread (call after current thread is suspended) - */ -void Reschedule(); - -/** - * Arbitrate the highest priority thread that is waiting - * @param address The address for which waiting threads should be arbitrated - */ -Thread* ArbitrateHighestPriorityThread(VAddr address); - -/** - * Arbitrate all threads currently waiting. - * @param address The address for which waiting threads should be arbitrated - */ -void ArbitrateAllThreads(VAddr address); - -/** * Gets the current thread */ Thread* GetCurrentThread(); @@ -301,9 +280,4 @@ void ThreadingInit(); */ void ThreadingShutdown(); -/** - * Get a const reference to the thread list for debug use - */ -const std::vector<SharedPtr<Thread>>& GetThreadList(); - } // namespace Kernel diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index a93a6c87a..8da745634 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -111,4 +111,4 @@ void TimersInit() { void TimersShutdown() {} -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index 82552372d..82d19cefc 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -76,4 +76,4 @@ void TimersInit(); /// Tears down the timer variables void TimersShutdown(); -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index bf261699e..1c2f873aa 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include <iterator> #include "common/assert.h" #include "common/logging/log.h" @@ -10,15 +11,33 @@ #include "core/hle/kernel/errors.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" +#include "core/memory_hook.h" #include "core/memory_setup.h" -#include "core/mmio.h" namespace Kernel { static const char* GetMemoryStateName(MemoryState state) { static const char* names[] = { - "Free", "Reserved", "IO", "Static", "Code", "Private", - "Shared", "Continuous", "Aliased", "Alias", "AliasCode", "Locked", + "Unmapped", + "Io", + "Normal", + "CodeStatic", + "CodeMutable", + "Heap", + "Shared", + "Unknown1" + "ModuleCodeStatic", + "ModuleCodeMutable", + "IpcBuffer0", + "Mapped", + "ThreadLocal", + "TransferMemoryIsolated", + "TransferMemory", + "ProcessMemory", + "Unknown2" + "IpcBuffer1", + "IpcBuffer3", + "KernelStack", }; return names[(int)state]; @@ -60,8 +79,8 @@ void VMManager::Reset() { vma_map.emplace(initial_vma.base, initial_vma); page_table.pointers.fill(nullptr); + page_table.special_regions.clear(); page_table.attributes.fill(Memory::PageType::Unmapped); - page_table.cached_res_count.fill(0); UpdatePageTableForVMA(initial_vma); } @@ -121,7 +140,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state, - Memory::MMIORegionPointer mmio_handler) { + Memory::MemoryHookPointer mmio_handler) { // This is the appropriately sized VMA that will turn into our allocation. CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size)); VirtualMemoryArea& final_vma = vma_handle->second; @@ -141,7 +160,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { VirtualMemoryArea& vma = vma_handle->second; vma.type = VMAType::Free; vma.permissions = VMAPermission::None; - vma.meminfo_state = MemoryState::Free; + vma.meminfo_state = MemoryState::Unmapped; vma.backing_block = nullptr; vma.offset = 0; @@ -165,6 +184,9 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) { } ASSERT(FindVMA(target)->second.size >= size); + + Core::CPU().UnmapMemory(target, size); + return RESULT_SUCCESS; } @@ -206,7 +228,8 @@ void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) { void VMManager::LogLayout(Log::Level log_level) const { for (const auto& p : vma_map) { const VirtualMemoryArea& vma = p.second; - LOG_GENERIC(Log::Class::Kernel, log_level, "%08X - %08X size: %8X %c%c%c %s", vma.base, + LOG_GENERIC(Log::Class::Kernel, log_level, + "%016" PRIx64 " - %016" PRIx64 " size: %16" PRIx64 " %c%c%c %s", vma.base, vma.base + vma.size, vma.size, (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-', (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-', @@ -222,8 +245,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) { } ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); - ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", base); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size); + ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, base); VMAIter vma_handle = StripIterConstness(FindVMA(base)); if (vma_handle == vma_map.end()) { @@ -258,8 +281,8 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) { } ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) { - ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size); - ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", target); + ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size); + ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, target); VAddr target_end = target + size; ASSERT(target_end >= target); @@ -375,14 +398,4 @@ u64 VMManager::GetAddressSpaceSize() { return MAX_ADDRESS; } -VAddr VMManager::GetNewMapRegionBaseAddr() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return 0x8000000; -} - -u64 VMManager::GetNewMapRegionSize() { - LOG_WARNING(Kernel, "(STUBBED) called"); - return 0x8000000; -} - } // namespace Kernel diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 7a7fee54a..4d66146f6 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -10,7 +10,7 @@ #include "common/common_types.h" #include "core/hle/result.h" #include "core/memory.h" -#include "core/mmio.h" +#include "core/memory_hook.h" namespace Kernel { @@ -41,15 +41,24 @@ enum class VMAPermission : u8 { /// Set of values returned in MemoryInfo.state by svcQueryMemory. enum class MemoryState : u32 { - Free = 0, - IO = 1, - Normal = 2, - Code = 3, - Static = 4, - Heap = 5, - Shared = 6, - Mapped = 6, - ThreadLocalStorage = 12, + Unmapped = 0x0, + Io = 0x1, + Normal = 0x2, + CodeStatic = 0x3, + CodeMutable = 0x4, + Heap = 0x5, + Shared = 0x6, + ModuleCodeStatic = 0x8, + ModuleCodeMutable = 0x9, + IpcBuffer0 = 0xA, + Mapped = 0xB, + ThreadLocal = 0xC, + TransferMemoryIsolated = 0xD, + TransferMemory = 0xE, + ProcessMemory = 0xF, + IpcBuffer1 = 0x11, + IpcBuffer3 = 0x12, + KernelStack = 0x13, }; /** @@ -66,7 +75,7 @@ struct VirtualMemoryArea { VMAType type = VMAType::Free; VMAPermission permissions = VMAPermission::None; /// Tag returned by svcQueryMemory. Not otherwise used. - MemoryState meminfo_state = MemoryState::Free; + MemoryState meminfo_state = MemoryState::Unmapped; // Settings for type = AllocatedMemoryBlock /// Memory block backing this VMA. @@ -81,7 +90,7 @@ struct VirtualMemoryArea { // Settings for type = MMIO /// Physical address of the register area this VMA maps to. PAddr paddr = 0; - Memory::MMIORegionPointer mmio_handler = nullptr; + Memory::MemoryHookPointer mmio_handler = nullptr; /// Tests if this area can be merged to the right with `next`. bool CanBeMergedWith(const VirtualMemoryArea& next) const; @@ -160,7 +169,7 @@ public: * @param mmio_handler The handler that will implement read and write for this MMIO region. */ ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state, - Memory::MMIORegionPointer mmio_handler); + Memory::MemoryHookPointer mmio_handler); /// Unmaps a range of addresses, splitting VMAs as necessary. ResultCode UnmapRange(VAddr target, u64 size); @@ -192,12 +201,6 @@ public: /// Gets the total address space address size, used by svcGetInfo u64 GetAddressSpaceSize(); - /// Gets the base address for a new memory region, used by svcGetInfo - VAddr GetNewMapRegionBaseAddr(); - - /// Gets the size for a new memory region, used by svcGetInfo - u64 GetNewMapRegionSize(); - /// Each VMManager has its own page table, which is set as the main one when the owning process /// is scheduled. Memory::PageTable page_table; diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index ec147b84c..b08ac72c1 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp @@ -39,7 +39,8 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { for (const auto& thread : waiting_threads) { // The list of waiting threads must not contain threads that are not waiting to be awakened. ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || - thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || + thread->status == THREADSTATUS_WAIT_HLE_EVENT, "Inconsistent thread statuses in waiting_threads"); if (thread->current_priority >= candidate_priority) diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 10ddc4feb..052f49979 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -19,6 +19,8 @@ enum class ErrorDescription : u32 { Success = 0, RemoteProcessDead = 301, + InvalidOffset = 6061, + InvalidLength = 6062, }; /** @@ -106,11 +108,11 @@ union ResultCode { } constexpr bool IsSuccess() const { - return is_error.ExtractValue(raw) == 0; + return raw == 0; } constexpr bool IsError() const { - return is_error.ExtractValue(raw) == 1; + return raw != 0; } }; @@ -198,6 +200,9 @@ public: } ResultVal& operator=(const ResultVal& o) { + if (this == &o) { + return *this; + } if (!empty()) { if (!o.empty()) { object = o.object; diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 147f4e62e..52c3491d5 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -9,15 +9,113 @@ namespace Service { namespace Account { +using Uid = std::array<u64, 2>; +static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull}; + +class IProfile final : public ServiceFramework<IProfile> { +public: + IProfile() : ServiceFramework("IProfile") { + static const FunctionInfo functions[] = { + {1, &IProfile::GetBase, "GetBase"}, + }; + RegisterHandlers(functions); + } + +private: + void GetBase(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + ProfileBase profile_base{}; + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); + } +}; + +class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { +public: + IManagerForApplication() : ServiceFramework("IManagerForApplication") { + static const FunctionInfo functions[] = { + {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, + {1, &IManagerForApplication::GetAccountId, "GetAccountId"}, + }; + RegisterHandlers(functions); + } + +private: + void CheckAvailability(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(true); // TODO: Check when this is supposed to return true and when not + } + + void GetAccountId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0x12345678ABCDEF); + } +}; + +void ACC_U0::GetUserExistence(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(true); // TODO: Check when this is supposed to return true and when not +} + +void ACC_U0::ListAllUsers(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; + ctx.WriteBuffer(user_ids.data(), user_ids.size()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ACC_U0::ListOpenUsers(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID}; + ctx.WriteBuffer(user_ids.data(), user_ids.size()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ACC_U0::GetProfile(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IProfile>(); + LOG_DEBUG(Service_ACC, "called"); +} + void ACC_U0::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2}; + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ACC_U0::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IManagerForApplication>(); + LOG_DEBUG(Service_ACC, "called"); +} + +void ACC_U0::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); + rb.PushRaw(DEFAULT_USER_ID); } ACC_U0::ACC_U0() : ServiceFramework("acc:u0") { static const FunctionInfo functions[] = { + {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, + {2, &ACC_U0::ListAllUsers, "ListAllUsers"}, + {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_U0::GetProfile, "GetProfile"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, + {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index ac243d5b8..222f37282 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -9,13 +9,31 @@ namespace Service { namespace Account { +// TODO: RE this structure +struct UserData { + INSERT_PADDING_BYTES(0x80); +}; +static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); + +// TODO: RE this structure +struct ProfileBase { + INSERT_PADDING_BYTES(0x38); +}; +static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); + class ACC_U0 final : public ServiceFramework<ACC_U0> { public: ACC_U0(); ~ACC_U0() = default; private: + void GetUserExistence(Kernel::HLERequestContext& ctx); + void ListAllUsers(Kernel::HLERequestContext& ctx); + void ListOpenUsers(Kernel::HLERequestContext& ctx); + void GetLastOpenedUser(Kernel::HLERequestContext& ctx); + void GetProfile(Kernel::HLERequestContext& ctx); void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); + void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); }; } // namespace Account diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index a761bea65..bab338205 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,14 +2,493 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> +#include "core/file_sys/filesystem.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/am/am.h" +#include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/apm/apm.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/nvflinger/nvflinger.h" +#include "core/settings.h" namespace Service { namespace AM { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<AppletOE>()->InstallAsService(service_manager); +IWindowController::IWindowController() : ServiceFramework("IWindowController") { + static const FunctionInfo functions[] = { + {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"}, + {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"}, + }; + RegisterHandlers(functions); +} + +void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); +} + +void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +IAudioController::IAudioController() : ServiceFramework("IAudioController") { + static const FunctionInfo functions[] = { + {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"}, + {1, &IAudioController::GetMainAppletExpectedMasterVolume, + "GetMainAppletExpectedMasterVolume"}, + {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, + "GetLibraryAppletExpectedMasterVolume"}, + {3, nullptr, "ChangeMainAppletMasterVolume"}, + {4, nullptr, "SetTransparentVolumeRate"}, + }; + RegisterHandlers(functions); +} + +void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(volume); +} + +void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(volume); +} + +IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {} + +IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {} + +ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { + static const FunctionInfo functions[] = { + {1, &ISelfController::LockExit, "LockExit"}, + {2, &ISelfController::UnlockExit, "UnlockExit"}, + {9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"}, + {10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"}, + {11, &ISelfController::SetOperationModeChangedNotification, + "SetOperationModeChangedNotification"}, + {12, &ISelfController::SetPerformanceModeChangedNotification, + "SetPerformanceModeChangedNotification"}, + {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"}, + {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"}, + {16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"}, + {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, + }; + RegisterHandlers(functions); + + launchable_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent"); +} + +void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { + // Takes 3 input u8s with each field located immediately after the previous u8, these are + // bool flags. No output. + + IPC::RequestParser rp{ctx}; + + struct FocusHandlingModeParams { + u8 unknown0; + u8 unknown1; + u8 unknown2; + }; + auto flags = rp.PopRaw<FocusHandlingModeParams>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + bool flag = rp.Pop<bool>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag)); +} + +void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + bool flag = rp.Pop<bool>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag)); +} + +void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) { + // Takes 3 input u8s with each field located immediately after the previous u8, these are + // bool flags. No output. + IPC::RequestParser rp{ctx}; + + bool enabled = rp.Pop<bool>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called enabled=%u", static_cast<u32>(enabled)); +} + +void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) { + launchable_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(launchable_event); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) { + // TODO(Subv): Find out how AM determines the display to use, for now just create the layer + // in the Default display. + u64 display_id = nvflinger->OpenDisplay("Default"); + u64 layer_id = nvflinger->CreateLayer(display_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(layer_id); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { + static const FunctionInfo functions[] = { + {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, + {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"}, + {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"}, + {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"}, + {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, + }; + RegisterHandlers(functions); + + event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event"); +} + +void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { + event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(15); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u8>(FocusState::InFocus)); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld)); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { + const bool use_docked_mode{Settings::values.use_docked_mode}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked + : APM::PerformanceMode::Handheld)); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { +public: + explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { + static const FunctionInfo functions[] = { + {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, + {1, nullptr, "IsCompleted"}, + {10, nullptr, "Start"}, + {20, nullptr, "RequestExit"}, + {25, nullptr, "Terminate"}, + {30, nullptr, "GetResult"}, + {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, + {100, nullptr, "PushInData"}, + {101, nullptr, "PopOutData"}, + {102, nullptr, "PushExtraStorage"}, + {103, nullptr, "PushInteractiveInData"}, + {104, nullptr, "PopInteractiveOutData"}, + {105, nullptr, "GetPopOutDataEvent"}, + {106, nullptr, "GetPopInteractiveOutDataEvent"}, + {120, nullptr, "NeedsToExitProcess"}, + {120, nullptr, "GetLibraryAppletInfo"}, + {150, nullptr, "RequestForAppletToGetForeground"}, + {160, nullptr, "GetIndirectLayerConsumerHandle"}, + }; + RegisterHandlers(functions); + + state_changed_event = Kernel::Event::Create(Kernel::ResetType::OneShot, + "ILibraryAppletAccessor:StateChangedEvent"); + } + +private: + void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { + state_changed_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(state_changed_event); + + LOG_WARNING(Service_AM, "(STUBBED) called"); + } + + Kernel::SharedPtr<Kernel::Event> state_changed_event; +}; + +ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { + static const FunctionInfo functions[] = { + {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, + {1, nullptr, "TerminateAllLibraryApplets"}, + {2, nullptr, "AreAnyLibraryAppletsLeft"}, + {10, nullptr, "CreateStorage"}, + {11, nullptr, "CreateTransferMemoryStorage"}, + {12, nullptr, "CreateHandleStorage"}, + }; + RegisterHandlers(functions); +} + +void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::ILibraryAppletAccessor>(); + + LOG_DEBUG(Service_AM, "called"); +} + +class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +public: + explicit IStorageAccessor(std::vector<u8> buffer) + : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + static const FunctionInfo functions[] = { + {0, &IStorageAccessor::GetSize, "GetSize"}, + {11, &IStorageAccessor::Read, "Read"}, + }; + RegisterHandlers(functions); + } + +private: + std::vector<u8> buffer; + + void GetSize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(buffer.size())); + + LOG_DEBUG(Service_AM, "called"); + } + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + u64 offset = rp.Pop<u64>(); + + const size_t size{ctx.GetWriteBufferSize()}; + + ASSERT(offset + size <= buffer.size()); + + ctx.WriteBuffer(buffer.data() + offset, size); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_AM, "called"); + } +}; + +class IStorage final : public ServiceFramework<IStorage> { +public: + explicit IStorage(std::vector<u8> buffer) + : ServiceFramework("IStorage"), buffer(std::move(buffer)) { + static const FunctionInfo functions[] = { + {0, &IStorage::Open, "Open"}, + }; + RegisterHandlers(functions); + } + +private: + std::vector<u8> buffer; + + void Open(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + + LOG_DEBUG(Service_AM, "called"); + } +}; + +IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { + static const FunctionInfo functions[] = { + {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, + {20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"}, + {21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"}, + {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"}, + {66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"}, + {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"}, + {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, + }; + RegisterHandlers(functions); +} + +void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { + constexpr u8 data[0x88] = { + 0xca, 0x97, 0x94, 0xc7, // Magic + 1, 0, 0, 0, // IsAccountSelected (bool) + 1, 0, 0, 0, // User Id (word 0) + 0, 0, 0, 0, // User Id (word 1) + 0, 0, 0, 0, // User Id (word 2) + 0, 0, 0, 0 // User Id (word 3) + }; + + std::vector<u8> buffer(data, data + sizeof(data)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorage>(buffer); + + LOG_DEBUG(Service_AM, "called"); +} + +void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u128 uid = rp.PopRaw<u128>(); + + LOG_WARNING(Service, "(STUBBED) called uid = %016" PRIX64 "%016" PRIX64, uid[1], uid[0]); + + IPC::ResponseBuilder rb{ctx, 4}; + + FileSys::Path unused; + auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused); + if (savedata.Failed()) { + // Create the save data and return an error indicating that the operation was performed. + FileSystem::FormatFileSystem(FileSystem::Type::SaveData); + // TODO(Subv): Find out the correct error code for this. + rb.Push(ResultCode(ErrorModule::FS, 40)); + } else { + rb.Push(RESULT_SUCCESS); + } + + rb.Push<u64>(0); +} + +void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { + // Takes an input u32 Result, no output. + // For example, in some cases official apps use this with error 0x2A2 then uses svcBreak. + + IPC::RequestParser rp{ctx}; + u32 result = rp.Pop<u32>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called, result=0x%08X", result); +} + +void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(SystemLanguage::English); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u8>(0); // Unknown, seems to be ignored by official processes + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void InstallInterfaces(SM::ServiceManager& service_manager, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger) { + std::make_shared<AppletAE>(nvflinger)->InstallAsService(service_manager); + std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager); } } // namespace AM diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 63d86cd41..27dbd8c95 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -4,13 +4,126 @@ #pragma once +#include <memory> #include "core/hle/service/service.h" +namespace Kernel { +class Event; +} + namespace Service { +namespace NVFlinger { +class NVFlinger; +} + namespace AM { +// TODO: Add more languages +enum SystemLanguage { + Japanese = 0, + English = 1, +}; + +class IWindowController final : public ServiceFramework<IWindowController> { +public: + IWindowController(); + +private: + void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); + void AcquireForegroundRights(Kernel::HLERequestContext& ctx); +}; + +class IAudioController final : public ServiceFramework<IAudioController> { +public: + IAudioController(); + +private: + void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx); + void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); + void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); + + u32 volume{100}; +}; + +class IDisplayController final : public ServiceFramework<IDisplayController> { +public: + IDisplayController(); +}; + +class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { +public: + IDebugFunctions(); +}; + +class ISelfController final : public ServiceFramework<ISelfController> { +public: + ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); + +private: + void SetFocusHandlingMode(Kernel::HLERequestContext& ctx); + void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx); + void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx); + void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx); + void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx); + void LockExit(Kernel::HLERequestContext& ctx); + void UnlockExit(Kernel::HLERequestContext& ctx); + void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx); + void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); + void SetScreenShotPermission(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; + Kernel::SharedPtr<Kernel::Event> launchable_event; +}; + +class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { +public: + ICommonStateGetter(); + +private: + enum class FocusState : u8 { + InFocus = 1, + NotInFocus = 2, + }; + + enum class OperationMode : u8 { + Handheld = 0, + Docked = 1, + }; + + void GetEventHandle(Kernel::HLERequestContext& ctx); + void ReceiveMessage(Kernel::HLERequestContext& ctx); + void GetCurrentFocusState(Kernel::HLERequestContext& ctx); + void GetOperationMode(Kernel::HLERequestContext& ctx); + void GetPerformanceMode(Kernel::HLERequestContext& ctx); + + Kernel::SharedPtr<Kernel::Event> event; +}; + +class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { +public: + ILibraryAppletCreator(); + +private: + void CreateLibraryApplet(Kernel::HLERequestContext& ctx); +}; + +class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { +public: + IApplicationFunctions(); + +private: + void PopLaunchParameter(Kernel::HLERequestContext& ctx); + void EnsureSaveData(Kernel::HLERequestContext& ctx); + void SetTerminateResult(Kernel::HLERequestContext& ctx); + void GetDesiredLanguage(Kernel::HLERequestContext& ctx); + void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx); + void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx); + void NotifyRunning(Kernel::HLERequestContext& ctx); +}; + /// Registers all AM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger); } // namespace AM } // namespace Service diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp new file mode 100644 index 000000000..0e51caa70 --- /dev/null +++ b/src/core/hle/service/am/applet_ae.cpp @@ -0,0 +1,112 @@ +// Copyright 2018 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/am/am.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/nvflinger/nvflinger.h" + +namespace Service { +namespace AM { + +class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> { +public: + ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)) { + static const FunctionInfo functions[] = { + {0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, + {1, &ILibraryAppletProxy::GetSelfController, "GetSelfController"}, + {2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"}, + {3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"}, + {4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"}, + {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, + {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"}, + {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, + }; + RegisterHandlers(functions); + } + +private: + void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ICommonStateGetter>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetSelfController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISelfController>(nvflinger); + LOG_DEBUG(Service_AM, "called"); + } + + void GetWindowController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IWindowController>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetAudioController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IAudioController>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetDisplayController(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDisplayController>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetDebugFunctions(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDebugFunctions>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ILibraryAppletCreator>(); + LOG_DEBUG(Service_AM, "called"); + } + + void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IApplicationFunctions>(); + LOG_DEBUG(Service_AM, "called"); + } + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; +}; + +void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger); + LOG_DEBUG(Service_AM, "called"); +} + +AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) { + static const FunctionInfo functions[] = { + {100, nullptr, "OpenSystemAppletProxy"}, + {200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"}, + {201, nullptr, "OpenLibraryAppletProxy"}, + {300, nullptr, "OpenOverlayAppletProxy"}, + {350, nullptr, "OpenSystemApplicationProxy"}, + {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"}, + }; + RegisterHandlers(functions); +} + +} // namespace AM +} // namespace Service diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h new file mode 100644 index 000000000..38fc428fb --- /dev/null +++ b/src/core/hle/service/am/applet_ae.h @@ -0,0 +1,30 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NVFlinger { +class NVFlinger; +} + +namespace AM { + +class AppletAE final : public ServiceFramework<AppletAE> { +public: + AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); + ~AppletAE() = default; + +private: + void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; +}; + +} // namespace AM +} // namespace Service diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index b360e7e5f..bdcebe689 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -4,260 +4,17 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_oe.h" -#include "core/hle/service/apm/apm.h" +#include "core/hle/service/nvflinger/nvflinger.h" namespace Service { namespace AM { -class IWindowController final : public ServiceFramework<IWindowController> { -public: - IWindowController() : ServiceFramework("IWindowController") { - static const FunctionInfo functions[] = { - {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"}, - {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"}, - }; - RegisterHandlers(functions); - } - -private: - void GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0); - } - - void AcquireForegroundRights(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } -}; - -class IAudioController final : public ServiceFramework<IAudioController> { -public: - IAudioController() : ServiceFramework("IAudioController") {} -}; - -class IDisplayController final : public ServiceFramework<IDisplayController> { -public: - IDisplayController() : ServiceFramework("IDisplayController") {} -}; - -class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { -public: - IDebugFunctions() : ServiceFramework("IDebugFunctions") {} -}; - -class ISelfController final : public ServiceFramework<ISelfController> { -public: - ISelfController() : ServiceFramework("ISelfController") { - static const FunctionInfo functions[] = { - {11, &ISelfController::SetOperationModeChangedNotification, - "SetOperationModeChangedNotification"}, - {12, &ISelfController::SetPerformanceModeChangedNotification, - "SetPerformanceModeChangedNotification"}, - {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"}, - {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"}, - {16, &ISelfController::SetOutOfFocusSuspendingEnabled, - "SetOutOfFocusSuspendingEnabled"}, - }; - RegisterHandlers(functions); - } - -private: - void SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { - // Takes 3 input u8s with each field located immediately after the previous u8, these are - // bool flags. No output. - - IPC::RequestParser rp{ctx}; - - struct FocusHandlingModeParams { - u8 unknown0; - u8 unknown1; - u8 unknown2; - }; - auto flags = rp.PopRaw<FocusHandlingModeParams>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - bool flag = rp.Pop<bool>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called flag=%u", static_cast<u32>(flag)); - } - - void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - bool flag = rp.Pop<bool>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called flag=%u", static_cast<u32>(flag)); - } - - void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) { - // Takes 3 input u8s with each field located immediately after the previous u8, these are - // bool flags. No output. - IPC::RequestParser rp{ctx}; - - bool enabled = rp.Pop<bool>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called enabled=%u", static_cast<u32>(enabled)); - } -}; - -class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { -public: - ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { - static const FunctionInfo functions[] = { - {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, - {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"}, - {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"}, - {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"}, - {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, - }; - RegisterHandlers(functions); - - event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event"); - } - -private: - enum class FocusState : u8 { - InFocus = 1, - NotInFocus = 2, - }; - - enum class OperationMode : u8 { - Handheld = 0, - Docked = 1, - }; - - void GetEventHandle(Kernel::HLERequestContext& ctx) { - event->Signal(); - - IPC::RequestBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(event); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void ReceiveMessage(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(15); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void GetCurrentFocusState(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u8>(FocusState::InFocus)); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void GetOperationMode(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u8>(OperationMode::Handheld)); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void GetPerformanceMode(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld)); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - Kernel::SharedPtr<Kernel::Event> event; -}; - -class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { -public: - IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { - static const FunctionInfo functions[] = { - {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"}, - {66, &IApplicationFunctions::InitializeGamePlayRecording, - "InitializeGamePlayRecording"}, - {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"}, - {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, - }; - RegisterHandlers(functions); - } - -private: - void SetTerminateResult(Kernel::HLERequestContext& ctx) { - // Takes an input u32 Result, no output. - // For example, in some cases official apps use this with error 0x2A2 then uses svcBreak. - - IPC::RequestParser rp{ctx}; - u32 result = rp.Pop<u32>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called, result=0x%08X", result); - } - - void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service, "(STUBBED) called"); - } - - void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called"); - } - - void NotifyRunning(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u8>(0); // Unknown, seems to be ignored by official processes - - LOG_WARNING(Service, "(STUBBED) called"); - } -}; - -class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { -public: - ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {} -}; - class IApplicationProxy final : public ServiceFramework<IApplicationProxy> { public: - IApplicationProxy() : ServiceFramework("IApplicationProxy") { + IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)) { static const FunctionInfo functions[] = { {0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"}, {1, &IApplicationProxy::GetSelfController, "GetSelfController"}, @@ -273,70 +30,73 @@ public: private: void GetAudioController(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(); - LOG_DEBUG(Service, "called"); + rb.PushIpcInterface<ISelfController>(nvflinger); + LOG_DEBUG(Service_AM, "called"); } void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationFunctions>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_AM, "called"); } + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationProxy>(); - LOG_DEBUG(Service, "called"); + rb.PushIpcInterface<IApplicationProxy>(nvflinger); + LOG_DEBUG(Service_AM, "called"); } -AppletOE::AppletOE() : ServiceFramework("appletOE") { +AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)) { static const FunctionInfo functions[] = { {0x00000000, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, }; diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index beb75bf2a..d2ab44c67 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -4,19 +4,26 @@ #pragma once +#include <memory> #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" namespace Service { +namespace NVFlinger { +class NVFlinger; +} + namespace AM { class AppletOE final : public ServiceFramework<AppletOE> { public: - AppletOE(); + AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); ~AppletOE() = default; private: void OpenApplicationProxy(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nvflinger; }; } // namespace AM diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 260683201..8b55d2fcb 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -2,16 +2,44 @@ // 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/aoc/aoc_u.h" namespace Service { namespace AOC { +AOC_U::AOC_U() : ServiceFramework("aoc:u") { + static const FunctionInfo functions[] = { + {0, nullptr, "CountAddOnContentByApplicationId"}, + {1, nullptr, "ListAddOnContentByApplicationId"}, + {2, &AOC_U::CountAddOnContent, "CountAddOnContent"}, + {3, &AOC_U::ListAddOnContent, "ListAddOnContent"}, + {4, nullptr, "GetAddOnContentBaseIdByApplicationId"}, + {5, nullptr, "GetAddOnContentBaseId"}, + {6, nullptr, "PrepareAddOnContentByApplicationId"}, + {7, nullptr, "PrepareAddOnContent"}, + }; + RegisterHandlers(functions); +} + +void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); + LOG_WARNING(Service_AOC, "(STUBBED) called"); +} + +void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); + LOG_WARNING(Service_AOC, "(STUBBED) called"); +} + void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<AOC_U>()->InstallAsService(service_manager); } -AOC_U::AOC_U() : ServiceFramework("aoc:u") {} - } // namespace AOC } // namespace Service diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 0cbbf1e5d..6e0ba15a5 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -13,6 +13,10 @@ class AOC_U final : public ServiceFramework<AOC_U> { public: AOC_U(); ~AOC_U() = default; + +private: + void CountAddOnContent(Kernel::HLERequestContext& ctx); + void ListAddOnContent(Kernel::HLERequestContext& ctx); }; /// Registers all AOC services with the specified service manager. diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index 66d94ff52..c4b09b435 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -5,61 +5,15 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" +#include "core/hle/service/apm/interface.h" namespace Service { namespace APM { void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<APM>()->InstallAsService(service_manager); -} - -class ISession final : public ServiceFramework<ISession> { -public: - ISession() : ServiceFramework("ISession") { - static const FunctionInfo functions[] = { - {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, - {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, - }; - RegisterHandlers(functions); - } - -private: - void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); - u32 config = rp.Pop<u32>(); - - IPC::RequestBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode), config); - } - - void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - - auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); - - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); // Performance configuration - - LOG_WARNING(Service, "(STUBBED) called mode=%u", static_cast<u32>(mode)); - } -}; - -APM::APM() : ServiceFramework("apm") { - static const FunctionInfo functions[] = { - {0x00000000, &APM::OpenSession, "OpenSession"}, {0x00000001, nullptr, "GetPerformanceMode"}, - }; - RegisterHandlers(functions); -} - -void APM::OpenSession(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISession>(); + auto module_ = std::make_shared<Module>(); + std::make_shared<APM>(module_, "apm")->InstallAsService(service_manager); + std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager); } } // namespace APM diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index 90a1afbbc..070ab21f8 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -14,13 +14,10 @@ enum class PerformanceMode : u8 { Docked = 1, }; -class APM final : public ServiceFramework<APM> { +class Module final { public: - APM(); - ~APM() = default; - -private: - void OpenSession(Kernel::HLERequestContext& ctx); + Module() = default; + ~Module() = default; }; /// Registers all AM services with the specified service manager. diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp new file mode 100644 index 000000000..0179351ba --- /dev/null +++ b/src/core/hle/service/apm/interface.cpp @@ -0,0 +1,66 @@ +// Copyright 2018 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/apm/apm.h" +#include "core/hle/service/apm/interface.h" + +namespace Service { +namespace APM { + +class ISession final : public ServiceFramework<ISession> { +public: + ISession() : ServiceFramework("ISession") { + static const FunctionInfo functions[] = { + {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, + {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, + }; + RegisterHandlers(functions); + } + +private: + void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); + u32 config = rp.Pop<u32>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_APM, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode), + config); + } + + void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // Performance configuration + + LOG_WARNING(Service_APM, "(STUBBED) called mode=%u", static_cast<u32>(mode)); + } +}; + +APM::APM(std::shared_ptr<Module> apm, const char* name) + : ServiceFramework(name), apm(std::move(apm)) { + static const FunctionInfo functions[] = { + {0, &APM::OpenSession, "OpenSession"}, + {1, nullptr, "GetPerformanceMode"}, + }; + RegisterHandlers(functions); +} + +void APM::OpenSession(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISession>(); +} + +} // namespace APM +} // namespace Service diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h new file mode 100644 index 000000000..7d53721de --- /dev/null +++ b/src/core/hle/service/apm/interface.h @@ -0,0 +1,27 @@ +// Copyright 2018 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 { +namespace APM { + +class APM final : public ServiceFramework<APM> { +public: + APM(std::shared_ptr<Module> apm, const char* name); + ~APM() = default; + +private: + void OpenSession(Kernel::HLERequestContext& ctx); + + std::shared_ptr<Module> apm; +}; + +/// Registers all AM services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace APM +} // namespace Service diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp new file mode 100644 index 000000000..ee749fddd --- /dev/null +++ b/src/core/hle/service/audio/audin_u.cpp @@ -0,0 +1,41 @@ +// Copyright 2018 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/kernel/hle_ipc.h" +#include "core/hle/service/audio/audin_u.h" + +namespace Service { +namespace Audio { + +class IAudioIn final : public ServiceFramework<IAudioIn> { +public: + IAudioIn() : ServiceFramework("IAudioIn") { + static const FunctionInfo functions[] = { + {0x0, nullptr, "GetAudioInState"}, + {0x1, nullptr, "StartAudioIn"}, + {0x2, nullptr, "StopAudioIn"}, + {0x3, nullptr, "AppendAudioInBuffer_1"}, + {0x4, nullptr, "RegisterBufferEvent"}, + {0x5, nullptr, "GetReleasedAudioInBuffer_1"}, + {0x6, nullptr, "ContainsAudioInBuffer"}, + {0x7, nullptr, "AppendAudioInBuffer_2"}, + {0x8, nullptr, "GetReleasedAudioInBuffer_2"}, + }; + RegisterHandlers(functions); + } + ~IAudioIn() = default; +}; + +AudInU::AudInU() : ServiceFramework("audin:u") { + static const FunctionInfo functions[] = { + {0x00000000, nullptr, "ListAudioIns"}, + {0x00000001, nullptr, "OpenAudioIn"}, + }; + RegisterHandlers(functions); +} + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h new file mode 100644 index 000000000..2b8576756 --- /dev/null +++ b/src/core/hle/service/audio/audin_u.h @@ -0,0 +1,23 @@ +// Copyright 2018 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 Kernel { +class HLERequestContext; +} + +namespace Service { +namespace Audio { + +class AudInU final : public ServiceFramework<AudInU> { +public: + explicit AudInU(); + ~AudInU() = default; +}; + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 2b4c6c5d0..3f7fb44eb 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -2,14 +2,22 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/audio/audin_u.h" #include "core/hle/service/audio/audio.h" #include "core/hle/service/audio/audout_u.h" +#include "core/hle/service/audio/audrec_u.h" +#include "core/hle/service/audio/audren_u.h" +#include "core/hle/service/audio/codecctl.h" namespace Service { namespace Audio { void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<AudOutU>()->InstallAsService(service_manager); + std::make_shared<AudInU>()->InstallAsService(service_manager); + std::make_shared<AudRecU>()->InstallAsService(service_manager); + std::make_shared<AudRenU>()->InstallAsService(service_manager); + std::make_shared<CodecCtl>()->InstallAsService(service_manager); } } // namespace Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index c028262c6..e873d768f 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -2,23 +2,195 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <vector> #include "common/logging/log.h" +#include "core/core_timing.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audout_u.h" namespace Service { namespace Audio { +/// Switch sample rate frequency +constexpr u32 sample_rate{48000}; +/// TODO(st4rk): dynamic number of channels, as I think Switch has support +/// to more audio channels (probably when Docked I guess) +constexpr u32 audio_channels{2}; +/// TODO(st4rk): find a proper value for the audio_ticks +constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 500)}; + +class IAudioOut final : public ServiceFramework<IAudioOut> { +public: + IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) { + static const FunctionInfo functions[] = { + {0x0, nullptr, "GetAudioOutState"}, + {0x1, &IAudioOut::StartAudioOut, "StartAudioOut"}, + {0x2, &IAudioOut::StopAudioOut, "StopAudioOut"}, + {0x3, &IAudioOut::AppendAudioOutBuffer_1, "AppendAudioOutBuffer_1"}, + {0x4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, + {0x5, &IAudioOut::GetReleasedAudioOutBuffer_1, "GetReleasedAudioOutBuffer_1"}, + {0x6, nullptr, "ContainsAudioOutBuffer"}, + {0x7, nullptr, "AppendAudioOutBuffer_2"}, + {0x8, nullptr, "GetReleasedAudioOutBuffer_2"}, + }; + RegisterHandlers(functions); + + // This is the event handle used to check if the audio buffer was released + buffer_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent"); + + // Register event callback to update the Audio Buffer + audio_event = CoreTiming::RegisterEvent( + "IAudioOut::UpdateAudioBuffersCallback", [this](u64 userdata, int cycles_late) { + UpdateAudioBuffersCallback(); + CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event); + }); + + // Start the audio event + CoreTiming::ScheduleEvent(audio_ticks, audio_event); + } + + ~IAudioOut() { + CoreTiming::UnscheduleEvent(audio_event, 0); + } + +private: + void StartAudioOut(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + // Start audio + audio_out_state = AudioState::Started; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void StopAudioOut(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + // Stop audio + audio_out_state = AudioState::Stopped; + + queue_keys.clear(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(buffer_event); + } + + void AppendAudioOutBuffer_1(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const u64 key{rp.Pop<u64>()}; + queue_keys.insert(queue_keys.begin(), key); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetReleasedAudioOutBuffer_1(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + // TODO(st4rk): This is how libtransistor currently implements the + // GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address + // is used to know which buffer should be filled with data and send again to the service + // through AppendAudioOutBuffer. Check if this is the proper way to do it. + u64 key{0}; + + if (queue_keys.size()) { + key = queue_keys.back(); + queue_keys.pop_back(); + } + + ctx.WriteBuffer(&key, sizeof(u64)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + // TODO(st4rk): This might be the total of released buffers, needs to be verified on + // hardware + rb.Push<u32>(static_cast<u32>(queue_keys.size())); + } + + void UpdateAudioBuffersCallback() { + if (audio_out_state != AudioState::Started) { + return; + } + + if (queue_keys.empty()) { + return; + } + + buffer_event->Signal(); + } + + enum class AudioState : u32 { + Started, + Stopped, + }; + + /// This is used to trigger the audio event callback that is going to read the samples from the + /// audio_buffer list and enqueue the samples using the sink (audio_core). + CoreTiming::EventType* audio_event; + + /// This is the evend handle used to check if the audio buffer was released + Kernel::SharedPtr<Kernel::Event> buffer_event; + + /// (st4rk): This is just a temporary workaround for the future implementation. Libtransistor + /// uses the key as an address in the App, so we need to return when the + /// GetReleasedAudioOutBuffer_1 is called, otherwise we'll run in problems, because + /// libtransistor uses the key returned as an pointer. + std::vector<u64> queue_keys; + + AudioState audio_out_state; +}; + void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2}; + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const std::string audio_interface = "AudioInterface"; + ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size()); + + IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + + rb.Push(RESULT_SUCCESS); + // TODO(st4rk): We're currently returning only one audio interface (stringlist size). However, + // it's highly possible to have more than one interface (despite that libtransistor requires + // only one). + rb.Push<u32>(1); +} + +void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + if (!audio_out_interface) { + audio_out_interface = std::make_shared<IAudioOut>(); + } + + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); + rb.Push<u32>(sample_rate); + rb.Push<u32>(audio_channels); + rb.Push<u32>(static_cast<u32>(PcmFormat::Int16)); + rb.Push<u32>(0); // This field is unknown + rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface); } AudOutU::AudOutU() : ServiceFramework("audout:u") { - static const FunctionInfo functions[] = { - {0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"}, - }; + static const FunctionInfo functions[] = {{0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"}, + {0x00000001, &AudOutU::OpenAudioOut, "OpenAudioOut"}, + {0x00000002, nullptr, "Unknown2"}, + {0x00000003, nullptr, "Unknown3"}}; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index 42680af94..7fbce2225 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -4,19 +4,37 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +namespace Kernel { +class HLERequestContext; +} + namespace Service { namespace Audio { +class IAudioOut; + class AudOutU final : public ServiceFramework<AudOutU> { public: AudOutU(); ~AudOutU() = default; private: + std::shared_ptr<IAudioOut> audio_out_interface; + void ListAudioOuts(Kernel::HLERequestContext& ctx); + void OpenAudioOut(Kernel::HLERequestContext& ctx); + + enum class PcmFormat : u32 { + Invalid = 0, + Int8 = 1, + Int16 = 2, + Int24 = 3, + Int32 = 4, + PcmFloat = 5, + Adpcm = 6, + }; }; } // namespace Audio diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp new file mode 100644 index 000000000..f2626ec70 --- /dev/null +++ b/src/core/hle/service/audio/audrec_u.cpp @@ -0,0 +1,38 @@ +// Copyright 2018 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/kernel/hle_ipc.h" +#include "core/hle/service/audio/audrec_u.h" + +namespace Service { +namespace Audio { + +class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> { +public: + IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") { + static const FunctionInfo functions[] = { + {0x0, nullptr, "GetFinalOutputRecorderState"}, + {0x1, nullptr, "StartFinalOutputRecorder"}, + {0x2, nullptr, "StopFinalOutputRecorder"}, + {0x3, nullptr, "AppendFinalOutputRecorderBuffer"}, + {0x4, nullptr, "RegisterBufferEvent"}, + {0x5, nullptr, "GetReleasedFinalOutputRecorderBuffer"}, + {0x6, nullptr, "ContainsFinalOutputRecorderBuffer"}, + }; + RegisterHandlers(functions); + } + ~IFinalOutputRecorder() = default; +}; + +AudRecU::AudRecU() : ServiceFramework("audrec:u") { + static const FunctionInfo functions[] = { + {0x00000000, nullptr, "OpenFinalOutputRecorder"}, + }; + RegisterHandlers(functions); +} + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h new file mode 100644 index 000000000..c31e412c1 --- /dev/null +++ b/src/core/hle/service/audio/audrec_u.h @@ -0,0 +1,23 @@ +// Copyright 2018 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 Kernel { +class HLERequestContext; +} + +namespace Service { +namespace Audio { + +class AudRecU final : public ServiceFramework<AudRecU> { +public: + explicit AudRecU(); + ~AudRecU() = default; +}; + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp new file mode 100644 index 000000000..6d0461bbc --- /dev/null +++ b/src/core/hle/service/audio/audren_u.cpp @@ -0,0 +1,260 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/core_timing.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/audio/audren_u.h" + +namespace Service { +namespace Audio { + +/// TODO(bunnei): Find a proper value for the audio_ticks +constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 200)}; + +class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { +public: + IAudioRenderer() : ServiceFramework("IAudioRenderer") { + static const FunctionInfo functions[] = { + {0x0, nullptr, "GetAudioRendererSampleRate"}, + {0x1, nullptr, "GetAudioRendererSampleCount"}, + {0x2, nullptr, "GetAudioRendererMixBufferCount"}, + {0x3, nullptr, "GetAudioRendererState"}, + {0x4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, + {0x5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, + {0x6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, + {0x7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, + {0x8, nullptr, "SetAudioRendererRenderingTimeLimit"}, + {0x9, nullptr, "GetAudioRendererRenderingTimeLimit"}, + }; + RegisterHandlers(functions); + + system_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioRenderer:SystemEvent"); + + // Register event callback to update the Audio Buffer + audio_event = CoreTiming::RegisterEvent( + "IAudioRenderer::UpdateAudioCallback", [this](u64 userdata, int cycles_late) { + UpdateAudioCallback(); + CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event); + }); + + // Start the audio event + CoreTiming::ScheduleEvent(audio_ticks, audio_event); + } + ~IAudioRenderer() { + CoreTiming::UnscheduleEvent(audio_event, 0); + } + +private: + void UpdateAudioCallback() { + system_event->Signal(); + } + + void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "%s", ctx.Description().c_str()); + AudioRendererResponseData response_data{}; + + response_data.section_0_size = + static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry)); + response_data.section_1_size = static_cast<u32>(response_data.section_1.size()); + response_data.section_2_size = static_cast<u32>(response_data.section_2.size()); + response_data.section_3_size = static_cast<u32>(response_data.section_3.size()); + response_data.section_4_size = static_cast<u32>(response_data.section_4.size()); + response_data.section_5_size = static_cast<u32>(response_data.section_5.size()); + response_data.total_size = sizeof(AudioRendererResponseData); + + for (unsigned i = 0; i < response_data.state_entries.size(); i++) { + // 4 = Busy and 5 = Ready? + response_data.state_entries[i].state = 5; + } + + ctx.WriteBuffer(&response_data, response_data.total_size); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_Audio, "(STUBBED) called"); + } + + void StartAudioRenderer(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_Audio, "(STUBBED) called"); + } + + void StopAudioRenderer(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_Audio, "(STUBBED) called"); + } + + void QuerySystemEvent(Kernel::HLERequestContext& ctx) { + // system_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(system_event); + + LOG_WARNING(Service_Audio, "(STUBBED) called"); + } + + struct AudioRendererStateEntry { + u32_le state; + u32_le unknown_4; + u32_le unknown_8; + u32_le unknown_c; + }; + static_assert(sizeof(AudioRendererStateEntry) == 0x10, + "AudioRendererStateEntry has wrong size"); + + struct AudioRendererResponseData { + u32_le unknown_0; + u32_le section_5_size; + u32_le section_0_size; + u32_le section_1_size; + u32_le unknown_10; + u32_le section_2_size; + u32_le unknown_18; + u32_le section_3_size; + u32_le section_4_size; + u32_le unknown_24; + u32_le unknown_28; + u32_le unknown_2c; + u32_le unknown_30; + u32_le unknown_34; + u32_le unknown_38; + u32_le total_size; + + std::array<AudioRendererStateEntry, 0x18e> state_entries; + + std::array<u8, 0x600> section_1; + std::array<u8, 0xe0> section_2; + std::array<u8, 0x20> section_3; + std::array<u8, 0x10> section_4; + std::array<u8, 0xb0> section_5; + }; + static_assert(sizeof(AudioRendererResponseData) == 0x20e0, + "AudioRendererResponseData has wrong size"); + + /// This is used to trigger the audio event callback. + CoreTiming::EventType* audio_event; + + Kernel::SharedPtr<Kernel::Event> system_event; +}; + +class IAudioDevice final : public ServiceFramework<IAudioDevice> { +public: + IAudioDevice() : ServiceFramework("IAudioDevice") { + static const FunctionInfo functions[] = { + {0x0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, + {0x1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, + {0x2, nullptr, "GetAudioDeviceOutputVolume"}, + {0x3, nullptr, "GetActiveAudioDeviceName"}, + {0x4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, + {0x5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, + {0x6, nullptr, "ListAudioDeviceNameAuto"}, + {0x7, nullptr, "SetAudioDeviceOutputVolumeAuto"}, + {0x8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, + {0x10, nullptr, "GetActiveAudioDeviceNameAuto"}, + {0x11, nullptr, "QueryAudioDeviceInputEvent"}, + {0x12, nullptr, "QueryAudioDeviceOutputEvent"}}; + RegisterHandlers(functions); + + buffer_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent"); + } + +private: + void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + + const std::string audio_interface = "AudioInterface"; + ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size()); + + IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::RequestParser rp{ctx}; + f32 volume = static_cast<f32>(rp.Pop<u32>()); + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } + + void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + buffer_event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(buffer_event); + } + + void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + } + + Kernel::SharedPtr<Kernel::Event> buffer_event; + +}; // namespace Audio + +AudRenU::AudRenU() : ServiceFramework("audren:u") { + static const FunctionInfo functions[] = { + {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, + {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"}, + {2, &AudRenU::GetAudioDevice, "GetAudioDevice"}, + }; + RegisterHandlers(functions); +} + +void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<Audio::IAudioRenderer>(); + + LOG_DEBUG(Service_Audio, "called"); +} + +void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0x400); + + LOG_WARNING(Service_Audio, "(STUBBED) called"); +} + +void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<Audio::IAudioDevice>(); + + LOG_DEBUG(Service_Audio, "called"); +} + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h new file mode 100644 index 000000000..f59d1627d --- /dev/null +++ b/src/core/hle/service/audio/audren_u.h @@ -0,0 +1,28 @@ +// Copyright 2018 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 Kernel { +class HLERequestContext; +} + +namespace Service { +namespace Audio { + +class AudRenU final : public ServiceFramework<AudRenU> { +public: + explicit AudRenU(); + ~AudRenU() = default; + +private: + void OpenAudioRenderer(Kernel::HLERequestContext& ctx); + void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); + void GetAudioDevice(Kernel::HLERequestContext& ctx); +}; + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp new file mode 100644 index 000000000..d2a7f4cd0 --- /dev/null +++ b/src/core/hle/service/audio/codecctl.cpp @@ -0,0 +1,33 @@ +// Copyright 2018 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/kernel/hle_ipc.h" +#include "core/hle/service/audio/codecctl.h" + +namespace Service { +namespace Audio { + +CodecCtl::CodecCtl() : ServiceFramework("codecctl") { + static const FunctionInfo functions[] = { + {0x00000000, nullptr, "InitializeCodecController"}, + {0x00000001, nullptr, "FinalizeCodecController"}, + {0x00000002, nullptr, "SleepCodecController"}, + {0x00000003, nullptr, "WakeCodecController"}, + {0x00000004, nullptr, "SetCodecVolume"}, + {0x00000005, nullptr, "GetCodecVolumeMax"}, + {0x00000006, nullptr, "GetCodecVolumeMin"}, + {0x00000007, nullptr, "SetCodecActiveTarget"}, + {0x00000008, nullptr, "Unknown"}, + {0x00000009, nullptr, "BindCodecHeadphoneMicJackInterrupt"}, + {0x0000000A, nullptr, "IsCodecHeadphoneMicJackInserted"}, + {0x0000000B, nullptr, "ClearCodecHeadphoneMicJackInterrupt"}, + {0x0000000C, nullptr, "IsCodecDeviceRequested"}, + }; + RegisterHandlers(functions); +} + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h new file mode 100644 index 000000000..1121ab0b1 --- /dev/null +++ b/src/core/hle/service/audio/codecctl.h @@ -0,0 +1,23 @@ +// Copyright 2018 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 Kernel { +class HLERequestContext; +} + +namespace Service { +namespace Audio { + +class CodecCtl final : public ServiceFramework<CodecCtl> { +public: + explicit CodecCtl(); + ~CodecCtl() = default; +}; + +} // namespace Audio +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp new file mode 100644 index 000000000..1a18e0051 --- /dev/null +++ b/src/core/hle/service/fatal/fatal.cpp @@ -0,0 +1,38 @@ +// Copyright 2018 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/fatal/fatal.h" +#include "core/hle/service/fatal/fatal_p.h" +#include "core/hle/service/fatal/fatal_u.h" + +namespace Service { +namespace Fatal { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::FatalSimple(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + u32 error_code = rp.Pop<u32>(); + LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x%X", error_code); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::TransitionToFatalError(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Fatal, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<Fatal_P>(module)->InstallAsService(service_manager); + std::make_shared<Fatal_U>(module)->InstallAsService(service_manager); +} + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h new file mode 100644 index 000000000..85272b4be --- /dev/null +++ b/src/core/hle/service/fatal/fatal.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 { +namespace Fatal { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void FatalSimple(Kernel::HLERequestContext& ctx); + void TransitionToFatalError(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp new file mode 100644 index 000000000..ba194e340 --- /dev/null +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -0,0 +1,14 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/fatal/fatal_p.h" + +namespace Service { +namespace Fatal { + +Fatal_P::Fatal_P(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "fatal:p") {} + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h new file mode 100644 index 000000000..d77b24bc4 --- /dev/null +++ b/src/core/hle/service/fatal/fatal_p.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/fatal/fatal.h" + +namespace Service { +namespace Fatal { + +class Fatal_P final : public Module::Interface { +public: + explicit Fatal_P(std::shared_ptr<Module> module); +}; + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp new file mode 100644 index 000000000..065cc868d --- /dev/null +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/fatal/fatal_u.h" + +namespace Service { +namespace Fatal { + +Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { + static const FunctionInfo functions[] = { + {1, &Fatal_U::FatalSimple, "FatalSimple"}, + {2, &Fatal_U::TransitionToFatalError, "TransitionToFatalError"}, + }; + RegisterHandlers(functions); +} + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h new file mode 100644 index 000000000..22374755e --- /dev/null +++ b/src/core/hle/service/fatal/fatal_u.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/fatal/fatal.h" + +namespace Service { +namespace Fatal { + +class Fatal_U final : public Module::Interface { +public: + explicit Fatal_U(std::shared_ptr<Module> module); +}; + +} // namespace Fatal +} // namespace Service diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp new file mode 100644 index 000000000..945832e98 --- /dev/null +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -0,0 +1,79 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <boost/container/flat_map.hpp> +#include "common/file_util.h" +#include "core/file_sys/filesystem.h" +#include "core/file_sys/savedata_factory.h" +#include "core/file_sys/sdmc_factory.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp_srv.h" + +namespace Service { +namespace FileSystem { + +/** + * Map of registered file systems, identified by type. Once an file system is registered here, it + * is never removed until UnregisterFileSystems is called. + */ +static boost::container::flat_map<Type, std::unique_ptr<FileSys::FileSystemFactory>> filesystem_map; + +ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type) { + auto result = filesystem_map.emplace(type, std::move(factory)); + + bool inserted = result.second; + ASSERT_MSG(inserted, "Tried to register more than one system with same id code"); + + auto& filesystem = result.first->second; + LOG_DEBUG(Service_FS, "Registered file system %s with id code 0x%08X", + filesystem->GetName().c_str(), static_cast<u32>(type)); + return RESULT_SUCCESS; +} + +ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, + FileSys::Path& path) { + LOG_TRACE(Service_FS, "Opening FileSystem with type=%d", type); + + auto itr = filesystem_map.find(type); + if (itr == filesystem_map.end()) { + // TODO(bunnei): Find a better error code for this + return ResultCode(-1); + } + + return itr->second->Open(path); +} + +ResultCode FormatFileSystem(Type type) { + LOG_TRACE(Service_FS, "Formatting FileSystem with type=%d", type); + + auto itr = filesystem_map.find(type); + if (itr == filesystem_map.end()) { + // TODO(bunnei): Find a better error code for this + return ResultCode(-1); + } + + FileSys::Path unused; + return itr->second->Format(unused); +} + +void RegisterFileSystems() { + filesystem_map.clear(); + + std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); + std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX); + + auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory)); + RegisterFileSystem(std::move(savedata), Type::SaveData); + + auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory)); + RegisterFileSystem(std::move(sdcard), Type::SDMC); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + RegisterFileSystems(); + std::make_shared<FSP_SRV>()->InstallAsService(service_manager); +} + +} // namespace FileSystem +} // namespace Service diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h new file mode 100644 index 000000000..56d26146e --- /dev/null +++ b/src/core/hle/service/filesystem/filesystem.h @@ -0,0 +1,59 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/common_types.h" +#include "core/hle/result.h" + +namespace FileSys { +class FileSystemBackend; +class FileSystemFactory; +class Path; +} // namespace FileSys + +namespace Service { + +namespace SM { +class ServiceManager; +} // namespace SM + +namespace FileSystem { + +/// Supported FileSystem types +enum class Type { + RomFS = 1, + SaveData = 2, + SDMC = 3, +}; + +/** + * Registers a FileSystem, instances of which can later be opened using its IdCode. + * @param factory FileSystem backend interface to use + * @param type Type used to access this type of FileSystem + */ +ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type); + +/** + * Opens a file system + * @param type Type of the file system to open + * @param path Path to the file system, used with Binary paths + * @return FileSys::FileSystemBackend interface to the file system + */ +ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, + FileSys::Path& path); + +/** + * Formats a file system + * @param type Type of the file system to format + * @return ResultCode of the operation + */ +ResultCode FormatFileSystem(Type type); + +/// Registers all Filesystem services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace FileSystem +} // namespace Service diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp new file mode 100644 index 000000000..89fa70ae6 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -0,0 +1,464 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/directory.h" +#include "core/file_sys/filesystem.h" +#include "core/file_sys/storage.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp_srv.h" + +namespace Service { +namespace FileSystem { + +class IStorage final : public ServiceFramework<IStorage> { +public: + IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend) + : ServiceFramework("IStorage"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"}, + {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::StorageBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s64 offset = rp.Pop<s64>(); + const s64 length = rp.Pop<s64>(); + + LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length); + + // Error checking + if (length < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); + return; + } + if (offset < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); + return; + } + + // Read the data from the Storage backend + std::vector<u8> output(length); + ResultVal<size_t> res = backend->Read(offset, length, output.data()); + if (res.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } +}; + +class IFile final : public ServiceFramework<IFile> { +public: + explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend) + : ServiceFramework("IFile"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, nullptr, "Flush"}, + {3, &IFile::SetSize, "SetSize"}, {4, &IFile::GetSize, "GetSize"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::StorageBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + const s64 offset = rp.Pop<s64>(); + const s64 length = rp.Pop<s64>(); + + LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length); + + // Error checking + if (length < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); + return; + } + if (offset < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); + return; + } + + // Read the data from the Storage backend + std::vector<u8> output(length); + ResultVal<size_t> res = backend->Read(offset, length, output.data()); + if (res.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(*res)); + } + + void Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + const s64 offset = rp.Pop<s64>(); + const s64 length = rp.Pop<s64>(); + + LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length); + + // Error checking + if (length < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); + return; + } + if (offset < 0) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); + return; + } + + // Write the data to the Storage backend + std::vector<u8> data = ctx.ReadBuffer(); + ResultVal<size_t> res = backend->Write(offset, length, true, data.data()); + if (res.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 size = rp.Pop<u64>(); + backend->SetSize(size); + LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(size); + } +}; + +class IDirectory final : public ServiceFramework<IDirectory> { +public: + explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend) + : ServiceFramework("IDirectory"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IDirectory::Read, "Read"}, + {1, &IDirectory::GetEntryCount, "GetEntryCount"}, + }; + RegisterHandlers(functions); + } + +private: + std::unique_ptr<FileSys::DirectoryBackend> backend; + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 unk = rp.Pop<u64>(); + + LOG_DEBUG(Service_FS, "called, unk=0x%llx", unk); + + // Calculate how many entries we can fit in the output buffer + u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); + + // Read the data from the Directory backend + std::vector<FileSys::Entry> entries(count_entries); + u64 read_entries = backend->Read(count_entries, entries.data()); + + // Convert the data into a byte array + std::vector<u8> output(entries.size() * sizeof(FileSys::Entry)); + std::memcpy(output.data(), entries.data(), output.size()); + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(read_entries); + } + + void GetEntryCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + u64 count = backend->GetEntryCount(); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(count); + } +}; + +class IFileSystem final : public ServiceFramework<IFileSystem> { +public: + explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend) + : ServiceFramework("IFileSystem"), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0, &IFileSystem::CreateFile, "CreateFile"}, + {2, &IFileSystem::CreateDirectory, "CreateDirectory"}, + {7, &IFileSystem::GetEntryType, "GetEntryType"}, + {8, &IFileSystem::OpenFile, "OpenFile"}, + {9, &IFileSystem::OpenDirectory, "OpenDirectory"}, + {10, &IFileSystem::Commit, "Commit"}, + }; + RegisterHandlers(functions); + } + + void CreateFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + u64 mode = rp.Pop<u64>(); + u32 size = rp.Pop<u32>(); + + LOG_DEBUG(Service_FS, "called file %s mode 0x%" PRIX64 " size 0x%08X", name.c_str(), mode, + size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->CreateFile(name, size)); + } + + void CreateDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + LOG_DEBUG(Service_FS, "called directory %s", name.c_str()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend->CreateDirectory(name)); + } + + void OpenFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>()); + + LOG_DEBUG(Service_FS, "called file %s mode %u", name.c_str(), static_cast<u32>(mode)); + + auto result = backend->OpenFile(name, mode); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + auto file = std::move(result.Unwrap()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFile>(std::move(file)); + } + + void OpenDirectory(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + // TODO(Subv): Implement this filter. + u32 filter_flags = rp.Pop<u32>(); + + LOG_DEBUG(Service_FS, "called directory %s filter %u", name.c_str(), filter_flags); + + auto result = backend->OpenDirectory(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + auto directory = std::move(result.Unwrap()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDirectory>(std::move(directory)); + } + + void GetEntryType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto file_buffer = ctx.ReadBuffer(); + auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); + + std::string name(file_buffer.begin(), end); + + LOG_DEBUG(Service_FS, "called file %s", name.c_str()); + + auto result = backend->GetEntryType(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(*result)); + } + + void Commit(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + +private: + std::unique_ptr<FileSys::FileSystemBackend> backend; +}; + +FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { + static const FunctionInfo functions[] = { + {1, &FSP_SRV::Initalize, "Initalize"}, + {18, &FSP_SRV::MountSdCard, "MountSdCard"}, + {22, &FSP_SRV::CreateSaveData, "CreateSaveData"}, + {51, &FSP_SRV::MountSaveData, "MountSaveData"}, + {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, + {202, nullptr, "OpenDataStorageByDataId"}, + {203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"}, + {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"}, + }; + RegisterHandlers(functions); +} + +void FSP_SRV::TryLoadRomFS() { + if (romfs) { + return; + } + FileSys::Path unused; + auto res = OpenFileSystem(Type::RomFS, unused); + if (res.Succeeded()) { + romfs = std::move(res.Unwrap()); + } +} + +void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + FileSys::Path unused; + auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); +} + +void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto save_struct = rp.PopRaw<std::array<u8, 0x40>>(); + auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); + u128 uid = rp.PopRaw<u128>(); + + LOG_WARNING(Service_FS, "(STUBBED) called uid = %016" PRIX64 "%016" PRIX64, uid[1], uid[0]); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + FileSys::Path unused; + auto filesystem = OpenFileSystem(Type::SaveData, unused).Unwrap(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); +} + +void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(5); +} + +void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + TryLoadRomFS(); + if (!romfs) { + // TODO (bunnei): Find the right error code to use here + LOG_CRITICAL(Service_FS, "no file system interface available!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + + // Attempt to open a StorageBackend interface to the RomFS + auto storage = romfs->OpenFile({}, {}); + if (storage.Failed()) { + LOG_CRITICAL(Service_FS, "no storage interface available!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(storage.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap())); +} + +void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess"); + OpenDataStorageByCurrentProcess(ctx); +} + +} // namespace FileSystem +} // namespace Service diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h new file mode 100644 index 000000000..e15ba4375 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -0,0 +1,37 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/hle/service/service.h" + +namespace FileSys { +class FileSystemBackend; +} + +namespace Service { +namespace FileSystem { + +class FSP_SRV final : public ServiceFramework<FSP_SRV> { +public: + explicit FSP_SRV(); + ~FSP_SRV() = default; + +private: + void TryLoadRomFS(); + + void Initalize(Kernel::HLERequestContext& ctx); + void MountSdCard(Kernel::HLERequestContext& ctx); + void CreateSaveData(Kernel::HLERequestContext& ctx); + void MountSaveData(Kernel::HLERequestContext& ctx); + void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); + void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void OpenRomStorage(Kernel::HLERequestContext& ctx); + + std::unique_ptr<FileSys::FileSystemBackend> romfs; +}; + +} // namespace FileSystem +} // namespace Service diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp new file mode 100644 index 000000000..26593bb0c --- /dev/null +++ b/src/core/hle/service/friend/friend.cpp @@ -0,0 +1,28 @@ +// Copyright 2018 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/friend/friend.h" +#include "core/hle/service/friend/friend_a.h" + +namespace Service { +namespace Friend { + +void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_Friend, "(STUBBED) called"); +} + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<Friend_A>(module)->InstallAsService(service_manager); +} + +} // namespace Friend +} // namespace Service diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h new file mode 100644 index 000000000..ffa498397 --- /dev/null +++ b/src/core/hle/service/friend/friend.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 { +namespace Friend { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void Unknown(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +/// Registers all Friend services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Friend +} // namespace Service diff --git a/src/core/hle/service/friend/friend_a.cpp b/src/core/hle/service/friend/friend_a.cpp new file mode 100644 index 000000000..e1f2397c2 --- /dev/null +++ b/src/core/hle/service/friend/friend_a.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/friend/friend_a.h" + +namespace Service { +namespace Friend { + +Friend_A::Friend_A(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "friend:a") { + static const FunctionInfo functions[] = { + {0, &Friend_A::Unknown, "Unknown"}, + }; + RegisterHandlers(functions); +} + +} // namespace Friend +} // namespace Service diff --git a/src/core/hle/service/friend/friend_a.h b/src/core/hle/service/friend/friend_a.h new file mode 100644 index 000000000..68fa58297 --- /dev/null +++ b/src/core/hle/service/friend/friend_a.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/friend/friend.h" + +namespace Service { +namespace Friend { + +class Friend_A final : public Module::Interface { +public: + explicit Friend_A(std::shared_ptr<Module> module); +}; + +} // namespace Friend +} // namespace Service diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 6254237fa..019a09444 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -9,6 +9,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/service.h" @@ -18,9 +19,9 @@ namespace HID { // Updating period for each HID device. // TODO(shinyquagsire23): These need better values. -constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 234; -constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 104; -constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 101; +constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 10000; +constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 10000; +constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 10000; class IAppletResource final : public ServiceFramework<IAppletResource> { public: @@ -44,12 +45,16 @@ public: CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } + ~IAppletResource() { + CoreTiming::UnscheduleEvent(pad_update_event, 0); + } + private: void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 1}; + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(shared_mem); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_HID, "called"); } void LoadInputDevices() { @@ -60,13 +65,14 @@ private: } void UpdatePadCallback(u64 userdata, int cycles_late) { - SharedMemory* mem = reinterpret_cast<SharedMemory*>(shared_mem->GetPointer()); + SharedMemory mem{}; + std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory)); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); // Set up controllers as neon red+blue Joy-Con attached to console - ControllerHeader& controller_header = mem->controllers[Controller_Handheld].header; + ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header; controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair; controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent; controller_header.right_color_body = JOYCON_BODY_NEON_RED; @@ -74,8 +80,8 @@ private: controller_header.left_color_body = JOYCON_BODY_NEON_BLUE; controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE; - for (int layoutIdx = 0; layoutIdx < HID_NUM_LAYOUTS; layoutIdx++) { - ControllerLayout& layout = mem->controllers[Controller_Handheld].layouts[layoutIdx]; + for (int index = 0; index < HID_NUM_LAYOUTS; index++) { + ControllerLayout& layout = mem.controllers[Controller_Handheld].layouts[index]; layout.header.num_entries = HID_NUM_ENTRIES; layout.header.max_entry_index = HID_NUM_ENTRIES - 1; @@ -131,10 +137,25 @@ private: // layouts) } - // TODO(shinyquagsire23): Update touch info + // TODO(bunnei): Properly implement the touch screen, the below will just write empty data + + TouchScreen& touchscreen = mem.touchscreen; + const u64 last_entry = touchscreen.header.latest_entry; + const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size(); + const u64 timestamp = CoreTiming::GetTicks(); + const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1; + touchscreen.header.timestamp_ticks = timestamp; + touchscreen.header.num_entries = touchscreen.entries.size(); + touchscreen.header.latest_entry = curr_entry; + touchscreen.header.max_entry_index = touchscreen.entries.size(); + touchscreen.header.timestamp = timestamp; + touchscreen.entries[curr_entry].header.timestamp = sample_counter; + touchscreen.entries[curr_entry].header.num_touches = 0; // TODO(shinyquagsire23): Signal events + std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory)); + // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } @@ -151,29 +172,203 @@ private: buttons; }; +class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { +public: + IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") { + static const FunctionInfo functions[] = { + {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"}, + }; + RegisterHandlers(functions); + } + +private: + void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } +}; + class Hid final : public ServiceFramework<Hid> { public: Hid() : ServiceFramework("hid") { static const FunctionInfo functions[] = { - {0x00000000, &Hid::CreateAppletResource, "CreateAppletResource"}, + {0, &Hid::CreateAppletResource, "CreateAppletResource"}, + {1, &Hid::ActivateDebugPad, "ActivateDebugPad"}, + {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"}, + {21, &Hid::ActivateMouse, "ActivateMouse"}, + {31, &Hid::ActivateKeyboard, "ActivateKeyboard"}, + {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, + {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"}, + {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, + {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, + {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, + {103, &Hid::ActivateNpad, "ActivateNpad"}, + {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, + "AcquireNpadStyleSetUpdateEventHandle"}, + {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"}, + {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, + {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, + "SetNpadJoyAssignmentModeSingleByDefault"}, + {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, + {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, + {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"}, + {201, &Hid::SendVibrationValue, "SendVibrationValue"}, + {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, + {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, + {206, &Hid::SendVibrationValues, "SendVibrationValues"}, }; RegisterHandlers(functions); + + event = Kernel::Event::Create(Kernel::ResetType::OneShot, "hid:EventHandle"); } ~Hid() = default; private: + std::shared_ptr<IAppletResource> applet_resource; + u32 joy_hold_type{0}; + Kernel::SharedPtr<Kernel::Event> event; + void CreateAppletResource(Kernel::HLERequestContext& ctx) { - auto client_port = std::make_shared<IAppletResource>()->CreatePort(); - auto session = client_port->Connect(); - if (session.Succeeded()) { - LOG_DEBUG(Service, "called, initialized IAppletResource -> session=%u", - (*session)->GetObjectId()); - IPC::RequestBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(std::move(session).Unwrap()); - } else { - UNIMPLEMENTED(); + if (applet_resource == nullptr) { + applet_resource = std::make_shared<IAppletResource>(); } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IAppletResource>(applet_resource); + LOG_DEBUG(Service_HID, "called"); + } + + void ActivateDebugPad(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateTouchScreen(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateMouse(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateKeyboard(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void StartSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateNpad(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(joy_hold_type); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SendVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IActiveVibrationDeviceList>(); + LOG_DEBUG(Service_HID, "called"); + } + + void SendVibrationValues(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); } }; diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 2d0d2fb65..b8e53d2c7 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <sstream> #include <string> #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" @@ -28,25 +29,35 @@ private: IsHead = 1, IsTail = 2, }; + enum Severity : u32_le { + Trace, + Info, + Warning, + Error, + Critical, + }; u64_le pid; u64_le threadContext; union { BitField<0, 16, Flags> flags; - BitField<16, 8, u32_le> severity; + BitField<16, 8, Severity> severity; BitField<24, 8, u32_le> verbosity; }; u32_le payload_size; - /// Returns true if this is part of a single log message - bool IsSingleMessage() const { - return (flags & Flags::IsHead) && (flags & Flags::IsTail); + bool IsHeadLog() const { + return flags & Flags::IsHead; + } + bool IsTailLog() const { + return flags & Flags::IsTail; } }; static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size"); /// Log field type enum class Field : u8 { + Skip = 1, Message = 2, Line = 3, Filename = 4, @@ -56,7 +67,7 @@ private: }; /** - * LM::Initialize service function + * LM::Log service function * Inputs: * 0: 0x00000000 * Outputs: @@ -64,7 +75,7 @@ private: */ void Log(Kernel::HLERequestContext& ctx) { // This function only succeeds - Get that out of the way - IPC::RequestBuilder rb{ctx, 1}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); // Read MessageHeader, despite not doing anything with it right now @@ -74,9 +85,9 @@ private: Memory::ReadBlock(addr, &header, sizeof(MessageHeader)); addr += sizeof(MessageHeader); - if (!header.IsSingleMessage()) { - LOG_WARNING(Service, "Multi message logs are unimplemeneted"); - return; + if (header.IsHeadLog()) { + log_stream.str(""); + log_stream.clear(); } // Parse out log metadata @@ -84,7 +95,12 @@ private: std::string message, filename, function; while (addr < end_addr) { const Field field{static_cast<Field>(Memory::Read8(addr++))}; - size_t length{Memory::Read8(addr++)}; + const size_t length{Memory::Read8(addr++)}; + + if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) { + ++addr; + } + switch (field) { case Field::Message: message = Memory::ReadCString(addr, length); @@ -99,32 +115,52 @@ private: function = Memory::ReadCString(addr, length); break; } + addr += length; } // Empty log - nothing to do here - if (message.empty()) { + if (log_stream.str().empty() && message.empty()) { return; } // Format a nicely printable string out of the log metadata - std::string output; - if (filename.size()) { - output += filename + ':'; + if (!filename.empty()) { + log_stream << filename << ':'; } - if (function.size()) { - output += function + ':'; + if (!function.empty()) { + log_stream << function << ':'; } if (line) { - output += std::to_string(line) + ':'; + log_stream << std::to_string(line) << ':'; } - if (output.back() == ':') { - output += ' '; + if (log_stream.str().length() > 0 && log_stream.str().back() == ':') { + log_stream << ' '; } - output += message; + log_stream << message; - LOG_DEBUG(Debug_Emulated, "%s", output.c_str()); + if (header.IsTailLog()) { + switch (header.severity) { + case MessageHeader::Severity::Trace: + LOG_TRACE(Debug_Emulated, "%s", log_stream.str().c_str()); + break; + case MessageHeader::Severity::Info: + LOG_INFO(Debug_Emulated, "%s", log_stream.str().c_str()); + break; + case MessageHeader::Severity::Warning: + LOG_WARNING(Debug_Emulated, "%s", log_stream.str().c_str()); + break; + case MessageHeader::Severity::Error: + LOG_ERROR(Debug_Emulated, "%s", log_stream.str().c_str()); + break; + case MessageHeader::Severity::Critical: + LOG_CRITICAL(Debug_Emulated, "%s", log_stream.str().c_str()); + break; + } + } } + + std::ostringstream log_stream; }; void InstallInterfaces(SM::ServiceManager& service_manager) { @@ -139,20 +175,11 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { * 0: ResultCode */ void LM::Initialize(Kernel::HLERequestContext& ctx) { - auto client_port = std::make_shared<Logger>()->CreatePort(); - auto session = client_port->Connect(); - if (session.Succeeded()) { - LOG_DEBUG(Service_SM, "called, initialized logger -> session=%u", - (*session)->GetObjectId()); - IPC::RequestBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(std::move(session).Unwrap()); - registered_loggers.emplace_back(std::move(client_port)); - } else { - UNIMPLEMENTED(); - } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<Logger>(); - LOG_INFO(Service_SM, "called"); + LOG_DEBUG(Service_LM, "called"); } LM::LM() : ServiceFramework("lm") { diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h index 4b954bdb2..371135057 100644 --- a/src/core/hle/service/lm/lm.h +++ b/src/core/hle/service/lm/lm.h @@ -5,7 +5,6 @@ #pragma once #include <vector> -#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" #include "core/hle/service/service.h" @@ -19,8 +18,6 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx); - - std::vector<Kernel::SharedPtr<Kernel::ClientPort>> registered_loggers; }; /// Registers all LM services with the specified service manager. diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp new file mode 100644 index 000000000..49870841c --- /dev/null +++ b/src/core/hle/service/nfp/nfp.cpp @@ -0,0 +1,28 @@ +// Copyright 2018 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/nfp/nfp.h" +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service { +namespace NFP { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<NFP_User>(module)->InstallAsService(service_manager); +} + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h new file mode 100644 index 000000000..1163e9954 --- /dev/null +++ b/src/core/hle/service/nfp/nfp.h @@ -0,0 +1,28 @@ +// Copyright 2018 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 { +namespace NFP { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void Unknown(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp new file mode 100644 index 000000000..14e5647c4 --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nfp/nfp_user.h" + +namespace Service { +namespace NFP { + +NFP_User::NFP_User(std::shared_ptr<Module> module) + : Module::Interface(std::move(module), "nfp:user") { + static const FunctionInfo functions[] = { + {0, &NFP_User::Unknown, "Unknown"}, + }; + RegisterHandlers(functions); +} + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h new file mode 100644 index 000000000..1606444ca --- /dev/null +++ b/src/core/hle/service/nfp/nfp_user.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nfp/nfp.h" + +namespace Service { +namespace NFP { + +class NFP_User final : public Module::Interface { +public: + explicit NFP_User(std::shared_ptr<Module> module); +}; + +} // namespace NFP +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp new file mode 100644 index 000000000..dd2d5fe63 --- /dev/null +++ b/src/core/hle/service/nifm/nifm.cpp @@ -0,0 +1,213 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/nifm/nifm.h" +#include "core/hle/service/nifm/nifm_a.h" +#include "core/hle/service/nifm/nifm_s.h" +#include "core/hle/service/nifm/nifm_u.h" + +namespace Service { +namespace NIFM { + +class IScanRequest final : public ServiceFramework<IScanRequest> { +public: + explicit IScanRequest() : ServiceFramework("IScanRequest") { + static const FunctionInfo functions[] = { + {0, nullptr, "Submit"}, + {1, nullptr, "IsProcessing"}, + {2, nullptr, "GetResult"}, + {3, nullptr, "GetSystemEventReadableHandle"}, + }; + RegisterHandlers(functions); + } +}; + +class IRequest final : public ServiceFramework<IRequest> { +public: + explicit IRequest() : ServiceFramework("IRequest") { + static const FunctionInfo functions[] = { + {0, &IRequest::GetRequestState, "GetRequestState"}, + {1, &IRequest::GetResult, "GetResult"}, + {2, &IRequest::GetSystemEventReadableHandles, "GetSystemEventReadableHandles"}, + {3, &IRequest::Cancel, "Cancel"}, + {4, nullptr, "Submit"}, + {5, nullptr, "SetRequirement"}, + {6, nullptr, "SetRequirementPreset"}, + {8, nullptr, "SetPriority"}, + {9, nullptr, "SetNetworkProfileId"}, + {10, nullptr, "SetRejectable"}, + {11, nullptr, "SetConnectionConfirmationOption"}, + {12, nullptr, "SetPersistent"}, + {13, nullptr, "SetInstant"}, + {14, nullptr, "SetSustainable"}, + {15, nullptr, "SetRawPriority"}, + {16, nullptr, "SetGreedy"}, + {17, nullptr, "SetSharable"}, + {18, nullptr, "SetRequirementByRevision"}, + {19, nullptr, "GetRequirement"}, + {20, nullptr, "GetRevision"}, + {21, nullptr, "GetAppletInfo"}, + {22, nullptr, "GetAdditionalInfo"}, + {23, nullptr, "SetKeptInSleep"}, + {24, nullptr, "RegisterSocketDescriptor"}, + {25, nullptr, "UnregisterSocketDescriptor"}, + }; + RegisterHandlers(functions); + + event1 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event1"); + event2 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event2"); + } + +private: + void GetRequestState(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + } + void GetResult(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + } + void GetSystemEventReadableHandles(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 2}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event1, event2); + } + void Cancel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Kernel::SharedPtr<Kernel::Event> event1, event2; +}; + +class INetworkProfile final : public ServiceFramework<INetworkProfile> { +public: + explicit INetworkProfile() : ServiceFramework("INetworkProfile") { + static const FunctionInfo functions[] = { + {0, nullptr, "Update"}, + {1, nullptr, "PersistOld"}, + {2, nullptr, "Persist"}, + }; + RegisterHandlers(functions); + } +}; + +class IGeneralService final : public ServiceFramework<IGeneralService> { +public: + IGeneralService(); + +private: + void GetClientId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(0); + } + void CreateScanRequest(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IScanRequest>(); + + LOG_DEBUG(Service_NIFM, "called"); + } + void CreateRequest(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IRequest>(); + + LOG_DEBUG(Service_NIFM, "called"); + } + void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<INetworkProfile>(); + + LOG_DEBUG(Service_NIFM, "called"); + } +}; + +IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { + static const FunctionInfo functions[] = { + {1, &IGeneralService::GetClientId, "GetClientId"}, + {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"}, + {4, &IGeneralService::CreateRequest, "CreateRequest"}, + {5, nullptr, "GetCurrentNetworkProfile"}, + {6, nullptr, "EnumerateNetworkInterfaces"}, + {7, nullptr, "EnumerateNetworkProfiles"}, + {8, nullptr, "GetNetworkProfile"}, + {9, nullptr, "SetNetworkProfile"}, + {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"}, + {11, nullptr, "GetScanDataOld"}, + {12, nullptr, "GetCurrentIpAddress"}, + {13, nullptr, "GetCurrentAccessPointOld"}, + {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"}, + {15, nullptr, "GetCurrentIpConfigInfo"}, + {16, nullptr, "SetWirelessCommunicationEnabled"}, + {17, nullptr, "IsWirelessCommunicationEnabled"}, + {18, nullptr, "GetInternetConnectionStatus"}, + {19, nullptr, "SetEthernetCommunicationEnabled"}, + {20, nullptr, "IsEthernetCommunicationEnabled"}, + {21, nullptr, "IsAnyInternetRequestAccepted"}, + {22, nullptr, "IsAnyForegroundRequestAccepted"}, + {23, nullptr, "PutToSleep"}, + {24, nullptr, "WakeUp"}, + {25, nullptr, "GetSsidListVersion"}, + {26, nullptr, "SetExclusiveClient"}, + {27, nullptr, "GetDefaultIpSetting"}, + {28, nullptr, "SetDefaultIpSetting"}, + {29, nullptr, "SetWirelessCommunicationEnabledForTest"}, + {30, nullptr, "SetEthernetCommunicationEnabledForTest"}, + {31, nullptr, "GetTelemetorySystemEventReadableHandle"}, + {32, nullptr, "GetTelemetryInfo"}, + {33, nullptr, "ConfirmSystemAvailability"}, + {34, nullptr, "SetBackgroundRequestEnabled"}, + {35, nullptr, "GetScanData"}, + {36, nullptr, "GetCurrentAccessPoint"}, + {37, nullptr, "Shutdown"}, + }; + RegisterHandlers(functions); +} + +void Module::Interface::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IGeneralService>(); + LOG_DEBUG(Service_NIFM, "called"); +} + +void Module::Interface::CreateGeneralService(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IGeneralService>(); + LOG_DEBUG(Service_NIFM, "called"); +} + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<NIFM_A>(module)->InstallAsService(service_manager); + std::make_shared<NIFM_S>(module)->InstallAsService(service_manager); + std::make_shared<NIFM_U>(module)->InstallAsService(service_manager); +} + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h new file mode 100644 index 000000000..11d263b12 --- /dev/null +++ b/src/core/hle/service/nifm/nifm.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 { +namespace NIFM { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx); + void CreateGeneralService(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_a.cpp b/src/core/hle/service/nifm/nifm_a.cpp new file mode 100644 index 000000000..f75df8c04 --- /dev/null +++ b/src/core/hle/service/nifm/nifm_a.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nifm/nifm_a.h" + +namespace Service { +namespace NIFM { + +NIFM_A::NIFM_A(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:a") { + static const FunctionInfo functions[] = { + {4, &NIFM_A::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, + {5, &NIFM_A::CreateGeneralService, "CreateGeneralService"}, + }; + RegisterHandlers(functions); +} + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_a.h b/src/core/hle/service/nifm/nifm_a.h new file mode 100644 index 000000000..eaea14e29 --- /dev/null +++ b/src/core/hle/service/nifm/nifm_a.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nifm/nifm.h" + +namespace Service { +namespace NIFM { + +class NIFM_A final : public Module::Interface { +public: + explicit NIFM_A(std::shared_ptr<Module> module); +}; + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_s.cpp b/src/core/hle/service/nifm/nifm_s.cpp new file mode 100644 index 000000000..9c0b300e4 --- /dev/null +++ b/src/core/hle/service/nifm/nifm_s.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nifm/nifm_s.h" + +namespace Service { +namespace NIFM { + +NIFM_S::NIFM_S(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:s") { + static const FunctionInfo functions[] = { + {4, &NIFM_S::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, + {5, &NIFM_S::CreateGeneralService, "CreateGeneralService"}, + }; + RegisterHandlers(functions); +} + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_s.h b/src/core/hle/service/nifm/nifm_s.h new file mode 100644 index 000000000..f9e2d8039 --- /dev/null +++ b/src/core/hle/service/nifm/nifm_s.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nifm/nifm.h" + +namespace Service { +namespace NIFM { + +class NIFM_S final : public Module::Interface { +public: + explicit NIFM_S(std::shared_ptr<Module> module); +}; + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_u.cpp b/src/core/hle/service/nifm/nifm_u.cpp new file mode 100644 index 000000000..44e6f483d --- /dev/null +++ b/src/core/hle/service/nifm/nifm_u.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/nifm/nifm_u.h" + +namespace Service { +namespace NIFM { + +NIFM_U::NIFM_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:u") { + static const FunctionInfo functions[] = { + {4, &NIFM_U::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, + {5, &NIFM_U::CreateGeneralService, "CreateGeneralService"}, + }; + RegisterHandlers(functions); +} + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/nifm/nifm_u.h b/src/core/hle/service/nifm/nifm_u.h new file mode 100644 index 000000000..912006775 --- /dev/null +++ b/src/core/hle/service/nifm/nifm_u.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/nifm/nifm.h" + +namespace Service { +namespace NIFM { + +class NIFM_U final : public Module::Interface { +public: + explicit NIFM_U(std::shared_ptr<Module> module); +}; + +} // namespace NIFM +} // namespace Service diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp new file mode 100644 index 000000000..45681c50f --- /dev/null +++ b/src/core/hle/service/ns/ns.cpp @@ -0,0 +1,16 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ns/ns.h" +#include "core/hle/service/ns/pl_u.h" + +namespace Service { +namespace NS { + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<PL_U>()->InstallAsService(service_manager); +} + +} // namespace NS +} // namespace Service diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h new file mode 100644 index 000000000..a4b7e3ded --- /dev/null +++ b/src/core/hle/service/ns/ns.h @@ -0,0 +1,16 @@ +// Copyright 2018 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 { +namespace NS { + +/// Registers all NS services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace NS +} // namespace Service diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp new file mode 100644 index 000000000..ef3c7799a --- /dev/null +++ b/src/core/hle/service/ns/pl_u.cpp @@ -0,0 +1,122 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/ns/pl_u.h" + +namespace Service { +namespace NS { + +struct FontRegion { + u32 offset; + u32 size; +}; + +// The below data is specific to shared font data dumped from Switch on f/w 2.2 +// Virtual address and offsets/sizes likely will vary by dump +static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; +static constexpr std::array<FontRegion, 6> SHARED_FONT_REGIONS{ + FontRegion{0x00000008, 0x001fe764}, FontRegion{0x001fe774, 0x00773e58}, + FontRegion{0x009725d4, 0x0001aca8}, FontRegion{0x0098d284, 0x00369cec}, + FontRegion{0x00cf6f78, 0x0039b858}, FontRegion{0x010927d8, 0x00019e80}, +}; + +enum class LoadState : u32 { + Loading = 0, + Done = 1, +}; + +PL_U::PL_U() : ServiceFramework("pl:u") { + static const FunctionInfo functions[] = { + {0, &PL_U::RequestLoad, "RequestLoad"}, + {1, &PL_U::GetLoadState, "GetLoadState"}, + {2, &PL_U::GetSize, "GetSize"}, + {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, + {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}}; + RegisterHandlers(functions); + + // Attempt to load shared font data from disk + const std::string filepath{FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT}; + FileUtil::CreateFullPath(filepath); // Create path if not already created + FileUtil::IOFile file(filepath, "rb"); + + if (file.IsOpen()) { + // Read shared font data + ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE); + shared_font = std::make_shared<std::vector<u8>>(static_cast<size_t>(file.GetSize())); + file.ReadBytes(shared_font->data(), shared_font->size()); + } else { + LOG_WARNING(Service_NS, "Unable to load shared font: %s", filepath.c_str()); + } +} + +void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 shared_font_type{rp.Pop<u32>()}; + + LOG_DEBUG(Service_NS, "called, shared_font_type=%d", shared_font_type); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop<u32>()}; + + LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(LoadState::Done)); +} + +void PL_U::GetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop<u32>()}; + + LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size); +} + +void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 font_id{rp.Pop<u32>()}; + + LOG_DEBUG(Service_NS, "called, font_id=%d", font_id); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset); +} + +void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { + if (shared_font != nullptr) { + // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared + // font data. This (likely) relies on exact address, size, and offsets from the original + // dump. In the future, we need to replace this with a more robust solution. + + // Map backing memory for the font data + Core::CurrentProcess()->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, shared_font, 0, + SHARED_FONT_MEM_SIZE, + Kernel::MemoryState::Shared); + + // Create shared font memory object + shared_font_mem = Kernel::SharedMemory::Create( + Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, + "PL_U:shared_font_mem"); + } + + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(shared_font_mem); +} + +} // namespace NS +} // namespace Service diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h new file mode 100644 index 000000000..360482d13 --- /dev/null +++ b/src/core/hle/service/ns/pl_u.h @@ -0,0 +1,34 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NS { + +class PL_U final : public ServiceFramework<PL_U> { +public: + PL_U(); + ~PL_U() = default; + +private: + void RequestLoad(Kernel::HLERequestContext& ctx); + void GetLoadState(Kernel::HLERequestContext& ctx); + void GetSize(Kernel::HLERequestContext& ctx); + void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx); + void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); + + /// Handle to shared memory region designated for a shared font + Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; + + /// Backing memory for the shared font data + std::shared_ptr<std::vector<u8>> shared_font; +}; + +} // namespace NS +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 5ee33b3d6..cdc25b059 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -5,7 +5,9 @@ #pragma once #include <vector> +#include "common/bit_field.h" #include "common/common_types.h" +#include "common/swap.h" namespace Service { namespace Nvidia { @@ -17,6 +19,14 @@ class nvdevice { public: nvdevice() = default; virtual ~nvdevice() = default; + union Ioctl { + u32_le raw; + BitField<0, 8, u32_le> cmd; + BitField<8, 8, u32_le> group; + BitField<16, 14, u32_le> length; + BitField<30, 1, u32_le> is_in; + BitField<31, 1, u32_le> is_out; + }; /** * Handles an ioctl request. @@ -25,7 +35,7 @@ public: * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) = 0; + virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0; }; } // namespace Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index b65d79f11..87b3a2d74 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -14,24 +14,25 @@ namespace Service { namespace Nvidia { namespace Devices { -u32 nvdisp_disp0::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { UNIMPLEMENTED(); return 0; } void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, - u32 stride) { + u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform) { VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle); LOG_WARNING(Service, - "Drawing from address %llx offset %08X Width %u Height %u Stride %u Format %u", - addr, offset, width, height, stride, format); + "Drawing from address %lx offset %08X Width %u Height %u Stride %u Format %u", addr, + offset, width, height, stride, format); - using PixelFormat = RendererBase::FramebufferInfo::PixelFormat; - const RendererBase::FramebufferInfo framebuffer_info{ - addr, offset, width, height, stride, static_cast<PixelFormat>(format)}; + using PixelFormat = Tegra::FramebufferConfig::PixelFormat; + const Tegra::FramebufferConfig framebuffer{ + addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform}; Core::System::GetInstance().perf_stats.EndGameFrame(); - VideoCore::g_renderer->SwapBuffers(framebuffer_info); + + VideoCore::g_renderer->SwapBuffers(framebuffer); } } // namespace Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index f5f9de3f4..66f56f23d 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -8,6 +8,7 @@ #include <vector> #include "common/common_types.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" +#include "core/hle/service/nvflinger/buffer_queue.h" namespace Service { namespace Nvidia { @@ -20,10 +21,11 @@ public: nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvdevice(), nvmap_dev(std::move(nvmap_dev)) {} ~nvdisp_disp0() = default; - u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. - void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride); + void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, + NVFlinger::BufferQueue::BufferTransformFlags transform); private: std::shared_ptr<nvmap> nvmap_dev; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 9db08339a..9892402fa 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -2,16 +2,116 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" +#include "core/hle/service/nvdrv/devices/nvmap.h" namespace Service { namespace Nvidia { namespace Devices { -u32 nvhost_as_gpu::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) { - UNIMPLEMENTED(); +u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", + command.raw, input.size(), output.size()); + + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::IocInitalizeExCommand: + return InitalizeEx(input, output); + case IoctlCommand::IocAllocateSpaceCommand: + return AllocateSpace(input, output); + case IoctlCommand::IocMapBufferExCommand: + return MapBufferEx(input, output); + case IoctlCommand::IocBindChannelCommand: + return BindChannel(input, output); + case IoctlCommand::IocGetVaRegionsCommand: + return GetVARegions(input, output); + } + return 0; +} + +u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlInitalizeEx params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x%x", params.big_page_size); + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlAllocSpace params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, pages=%x, page_size=%x, flags=%x", params.pages, + params.page_size, params.flags); + + auto& gpu = Core::System::GetInstance().GPU(); + const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)}; + if (params.flags & 1) { + params.offset = gpu.memory_manager->AllocateSpace(params.offset, size, 1); + } else { + params.offset = gpu.memory_manager->AllocateSpace(size, params.align); + } + + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlMapBufferEx params{}; + std::memcpy(¶ms, input.data(), input.size()); + + LOG_DEBUG(Service_NVDRV, + "called, flags=%x, nvmap_handle=%x, buffer_offset=%" PRIu64 ", mapping_size=%" PRIu64 + ", offset=%" PRIu64, + params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size, + params.offset); + + if (!params.nvmap_handle) { + return 0; + } + + auto object = nvmap_dev->GetObject(params.nvmap_handle); + ASSERT(object); + + auto& gpu = Core::System::GetInstance().GPU(); + + if (params.flags & 1) { + params.offset = gpu.memory_manager->MapBufferEx(object->addr, params.offset, object->size); + } else { + params.offset = gpu.memory_manager->MapBufferEx(object->addr, object->size); + } + + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlBindChannel params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.fd); + channel = params.fd; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGetVaRegions params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr=%" PRIu64 ", buf_size=%x", + params.buf_addr, params.buf_size); + + params.buf_size = 0x30; + params.regions[0].offset = 0x04000000; + params.regions[0].page_size = 0x1000; + params.regions[0].pages = 0x3fbfff; + + params.regions[1].offset = 0x04000000; + params.regions[1].page_size = 0x10000; + params.regions[1].pages = 0x1bffff; + // TODO(ogniK): This probably can stay stubbed but should add support way way later + std::memcpy(output.data(), ¶ms, output.size()); return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 01f8861c8..f8a60cce7 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -4,20 +4,100 @@ #pragma once +#include <memory> +#include <utility> #include <vector> #include "common/common_types.h" +#include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" namespace Service { namespace Nvidia { namespace Devices { +class nvmap; + class nvhost_as_gpu final : public nvdevice { public: - nvhost_as_gpu() = default; + nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} ~nvhost_as_gpu() override = default; - u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + +private: + enum class IoctlCommand : u32_le { + IocInitalizeExCommand = 0x40284109, + IocAllocateSpaceCommand = 0xC0184102, + IocMapBufferExCommand = 0xC0284106, + IocBindChannelCommand = 0x40044101, + IocGetVaRegionsCommand = 0xC0404108, + }; + + struct IoctlInitalizeEx { + u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default + s32_le as_fd; // ignored; passes 0 + u32_le flags; // passes 0 + u32_le reserved; // ignored; passes 0 + u64_le unk0; + u64_le unk1; + u64_le unk2; + }; + static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size"); + + struct IoctlAllocSpace { + u32_le pages; + u32_le page_size; + u32_le flags; + INSERT_PADDING_WORDS(1); + union { + u64_le offset; + u64_le align; + }; + }; + static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size"); + + struct IoctlMapBufferEx { + u32_le flags; // bit0: fixed_offset, bit2: cacheable + u32_le kind; // -1 is default + u32_le nvmap_handle; + u32_le page_size; // 0 means don't care + u64_le buffer_offset; + u64_le mapping_size; + u64_le offset; + }; + static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size"); + + struct IoctlBindChannel { + u32_le fd; + }; + static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size"); + + struct IoctlVaRegion { + u64_le offset; + u32_le page_size; + INSERT_PADDING_WORDS(1); + u64_le pages; + }; + static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size"); + + struct IoctlGetVaRegions { + u64_le buf_addr; // (contained output user ptr on linux, ignored) + u32_le buf_size; // forced to 2*sizeof(struct va_region) + u32_le reserved; + IoctlVaRegion regions[2]; + }; + static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2, + "IoctlGetVaRegions is incorrect size"); + + u32 channel{}; + + u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output); + u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output); + u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); + u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output); + + std::shared_ptr<nvmap> nvmap_dev; }; } // namespace Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp new file mode 100644 index 000000000..45711d686 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -0,0 +1,64 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", + command.raw, input.size(), output.size()); + + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::IocGetConfigCommand: + return NvOsGetConfigU32(input, output); + case IoctlCommand::IocCtrlEventWaitCommand: + return IocCtrlEventWait(input, output); + } + UNIMPLEMENTED(); + return 0; +} + +u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { + IocGetConfigParams params{}; + std::memcpy(¶ms, input.data(), sizeof(params)); + LOG_DEBUG(Service_NVDRV, "called, setting=%s!%s", params.domain_str.data(), + params.param_str.data()); + + if (!strcmp(params.domain_str.data(), "nv")) { + if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) { + params.config_str[0] = '0'; + } else if (!strcmp(params.param_str.data(), "NVN_THROUGH_OPENGL")) { + params.config_str[0] = '0'; + } else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) { + params.config_str[0] = '0'; + } else { + params.config_str[0] = '0'; + } + } else { + UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware + } + std::memcpy(output.data(), ¶ms, sizeof(params)); + return 0; +} + +u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output) { + IocCtrlEventWaitParams params{}; + std::memcpy(¶ms, input.data(), sizeof(params)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, syncpt_id=%u threshold=%u timeout=%d", + params.syncpt_id, params.threshold, params.timeout); + + // TODO(Subv): Implement actual syncpt waiting. + params.value = 0; + std::memcpy(output.data(), ¶ms, sizeof(params)); + return 0; +} + +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h new file mode 100644 index 000000000..0ca01aa6d --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -0,0 +1,60 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstdlib> +#include <cstring> +#include <vector> +#include "common/common_types.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +class nvhost_ctrl final : public nvdevice { +public: + nvhost_ctrl() = default; + ~nvhost_ctrl() override = default; + + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + +private: + enum class IoctlCommand : u32_le { + IocSyncptReadCommand = 0xC0080014, + IocSyncptIncrCommand = 0x40040015, + IocSyncptWaitCommand = 0xC00C0016, + IocModuleMutexCommand = 0x40080017, + IocModuleRegRDWRCommand = 0xC008010E, + IocSyncptWaitexCommand = 0xC0100019, + IocSyncptReadMaxCommand = 0xC008001A, + IocCtrlEventWaitCommand = 0xC010001D, + IocGetConfigCommand = 0xC183001B, + }; + + struct IocGetConfigParams { + std::array<char, 0x41> domain_str; + std::array<char, 0x41> param_str; + std::array<char, 0x101> config_str; + }; + static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size"); + + struct IocCtrlEventWaitParams { + u32_le syncpt_id; + u32_le threshold; + s32_le timeout; + u32_le value; + }; + static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size"); + + u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); + + u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output); +}; + +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp new file mode 100644 index 000000000..3b353d742 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -0,0 +1,127 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", + command.raw, input.size(), output.size()); + + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::IocGetCharacteristicsCommand: + return GetCharacteristics(input, output); + case IoctlCommand::IocGetTPCMasksCommand: + return GetTPCMasks(input, output); + case IoctlCommand::IocGetActiveSlotMaskCommand: + return GetActiveSlotMask(input, output); + case IoctlCommand::IocZcullGetCtxSizeCommand: + return ZCullGetCtxSize(input, output); + case IoctlCommand::IocZcullGetInfo: + return ZCullGetInfo(input, output); + } + UNIMPLEMENTED(); + return 0; +} + +u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlCharacteristics params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.gc.arch = 0x120; + params.gc.impl = 0xb; + params.gc.rev = 0xa1; + params.gc.num_gpc = 0x1; + params.gc.l2_cache_size = 0x40000; + params.gc.on_board_video_memory_size = 0x0; + params.gc.num_tpc_per_gpc = 0x2; + params.gc.bus_type = 0x20; + params.gc.big_page_size = 0x20000; + params.gc.compression_page_size = 0x20000; + params.gc.pde_coverage_bit_count = 0x1B; + params.gc.available_big_page_sizes = 0x30000; + params.gc.gpc_mask = 0x1; + params.gc.sm_arch_sm_version = 0x503; + params.gc.sm_arch_spa_version = 0x503; + params.gc.sm_arch_warp_count = 0x80; + params.gc.gpu_va_bit_count = 0x28; + params.gc.reserved = 0x0; + params.gc.flags = 0x55; + params.gc.twod_class = 0x902D; + params.gc.threed_class = 0xB197; + params.gc.compute_class = 0xB1C0; + params.gc.gpfifo_class = 0xB06F; + params.gc.inline_to_memory_class = 0xA140; + params.gc.dma_copy_class = 0xB0B5; + params.gc.max_fbps_count = 0x1; + params.gc.fbp_en_mask = 0x0; + params.gc.max_ltc_per_fbp = 0x2; + params.gc.max_lts_per_ltc = 0x1; + params.gc.max_tex_per_tpc = 0x0; + params.gc.max_gpc_count = 0x1; + params.gc.rop_l2_en_mask_0 = 0x21D70; + params.gc.rop_l2_en_mask_1 = 0x0; + params.gc.chipname = 0x6230326D67; + params.gc.gr_compbit_store_base_hw = 0x0; + params.gpu_characteristics_buf_size = 0xA0; + params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlGpuGetTpcMasksArgs params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, mask=0x%x, mask_buf_addr=0x%" PRIx64, + params.mask_buf_size, params.mask_buf_addr); + std::memcpy(output.data(), ¶ms, sizeof(params)); + return 0; +} + +u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlActiveSlotMask params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.slot = 0x07; + params.mask = 0x01; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlZcullGetCtxSize params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.size = 0x1; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlNvgpuGpuZcullGetInfoArgs params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.width_align_pixels = 0x20; + params.height_align_pixels = 0x20; + params.pixel_squares_by_aliquots = 0x400; + params.aliquot_total = 0x800; + params.region_byte_multiplier = 0x20; + params.region_header_size = 0x20; + params.subregion_header_size = 0xc0; + params.subregion_width_align_pixels = 0x20; + params.subregion_height_align_pixels = 0x40; + params.subregion_count = 0x10; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h new file mode 100644 index 000000000..dc0476993 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -0,0 +1,130 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +class nvhost_ctrl_gpu final : public nvdevice { +public: + nvhost_ctrl_gpu() = default; + ~nvhost_ctrl_gpu() override = default; + + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + +private: + enum class IoctlCommand : u32_le { + IocGetCharacteristicsCommand = 0xC0B04705, + IocGetTPCMasksCommand = 0xC0184706, + IocGetActiveSlotMaskCommand = 0x80084714, + IocZcullGetCtxSizeCommand = 0x80044701, + IocZcullGetInfo = 0x80284702, + }; + + struct IoctlGpuCharacteristics { + u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200) + u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B) + u32_le rev; // 0xA1 (Revision A1) + u32_le num_gpc; // 0x1 + u64_le l2_cache_size; // 0x40000 + u64_le on_board_video_memory_size; // 0x0 (not used) + u32_le num_tpc_per_gpc; // 0x2 + u32_le bus_type; // 0x20 (NVGPU_GPU_BUS_TYPE_AXI) + u32_le big_page_size; // 0x20000 + u32_le compression_page_size; // 0x20000 + u32_le pde_coverage_bit_count; // 0x1B + u32_le available_big_page_sizes; // 0x30000 + u32_le gpc_mask; // 0x1 + u32_le sm_arch_sm_version; // 0x503 (Maxwell Generation 5.0.3?) + u32_le sm_arch_spa_version; // 0x503 (Maxwell Generation 5.0.3?) + u32_le sm_arch_warp_count; // 0x80 + u32_le gpu_va_bit_count; // 0x28 + u32_le reserved; // NULL + u64_le flags; // 0x55 + u32_le twod_class; // 0x902D (FERMI_TWOD_A) + u32_le threed_class; // 0xB197 (MAXWELL_B) + u32_le compute_class; // 0xB1C0 (MAXWELL_COMPUTE_B) + u32_le gpfifo_class; // 0xB06F (MAXWELL_CHANNEL_GPFIFO_A) + u32_le inline_to_memory_class; // 0xA140 (KEPLER_INLINE_TO_MEMORY_B) + u32_le dma_copy_class; // 0xB0B5 (MAXWELL_DMA_COPY_A) + u32_le max_fbps_count; // 0x1 + u32_le fbp_en_mask; // 0x0 (disabled) + u32_le max_ltc_per_fbp; // 0x2 + u32_le max_lts_per_ltc; // 0x1 + u32_le max_tex_per_tpc; // 0x0 (not supported) + u32_le max_gpc_count; // 0x1 + u32_le rop_l2_en_mask_0; // 0x21D70 (fuse_status_opt_rop_l2_fbp_r) + u32_le rop_l2_en_mask_1; // 0x0 + u64_le chipname; // 0x6230326D67 ("gm20b") + u64_le gr_compbit_store_base_hw; // 0x0 (not supported) + }; + static_assert(sizeof(IoctlGpuCharacteristics) == 160, + "IoctlGpuCharacteristics is incorrect size"); + + struct IoctlCharacteristics { + u64_le gpu_characteristics_buf_size; // must not be NULL, but gets overwritten with + // 0xA0=max_size + u64_le gpu_characteristics_buf_addr; // ignored, but must not be NULL + IoctlGpuCharacteristics gc; + }; + static_assert(sizeof(IoctlCharacteristics) == 16 + sizeof(IoctlGpuCharacteristics), + "IoctlCharacteristics is incorrect size"); + + struct IoctlGpuGetTpcMasksArgs { + /// [in] TPC mask buffer size reserved by userspace. Should be at least + /// sizeof(__u32) * fls(gpc_mask) to receive TPC mask for each GPC. + /// [out] full kernel buffer size + u32_le mask_buf_size; + u32_le reserved; + + /// [in] pointer to TPC mask buffer. It will receive one 32-bit TPC mask per GPC or 0 if + /// GPC is not enabled or not present. This parameter is ignored if mask_buf_size is 0. + u64_le mask_buf_addr; + u64_le unk; // Nintendo add this? + }; + static_assert(sizeof(IoctlGpuGetTpcMasksArgs) == 24, + "IoctlGpuGetTpcMasksArgs is incorrect size"); + + struct IoctlActiveSlotMask { + u32_le slot; // always 0x07 + u32_le mask; + }; + static_assert(sizeof(IoctlActiveSlotMask) == 8, "IoctlActiveSlotMask is incorrect size"); + + struct IoctlZcullGetCtxSize { + u32_le size; + }; + static_assert(sizeof(IoctlZcullGetCtxSize) == 4, "IoctlZcullGetCtxSize is incorrect size"); + + struct IoctlNvgpuGpuZcullGetInfoArgs { + u32_le width_align_pixels; + u32_le height_align_pixels; + u32_le pixel_squares_by_aliquots; + u32_le aliquot_total; + u32_le region_byte_multiplier; + u32_le region_header_size; + u32_le subregion_header_size; + u32_le subregion_width_align_pixels; + u32_le subregion_height_align_pixels; + u32_le subregion_count; + }; + static_assert(sizeof(IoctlNvgpuGpuZcullGetInfoArgs) == 40, + "IoctlNvgpuGpuZcullGetInfoArgs is incorrect size"); + + u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); + u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); + u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output); +}; +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp new file mode 100644 index 000000000..da44c65f3 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -0,0 +1,147 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include <map> +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/service/nvdrv/devices/nvhost_gpu.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx", + command.raw, input.size(), output.size()); + + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::IocSetNVMAPfdCommand: + return SetNVMAPfd(input, output); + case IoctlCommand::IocSetClientDataCommand: + return SetClientData(input, output); + case IoctlCommand::IocGetClientDataCommand: + return GetClientData(input, output); + case IoctlCommand::IocZCullBind: + return ZCullBind(input, output); + case IoctlCommand::IocSetErrorNotifierCommand: + return SetErrorNotifier(input, output); + case IoctlCommand::IocChannelSetPriorityCommand: + return SetChannelPriority(input, output); + case IoctlCommand::IocAllocGPFIFOEx2Command: + return AllocGPFIFOEx2(input, output); + case IoctlCommand::IocAllocObjCtxCommand: + return AllocateObjectContext(input, output); + } + + if (command.group == NVGPU_IOCTL_MAGIC) { + if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) { + return SubmitGPFIFO(input, output); + } + } + + UNIMPLEMENTED(); + return 0; +}; + +u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSetNvmapFD params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlClientData params{}; + std::memcpy(¶ms, input.data(), input.size()); + user_data = params.data; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlClientData params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.data = user_data; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) { + std::memcpy(&zcull_params, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, gpu_va=%" PRIx64 ", mode=%x", zcull_params.gpu_va, + zcull_params.mode); + std::memcpy(output.data(), &zcull_params, output.size()); + return 0; +} + +u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlSetErrorNotifier params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset=%" PRIx64 ", size=%" PRIx64 ", mem=%x", + params.offset, params.size, params.mem); + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { + std::memcpy(&channel_priority, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority=%x", channel_priority); + std::memcpy(output.data(), &channel_priority, output.size()); + return 0; +} + +u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlAllocGpfifoEx2 params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, + "(STUBBED) called, num_entries=%x, flags=%x, unk0=%x, unk1=%x, unk2=%x, unk3=%x", + params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, + params.unk3); + params.fence_out.id = 0; + params.fence_out.value = 0; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) { + IoctlAllocObjCtx params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num=%x, flags=%x", params.class_num, + params.flags); + params.obj_id = 0x0; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) { + if (input.size() < sizeof(IoctlSubmitGpfifo)) + UNIMPLEMENTED(); + IoctlSubmitGpfifo params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo=%" PRIx64 ", num_entries=%x, flags=%x", + params.gpfifo, params.num_entries, params.flags); + + auto entries = std::vector<IoctlGpfifoEntry>(); + entries.resize(params.num_entries); + std::memcpy(&entries[0], &input.data()[sizeof(IoctlSubmitGpfifo)], + params.num_entries * sizeof(IoctlGpfifoEntry)); + for (auto entry : entries) { + VAddr va_addr = entry.Address(); + Core::System::GetInstance().GPU().ProcessCommandList(va_addr, entry.sz); + } + params.fence_out.id = 0; + params.fence_out.value = 0; + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h new file mode 100644 index 000000000..e7e9a0088 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -0,0 +1,144 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service { +namespace Nvidia { +namespace Devices { + +class nvmap; +constexpr u32 NVGPU_IOCTL_MAGIC('H'); +constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8); + +class nvhost_gpu final : public nvdevice { +public: + nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} + ~nvhost_gpu() override = default; + + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + +private: + enum class IoctlCommand : u32_le { + IocSetNVMAPfdCommand = 0x40044801, + IocSetClientDataCommand = 0x40084714, + IocGetClientDataCommand = 0x80084715, + IocZCullBind = 0xc010480b, + IocSetErrorNotifierCommand = 0xC018480C, + IocChannelSetPriorityCommand = 0x4004480D, + IocAllocGPFIFOEx2Command = 0xC020481A, + IocAllocObjCtxCommand = 0xC0104809, + }; + + enum class CtxObjects : u32_le { + Ctx2D = 0x902D, + Ctx3D = 0xB197, + CtxCompute = 0xB1C0, + CtxKepler = 0xA140, + CtxDMA = 0xB0B5, + CtxChannelGPFIFO = 0xB06F, + }; + + struct IoctlSetNvmapFD { + u32_le nvmap_fd; + }; + static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + + struct IoctlClientData { + u64_le data; + }; + static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size"); + + struct IoctlZCullBind { + u64_le gpu_va; + u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf + INSERT_PADDING_WORDS(1); + }; + static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size"); + + struct IoctlSetErrorNotifier { + u64_le offset; + u64_le size; + u32_le mem; // nvmap object handle + INSERT_PADDING_WORDS(1); + }; + static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size"); + + struct IoctlFence { + u32_le id; + u32_le value; + }; + static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size"); + + struct IoctlAllocGpfifoEx2 { + u32_le num_entries; // in + u32_le flags; // in + u32_le unk0; // in (1 works) + IoctlFence fence_out; // out + u32_le unk1; // in + u32_le unk2; // in + u32_le unk3; // in + }; + static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size"); + + struct IoctlAllocObjCtx { + u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA, + // 0xB06F=channel_gpfifo + u32_le flags; + u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported + }; + static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size"); + + struct IoctlGpfifoEntry { + u32_le entry0; // gpu_va_lo + union { + u32_le entry1; // gpu_va_hi | (unk_0x02 << 0x08) | (size << 0x0A) | (unk_0x01 << 0x1F) + BitField<0, 8, u32_le> gpu_va_hi; + BitField<8, 2, u32_le> unk1; + BitField<10, 21, u32_le> sz; + BitField<31, 1, u32_le> unk2; + }; + + VAddr Address() const { + return (static_cast<VAddr>(gpu_va_hi) << 32) | entry0; + } + }; + static_assert(sizeof(IoctlGpfifoEntry) == 8, "IoctlGpfifoEntry is incorrect size"); + + struct IoctlSubmitGpfifo { + u64_le gpfifo; // (ignored) pointer to gpfifo fence structs + u32_le num_entries; // number of fence objects being submitted + u32_le flags; + IoctlFence fence_out; // returned new fence object for others to wait on + }; + static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence), + "submit_gpfifo is incorrect size"); + + u32_le nvmap_fd{}; + u64_le user_data{}; + IoctlZCullBind zcull_params{}; + u32_le channel_priority{}; + + u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetClientData(const std::vector<u8>& input, std::vector<u8>& output); + u32 ZCullBind(const std::vector<u8>& input, std::vector<u8>& output); + u32 SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output); + u32 SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output); + u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); + u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); + u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); + + std::shared_ptr<nvmap> nvmap_dev; +}; + +} // namespace Devices +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index d37b5b159..b3842eb4c 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cinttypes> #include "common/assert.h" #include "common/logging/log.h" @@ -13,29 +14,28 @@ namespace Nvidia { namespace Devices { VAddr nvmap::GetObjectAddress(u32 handle) const { - auto itr = handles.find(handle); - ASSERT(itr != handles.end()); - - auto object = itr->second; + auto object = GetObject(handle); + ASSERT(object); ASSERT(object->status == Object::Status::Allocated); return object->addr; } -u32 nvmap::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) { - switch (command) { - case IocCreateCommand: +u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { + switch (static_cast<IoctlCommand>(command.raw)) { + case IoctlCommand::Create: return IocCreate(input, output); - case IocAllocCommand: + case IoctlCommand::Alloc: return IocAlloc(input, output); - case IocGetIdCommand: + case IoctlCommand::GetId: return IocGetId(input, output); - case IocFromIdCommand: + case IoctlCommand::FromId: return IocFromId(input, output); - case IocParamCommand: + case IoctlCommand::Param: return IocParam(input, output); } UNIMPLEMENTED(); + return 0; } u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { @@ -51,7 +51,7 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { u32 handle = next_handle++; handles[handle] = std::move(object); - LOG_WARNING(Service, "(STUBBED) size 0x%08X", params.size); + LOG_DEBUG(Service_NVDRV, "size=0x%08X", params.size); params.handle = handle; @@ -63,17 +63,16 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { IocAllocParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - auto itr = handles.find(params.handle); - ASSERT(itr != handles.end()); + auto object = GetObject(params.handle); + ASSERT(object); - auto object = itr->second; object->flags = params.flags; object->align = params.align; object->kind = params.kind; object->addr = params.addr; object->status = Object::Status::Allocated; - LOG_WARNING(Service, "(STUBBED) Allocated address 0x%llx", params.addr); + LOG_DEBUG(Service_NVDRV, "called, addr=0x%" PRIx64, params.addr); std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; @@ -83,12 +82,12 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { IocGetIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service, "called"); + LOG_WARNING(Service_NVDRV, "called"); - auto itr = handles.find(params.handle); - ASSERT(itr != handles.end()); + auto object = GetObject(params.handle); + ASSERT(object); - params.id = itr->second->id; + params.id = object->id; std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; @@ -98,17 +97,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { IocFromIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service, "(STUBBED) called"); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); auto itr = std::find_if(handles.begin(), handles.end(), [&](const auto& entry) { return entry.second->id == params.id; }); ASSERT(itr != handles.end()); - // Make a new handle for the object - u32 handle = next_handle++; - handles[handle] = itr->second; - - params.handle = handle; + // Return the existing handle instead of creating a new one. + params.handle = itr->first; std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; @@ -120,12 +116,10 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { IocParamParams params; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service, "(STUBBED) called type=%u", params.type); - - auto itr = handles.find(params.handle); - ASSERT(itr != handles.end()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called type=%u", params.type); - auto object = itr->second; + auto object = GetObject(params.handle); + ASSERT(object); ASSERT(object->status == Object::Status::Allocated); switch (static_cast<ParamTypes>(params.type)) { diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 6954c0324..4681e438b 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -24,10 +24,9 @@ public: /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; - u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; -private: - // Represents an nvmap object. + /// Represents an nvmap object. struct Object { enum class Status { Created, Allocated }; u32 id; @@ -39,21 +38,30 @@ private: Status status; }; + std::shared_ptr<Object> GetObject(u32 handle) const { + auto itr = handles.find(handle); + if (itr != handles.end()) { + return itr->second; + } + return {}; + } + +private: /// Id to use for the next handle that is created. u32 next_handle = 1; - // Id to use for the next object that is created. + /// Id to use for the next object that is created. u32 next_id = 1; /// Mapping of currently allocated handles to the objects they represent. std::unordered_map<u32, std::shared_ptr<Object>> handles; - enum IoctlCommands { - IocCreateCommand = 0xC0080101, - IocFromIdCommand = 0xC0080103, - IocAllocCommand = 0xC0200104, - IocParamCommand = 0xC00C0109, - IocGetIdCommand = 0xC008010E + enum class IoctlCommand : u32 { + Create = 0xC0080101, + FromId = 0xC0080103, + Alloc = 0xC0200104, + Param = 0xC00C0109, + GetId = 0xC008010E }; struct IocCreateParams { diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index 0670ca155..c70370f1f 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" @@ -11,58 +13,95 @@ namespace Service { namespace Nvidia { void NVDRV::Open(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_DEBUG(Service_NVDRV, "called"); - auto buffer = ctx.BufferDescriptorA()[0]; - - std::string device_name = Memory::ReadCString(buffer.Address(), buffer.Size()); + const auto& buffer = ctx.ReadBuffer(); + std::string device_name(buffer.begin(), buffer.end()); u32 fd = nvdrv->Open(device_name); - IPC::RequestBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(fd); rb.Push<u32>(0); } void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_DEBUG(Service_NVDRV, "called"); IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); u32 command = rp.Pop<u32>(); - auto input_buffer = ctx.BufferDescriptorA()[0]; - auto output_buffer = ctx.BufferDescriptorB()[0]; + std::vector<u8> output(ctx.GetWriteBufferSize()); - std::vector<u8> input(input_buffer.Size()); - std::vector<u8> output(output_buffer.Size()); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output)); - Memory::ReadBlock(input_buffer.Address(), input.data(), input_buffer.Size()); + ctx.WriteBuffer(output); +} - u32 nv_result = nvdrv->Ioctl(fd, command, input, output); +void NVDRV::Close(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); - Memory::WriteBlock(output_buffer.Address(), output.data(), output_buffer.Size()); + IPC::RequestParser rp{ctx}; + u32 fd = rp.Pop<u32>(); - IPC::RequestBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(nv_result); + auto result = nvdrv->Close(fd); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 3}; + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); +} + +void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u32 fd = rp.Pop<u32>(); + u32 event_id = rp.Pop<u32>(); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd=%x, event_id=%x", fd, event_id); + + IPC::ResponseBuilder rb{ctx, 3, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(query_event); + rb.Push<u32>(0); +} + +void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + pid = rp.Pop<u64>(); + + LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x%" PRIx64, pid); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); } +void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) : ServiceFramework(name), nvdrv(std::move(nvdrv)) { static const FunctionInfo functions[] = { {0, &NVDRV::Open, "Open"}, {1, &NVDRV::Ioctl, "Ioctl"}, + {2, &NVDRV::Close, "Close"}, {3, &NVDRV::Initialize, "Initialize"}, + {4, &NVDRV::QueryEvent, "QueryEvent"}, + {8, &NVDRV::SetClientPID, "SetClientPID"}, + {13, &NVDRV::FinishInitialize, "FinishInitialize"}, }; RegisterHandlers(functions); + + query_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NVDRV::query_event"); } } // namespace Nvidia diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 8c95b7217..daf2302af 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -6,6 +6,7 @@ #include <memory> #include <string> +#include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/service.h" @@ -20,9 +21,17 @@ public: private: void Open(Kernel::HLERequestContext& ctx); void Ioctl(Kernel::HLERequestContext& ctx); + void Close(Kernel::HLERequestContext& ctx); void Initialize(Kernel::HLERequestContext& ctx); + void QueryEvent(Kernel::HLERequestContext& ctx); + void SetClientPID(Kernel::HLERequestContext& ctx); + void FinishInitialize(Kernel::HLERequestContext& ctx); std::shared_ptr<Module> nvdrv; + + u64 pid{}; + + Kernel::SharedPtr<Kernel::Event> query_event; }; } // namespace Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index cf525a875..ea00240e6 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -6,9 +6,13 @@ #include "core/hle/service/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" +#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" +#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" +#include "core/hle/service/nvdrv/devices/nvhost_gpu.h" #include "core/hle/service/nvdrv/devices/nvmap.h" -#include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/interface.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/nvmemp.h" namespace Service { namespace Nvidia { @@ -19,14 +23,20 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { auto module_ = std::make_shared<Module>(); std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager); + std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager); + std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager); + std::make_shared<NVMEMP>()->InstallAsService(service_manager); nvdrv = module_; } Module::Module() { auto nvmap_dev = std::make_shared<Devices::nvmap>(); - devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(); + devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(nvmap_dev); + devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(nvmap_dev); + devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(); devices["/dev/nvmap"] = nvmap_dev; devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev); + devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(); } u32 Module::Open(std::string device_name) { @@ -41,12 +51,22 @@ u32 Module::Open(std::string device_name) { return fd; } -u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 Module::Ioctl(u32 fd, u32_le command, const std::vector<u8>& input, std::vector<u8>& output) { auto itr = open_files.find(fd); ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); auto device = itr->second; - return device->ioctl(command, input, output); + return device->ioctl({command}, input, output); +} + +ResultCode Module::Close(u32 fd) { + auto itr = open_files.find(fd); + ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); + + open_files.erase(itr); + + // TODO(flerovium): return correct result code if operation failed. + return RESULT_SUCCESS; } } // namespace Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 1940ced99..6a55ff96d 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -17,6 +17,13 @@ namespace Devices { class nvdevice; } +struct IoctlFence { + u32 id; + u32 value; +}; + +static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size"); + class Module final { public: Module(); @@ -35,6 +42,8 @@ public: u32 Open(std::string device_name); /// Sends an ioctl command to the specified file descriptor. u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output); + /// Closes a device file descriptor and returns operation success. + ResultCode Close(u32 fd); private: /// Id to use for the next open file descriptor. diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp new file mode 100644 index 000000000..5a13732c7 --- /dev/null +++ b/src/core/hle/service/nvdrv/nvmemp.cpp @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvdrv/nvmemp.h" + +namespace Service { +namespace Nvidia { + +NVMEMP::NVMEMP() : ServiceFramework("nvmemp") { + static const FunctionInfo functions[] = { + {0, &NVMEMP::Unknown0, "Unknown0"}, + {1, &NVMEMP::Unknown1, "Unknown1"}, + }; + RegisterHandlers(functions); +} + +void NVMEMP::Unknown0(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED(); +} + +void NVMEMP::Unknown1(Kernel::HLERequestContext& ctx) { + UNIMPLEMENTED(); +} + +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h new file mode 100644 index 000000000..a6b5fbb82 --- /dev/null +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -0,0 +1,23 @@ +// Copyright 2018 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 { +namespace Nvidia { + +class NVMEMP final : public ServiceFramework<NVMEMP> { +public: + NVMEMP(); + ~NVMEMP() = default; + +private: + void Unknown0(Kernel::HLERequestContext& ctx); + void Unknown1(Kernel::HLERequestContext& ctx); +}; + +} // namespace Nvidia +} // namespace Service diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp new file mode 100644 index 000000000..e4ff2e267 --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -0,0 +1,115 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "core/core_timing.h" +#include "core/hle/service/nvflinger/buffer_queue.h" + +namespace Service { +namespace NVFlinger { + +BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { + native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle"); + native_handle->Signal(); +} + +void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) { + Buffer buffer{}; + buffer.slot = slot; + buffer.igbp_buffer = igbp_buffer; + buffer.status = Buffer::Status::Free; + + LOG_WARNING(Service, "Adding graphics buffer %u", slot); + + queue.emplace_back(buffer); + + if (buffer_wait_event) { + buffer_wait_event->Signal(); + } +} + +boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) { + auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { + // Only consider free buffers. Buffers become free once again after they've been Acquired + // and Released by the compositor, see the NVFlinger::Compose method. + if (buffer.status != Buffer::Status::Free) { + return false; + } + + // Make sure that the parameters match. + return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height; + }); + + if (itr == queue.end()) { + return boost::none; + } + + buffer_wait_event = nullptr; + + itr->status = Buffer::Status::Dequeued; + return itr->slot; +} + +const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { + auto itr = std::find_if(queue.begin(), queue.end(), + [&](const Buffer& buffer) { return buffer.slot == slot; }); + ASSERT(itr != queue.end()); + ASSERT(itr->status == Buffer::Status::Dequeued); + return itr->igbp_buffer; +} + +void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform) { + auto itr = std::find_if(queue.begin(), queue.end(), + [&](const Buffer& buffer) { return buffer.slot == slot; }); + ASSERT(itr != queue.end()); + ASSERT(itr->status == Buffer::Status::Dequeued); + itr->status = Buffer::Status::Queued; + itr->transform = transform; +} + +boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() { + auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) { + return buffer.status == Buffer::Status::Queued; + }); + if (itr == queue.end()) + return boost::none; + itr->status = Buffer::Status::Acquired; + return *itr; +} + +void BufferQueue::ReleaseBuffer(u32 slot) { + auto itr = std::find_if(queue.begin(), queue.end(), + [&](const Buffer& buffer) { return buffer.slot == slot; }); + ASSERT(itr != queue.end()); + ASSERT(itr->status == Buffer::Status::Acquired); + itr->status = Buffer::Status::Free; + + if (buffer_wait_event) { + buffer_wait_event->Signal(); + } +} + +u32 BufferQueue::Query(QueryType type) { + LOG_WARNING(Service, "(STUBBED) called type=%u", static_cast<u32>(type)); + switch (type) { + case QueryType::NativeWindowFormat: + // TODO(Subv): Use an enum for this + static constexpr u32 FormatABGR8 = 1; + return FormatABGR8; + } + + UNIMPLEMENTED(); + return 0; +} + +void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event) { + ASSERT_MSG(!buffer_wait_event, "buffer_wait_event only supports a single waiting thread!"); + buffer_wait_event = std::move(wait_event); +} + +} // namespace NVFlinger +} // namespace Service diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h new file mode 100644 index 000000000..1de5767cb --- /dev/null +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -0,0 +1,102 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <boost/optional.hpp> +#include "common/swap.h" +#include "core/hle/kernel/event.h" + +namespace CoreTiming { +struct EventType; +} + +namespace Service { +namespace NVFlinger { + +struct IGBPBuffer { + u32_le magic; + u32_le width; + u32_le height; + u32_le stride; + u32_le format; + u32_le usage; + INSERT_PADDING_WORDS(1); + u32_le index; + INSERT_PADDING_WORDS(3); + u32_le gpu_buffer_id; + INSERT_PADDING_WORDS(17); + u32_le nvmap_handle; + u32_le offset; + INSERT_PADDING_WORDS(60); +}; + +static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size"); + +class BufferQueue final { +public: + enum class QueryType { + NativeWindowWidth = 0, + NativeWindowHeight = 1, + NativeWindowFormat = 2, + }; + + BufferQueue(u32 id, u64 layer_id); + ~BufferQueue() = default; + + enum class BufferTransformFlags : u32 { + /// No transform flags are set + Unset = 0x00, + /// Flip source image horizontally (around the vertical axis) + FlipH = 0x01, + /// Flip source image vertically (around the horizontal axis) + FlipV = 0x02, + /// Rotate source image 90 degrees clockwise + Rotate90 = 0x04, + /// Rotate source image 180 degrees + Roate180 = 0x03, + /// Rotate source image 270 degrees clockwise + Roate270 = 0x07, + }; + + struct Buffer { + enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 }; + + u32 slot; + Status status = Status::Free; + IGBPBuffer igbp_buffer; + BufferTransformFlags transform; + }; + + void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer); + boost::optional<u32> DequeueBuffer(u32 width, u32 height); + const IGBPBuffer& RequestBuffer(u32 slot) const; + void QueueBuffer(u32 slot, BufferTransformFlags transform); + boost::optional<const Buffer&> AcquireBuffer(); + void ReleaseBuffer(u32 slot); + u32 Query(QueryType type); + void SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event); + + u32 GetId() const { + return id; + } + + Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const { + return native_handle; + } + +private: + u32 id; + u64 layer_id; + + std::vector<Buffer> queue; + Kernel::SharedPtr<Kernel::Event> native_handle; + + /// Used to signal waiting thread when no buffers are available + Kernel::SharedPtr<Kernel::Event> buffer_wait_event; +}; + +} // namespace NVFlinger +} // namespace Service diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp new file mode 100644 index 000000000..0d30f54dc --- /dev/null +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -0,0 +1,166 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" +#include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvflinger/buffer_queue.h" +#include "core/hle/service/nvflinger/nvflinger.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" + +namespace Service { +namespace NVFlinger { + +constexpr size_t SCREEN_REFRESH_RATE = 60; +constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); + +NVFlinger::NVFlinger() { + // Add the different displays to the list of displays. + Display default_{0, "Default"}; + Display external{1, "External"}; + Display edid{2, "Edid"}; + Display internal{3, "Internal"}; + + displays.emplace_back(default_); + displays.emplace_back(external); + displays.emplace_back(edid); + displays.emplace_back(internal); + + // Schedule the screen composition events + composition_event = + CoreTiming::RegisterEvent("ScreenCompositioin", [this](u64 userdata, int cycles_late) { + Compose(); + CoreTiming::ScheduleEvent(frame_ticks - cycles_late, composition_event); + }); + + CoreTiming::ScheduleEvent(frame_ticks, composition_event); +} + +NVFlinger::~NVFlinger() { + CoreTiming::UnscheduleEvent(composition_event, 0); +} + +u64 NVFlinger::OpenDisplay(const std::string& name) { + LOG_WARNING(Service, "Opening display %s", name.c_str()); + + // TODO(Subv): Currently we only support the Default display. + ASSERT(name == "Default"); + + auto itr = std::find_if(displays.begin(), displays.end(), + [&](const Display& display) { return display.name == name; }); + + ASSERT(itr != displays.end()); + + return itr->id; +} + +u64 NVFlinger::CreateLayer(u64 display_id) { + auto& display = GetDisplay(display_id); + + ASSERT_MSG(display.layers.empty(), "Only one layer is supported per display at the moment"); + + u64 layer_id = next_layer_id++; + u32 buffer_queue_id = next_buffer_queue_id++; + auto buffer_queue = std::make_shared<BufferQueue>(buffer_queue_id, layer_id); + display.layers.emplace_back(layer_id, buffer_queue); + buffer_queues.emplace_back(std::move(buffer_queue)); + return layer_id; +} + +u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) { + const auto& layer = GetLayer(display_id, layer_id); + return layer.buffer_queue->GetId(); +} + +Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) { + const auto& display = GetDisplay(display_id); + return display.vsync_event; +} + +std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const { + auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), + [&](const auto& queue) { return queue->GetId() == id; }); + + ASSERT(itr != buffer_queues.end()); + return *itr; +} + +Display& NVFlinger::GetDisplay(u64 display_id) { + auto itr = std::find_if(displays.begin(), displays.end(), + [&](const Display& display) { return display.id == display_id; }); + + ASSERT(itr != displays.end()); + return *itr; +} + +Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) { + auto& display = GetDisplay(display_id); + + auto itr = std::find_if(display.layers.begin(), display.layers.end(), + [&](const Layer& layer) { return layer.id == layer_id; }); + + ASSERT(itr != display.layers.end()); + return *itr; +} + +void NVFlinger::Compose() { + for (auto& display : displays) { + // Trigger vsync for this display at the end of drawing + SCOPE_EXIT({ display.vsync_event->Signal(); }); + + // Don't do anything for displays without layers. + if (display.layers.empty()) + continue; + + // TODO(Subv): Support more than 1 layer. + ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported"); + + Layer& layer = display.layers[0]; + auto& buffer_queue = layer.buffer_queue; + + // Search for a queued buffer and acquire it + auto buffer = buffer_queue->AcquireBuffer(); + + if (buffer == boost::none) { + // There was no queued buffer to draw, render previous frame + Core::System::GetInstance().perf_stats.EndGameFrame(); + VideoCore::g_renderer->SwapBuffers({}); + continue; + } + + auto& igbp_buffer = buffer->igbp_buffer; + + // Now send the buffer to the GPU for drawing. + auto nvdrv = Nvidia::nvdrv.lock(); + ASSERT(nvdrv); + + // TODO(Subv): Support more than just disp0. The display device selection is probably based + // on which display we're drawing (Default, Internal, External, etc) + auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); + ASSERT(nvdisp); + + nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format, + igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform); + + buffer_queue->ReleaseBuffer(buffer->slot); + + // TODO(Subv): Figure out when we should actually signal this event. + buffer_queue->GetNativeHandle()->Signal(); + } +} + +Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {} + +Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { + vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event"); +} + +} // namespace NVFlinger +} // namespace Service diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h new file mode 100644 index 000000000..3126018ad --- /dev/null +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -0,0 +1,84 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <boost/optional.hpp> +#include "core/hle/kernel/event.h" + +namespace CoreTiming { +struct EventType; +} + +namespace Service { +namespace NVFlinger { + +class BufferQueue; + +struct Layer { + Layer(u64 id, std::shared_ptr<BufferQueue> queue); + ~Layer() = default; + + u64 id; + std::shared_ptr<BufferQueue> buffer_queue; +}; + +struct Display { + Display(u64 id, std::string name); + ~Display() = default; + + u64 id; + std::string name; + + std::vector<Layer> layers; + Kernel::SharedPtr<Kernel::Event> vsync_event; +}; + +class NVFlinger final { +public: + NVFlinger(); + ~NVFlinger(); + + /// Opens the specified display and returns the id. + u64 OpenDisplay(const std::string& name); + + /// Creates a layer on the specified display and returns the layer id. + u64 CreateLayer(u64 display_id); + + /// Gets the buffer queue id of the specified layer in the specified display. + u32 GetBufferQueueId(u64 display_id, u64 layer_id); + + /// Gets the vsync event for the specified display. + Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id); + + /// Obtains a buffer queue identified by the id. + std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const; + + /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when + /// finished. + void Compose(); + +private: + /// Returns the display identified by the specified id. + Display& GetDisplay(u64 display_id); + + /// Returns the layer identified by the specified id in the desired display. + Layer& GetLayer(u64 display_id, u64 layer_id); + + std::vector<Display> displays; + std::vector<std::shared_ptr<BufferQueue>> buffer_queues; + + /// Id to use for the next layer that is created, this counter is shared among all displays. + u64 next_layer_id = 1; + /// Id to use for the next buffer queue that is created, this counter is shared among all + /// layers. + u32 next_buffer_queue_id = 1; + + /// CoreTiming event that handles screen composition. + CoreTiming::EventType* composition_event; +}; + +} // namespace NVFlinger +} // namespace Service diff --git a/src/core/hle/service/pctl/pctl_a.cpp b/src/core/hle/service/pctl/pctl_a.cpp index 7978aecb8..c65fffa07 100644 --- a/src/core/hle/service/pctl/pctl_a.cpp +++ b/src/core/hle/service/pctl/pctl_a.cpp @@ -15,10 +15,10 @@ public: }; void PCTL_A::GetService(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IParentalControlService>(); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service_PCTL, "called"); } PCTL_A::PCTL_A() : ServiceFramework("pctl:a") { diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index fe76b381c..8011d0d71 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -7,6 +7,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" @@ -19,13 +20,23 @@ #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/audio/audio.h" +#include "core/hle/service/fatal/fatal.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/friend/friend.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/lm/lm.h" +#include "core/hle/service/nfp/nfp.h" +#include "core/hle/service/nifm/nifm.h" +#include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/pctl/pctl.h" #include "core/hle/service/service.h" +#include "core/hle/service/set/settings.h" #include "core/hle/service/sm/controller.h" #include "core/hle/service/sm/sm.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/spl/module.h" +#include "core/hle/service/ssl/ssl.h" #include "core/hle/service/time/time.h" #include "core/hle/service/vi/vi.h" @@ -129,7 +140,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) { switch (context.GetCommandType()) { case IPC::CommandType::Close: { - IPC::RequestBuilder rb{context, 1}; + IPC::ResponseBuilder rb{context, 2}; rb.Push(RESULT_SUCCESS); return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead); } @@ -142,12 +153,10 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co break; } default: - UNIMPLEMENTED_MSG("command_type=%d", context.GetCommandType()); + UNIMPLEMENTED_MSG("command_type=%d", static_cast<int>(context.GetCommandType())); } - u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress()); - context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, - Kernel::g_handle_table); + context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread()); return RESULT_SUCCESS; } @@ -162,20 +171,34 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) { /// Initialize ServiceManager void Init() { + // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it + // here and pass it into the respective InstallInterfaces functions. + auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(); + SM::g_service_manager = std::make_shared<SM::ServiceManager>(); SM::ServiceManager::InstallInterfaces(SM::g_service_manager); Account::InstallInterfaces(*SM::g_service_manager); - AM::InstallInterfaces(*SM::g_service_manager); + AM::InstallInterfaces(*SM::g_service_manager, nv_flinger); AOC::InstallInterfaces(*SM::g_service_manager); APM::InstallInterfaces(*SM::g_service_manager); Audio::InstallInterfaces(*SM::g_service_manager); + Fatal::InstallInterfaces(*SM::g_service_manager); + FileSystem::InstallInterfaces(*SM::g_service_manager); + Friend::InstallInterfaces(*SM::g_service_manager); HID::InstallInterfaces(*SM::g_service_manager); LM::InstallInterfaces(*SM::g_service_manager); + NFP::InstallInterfaces(*SM::g_service_manager); + NIFM::InstallInterfaces(*SM::g_service_manager); + NS::InstallInterfaces(*SM::g_service_manager); Nvidia::InstallInterfaces(*SM::g_service_manager); PCTL::InstallInterfaces(*SM::g_service_manager); + Sockets::InstallInterfaces(*SM::g_service_manager); + SPL::InstallInterfaces(*SM::g_service_manager); + SSL::InstallInterfaces(*SM::g_service_manager); Time::InstallInterfaces(*SM::g_service_manager); - VI::InstallInterfaces(*SM::g_service_manager); + VI::InstallInterfaces(*SM::g_service_manager, nv_flinger); + Set::InstallInterfaces(*SM::g_service_manager); LOG_DEBUG(Service, "initialized OK"); } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 8e1c5b399..9c2e826da 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -21,7 +21,7 @@ class ClientPort; class ServerPort; class ServerSession; class HLERequestContext; -} +} // namespace Kernel namespace Service { @@ -189,4 +189,4 @@ extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_ /// Adds a port to the named port table void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port); -} // namespace +} // namespace Service diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp new file mode 100644 index 000000000..aa7c924e7 --- /dev/null +++ b/src/core/hle/service/set/set.cpp @@ -0,0 +1,44 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/service/set/set.h" + +namespace Service { +namespace Set { + +void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u32 id = rp.Pop<u32>(); + constexpr std::array<u8, 13> lang_codes{}; + + ctx.WriteBuffer(lang_codes.data(), lang_codes.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_SET, "(STUBBED) called"); +} + +SET::SET() : ServiceFramework("set") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetLanguageCode"}, + {1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"}, + {2, nullptr, "MakeLanguageCode"}, + {3, nullptr, "GetAvailableLanguageCodeCount"}, + {4, nullptr, "GetRegionCode"}, + {5, nullptr, "GetAvailableLanguageCodes2"}, + {6, nullptr, "GetAvailableLanguageCodeCount2"}, + {7, nullptr, "GetKeyCodeMap"}, + }; + RegisterHandlers(functions); +} + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h new file mode 100644 index 000000000..7b7814ed1 --- /dev/null +++ b/src/core/hle/service/set/set.h @@ -0,0 +1,22 @@ +// Copyright 2018 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 { +namespace Set { + +class SET final : public ServiceFramework<SET> { +public: + explicit SET(); + ~SET() = default; + +private: + void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx); +}; + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp new file mode 100644 index 000000000..6231acd96 --- /dev/null +++ b/src/core/hle/service/set/set_cal.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set_cal.h" + +namespace Service { +namespace Set { + +SET_CAL::SET_CAL() : ServiceFramework("set:cal") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetBluetoothBdAddress"}, + {1, nullptr, "GetConfigurationId1"}, + {2, nullptr, "GetAccelerometerOffset"}, + {3, nullptr, "GetAccelerometerScale"}, + {4, nullptr, "GetGyroscopeOffset"}, + {5, nullptr, "GetGyroscopeScale"}, + {6, nullptr, "GetWirelessLanMacAddress"}, + {7, nullptr, "GetWirelessLanCountryCodeCount"}, + {8, nullptr, "GetWirelessLanCountryCodes"}, + {9, nullptr, "GetSerialNumber"}, + {10, nullptr, "SetInitialSystemAppletProgramId"}, + {11, nullptr, "SetOverlayDispProgramId"}, + {12, nullptr, "GetBatteryLot"}, + {14, nullptr, "GetEciDeviceCertificate"}, + {15, nullptr, "GetEticketDeviceCertificate"}, + {16, nullptr, "GetSslKey"}, + {17, nullptr, "GetSslCertificate"}, + {18, nullptr, "GetGameCardKey"}, + {19, nullptr, "GetGameCardCertificate"}, + {20, nullptr, "GetEciDeviceKey"}, + {21, nullptr, "GetEticketDeviceKey"}, + {22, nullptr, "GetSpeakerParameter"}, + {23, nullptr, "GetLcdVendorId"}, + }; + RegisterHandlers(functions); +} + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h new file mode 100644 index 000000000..9c0b851d0 --- /dev/null +++ b/src/core/hle/service/set/set_cal.h @@ -0,0 +1,19 @@ +// Copyright 2018 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 { +namespace Set { + +class SET_CAL final : public ServiceFramework<SET_CAL> { +public: + explicit SET_CAL(); + ~SET_CAL() = default; +}; + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp new file mode 100644 index 000000000..8320d4250 --- /dev/null +++ b/src/core/hle/service/set/set_fd.cpp @@ -0,0 +1,25 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set_fd.h" + +namespace Service { +namespace Set { + +SET_FD::SET_FD() : ServiceFramework("set:fd") { + static const FunctionInfo functions[] = { + {2, nullptr, "SetSettingsItemValue"}, + {3, nullptr, "ResetSettingsItemValue"}, + {4, nullptr, "CreateSettingsItemKeyIterator"}, + {10, nullptr, "ReadSettings"}, + {11, nullptr, "ResetSettings"}, + {20, nullptr, "SetWebInspectorFlag"}, + {21, nullptr, "SetAllowedSslHosts"}, + {22, nullptr, "SetHostFsMountPoint"}, + }; + RegisterHandlers(functions); +} + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h new file mode 100644 index 000000000..65b36bcb3 --- /dev/null +++ b/src/core/hle/service/set/set_fd.h @@ -0,0 +1,19 @@ +// Copyright 2018 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 { +namespace Set { + +class SET_FD final : public ServiceFramework<SET_FD> { +public: + explicit SET_FD(); + ~SET_FD() = default; +}; + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp new file mode 100644 index 000000000..363abd10a --- /dev/null +++ b/src/core/hle/service/set/set_sys.cpp @@ -0,0 +1,167 @@ +// Copyright 2018 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/kernel/client_port.h" +#include "core/hle/service/set/set_sys.h" + +namespace Service { +namespace Set { + +void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) { + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + + LOG_WARNING(Service_SET, "(STUBBED) called"); +} + +SET_SYS::SET_SYS() : ServiceFramework("set:sys") { + static const FunctionInfo functions[] = { + {0, nullptr, "SetLanguageCode"}, + {1, nullptr, "SetNetworkSettings"}, + {2, nullptr, "GetNetworkSettings"}, + {3, nullptr, "GetFirmwareVersion"}, + {4, nullptr, "GetFirmwareVersion2"}, + {7, nullptr, "GetLockScreenFlag"}, + {8, nullptr, "SetLockScreenFlag"}, + {9, nullptr, "GetBacklightSettings"}, + {10, nullptr, "SetBacklightSettings"}, + {11, nullptr, "SetBluetoothDevicesSettings"}, + {12, nullptr, "GetBluetoothDevicesSettings"}, + {13, nullptr, "GetExternalSteadyClockSourceId"}, + {14, nullptr, "SetExternalSteadyClockSourceId"}, + {15, nullptr, "GetUserSystemClockContext"}, + {16, nullptr, "SetUserSystemClockContext"}, + {17, nullptr, "GetAccountSettings"}, + {18, nullptr, "SetAccountSettings"}, + {19, nullptr, "GetAudioVolume"}, + {20, nullptr, "SetAudioVolume"}, + {21, nullptr, "GetEulaVersions"}, + {22, nullptr, "SetEulaVersions"}, + {23, &SET_SYS::GetColorSetId, "GetColorSetId"}, + {24, nullptr, "SetColorSetId"}, + {25, nullptr, "GetConsoleInformationUploadFlag"}, + {26, nullptr, "SetConsoleInformationUploadFlag"}, + {27, nullptr, "GetAutomaticApplicationDownloadFlag"}, + {28, nullptr, "SetAutomaticApplicationDownloadFlag"}, + {29, nullptr, "GetNotificationSettings"}, + {30, nullptr, "SetNotificationSettings"}, + {31, nullptr, "GetAccountNotificationSettings"}, + {32, nullptr, "SetAccountNotificationSettings"}, + {35, nullptr, "GetVibrationMasterVolume"}, + {36, nullptr, "SetVibrationMasterVolume"}, + {37, nullptr, "GetSettingsItemValueSize"}, + {38, nullptr, "GetSettingsItemValue"}, + {39, nullptr, "GetTvSettings"}, + {40, nullptr, "SetTvSettings"}, + {41, nullptr, "GetEdid"}, + {42, nullptr, "SetEdid"}, + {43, nullptr, "GetAudioOutputMode"}, + {44, nullptr, "SetAudioOutputMode"}, + {45, nullptr, "IsForceMuteOnHeadphoneRemoved"}, + {46, nullptr, "SetForceMuteOnHeadphoneRemoved"}, + {47, nullptr, "GetQuestFlag"}, + {48, nullptr, "SetQuestFlag"}, + {49, nullptr, "GetDataDeletionSettings"}, + {50, nullptr, "SetDataDeletionSettings"}, + {51, nullptr, "GetInitialSystemAppletProgramId"}, + {52, nullptr, "GetOverlayDispProgramId"}, + {53, nullptr, "GetDeviceTimeZoneLocationName"}, + {54, nullptr, "SetDeviceTimeZoneLocationName"}, + {55, nullptr, "GetWirelessCertificationFileSize"}, + {56, nullptr, "GetWirelessCertificationFile"}, + {57, nullptr, "SetRegionCode"}, + {58, nullptr, "GetNetworkSystemClockContext"}, + {59, nullptr, "SetNetworkSystemClockContext"}, + {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"}, + {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"}, + {62, nullptr, "GetDebugModeFlag"}, + {63, nullptr, "GetPrimaryAlbumStorage"}, + {64, nullptr, "SetPrimaryAlbumStorage"}, + {65, nullptr, "GetUsb30EnableFlag"}, + {66, nullptr, "SetUsb30EnableFlag"}, + {67, nullptr, "GetBatteryLot"}, + {68, nullptr, "GetSerialNumber"}, + {69, nullptr, "GetNfcEnableFlag"}, + {70, nullptr, "SetNfcEnableFlag"}, + {71, nullptr, "GetSleepSettings"}, + {72, nullptr, "SetSleepSettings"}, + {73, nullptr, "GetWirelessLanEnableFlag"}, + {74, nullptr, "SetWirelessLanEnableFlag"}, + {75, nullptr, "GetInitialLaunchSettings"}, + {76, nullptr, "SetInitialLaunchSettings"}, + {77, nullptr, "GetDeviceNickName"}, + {78, nullptr, "SetDeviceNickName"}, + {79, nullptr, "GetProductModel"}, + {80, nullptr, "GetLdnChannel"}, + {81, nullptr, "SetLdnChannel"}, + {82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"}, + {83, nullptr, "GetTelemetryDirtyFlags"}, + {84, nullptr, "GetPtmBatteryLot"}, + {85, nullptr, "SetPtmBatteryLot"}, + {86, nullptr, "GetPtmFuelGaugeParameter"}, + {87, nullptr, "SetPtmFuelGaugeParameter"}, + {88, nullptr, "GetBluetoothEnableFlag"}, + {89, nullptr, "SetBluetoothEnableFlag"}, + {90, nullptr, "GetMiiAuthorId"}, + {91, nullptr, "SetShutdownRtcValue"}, + {92, nullptr, "GetShutdownRtcValue"}, + {93, nullptr, "AcquireFatalDirtyFlagEventHandle"}, + {94, nullptr, "GetFatalDirtyFlags"}, + {95, nullptr, "GetAutoUpdateEnableFlag"}, + {96, nullptr, "SetAutoUpdateEnableFlag"}, + {97, nullptr, "GetNxControllerSettings"}, + {98, nullptr, "SetNxControllerSettings"}, + {99, nullptr, "GetBatteryPercentageFlag"}, + {100, nullptr, "SetBatteryPercentageFlag"}, + {101, nullptr, "GetExternalRtcResetFlag"}, + {102, nullptr, "SetExternalRtcResetFlag"}, + {103, nullptr, "GetUsbFullKeyEnableFlag"}, + {104, nullptr, "SetUsbFullKeyEnableFlag"}, + {105, nullptr, "SetExternalSteadyClockInternalOffset"}, + {106, nullptr, "GetExternalSteadyClockInternalOffset"}, + {107, nullptr, "GetBacklightSettingsEx"}, + {108, nullptr, "SetBacklightSettingsEx"}, + {109, nullptr, "GetHeadphoneVolumeWarningCount"}, + {110, nullptr, "SetHeadphoneVolumeWarningCount"}, + {111, nullptr, "GetBluetoothAfhEnableFlag"}, + {112, nullptr, "SetBluetoothAfhEnableFlag"}, + {113, nullptr, "GetBluetoothBoostEnableFlag"}, + {114, nullptr, "SetBluetoothBoostEnableFlag"}, + {115, nullptr, "GetInRepairProcessEnableFlag"}, + {116, nullptr, "SetInRepairProcessEnableFlag"}, + {117, nullptr, "GetHeadphoneVolumeUpdateFlag"}, + {118, nullptr, "SetHeadphoneVolumeUpdateFlag"}, + {119, nullptr, "NeedsToUpdateHeadphoneVolume"}, + {120, nullptr, "GetPushNotificationActivityModeOnSleep"}, + {121, nullptr, "SetPushNotificationActivityModeOnSleep"}, + {122, nullptr, "GetServiceDiscoveryControlSettings"}, + {123, nullptr, "SetServiceDiscoveryControlSettings"}, + {124, nullptr, "GetErrorReportSharePermission"}, + {125, nullptr, "SetErrorReportSharePermission"}, + {126, nullptr, "GetAppletLaunchFlags"}, + {127, nullptr, "SetAppletLaunchFlags"}, + {128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"}, + {129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"}, + {130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"}, + {131, nullptr, "SetConsoleSixAxisSensorAngularVelocityBias"}, + {132, nullptr, "GetConsoleSixAxisSensorAccelerationGain"}, + {133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"}, + {134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"}, + {135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"}, + {136, nullptr, "GetKeyboardLayout"}, + {137, nullptr, "SetKeyboardLayout"}, + {138, nullptr, "GetWebInspectorFlag"}, + {139, nullptr, "GetAllowedSslHosts"}, + {140, nullptr, "GetHostFsMountPoint"}, + }; + RegisterHandlers(functions); +} + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h new file mode 100644 index 000000000..105f1a3c7 --- /dev/null +++ b/src/core/hle/service/set/set_sys.h @@ -0,0 +1,22 @@ +// Copyright 2018 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 { +namespace Set { + +class SET_SYS final : public ServiceFramework<SET_SYS> { +public: + explicit SET_SYS(); + ~SET_SYS() = default; + +private: + void GetColorSetId(Kernel::HLERequestContext& ctx); +}; + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp new file mode 100644 index 000000000..c6bc9e240 --- /dev/null +++ b/src/core/hle/service/set/settings.cpp @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/set/set.h" +#include "core/hle/service/set/set_cal.h" +#include "core/hle/service/set/set_fd.h" +#include "core/hle/service/set/set_sys.h" +#include "core/hle/service/set/settings.h" + +namespace Service { +namespace Set { + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<SET>()->InstallAsService(service_manager); + std::make_shared<SET_CAL>()->InstallAsService(service_manager); + std::make_shared<SET_FD>()->InstallAsService(service_manager); + std::make_shared<SET_SYS>()->InstallAsService(service_manager); +} + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h new file mode 100644 index 000000000..6c8d5a58c --- /dev/null +++ b/src/core/hle/service/set/settings.h @@ -0,0 +1,16 @@ +// Copyright 2018 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 { +namespace Set { + +/// Registers all Settings services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Set +} // namespace Service diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index e8f5d0e4a..e12c53442 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -4,36 +4,43 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/domain.h" +#include "core/hle/kernel/session.h" #include "core/hle/service/sm/controller.h" namespace Service { namespace SM { void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { - auto domain = Kernel::Domain::CreateFromSession(*ctx.ServerSession()->parent).Unwrap(); + ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain"); + ctx.Session()->ConvertToDomain(); - IPC::RequestBuilder rb{ctx, 3}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(domain->request_handlers.size())); + rb.Push<u32>(1); // Converted sessions start with 1 request handler - LOG_DEBUG(Service, "called, domain=%d", domain->GetObjectId()); + LOG_DEBUG(Service, "called, server_session=%d", ctx.Session()->GetObjectId()); } void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 2, 0, 1}; + // TODO(bunnei): This is just creating a new handle to the same Session. I assume this is wrong + // and that we probably want to actually make an entirely new Session, but we still need to + // verify this on hardware. + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(RESULT_SUCCESS); - // TODO(Subv): Check if this is correct - if (ctx.IsDomain()) - rb.PushMoveObjects(ctx.Domain()); - else - rb.PushMoveObjects(ctx.ServerSession()); + Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client}; + rb.PushMoveObjects(session); - LOG_DEBUG(Service, "called"); + LOG_DEBUG(Service, "called, session=%u", session->GetObjectId()); +} + +void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called, using DuplicateSession"); + + DuplicateSession(ctx); } void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 3}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0x500); @@ -46,7 +53,7 @@ Controller::Controller() : ServiceFramework("IpcController") { {0x00000001, nullptr, "ConvertDomainToSession"}, {0x00000002, &Controller::DuplicateSession, "DuplicateSession"}, {0x00000003, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"}, - {0x00000004, nullptr, "DuplicateSessionEx"}, + {0x00000004, &Controller::DuplicateSessionEx, "DuplicateSessionEx"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h index 0b873b492..7b4bc4b75 100644 --- a/src/core/hle/service/sm/controller.h +++ b/src/core/hle/service/sm/controller.h @@ -17,6 +17,7 @@ public: private: void ConvertSessionToDomain(Kernel::HLERequestContext& ctx); void DuplicateSession(Kernel::HLERequestContext& ctx); + void DuplicateSessionEx(Kernel::HLERequestContext& ctx); void QueryPointerBufferSize(Kernel::HLERequestContext& ctx); }; diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index f3bffac54..bc72512a0 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -83,7 +83,7 @@ std::shared_ptr<ServiceManager> g_service_manager; * 0: ResultCode */ void SM::Initialize(Kernel::HLERequestContext& ctx) { - IPC::RequestBuilder rb{ctx, 1}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_SM, "called"); } @@ -99,10 +99,13 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { auto client_port = service_manager->GetServicePort(name); if (client_port.Failed()) { - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(client_port.Code()); LOG_ERROR(Service_SM, "called service=%s -> error 0x%08X", name.c_str(), client_port.Code().raw); + if (name.length() == 0) + return; // LibNX Fix + UNIMPLEMENTED(); return; } @@ -111,7 +114,8 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { if (session.Succeeded()) { LOG_DEBUG(Service_SM, "called service=%s -> session=%u", name.c_str(), (*session)->GetObjectId()); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 1, 0); + IPC::ResponseBuilder rb = + rp.MakeBuilder(2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles); rb.Push(session.Code()); rb.PushMoveObjects(std::move(session).Unwrap()); } diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp new file mode 100644 index 000000000..790ff82b3 --- /dev/null +++ b/src/core/hle/service/sockets/bsd.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/sockets/bsd.h" + +namespace Service { +namespace Sockets { + +void BSD::RegisterClient(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // bsd errno +} + +void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // bsd errno +} + +void BSD::Socket(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + u32 domain = rp.Pop<u32>(); + u32 type = rp.Pop<u32>(); + u32 protocol = rp.Pop<u32>(); + + LOG_WARNING(Service, "(STUBBED) called domain=%u type=%u protocol=%u", domain, type, protocol); + + u32 fd = next_fd++; + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(fd); + rb.Push<u32>(0); // bsd errno +} + +void BSD::Connect(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"); + + IPC::ResponseBuilder rb{ctx, 4}; + + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); // ret + rb.Push<u32>(0); // bsd errno +} + +void BSD::Close(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 +} + +BSD::BSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {0, &BSD::RegisterClient, "RegisterClient"}, + {1, &BSD::StartMonitoring, "StartMonitoring"}, + {2, &BSD::Socket, "Socket"}, + {11, &BSD::SendTo, "SendTo"}, + {14, &BSD::Connect, "Connect"}, + {26, &BSD::Close, "Close"}, + }; + RegisterHandlers(functions); +} + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h new file mode 100644 index 000000000..32d949e95 --- /dev/null +++ b/src/core/hle/service/sockets/bsd.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace Sockets { + +class BSD final : public ServiceFramework<BSD> { +public: + explicit BSD(const char* name); + ~BSD() = default; + +private: + void RegisterClient(Kernel::HLERequestContext& ctx); + void StartMonitoring(Kernel::HLERequestContext& ctx); + void Socket(Kernel::HLERequestContext& ctx); + void Connect(Kernel::HLERequestContext& ctx); + void SendTo(Kernel::HLERequestContext& ctx); + void Close(Kernel::HLERequestContext& ctx); + + /// Id to use for the next open file descriptor. + u32 next_fd = 1; +}; + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp new file mode 100644 index 000000000..e3542d325 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.cpp @@ -0,0 +1,34 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/sockets/nsd.h" + +namespace Service { +namespace Sockets { + +NSD::NSD(const char* name) : ServiceFramework(name) { + static const FunctionInfo functions[] = { + {10, nullptr, "GetSettingName"}, + {11, nullptr, "GetEnvironmentIdentifier"}, + {12, nullptr, "GetDeviceId"}, + {13, nullptr, "DeleteSettings"}, + {14, nullptr, "ImportSettings"}, + {20, nullptr, "Resolve"}, + {21, nullptr, "ResolveEx"}, + {30, nullptr, "GetNasServiceSetting"}, + {31, nullptr, "GetNasServiceSettingEx"}, + {40, nullptr, "GetNasRequestFqdn"}, + {41, nullptr, "GetNasRequestFqdnEx"}, + {42, nullptr, "GetNasApiFqdn"}, + {43, nullptr, "GetNasApiFqdnEx"}, + {50, nullptr, "GetCurrentSetting"}, + {60, nullptr, "ReadSaveDataFromFsForTest"}, + {61, nullptr, "WriteSaveDataToFsForTest"}, + {62, nullptr, "DeleteSaveDataOfFsForTest"}, + }; + RegisterHandlers(functions); +} + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h new file mode 100644 index 000000000..a7c15a860 --- /dev/null +++ b/src/core/hle/service/sockets/nsd.h @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace Sockets { + +class NSD final : public ServiceFramework<NSD> { +public: + explicit NSD(const char* name); + ~NSD() = default; +}; + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp new file mode 100644 index 000000000..eb4b5fa57 --- /dev/null +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -0,0 +1,38 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/sockets/sfdnsres.h" + +namespace Service { +namespace Sockets { + +void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); +} + +SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { + static const FunctionInfo functions[] = { + {0, nullptr, "SetDnsAddressesPrivate"}, + {1, nullptr, "GetDnsAddressPrivate"}, + {2, nullptr, "GetHostByName"}, + {3, nullptr, "GetHostByAddr"}, + {4, nullptr, "GetHostStringError"}, + {5, nullptr, "GetGaiStringError"}, + {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"}, + {7, nullptr, "GetNameInfo"}, + {8, nullptr, "RequestCancelHandle"}, + {9, nullptr, "CancelSocketCall"}, + }; + RegisterHandlers(functions); +} + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h new file mode 100644 index 000000000..c07cc1594 --- /dev/null +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -0,0 +1,23 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace Sockets { + +class SFDNSRES final : public ServiceFramework<SFDNSRES> { +public: + explicit SFDNSRES(); + ~SFDNSRES() = default; + +private: + void GetAddrInfo(Kernel::HLERequestContext& ctx); +}; + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp new file mode 100644 index 000000000..cedc276d9 --- /dev/null +++ b/src/core/hle/service/sockets/sockets.cpp @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/sockets/nsd.h" +#include "core/hle/service/sockets/sfdnsres.h" +#include "core/hle/service/sockets/sockets.h" + +namespace Service { +namespace Sockets { + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager); + std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager); + std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager); + std::make_shared<SFDNSRES>()->InstallAsService(service_manager); +} + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h new file mode 100644 index 000000000..7e89c8d2c --- /dev/null +++ b/src/core/hle/service/sockets/sockets.h @@ -0,0 +1,16 @@ +// Copyright 2018 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 { +namespace Sockets { + +/// Registers all Sockets services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace Sockets +} // namespace Service diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp new file mode 100644 index 000000000..cde05717a --- /dev/null +++ b/src/core/hle/service/spl/csrng.cpp @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/spl/csrng.h" + +namespace Service { +namespace SPL { + +CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") { + static const FunctionInfo functions[] = { + {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, + }; + RegisterHandlers(functions); +} + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h new file mode 100644 index 000000000..59ca794dd --- /dev/null +++ b/src/core/hle/service/spl/csrng.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/spl/module.h" + +namespace Service { +namespace SPL { + +class CSRNG final : public Module::Interface { +public: + explicit CSRNG(std::shared_ptr<Module> module); +}; + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp new file mode 100644 index 000000000..fc1bcd94c --- /dev/null +++ b/src/core/hle/service/spl/module.cpp @@ -0,0 +1,42 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstdlib> +#include <vector> +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/spl/csrng.h" +#include "core/hle/service/spl/module.h" +#include "core/hle/service/spl/spl.h" + +namespace Service { +namespace SPL { + +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) + : ServiceFramework(name), module(std::move(module)) {} + +void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + size_t size = ctx.GetWriteBufferSize(); + + std::vector<u8> data(size); + std::generate(data.begin(), data.end(), std::rand); + + ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_SPL, "called"); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto module = std::make_shared<Module>(); + std::make_shared<CSRNG>(module)->InstallAsService(service_manager); + std::make_shared<SPL>(module)->InstallAsService(service_manager); +} + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h new file mode 100644 index 000000000..12cdb2980 --- /dev/null +++ b/src/core/hle/service/spl/module.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 { +namespace SPL { + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name); + + void GetRandomBytes(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> module; + }; +}; + +/// Registers all SPL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp new file mode 100644 index 000000000..deab29b91 --- /dev/null +++ b/src/core/hle/service/spl/spl.cpp @@ -0,0 +1,41 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/spl/spl.h" + +namespace Service { +namespace SPL { + +SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") { + static const FunctionInfo functions[] = { + {0, nullptr, "GetConfig"}, + {1, nullptr, "UserExpMod"}, + {2, nullptr, "GenerateAesKek"}, + {3, nullptr, "LoadAesKey"}, + {4, nullptr, "GenerateAesKey"}, + {5, nullptr, "SetConfig"}, + {7, &SPL::GetRandomBytes, "GetRandomBytes"}, + {9, nullptr, "LoadSecureExpModKey"}, + {10, nullptr, "SecureExpMod"}, + {11, nullptr, "IsDevelopment"}, + {12, nullptr, "GenerateSpecificAesKey"}, + {13, nullptr, "DecryptPrivk"}, + {14, nullptr, "DecryptAesKey"}, + {15, nullptr, "DecryptAesCtr"}, + {16, nullptr, "ComputeCmac"}, + {17, nullptr, "LoadRsaOaepKey"}, + {18, nullptr, "UnwrapRsaOaepWrappedTitleKey"}, + {19, nullptr, "LoadTitleKey"}, + {20, nullptr, "UnwrapAesWrappedTitleKey"}, + {21, nullptr, "LockAesEngine"}, + {22, nullptr, "UnlockAesEngine"}, + {23, nullptr, "GetSplWaitEvent"}, + {24, nullptr, "SetSharedData"}, + {25, nullptr, "GetSharedData"}, + }; + RegisterHandlers(functions); +} + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h new file mode 100644 index 000000000..9fd6059af --- /dev/null +++ b/src/core/hle/service/spl/spl.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/spl/module.h" + +namespace Service { +namespace SPL { + +class SPL final : public Module::Interface { +public: + explicit SPL(std::shared_ptr<Module> module); +}; + +} // namespace SPL +} // namespace Service diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp new file mode 100644 index 000000000..afa8d5d79 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.cpp @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ssl/ssl.h" + +namespace Service { +namespace SSL { + +SSL::SSL() : ServiceFramework("ssl") {} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<SSL>()->InstallAsService(service_manager); +} + +} // namespace SSL +} // namespace Service diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h new file mode 100644 index 000000000..645dad003 --- /dev/null +++ b/src/core/hle/service/ssl/ssl.h @@ -0,0 +1,22 @@ +// Copyright 2018 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 { +namespace SSL { + +class SSL final : public ServiceFramework<SSL> { +public: + explicit SSL(); + ~SSL() = default; +}; + +/// Registers all SSL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager); + +} // namespace SSL +} // namespace Service diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index e3d58aa60..c3e46f866 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -2,14 +2,164 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <chrono> +#include "common/logging/log.h" +#include "core/core_timing.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/service/time/time.h" #include "core/hle/service/time/time_s.h" +#include "core/hle/service/time/time_u.h" namespace Service { namespace Time { +class ISystemClock final : public ServiceFramework<ISystemClock> { +public: + ISystemClock() : ServiceFramework("ISystemClock") { + static const FunctionInfo functions[] = { + {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, + {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}}; + RegisterHandlers(functions); + } + +private: + void GetCurrentTime(Kernel::HLERequestContext& ctx) { + const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count()}; + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(time_since_epoch); + } + + void GetSystemClockContext(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Time, "(STUBBED) called"); + SystemClockContext system_clock_ontext{}; + IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(system_clock_ontext); + } +}; + +class ISteadyClock final : public ServiceFramework<ISteadyClock> { +public: + ISteadyClock() : ServiceFramework("ISteadyClock") { + static const FunctionInfo functions[] = { + {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, + }; + RegisterHandlers(functions); + } + +private: + void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + SteadyClockTimePoint steady_clock_time_point{cyclesToMs(CoreTiming::GetTicks()) / 1000}; + IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(steady_clock_time_point); + } +}; + +class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { +public: + ITimeZoneService() : ServiceFramework("ITimeZoneService") { + 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"}, + {100, nullptr, "ToCalendarTime"}, + {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, + {200, nullptr, "ToPosixTime"}, + {201, nullptr, "ToPosixTimeWithMyRule"}, + }; + RegisterHandlers(functions); + } + +private: + void GetDeviceLocationName(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Time, "(STUBBED) called"); + LocationName location_name{}; + IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(location_name); + } + + void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Time, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + } + + void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Time, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 posix_time = rp.Pop<u64>(); + + LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x%016lX", posix_time); + + CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; + CalendarAdditionalInfo additional_info{}; + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_time); + rb.PushRaw(additional_info); + } +}; + +void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemClock>(); + LOG_DEBUG(Service_Time, "called"); +} + +void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemClock>(); + LOG_DEBUG(Service_Time, "called"); +} + +void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISteadyClock>(); + LOG_DEBUG(Service_Time, "called"); +} + +void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ITimeZoneService>(); + LOG_DEBUG(Service_Time, "called"); +} + +void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemClock>(); + LOG_DEBUG(Service_Time, "called"); +} + +Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) + : ServiceFramework(name), time(std::move(time)) {} + void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<TimeS>()->InstallAsService(service_manager); + auto time = std::make_shared<Module>(); + std::make_shared<TIME_S>(time)->InstallAsService(service_manager); + std::make_shared<TIME_U>(time)->InstallAsService(service_manager); } } // namespace Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index 7d0803e24..197029e7a 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -9,6 +9,60 @@ namespace Service { namespace Time { +// TODO(Rozelette) RE this structure +struct LocationName { + INSERT_PADDING_BYTES(0x24); +}; +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; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size"); + +// TODO(Rozelette) RE this structure +struct CalendarAdditionalInfo { + INSERT_PADDING_BYTES(0x18); +}; +static_assert(sizeof(CalendarAdditionalInfo) == 0x18, + "CalendarAdditionalInfo structure has incorrect size"); + +// TODO(bunnei) RE this structure +struct SystemClockContext { + INSERT_PADDING_BYTES(0x20); +}; +static_assert(sizeof(SystemClockContext) == 0x20, + "SystemClockContext structure has incorrect size"); + +struct SteadyClockTimePoint { + u64 value; + INSERT_PADDING_WORDS(4); +}; +static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); + +class Module final { +public: + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> time, const char* name); + + void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); + void GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx); + void GetStandardSteadyClock(Kernel::HLERequestContext& ctx); + void GetTimeZoneService(Kernel::HLERequestContext& ctx); + void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); + + protected: + std::shared_ptr<Module> time; + }; +}; + /// Registers all Time services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/time/time_s.cpp b/src/core/hle/service/time/time_s.cpp index 6b0597d8e..b172b2bd6 100644 --- a/src/core/hle/service/time/time_s.cpp +++ b/src/core/hle/service/time/time_s.cpp @@ -2,54 +2,18 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <chrono> -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" #include "core/hle/service/time/time_s.h" namespace Service { namespace Time { -class ISystemClock final : public ServiceFramework<ISystemClock> { -public: - ISystemClock() : ServiceFramework("ISystemClock") { - static const FunctionInfo functions[] = { - {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, - }; - RegisterHandlers(functions); - } - -private: - void GetCurrentTime(Kernel::HLERequestContext& ctx) { - const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::system_clock::now().time_since_epoch()) - .count()}; - IPC::RequestBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(time_since_epoch); - LOG_DEBUG(Service, "called"); - } -}; - -void TimeS::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { - auto client_port = std::make_shared<ISystemClock>()->CreatePort(); - auto session = client_port->Connect(); - if (session.Succeeded()) { - LOG_DEBUG(Service, "called, initialized ISystemClock -> session=%u", - (*session)->GetObjectId()); - IPC::RequestBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(std::move(session).Unwrap()); - } else { - UNIMPLEMENTED(); - } -} - -TimeS::TimeS() : ServiceFramework("time:s") { +TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:s") { static const FunctionInfo functions[] = { - {0x00000000, &TimeS::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, + {0, &TIME_S::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, + {1, &TIME_S::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"}, + {2, &TIME_S::GetStandardSteadyClock, "GetStandardSteadyClock"}, + {3, &TIME_S::GetTimeZoneService, "GetTimeZoneService"}, + {4, &TIME_S::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/time/time_s.h b/src/core/hle/service/time/time_s.h index 073227910..abc2a8c5a 100644 --- a/src/core/hle/service/time/time_s.h +++ b/src/core/hle/service/time/time_s.h @@ -4,19 +4,14 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/service.h" +#include "core/hle/service/time/time.h" namespace Service { namespace Time { -class TimeS final : public ServiceFramework<TimeS> { +class TIME_S final : public Module::Interface { public: - TimeS(); - ~TimeS() = default; - -private: - void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); + explicit TIME_S(std::shared_ptr<Module> time); }; } // namespace Time diff --git a/src/core/hle/service/time/time_u.cpp b/src/core/hle/service/time/time_u.cpp new file mode 100644 index 000000000..fc1ace325 --- /dev/null +++ b/src/core/hle/service/time/time_u.cpp @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/time/time_u.h" + +namespace Service { +namespace Time { + +TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:u") { + static const FunctionInfo functions[] = { + {0, &TIME_U::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, + {1, &TIME_U::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"}, + {2, &TIME_U::GetStandardSteadyClock, "GetStandardSteadyClock"}, + {3, &TIME_U::GetTimeZoneService, "GetTimeZoneService"}, + {4, &TIME_U::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, + }; + RegisterHandlers(functions); +} + +} // namespace Time +} // namespace Service diff --git a/src/core/hle/service/time/time_u.h b/src/core/hle/service/time/time_u.h new file mode 100644 index 000000000..f99d25057 --- /dev/null +++ b/src/core/hle/service/time/time_u.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/time.h" + +namespace Service { +namespace Time { + +class TIME_U final : public Module::Interface { +public: + explicit TIME_U(std::shared_ptr<Module> time); +}; + +} // namespace Time +} // namespace Service diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index cae2c4466..06c34e979 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -3,34 +3,46 @@ // Refer to the license.txt file included. #include <algorithm> - +#include <array> +#include <memory> +#include <boost/optional.hpp> #include "common/alignment.h" #include "common/scope_exit.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" +#include "core/hle/service/vi/vi_s.h" +#include "core/hle/service/vi/vi_u.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" namespace Service { namespace VI { -constexpr size_t SCREEN_REFRESH_RATE = 60; -constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); +struct DisplayInfo { + char display_name[0x40]{"Default"}; + u64 unknown_1{1}; + u64 unknown_2{1}; + u64 width{1920}; + u64 height{1080}; +}; +static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size"); class Parcel { public: // This default size was chosen arbitrarily. static constexpr size_t DefaultBufferSize = 0x40; Parcel() : buffer(DefaultBufferSize) {} - Parcel(std::vector<u8> data) : buffer(std::move(data)) {} + explicit Parcel(std::vector<u8> data) : buffer(std::move(data)) {} virtual ~Parcel() = default; template <typename T> T Read() { + ASSERT(read_index + sizeof(T) <= buffer.size()); T val; std::memcpy(&val, buffer.data() + read_index, sizeof(T)); read_index += sizeof(T); @@ -40,6 +52,7 @@ public: template <typename T> T ReadUnaligned() { + ASSERT(read_index + sizeof(T) <= buffer.size()); T val; std::memcpy(&val, buffer.data() + read_index, sizeof(T)); read_index += sizeof(T); @@ -47,8 +60,10 @@ public: } std::vector<u8> ReadBlock(size_t length) { - std::vector<u8> data(length); - std::memcpy(data.data(), buffer.data() + read_index, length); + ASSERT(read_index + length <= buffer.size()); + const u8* const begin = buffer.data() + read_index; + const u8* const end = begin + length; + std::vector<u8> data(begin, end); read_index += length; read_index = Common::AlignUp(read_index, 4); return data; @@ -78,7 +93,18 @@ public: write_index = Common::AlignUp(write_index, 4); } + template <typename T> + void WriteObject(const T& val) { + u32_le size = static_cast<u32>(sizeof(val)); + Write(size); + // TODO(Subv): Support file descriptors. + Write<u32_le>(0); // Fd count. + Write(val); + } + void Deserialize() { + ASSERT(buffer.size() > sizeof(Header)); + Header header{}; std::memcpy(&header, buffer.data(), sizeof(Header)); @@ -93,17 +119,19 @@ public: SerializeData(); Header header{}; + header.data_size = static_cast<u32_le>(write_index - sizeof(Header)); header.data_offset = sizeof(Header); - header.data_size = write_index - sizeof(Header); + header.objects_size = 4; + header.objects_offset = sizeof(Header) + header.data_size; std::memcpy(buffer.data(), &header, sizeof(Header)); return buffer; } protected: - virtual void SerializeData(){}; + virtual void SerializeData() {} - virtual void DeserializeData(){}; + virtual void DeserializeData() {} private: struct Header { @@ -121,7 +149,7 @@ private: class NativeWindow : public Parcel { public: - NativeWindow(u32 id) : Parcel() { + explicit NativeWindow(u32 id) : Parcel() { data.id = id; } ~NativeWindow() override = default; @@ -134,11 +162,11 @@ protected: private: struct Data { u32_le magic = 2; - u32_le process_id; + u32_le process_id = 1; u32_le id; - INSERT_PADDING_BYTES(0xC); - std::array<u8, 8> dspdrv = {'d', 's', 'p', 'd', 'r', 'v'}; - INSERT_PADDING_BYTES(8); + INSERT_PADDING_WORDS(3); + std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'}; + INSERT_PADDING_WORDS(2); }; static_assert(sizeof(Data) == 0x28, "ParcelData has wrong size"); @@ -147,12 +175,12 @@ private: class IGBPConnectRequestParcel : public Parcel { public: - IGBPConnectRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + explicit IGBPConnectRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { Deserialize(); } ~IGBPConnectRequestParcel() override = default; - void DeserializeData() { + void DeserializeData() override { std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } @@ -168,7 +196,7 @@ public: class IGBPConnectResponseParcel : public Parcel { public: - IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() { + explicit IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() { data.width = width; data.height = height; } @@ -194,16 +222,16 @@ private: class IGBPSetPreallocatedBufferRequestParcel : public Parcel { public: - IGBPSetPreallocatedBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + explicit IGBPSetPreallocatedBufferRequestParcel(const std::vector<u8>& buffer) + : Parcel(buffer) { Deserialize(); } ~IGBPSetPreallocatedBufferRequestParcel() override = default; - void DeserializeData() { + void DeserializeData() override { std::u16string token = ReadInterfaceToken(); data = Read<Data>(); - ASSERT(data.graphic_buffer_length == sizeof(IGBPBuffer)); - buffer = Read<IGBPBuffer>(); + buffer = Read<NVFlinger::IGBPBuffer>(); } struct Data { @@ -214,7 +242,7 @@ public: }; Data data; - IGBPBuffer buffer; + NVFlinger::IGBPBuffer buffer; }; class IGBPSetPreallocatedBufferResponseParcel : public Parcel { @@ -231,12 +259,12 @@ protected: class IGBPDequeueBufferRequestParcel : public Parcel { public: - IGBPDequeueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + explicit IGBPDequeueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { Deserialize(); } ~IGBPDequeueBufferRequestParcel() override = default; - void DeserializeData() { + void DeserializeData() override { std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } @@ -252,17 +280,28 @@ public: Data data; }; +struct BufferProducerFence { + u32 is_valid; + std::array<Nvidia::IoctlFence, 4> fences; +}; +static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong size"); + class IGBPDequeueBufferResponseParcel : public Parcel { public: - IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {} + explicit IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {} ~IGBPDequeueBufferResponseParcel() override = default; protected: void SerializeData() override { - Write(slot); // TODO(Subv): Find out how this Fence is used. - std::array<u32_le, 11> fence = {}; - Write(fence); + BufferProducerFence fence = {}; + fence.is_valid = 1; + for (auto& fence_ : fence.fences) + fence_.id = -1; + + Write(slot); + Write<u32_le>(1); + WriteObject(fence); Write<u32_le>(0); } @@ -271,12 +310,12 @@ protected: class IGBPRequestBufferRequestParcel : public Parcel { public: - IGBPRequestBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + explicit IGBPRequestBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { Deserialize(); } ~IGBPRequestBufferRequestParcel() override = default; - void DeserializeData() { + void DeserializeData() override { std::u16string token = ReadInterfaceToken(); slot = Read<u32_le>(); } @@ -286,51 +325,64 @@ public: class IGBPRequestBufferResponseParcel : public Parcel { public: - IGBPRequestBufferResponseParcel(IGBPBuffer buffer) : Parcel(), buffer(buffer) {} + explicit IGBPRequestBufferResponseParcel(NVFlinger::IGBPBuffer buffer) + : Parcel(), buffer(buffer) {} ~IGBPRequestBufferResponseParcel() override = default; protected: void SerializeData() override { - // TODO(Subv): Find out what this all means + // TODO(Subv): Figure out what this value means, writing non-zero here will make libnx try + // to read an IGBPBuffer object from the parcel. Write<u32_le>(1); - - Write<u32_le>(sizeof(IGBPBuffer)); - Write<u32_le>(0); // Unknown - - Write(buffer); - + WriteObject(buffer); Write<u32_le>(0); } - IGBPBuffer buffer; + NVFlinger::IGBPBuffer buffer; }; class IGBPQueueBufferRequestParcel : public Parcel { public: - IGBPQueueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + explicit IGBPQueueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { Deserialize(); } ~IGBPQueueBufferRequestParcel() override = default; - void DeserializeData() { + void DeserializeData() override { std::u16string token = ReadInterfaceToken(); data = Read<Data>(); } + struct Fence { + u32_le id; + u32_le value; + }; + static_assert(sizeof(Fence) == 8, "Fence has wrong size"); + struct Data { u32_le slot; - INSERT_PADDING_WORDS(2); + INSERT_PADDING_WORDS(3); u32_le timestamp; - INSERT_PADDING_WORDS(20); + s32_le is_auto_timestamp; + s32_le crop_left; + s32_le crop_top; + s32_le crop_right; + s32_le crop_bottom; + s32_le scaling_mode; + NVFlinger::BufferQueue::BufferTransformFlags transform; + u32_le sticky_transform; + INSERT_PADDING_WORDS(2); + u32_le fence_is_valid; + std::array<Fence, 2> fences; }; - static_assert(sizeof(Data) == 96, "ParcelData has wrong size"); + static_assert(sizeof(Data) == 80, "ParcelData has wrong size"); Data data; }; class IGBPQueueBufferResponseParcel : public Parcel { public: - IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() { + explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() { data.width = width; data.height = height; } @@ -354,15 +406,44 @@ private: Data data{}; }; +class IGBPQueryRequestParcel : public Parcel { +public: + explicit IGBPQueryRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) { + Deserialize(); + } + ~IGBPQueryRequestParcel() override = default; + + void DeserializeData() override { + std::u16string token = ReadInterfaceToken(); + type = Read<u32_le>(); + } + + u32 type; +}; + +class IGBPQueryResponseParcel : public Parcel { +public: + explicit IGBPQueryResponseParcel(u32 value) : Parcel(), value(value) {} + ~IGBPQueryResponseParcel() override = default; + +protected: + void SerializeData() override { + Write(value); + } + +private: + u32_le value; +}; + class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> { public: - IHOSBinderDriver(std::shared_ptr<NVFlinger> nv_flinger) + explicit IHOSBinderDriver(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) : ServiceFramework("IHOSBinderDriver"), nv_flinger(std::move(nv_flinger)) { static const FunctionInfo functions[] = { {0, &IHOSBinderDriver::TransactParcel, "TransactParcel"}, {1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"}, {2, &IHOSBinderDriver::GetNativeHandle, "GetNativeHandle"}, - {3, nullptr, "TransactParcelAuto"}, + {3, &IHOSBinderDriver::TransactParcel, "TransactParcelAuto"}, }; RegisterHandlers(functions); } @@ -391,64 +472,76 @@ private: u32 id = rp.Pop<u32>(); auto transaction = static_cast<TransactionId>(rp.Pop<u32>()); u32 flags = rp.Pop<u32>(); - - auto& input_buffer = ctx.BufferDescriptorA()[0]; - std::vector<u8> input_data(input_buffer.Size()); - Memory::ReadBlock(input_buffer.Address(), input_data.data(), input_buffer.Size()); - - auto& output_buffer = ctx.BufferDescriptorB()[0]; - auto buffer_queue = nv_flinger->GetBufferQueue(id); + LOG_DEBUG(Service_VI, "called, transaction=%x", static_cast<u32>(transaction)); + if (transaction == TransactionId::Connect) { - IGBPConnectRequestParcel request{input_data}; + IGBPConnectRequestParcel request{ctx.ReadBuffer()}; IGBPConnectResponseParcel response{1280, 720}; - auto response_buffer = response.Serialize(); - Memory::WriteBlock(output_buffer.Address(), response_buffer.data(), - output_buffer.Size()); + ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::SetPreallocatedBuffer) { - IGBPSetPreallocatedBufferRequestParcel request{input_data}; + IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()}; buffer_queue->SetPreallocatedBuffer(request.data.slot, request.buffer); IGBPSetPreallocatedBufferResponseParcel response{}; - auto response_buffer = response.Serialize(); - Memory::WriteBlock(output_buffer.Address(), response_buffer.data(), - output_buffer.Size()); + ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::DequeueBuffer) { - IGBPDequeueBufferRequestParcel request{input_data}; - - u32 slot = buffer_queue->DequeueBuffer(request.data.pixel_format, request.data.width, - request.data.height); - - IGBPDequeueBufferResponseParcel response{slot}; - auto response_buffer = response.Serialize(); - Memory::WriteBlock(output_buffer.Address(), response_buffer.data(), - output_buffer.Size()); + IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; + const u32 width{request.data.width}; + const u32 height{request.data.height}; + boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); + + if (slot != boost::none) { + // Buffer is available + IGBPDequeueBufferResponseParcel response{*slot}; + ctx.WriteBuffer(response.Serialize()); + } else { + // Wait the current thread until a buffer becomes available + auto wait_event = ctx.SleepClientThread( + Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1, + [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, + ThreadWakeupReason reason) { + // Repeat TransactParcel DequeueBuffer when a buffer is available + auto buffer_queue = nv_flinger->GetBufferQueue(id); + boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); + IGBPDequeueBufferResponseParcel response{*slot}; + ctx.WriteBuffer(response.Serialize()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + }); + buffer_queue->SetBufferWaitEvent(std::move(wait_event)); + } } else if (transaction == TransactionId::RequestBuffer) { - IGBPRequestBufferRequestParcel request{input_data}; + IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; auto& buffer = buffer_queue->RequestBuffer(request.slot); IGBPRequestBufferResponseParcel response{buffer}; - auto response_buffer = response.Serialize(); - Memory::WriteBlock(output_buffer.Address(), response_buffer.data(), - output_buffer.Size()); + ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::QueueBuffer) { - IGBPQueueBufferRequestParcel request{input_data}; + IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()}; - buffer_queue->QueueBuffer(request.data.slot); + buffer_queue->QueueBuffer(request.data.slot, request.data.transform); IGBPQueueBufferResponseParcel response{1280, 720}; - auto response_buffer = response.Serialize(); - Memory::WriteBlock(output_buffer.Address(), response_buffer.data(), - output_buffer.Size()); + ctx.WriteBuffer(response.Serialize()); + } else if (transaction == TransactionId::Query) { + IGBPQueryRequestParcel request{ctx.ReadBuffer()}; + + u32 value = + buffer_queue->Query(static_cast<NVFlinger::BufferQueue::QueryType>(request.type)); + + IGBPQueryResponseParcel response{value}; + ctx.WriteBuffer(response.Serialize()); + } else if (transaction == TransactionId::CancelBuffer) { + LOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); } else { ASSERT_MSG(false, "Unimplemented"); } - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -458,8 +551,8 @@ private: s32 addval = rp.PopRaw<s32>(); u32 type = rp.Pop<u32>(); - LOG_WARNING(Service, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type); - IPC::RequestBuilder rb{ctx, 2}; + LOG_WARNING(Service_VI, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -472,14 +565,14 @@ private: // TODO(Subv): Find out what this actually is. - LOG_WARNING(Service, "(STUBBED) called id=%u, unknown=%08X", id, unknown); - IPC::RequestBuilder rb{ctx, 2, 1}; + LOG_WARNING(Service_VI, "(STUBBED) called id=%u, unknown=%08X", id, unknown); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_queue->GetNativeHandle()); } - std::shared_ptr<NVFlinger> nv_flinger; -}; + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; +}; // namespace VI class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { public: @@ -494,19 +587,19 @@ public: private: void SetLayerZ(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); u64 z_value = rp.Pop<u64>(); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(RESULT_SUCCESS); } }; class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { public: - IManagerDisplayService(std::shared_ptr<NVFlinger> nv_flinger) + explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) : ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) { static const FunctionInfo functions[] = { {1020, &IManagerDisplayService::CloseDisplay, "CloseDisplay"}, @@ -520,16 +613,16 @@ public: private: void CloseDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u64 display = rp.Pop<u64>(); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(RESULT_SUCCESS); } void CreateManagedLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u32 unknown = rp.Pop<u32>(); rp.Skip(1, false); @@ -538,163 +631,173 @@ private: u64 layer_id = nv_flinger->CreateLayer(display); - IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0); + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); rb.Push(RESULT_SUCCESS); rb.Push(layer_id); } void AddToLayerStack(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_WARNING(Service_VI, "(STUBBED) called"); IPC::RequestParser rp{ctx}; u32 stack = rp.Pop<u32>(); u64 layer_id = rp.Pop<u64>(); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); rb.Push(RESULT_SUCCESS); } - std::shared_ptr<NVFlinger> nv_flinger; + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; -void IApplicationDisplayService::GetRelayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); -} +class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { +public: + IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~IApplicationDisplayService() = default; -void IApplicationDisplayService::GetSystemDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); +private: + void GetRelayService(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemDisplayService>(); -} + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + } -void IApplicationDisplayService::GetManagerDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + void GetSystemDisplayService(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IManagerDisplayService>(nv_flinger); -} + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISystemDisplayService>(); + } -void IApplicationDisplayService::GetIndirectDisplayTransactionService( - Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + void GetManagerDisplayService(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); -} + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IManagerDisplayService>(nv_flinger); + } -void IApplicationDisplayService::OpenDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); - auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); - std::string name(name_buf.begin(), end); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger); + } - ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); + void OpenDisplay(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); + auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); - IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(nv_flinger->OpenDisplay(name)); -} + std::string name(name_buf.begin(), end); -void IApplicationDisplayService::CloseDisplay(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u64 display_id = rp.Pop<u64>(); + ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); - IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0); - rb.Push(RESULT_SUCCESS); -} + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(nv_flinger->OpenDisplay(name)); + } -void IApplicationDisplayService::OpenLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); - auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + void CloseDisplay(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u64 display_id = rp.Pop<u64>(); - std::string display_name(name_buf.begin(), end); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } - u64 layer_id = rp.Pop<u64>(); - u64 aruid = rp.Pop<u64>(); + void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u32 scaling_mode = rp.Pop<u32>(); + u64 unknown = rp.Pop<u64>(); - auto& buffer = ctx.BufferDescriptorB()[0]; + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } - u64 display_id = nv_flinger->OpenDisplay(display_name); - u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); + void ListDisplays(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + DisplayInfo display_info; + ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(1); + LOG_WARNING(Service_VI, "(STUBBED) called"); + } - NativeWindow native_window{buffer_queue_id}; - auto data = native_window.Serialize(); - Memory::WriteBlock(buffer.Address(), data.data(), data.size()); + void OpenLayer(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + IPC::RequestParser rp{ctx}; + auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); + auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); - IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(data.size()); -} + std::string display_name(name_buf.begin(), end); -void IApplicationDisplayService::CreateStrayLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + u64 layer_id = rp.Pop<u64>(); + u64 aruid = rp.Pop<u64>(); - IPC::RequestParser rp{ctx}; - u32 flags = rp.Pop<u32>(); - u64 display_id = rp.Pop<u64>(); + u64 display_id = nv_flinger->OpenDisplay(display_name); + u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); - auto& buffer = ctx.BufferDescriptorB()[0]; + NativeWindow native_window{buffer_queue_id}; + IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); + } - // TODO(Subv): What's the difference between a Stray and a Managed layer? + void CreateStrayLayer(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); - u64 layer_id = nv_flinger->CreateLayer(display_id); - u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); + IPC::RequestParser rp{ctx}; + u32 flags = rp.Pop<u32>(); + rp.Pop<u32>(); // padding + u64 display_id = rp.Pop<u64>(); - NativeWindow native_window{buffer_queue_id}; - auto data = native_window.Serialize(); - Memory::WriteBlock(buffer.Address(), data.data(), data.size()); + // TODO(Subv): What's the difference between a Stray and a Managed layer? - IPC::RequestBuilder rb = rp.MakeBuilder(6, 0, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.Push(layer_id); - rb.Push<u64>(data.size()); -} + u64 layer_id = nv_flinger->CreateLayer(display_id); + u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); -void IApplicationDisplayService::DestroyStrayLayer(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + NativeWindow native_window{buffer_queue_id}; + IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(layer_id); + rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); + } - IPC::RequestParser rp{ctx}; - u64 layer_id = rp.Pop<u64>(); + void DestroyStrayLayer(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); - rb.Push(RESULT_SUCCESS); -} + IPC::RequestParser rp{ctx}; + u64 layer_id = rp.Pop<u64>(); -void IApplicationDisplayService::SetLayerScalingMode(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u32 scaling_mode = rp.Pop<u32>(); - u64 unknown = rp.Pop<u64>(); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + rb.Push(RESULT_SUCCESS); + } - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0); - rb.Push(RESULT_SUCCESS); -} + void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u64 display_id = rp.Pop<u64>(); -void IApplicationDisplayService::GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u64 display_id = rp.Pop<u64>(); + auto vsync_event = nv_flinger->GetVsyncEvent(display_id); - auto vsync_event = nv_flinger->GetVsyncEvent(display_id); + IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0); + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(vsync_event); + } - IPC::RequestBuilder rb = rp.MakeBuilder(2, 1, 0, 0); - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(vsync_event); -} + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; +}; -IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger> nv_flinger) +IApplicationDisplayService::IApplicationDisplayService( + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) : ServiceFramework("IApplicationDisplayService"), nv_flinger(std::move(nv_flinger)) { static const FunctionInfo functions[] = { {100, &IApplicationDisplayService::GetRelayService, "GetRelayService"}, @@ -702,7 +805,7 @@ IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger {102, &IApplicationDisplayService::GetManagerDisplayService, "GetManagerDisplayService"}, {103, &IApplicationDisplayService::GetIndirectDisplayTransactionService, "GetIndirectDisplayTransactionService"}, - {1000, nullptr, "ListDisplays"}, + {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"}, {1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"}, {1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"}, {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, @@ -714,212 +817,24 @@ IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger RegisterHandlers(functions); } -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<VI_M>()->InstallAsService(service_manager); -} - -NVFlinger::NVFlinger() { - // Add the different displays to the list of displays. - Display default_{0, "Default"}; - Display external{1, "External"}; - Display edid{2, "Edid"}; - Display internal{3, "Internal"}; - - displays.emplace_back(default_); - displays.emplace_back(external); - displays.emplace_back(edid); - displays.emplace_back(internal); - - // Schedule the screen composition events - composition_event = - CoreTiming::RegisterEvent("ScreenCompositioin", [this](u64 userdata, int cycles_late) { - Compose(); - CoreTiming::ScheduleEvent(frame_ticks - cycles_late, composition_event); - }); - - CoreTiming::ScheduleEvent(frame_ticks, composition_event); -} - -NVFlinger::~NVFlinger() { - CoreTiming::UnscheduleEvent(composition_event, 0); -} - -u64 NVFlinger::OpenDisplay(const std::string& name) { - LOG_WARNING(Service, "Opening display %s", name.c_str()); +Module::Interface::Interface(std::shared_ptr<Module> module, const char* name, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {} - // TODO(Subv): Currently we only support the Default display. - ASSERT(name == "Default"); - - auto itr = std::find_if(displays.begin(), displays.end(), - [&](const Display& display) { return display.name == name; }); - - ASSERT(itr != displays.end()); - - return itr->id; -} - -u64 NVFlinger::CreateLayer(u64 display_id) { - auto& display = GetDisplay(display_id); - - ASSERT_MSG(display.layers.empty(), "Only one layer is supported per display at the moment"); - - u64 layer_id = next_layer_id++; - u32 buffer_queue_id = next_buffer_queue_id++; - auto buffer_queue = std::make_shared<BufferQueue>(buffer_queue_id, layer_id); - display.layers.emplace_back(layer_id, buffer_queue); - buffer_queues.emplace_back(std::move(buffer_queue)); - return layer_id; -} - -u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) { - const auto& layer = GetLayer(display_id, layer_id); - return layer.buffer_queue->GetId(); -} +void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); -Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) { - const auto& display = GetDisplay(display_id); - return display.vsync_event; -} - -std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const { - auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [&](const auto& queue) { return queue->GetId() == id; }); - - ASSERT(itr != buffer_queues.end()); - return *itr; -} - -Display& NVFlinger::GetDisplay(u64 display_id) { - auto itr = std::find_if(displays.begin(), displays.end(), - [&](const Display& display) { return display.id == display_id; }); - - ASSERT(itr != displays.end()); - return *itr; -} - -Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) { - auto& display = GetDisplay(display_id); - - auto itr = std::find_if(display.layers.begin(), display.layers.end(), - [&](const Layer& layer) { return layer.id == layer_id; }); - - ASSERT(itr != display.layers.end()); - return *itr; -} - -void NVFlinger::Compose() { - for (auto& display : displays) { - // Trigger vsync for this display at the end of drawing - SCOPE_EXIT({ display.vsync_event->Signal(); }); - - // Don't do anything for displays without layers. - if (display.layers.empty()) - continue; - - // TODO(Subv): Support more than 1 layer. - ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported"); - - Layer& layer = display.layers[0]; - auto& buffer_queue = layer.buffer_queue; - - // Search for a queued buffer and acquire it - auto buffer = buffer_queue->AcquireBuffer(); - - if (buffer == boost::none) { - // There was no queued buffer to draw, render previous frame - VideoCore::g_renderer->SwapBuffers({}); - continue; - } - - auto& igbp_buffer = buffer->igbp_buffer; - - // Now send the buffer to the GPU for drawing. - auto nvdrv = Nvidia::nvdrv.lock(); - ASSERT(nvdrv); - - // TODO(Subv): Support more than just disp0. The display device selection is probably based - // on which display we're drawing (Default, Internal, External, etc) - auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); - ASSERT(nvdisp); - - nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format, - igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride); - - buffer_queue->ReleaseBuffer(buffer->slot); - } -} - -BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { - native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle"); -} - -void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) { - Buffer buffer{}; - buffer.slot = slot; - buffer.igbp_buffer = igbp_buffer; - buffer.status = Buffer::Status::Free; - - LOG_WARNING(Service, "Adding graphics buffer %u", slot); - - queue.emplace_back(buffer); -} - -u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) { - auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { - // Only consider free buffers. Buffers become free once again after they've been Acquired - // and Released by the compositor, see the NVFlinger::Compose method. - if (buffer.status != Buffer::Status::Free) - return false; - - // Make sure that the parameters match. - auto& igbp_buffer = buffer.igbp_buffer; - return igbp_buffer.format == pixel_format && igbp_buffer.width == width && - igbp_buffer.height == height; - }); - ASSERT(itr != queue.end()); - - itr->status = Buffer::Status::Dequeued; - return itr->slot; -} - -const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - return itr->igbp_buffer; -} - -void BufferQueue::QueueBuffer(u32 slot) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - itr->status = Buffer::Status::Queued; -} - -boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() { - auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) { - return buffer.status == Buffer::Status::Queued; - }); - if (itr == queue.end()) - return boost::none; - itr->status = Buffer::Status::Acquired; - return *itr; -} - -void BufferQueue::ReleaseBuffer(u32 slot) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Acquired); - itr->status = Buffer::Status::Free; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); } -Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {} - -Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { - vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event"); +void InstallInterfaces(SM::ServiceManager& service_manager, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) { + auto module = std::make_shared<Module>(); + std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager); } } // namespace VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index 81d4f3daa..985c9d27c 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -4,9 +4,7 @@ #pragma once -#include <memory> -#include <boost/optional.hpp> -#include "core/hle/kernel/event.h" +#include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/service.h" namespace CoreTiming { @@ -16,147 +14,24 @@ struct EventType; namespace Service { namespace VI { -struct IGBPBuffer { - u32_le magic; - u32_le width; - u32_le height; - u32_le stride; - u32_le format; - u32_le usage; - INSERT_PADDING_WORDS(1); - u32_le index; - INSERT_PADDING_WORDS(3); - u32_le gpu_buffer_id; - INSERT_PADDING_WORDS(17); - u32_le nvmap_handle; - u32_le offset; - INSERT_PADDING_WORDS(60); -}; - -static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size"); - -class BufferQueue { +class Module final { public: - BufferQueue(u32 id, u64 layer_id); - ~BufferQueue() = default; + class Interface : public ServiceFramework<Interface> { + public: + Interface(std::shared_ptr<Module> module, const char* name, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - struct Buffer { - enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 }; + void GetDisplayService(Kernel::HLERequestContext& ctx); - u32 slot; - Status status = Status::Free; - IGBPBuffer igbp_buffer; + protected: + std::shared_ptr<Module> module; + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; - - void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer); - u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height); - const IGBPBuffer& RequestBuffer(u32 slot) const; - void QueueBuffer(u32 slot); - boost::optional<const Buffer&> AcquireBuffer(); - void ReleaseBuffer(u32 slot); - - u32 GetId() const { - return id; - } - - Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const { - return native_handle; - } - -private: - u32 id; - u64 layer_id; - - std::vector<Buffer> queue; - Kernel::SharedPtr<Kernel::Event> native_handle; -}; - -struct Layer { - Layer(u64 id, std::shared_ptr<BufferQueue> queue); - ~Layer() = default; - - u64 id; - std::shared_ptr<BufferQueue> buffer_queue; -}; - -struct Display { - Display(u64 id, std::string name); - ~Display() = default; - - u64 id; - std::string name; - - std::vector<Layer> layers; - Kernel::SharedPtr<Kernel::Event> vsync_event; -}; - -class NVFlinger { -public: - NVFlinger(); - ~NVFlinger(); - - /// Opens the specified display and returns the id. - u64 OpenDisplay(const std::string& name); - - /// Creates a layer on the specified display and returns the layer id. - u64 CreateLayer(u64 display_id); - - /// Gets the buffer queue id of the specified layer in the specified display. - u32 GetBufferQueueId(u64 display_id, u64 layer_id); - - /// Gets the vsync event for the specified display. - Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id); - - /// Obtains a buffer queue identified by the id. - std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const; - - /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when - /// finished. - void Compose(); - -private: - /// Returns the display identified by the specified id. - Display& GetDisplay(u64 display_id); - - /// Returns the layer identified by the specified id in the desired display. - Layer& GetLayer(u64 display_id, u64 layer_id); - - std::vector<Display> displays; - std::vector<std::shared_ptr<BufferQueue>> buffer_queues; - - /// Id to use for the next layer that is created, this counter is shared among all displays. - u64 next_layer_id = 1; - /// Id to use for the next buffer queue that is created, this counter is shared among all - /// layers. - u32 next_buffer_queue_id = 1; - - /// CoreTiming event that handles screen composition. - CoreTiming::EventType* composition_event; -}; - -class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> { -public: - IApplicationDisplayService(std::shared_ptr<NVFlinger> nv_flinger); - ~IApplicationDisplayService() = default; - -private: - void GetRelayService(Kernel::HLERequestContext& ctx); - void GetSystemDisplayService(Kernel::HLERequestContext& ctx); - void GetManagerDisplayService(Kernel::HLERequestContext& ctx); - void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx); - void OpenDisplay(Kernel::HLERequestContext& ctx); - void CloseDisplay(Kernel::HLERequestContext& ctx); - void SetLayerScalingMode(Kernel::HLERequestContext& ctx); - void OpenLayer(Kernel::HLERequestContext& ctx); - void CreateStrayLayer(Kernel::HLERequestContext& ctx); - void DestroyStrayLayer(Kernel::HLERequestContext& ctx); - void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger> nv_flinger; }; /// Registers all VI services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); } // namespace VI } // namespace Service diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 1a5a28b0d..5781fa9ec 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -2,28 +2,18 @@ // 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/vi/vi.h" #include "core/hle/service/vi/vi_m.h" namespace Service { namespace VI { -void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); - - IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); -} - -VI_M::VI_M() : ServiceFramework("vi:m") { +VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, }; RegisterHandlers(functions); - nv_flinger = std::make_shared<NVFlinger>(); } } // namespace VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index 70ff7a2f3..0f7b799d6 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -4,21 +4,14 @@ #pragma once -#include <memory> -#include "core/hle/service/service.h" +#include "core/hle/service/vi/vi.h" namespace Service { namespace VI { -class VI_M final : public ServiceFramework<VI_M> { +class VI_M final : public Module::Interface { public: - VI_M(); - ~VI_M() = default; - -private: - void GetDisplayService(Kernel::HLERequestContext& ctx); - - std::shared_ptr<NVFlinger> nv_flinger; + explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); }; } // namespace VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp new file mode 100644 index 000000000..1f937b2a8 --- /dev/null +++ b/src/core/hle/service/vi/vi_s.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/vi/vi_s.h" + +namespace Service { +namespace VI { + +VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) { + static const FunctionInfo functions[] = { + {1, &VI_S::GetDisplayService, "GetDisplayService"}, + {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, + }; + RegisterHandlers(functions); +} + +} // namespace VI +} // namespace Service diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h new file mode 100644 index 000000000..7b32fdddc --- /dev/null +++ b/src/core/hle/service/vi/vi_s.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/vi/vi.h" + +namespace Service { +namespace VI { + +class VI_S final : public Module::Interface { +public: + explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); +}; + +} // namespace VI +} // namespace Service diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp new file mode 100644 index 000000000..14e375b86 --- /dev/null +++ b/src/core/hle/service/vi/vi_u.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/vi/vi_u.h" + +namespace Service { +namespace VI { + +VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) { + static const FunctionInfo functions[] = { + {0, &VI_U::GetDisplayService, "GetDisplayService"}, + {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, + }; + RegisterHandlers(functions); +} + +} // namespace VI +} // namespace Service diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h new file mode 100644 index 000000000..c557a2235 --- /dev/null +++ b/src/core/hle/service/vi/vi_u.h @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/vi/vi.h" + +namespace Service { +namespace VI { + +class VI_U final : public Module::Interface { +public: + explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); +}; + +} // namespace VI +} // namespace Service diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 9ce8af961..bba4a0715 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -82,4 +82,4 @@ void Init() { CoreTiming::ScheduleEvent(0, update_time_event); } -} // namespace +} // namespace SharedPage diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 864695ae1..a58259888 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -66,4 +66,4 @@ extern SharedPageDef shared_page; void Init(); -} // namespace +} // namespace SharedPage diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index a751b1d62..0db604c76 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -91,4 +91,4 @@ void Shutdown() { LCD::Shutdown(); LOG_DEBUG(HW, "shutdown OK"); } -} +} // namespace HW diff --git a/src/core/hw/hw.h b/src/core/hw/hw.h index a3c5d2ea3..5890d2b5c 100644 --- a/src/core/hw/hw.h +++ b/src/core/hw/hw.h @@ -47,4 +47,4 @@ void Init(); /// Shutdown hardware void Shutdown(); -} // namespace +} // namespace HW diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp index 763ac1c4d..690079b65 100644 --- a/src/core/hw/lcd.cpp +++ b/src/core/hw/lcd.cpp @@ -64,4 +64,4 @@ void Shutdown() { LOG_DEBUG(HW_LCD, "shutdown OK"); } -} // namespace +} // namespace LCD diff --git a/src/core/hw/lcd.h b/src/core/hw/lcd.h index 191fd44af..d2db9700f 100644 --- a/src/core/hw/lcd.h +++ b/src/core/hw/lcd.h @@ -83,4 +83,4 @@ void Init(); /// Shutdown hardware void Shutdown(); -} // namespace +} // namespace LCD diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp new file mode 100644 index 000000000..aa09ed323 --- /dev/null +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -0,0 +1,183 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include "common/common_funcs.h" +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/file_sys/romfs_factory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/deconstructed_rom_directory.h" +#include "core/loader/nso.h" +#include "core/memory.h" + +namespace Loader { + +static std::string FindRomFS(const std::string& directory) { + std::string filepath_romfs; + const auto callback = [&filepath_romfs](unsigned*, const std::string& directory, + const std::string& virtual_name) -> bool { + const std::string physical_name = directory + virtual_name; + if (FileUtil::IsDirectory(physical_name)) { + // Skip directories + return true; + } + + // Verify extension + const std::string extension = physical_name.substr(physical_name.find_last_of(".") + 1); + if (Common::ToLower(extension) != "romfs") { + return true; + } + + // Found it - we are done + filepath_romfs = std::move(physical_name); + return false; + }; + + // Search the specified directory recursively, looking for the first .romfs file, which will + // be used for the RomFS + FileUtil::ForeachDirectoryEntry(nullptr, directory, callback); + + return filepath_romfs; +} + +AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, + std::string filepath) + : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + +FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file, + const std::string& filepath) { + bool is_main_found{}; + bool is_npdm_found{}; + bool is_rtld_found{}; + bool is_sdk_found{}; + + const auto callback = [&](unsigned* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + // Skip directories + std::string physical_name = directory + virtual_name; + if (FileUtil::IsDirectory(physical_name)) { + return true; + } + + // Verify filename + if (Common::ToLower(virtual_name) == "main") { + is_main_found = true; + } else if (Common::ToLower(virtual_name) == "main.npdm") { + is_npdm_found = true; + return true; + } else if (Common::ToLower(virtual_name) == "rtld") { + is_rtld_found = true; + } else if (Common::ToLower(virtual_name) == "sdk") { + is_sdk_found = true; + } else { + // Contrinue searching + return true; + } + + // Verify file is an NSO + FileUtil::IOFile file(physical_name, "rb"); + if (AppLoader_NSO::IdentifyType(file, physical_name) != FileType::NSO) { + return false; + } + + // We are done if we've found and verified all required NSOs + return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found); + }; + + // Search the directory recursively, looking for the required modules + const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; + FileUtil::ForeachDirectoryEntry(nullptr, directory, callback); + + if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) { + return FileType::DeconstructedRomDirectory; + } + + return FileType::Error; +} + +ResultStatus AppLoader_DeconstructedRomDirectory::Load( + Kernel::SharedPtr<Kernel::Process>& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + if (!file.IsOpen()) { + return ResultStatus::Error; + } + + const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; + const std::string npdm_path = directory + DIR_SEP + "main.npdm"; + + ResultStatus result = metadata.Load(npdm_path); + if (result != ResultStatus::Success) { + return result; + } + metadata.Print(); + + // Load NSO modules + VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; + for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", + "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { + const std::string path = directory + DIR_SEP + module; + const VAddr load_addr = next_load_addr; + next_load_addr = AppLoader_NSO::LoadModule(path, load_addr); + if (next_load_addr) { + LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, module, load_addr); + } else { + next_load_addr = load_addr; + } + } + + process->program_id = metadata.GetTitleID(); + process->svc_access_mask.set(); + process->address_mappings = default_address_mappings; + process->resource_limit = + Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); + process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), + metadata.GetMainThreadStackSize()); + + // Find the RomFS by searching for a ".romfs" file in this directory + filepath_romfs = FindRomFS(directory); + + // Register the RomFS if a ".romfs" file was found + if (!filepath_romfs.empty()) { + Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this), + Service::FileSystem::Type::RomFS); + } + + is_loaded = true; + return ResultStatus::Success; +} + +ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS( + std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) { + + if (filepath_romfs.empty()) { + LOG_DEBUG(Loader, "No RomFS available"); + return ResultStatus::ErrorNotUsed; + } + + // We reopen the file, to allow its position to be independent + romfs_file = std::make_shared<FileUtil::IOFile>(filepath_romfs, "rb"); + if (!romfs_file->IsOpen()) { + return ResultStatus::Error; + } + + offset = 0; + size = romfs_file->GetSize(); + + LOG_DEBUG(Loader, "RomFS offset: 0x%016" PRIX64, offset); + LOG_DEBUG(Loader, "RomFS size: 0x%016" PRIX64, size); + + // Reset read pointer + file.Seek(0, SEEK_SET); + + return ResultStatus::Success; +} + +} // namespace Loader diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h new file mode 100644 index 000000000..23295d911 --- /dev/null +++ b/src/core/loader/deconstructed_rom_directory.h @@ -0,0 +1,48 @@ +// Copyright 2018 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/program_metadata.h" +#include "core/hle/kernel/kernel.h" +#include "core/loader/loader.h" + +namespace Loader { + +/** + * This class loads a "deconstructed ROM directory", which are the typical format we see for Switch + * game dumps. The path should be a "main" NSO, which must be in a directory that contains the other + * standard ExeFS NSOs (e.g. rtld, sdk, etc.). It will automatically find and load these. + * Furthermore, it will look for the first .romfs file (optionally) and use this for the RomFS. + */ +class AppLoader_DeconstructedRomDirectory final : public AppLoader { +public: + AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, std::string filepath); + + /** + * Returns the type of the file + * @param file FileUtil::IOFile open file + * @param filepath Path of the file that we are opening. + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); + + FileType GetFileType() override { + return IdentifyType(file, filepath); + } + + ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; + + ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, + u64& size) override; + +private: + std::string filepath_romfs; + std::string filepath; + FileSys::ProgramMetadata metadata; +}; + +} // namespace Loader diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 9ba913dbe..e9f462196 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -300,7 +300,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { std::vector<u8> program_image(total_image_size); size_t current_image_position = 0; - SharedPtr<CodeSet> codeset = CodeSet::Create("", 0); + SharedPtr<CodeSet> codeset = CodeSet::Create(""); for (unsigned int i = 0; i < header->e_phnum; ++i) { Elf32_Phdr* p = &segments[i]; @@ -323,8 +323,9 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { } if (codeset_segment->size != 0) { - LOG_ERROR(Loader, "ELF has more than one segment of the same type. Skipping extra " - "segment (id %i)", + LOG_ERROR(Loader, + "ELF has more than one segment of the same type. Skipping extra " + "segment (id %i)", i); continue; } @@ -364,7 +365,10 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const namespace Loader { -FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file) { +AppLoader_ELF::AppLoader_ELF(FileUtil::IOFile&& file, std::string filename) + : AppLoader(std::move(file)), filename(std::move(filename)) {} + +FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file, const std::string&) { static constexpr u16 ELF_MACHINE_ARM{0x28}; u32 magic = 0; @@ -402,7 +406,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); codeset->name = filename; - process = Kernel::Process::Create("main"); process->LoadModule(codeset, codeset->entrypoint); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; @@ -411,7 +414,7 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(codeset->entrypoint, 48, Kernel::DEFAULT_STACK_SIZE); + process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 113da5917..ee741a789 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -16,18 +16,18 @@ namespace Loader { /// Loads an ELF/AXF file class AppLoader_ELF final : public AppLoader { public: - AppLoader_ELF(FileUtil::IOFile&& file, std::string filename) - : AppLoader(std::move(file)), filename(std::move(filename)) {} + AppLoader_ELF(FileUtil::IOFile&& file, std::string filename); /** * Returns the type of the file * @param file FileUtil::IOFile open file + * @param filepath Path of the file that we are opening. * @return FileType found, or FileType::Error if this loader doesn't know it */ - static FileType IdentifyType(FileUtil::IOFile& file); + static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); FileType GetFileType() override { - return IdentifyType(file); + return IdentifyType(file, filename); } ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp index 87cc65e91..69198e3e3 100644 --- a/src/core/loader/linker.cpp +++ b/src/core/loader/linker.cpp @@ -84,7 +84,7 @@ void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector< } break; default: - LOG_CRITICAL(Loader, "Unknown relocation type: %d", rela.type); + LOG_CRITICAL(Loader, "Unknown relocation type: %d", static_cast<int>(rela.type)); break; } } diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 92defd381..2ec08506d 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,12 +7,11 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "core/hle/kernel/process.h" +#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" #include "core/loader/nro.h" #include "core/loader/nso.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// - namespace Loader { const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { @@ -21,14 +20,15 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { {0x1F000000, 0x600000, false}, // entire VRAM }; -FileType IdentifyFile(FileUtil::IOFile& file) { +FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) { FileType type; #define CHECK_TYPE(loader) \ - type = AppLoader_##loader::IdentifyType(file); \ + type = AppLoader_##loader::IdentifyType(file, filepath); \ if (FileType::Error != type) \ return type; + CHECK_TYPE(DeconstructedRomDirectory) CHECK_TYPE(ELF) CHECK_TYPE(NSO) CHECK_TYPE(NRO) @@ -45,13 +45,13 @@ FileType IdentifyFile(const std::string& file_name) { return FileType::Unknown; } - return IdentifyFile(file); + return IdentifyFile(file, file_name); } FileType GuessFromExtension(const std::string& extension_) { std::string extension = Common::ToLower(extension_); - if (extension == ".elf" || extension == ".axf") + if (extension == ".elf") return FileType::ELF; else if (extension == ".nro") return FileType::NRO; @@ -69,6 +69,8 @@ const char* GetFileTypeString(FileType type) { return "NRO"; case FileType::NSO: return "NSO"; + case FileType::DeconstructedRomDirectory: + return "Directory"; case FileType::Error: case FileType::Unknown: break; @@ -102,6 +104,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp case FileType::NRO: return std::make_unique<AppLoader_NRO>(std::move(file), filepath); + // NX deconstructed ROM directory. + case FileType::DeconstructedRomDirectory: + return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath); + default: return nullptr; } @@ -117,7 +123,7 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { std::string filename_filename, filename_extension; Common::SplitPath(filename, nullptr, &filename_filename, &filename_extension); - FileType type = IdentifyFile(file); + FileType type = IdentifyFile(file, filename); FileType filename_type = GuessFromExtension(filename_extension); if (type != filename_type) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index dd6bb4e64..dd44ee9a6 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -20,9 +20,6 @@ struct AddressMapping; class Process; } // namespace Kernel -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Loader namespace - namespace Loader { /// File types supported by CTR @@ -32,14 +29,16 @@ enum class FileType { ELF, NSO, NRO, + DeconstructedRomDirectory, }; /** * Identifies the type of a bootable file based on the magic value in its header. * @param file open file + * @param filepath Path of the file that we are opening. * @return FileType of file */ -FileType IdentifyFile(FileUtil::IOFile& file); +FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath); /** * Identifies the type of a bootable file based on the magic value in its header. diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index a5d09512b..b5133e4d6 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -5,8 +5,10 @@ #include <vector> #include "common/common_funcs.h" +#include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/loader/nro.h" @@ -45,7 +47,10 @@ struct ModHeader { }; static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); -FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file) { +AppLoader_NRO::AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath) + : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + +FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file, const std::string&) { // Read NSO header NroHeader nro_header{}; file.Seek(0, SEEK_SET); @@ -62,20 +67,6 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NroSegmentHeader& header) { - std::vector<u8> data; - data.resize(header.size); - - file.Seek(header.offset + sizeof(NroHeader), SEEK_SET); - size_t bytes_read{file.ReadBytes(data.data(), header.size)}; - if (header.size != PageAlignSize(static_cast<u32>(bytes_read))) { - LOG_CRITICAL(Loader, "Failed to read NRO segment bytes", header.size); - return {}; - } - - return data; -} - bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { FileUtil::IOFile file(path, "rb"); if (!file.IsOpen()) { @@ -93,9 +84,9 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { } // Build program image - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0); + Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); std::vector<u8> program_image; - program_image.resize(PageAlignSize(nro_header.file_size + nro_header.bss_size)); + program_image.resize(PageAlignSize(nro_header.file_size)); file.Seek(0, SEEK_SET); file.ReadBytes(program_image.data(), nro_header.file_size); @@ -107,28 +98,22 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) { // Read MOD header ModHeader mod_header{}; - u32 bss_size{Memory::PAGE_SIZE}; // Default .bss to page size if MOD0 section doesn't exist + // Default .bss to NRO header bss size if MOD0 section doesn't exist + u32 bss_size{PageAlignSize(nro_header.bss_size)}; std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset, sizeof(ModHeader)); const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; if (has_mod_header) { // Resize program image to include .bss section and page align each section bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); - codeset->data.size += bss_size; - } - program_image.resize(PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)); - - // Relocate symbols if there was a proper MOD header - This must happen after the image has been - // loaded into memory - if (has_mod_header) { - Relocate(program_image, nro_header.module_header_offset + mod_header.dynamic_offset, - load_base); } + codeset->data.size += bss_size; + program_image.resize(static_cast<u32>(program_image.size()) + bss_size); // Load codeset for current process codeset->name = path; codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Kernel::g_current_process->LoadModule(codeset, load_base); + Core::CurrentProcess()->LoadModule(codeset, load_base); return true; } @@ -141,9 +126,9 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { return ResultStatus::Error; } - // Load and relocate "main" and "sdk" NSO + // Load NRO static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR}; - process = Kernel::Process::Create("main"); + if (!LoadNro(filepath, base_addr)) { return ResultStatus::ErrorInvalidFormat; } @@ -152,9 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(base_addr, 48, Kernel::DEFAULT_STACK_SIZE); - - ResolveImports(); + process->Run(base_addr, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index e20fa1555..599adb253 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -4,10 +4,8 @@ #pragma once -#include <map> #include <string> #include "common/common_types.h" -#include "common/file_util.h" #include "core/hle/kernel/kernel.h" #include "core/loader/linker.h" #include "core/loader/loader.h" @@ -17,18 +15,18 @@ namespace Loader { /// Loads an NRO file class AppLoader_NRO final : public AppLoader, Linker { public: - AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath) - : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath); /** * Returns the type of the file * @param file FileUtil::IOFile open file + * @param filepath Path of the file that we are opening. * @return FileType found, or FileType::Error if this loader doesn't know it */ - static FileType IdentifyType(FileUtil::IOFile& file); + static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); FileType GetFileType() override { - return IdentifyType(file); + return IdentifyType(file, filepath); } ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index ff96e129b..3bc10ed0d 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include <vector> #include <lz4.h> - #include "common/common_funcs.h" +#include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/loader/nso.h" @@ -47,7 +49,10 @@ struct ModHeader { }; static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); -FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file) { +AppLoader_NSO::AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath) + : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + +FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) { u32 magic = 0; file.Seek(0, SEEK_SET); if (1 != file.ReadArray<u32>(&magic, 1)) { @@ -79,7 +84,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size); ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), - "%d != %d != %d", bytes_uncompressed, header.size, uncompressed_data.size()); + "%d != %u != %zu", bytes_uncompressed, header.size, uncompressed_data.size()); return uncompressed_data; } @@ -88,7 +93,7 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relocate) { +VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { FileUtil::IOFile file(path, "rb"); if (!file.IsOpen()) { return {}; @@ -105,7 +110,7 @@ VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relo } // Build program image - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0); + Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); std::vector<u8> program_image; for (int i = 0; i < nso_header.segments.size(); ++i) { std::vector<u8> data = @@ -135,16 +140,10 @@ VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relo const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; program_image.resize(image_size); - // Relocate symbols if there was a proper MOD header - This must happen after the image has been - // loaded into memory - if (has_mod_header && relocate) { - Relocate(program_image, module_offset + mod_header.dynamic_offset, load_base); - } - // Load codeset for current process codeset->name = path; codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Kernel::g_current_process->LoadModule(codeset, load_base); + Core::CurrentProcess()->LoadModule(codeset, load_base); return load_base + image_size; } @@ -157,31 +156,16 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { return ResultStatus::Error; } - process = Kernel::Process::Create("main"); - - // Load NSO modules - VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; - for (const auto& module : - {"rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4"}) { - const std::string path = filepath.substr(0, filepath.find_last_of("/\\")) + "/" + module; - const VAddr load_addr = next_load_addr; - next_load_addr = LoadNso(path, load_addr); - if (next_load_addr) { - LOG_DEBUG(Loader, "loaded module %s @ 0x%llx", module, load_addr); - } else { - next_load_addr = load_addr; - } - } - // Load "main" module - LoadNso(filepath, next_load_addr); + // Load module + LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR); + LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, filepath.c_str(), + Memory::PROCESS_IMAGE_VADDR); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Kernel::DEFAULT_STACK_SIZE); - - ResolveImports(); + process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 03424e70d..1ae30a824 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -4,10 +4,8 @@ #pragma once -#include <map> #include <string> #include "common/common_types.h" -#include "common/file_util.h" #include "core/hle/kernel/kernel.h" #include "core/loader/linker.h" #include "core/loader/loader.h" @@ -17,25 +15,25 @@ namespace Loader { /// Loads an NSO file class AppLoader_NSO final : public AppLoader, Linker { public: - AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath) - : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath); /** * Returns the type of the file * @param file FileUtil::IOFile open file + * @param filepath Path of the file that we are opening. * @return FileType found, or FileType::Error if this loader doesn't know it */ - static FileType IdentifyType(FileUtil::IOFile& file); + static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); FileType GetFileType() override { - return IdentifyType(file); + return IdentifyType(file, filepath); } + static VAddr LoadModule(const std::string& path, VAddr load_base); + ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; private: - VAddr LoadNso(const std::string& path, VAddr load_base, bool relocate = false); - std::string filepath; }; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 74a598852..291bf066f 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -2,8 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> +#include <cinttypes> #include <cstring> +#include <boost/optional.hpp> #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -21,7 +24,6 @@ namespace Memory { static std::array<u8, Memory::VRAM_SIZE> vram; -static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; static PageTable* current_page_table = nullptr; @@ -37,7 +39,7 @@ PageTable* GetCurrentPageTable() { } static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) { - LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, + LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, @@ -45,11 +47,10 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa VAddr end = base + size; while (base != end) { - ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base); + ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base); page_table.attributes[base] = type; page_table.pointers[base] = memory; - page_table.cached_res_count[base] = 0; base += 1; if (memory != nullptr) @@ -58,23 +59,61 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa } void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); } -void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MMIORegionPointer mmio_handler) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); +void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) { + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); - page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); + auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); + SpecialRegion region{SpecialRegion::Type::IODevice, mmio_handler}; + page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); } void UnmapRegion(PageTable& page_table, VAddr base, u64 size) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); + ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size); + ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base); MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); + + auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); + page_table.special_regions.erase(interval); +} + +void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) { + auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); + SpecialRegion region{SpecialRegion::Type::DebugHook, hook}; + page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); +} + +void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) { + auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); + SpecialRegion region{SpecialRegion::Type::DebugHook, hook}; + page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region})); +} + +/** + * This function should only be called for virtual addreses with attribute `PageType::Special`. + */ +static std::set<MemoryHookPointer> GetSpecialHandlers(const PageTable& page_table, VAddr vaddr, + u64 size) { + std::set<MemoryHookPointer> result; + auto interval = boost::icl::discrete_interval<VAddr>::closed(vaddr, vaddr + size - 1); + auto interval_list = page_table.special_regions.equal_range(interval); + for (auto it = interval_list.first; it != interval_list.second; ++it) { + for (const auto& region : it->second) { + result.insert(region.handler); + } + } + return result; +} + +static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) { + const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table; + return GetSpecialHandlers(page_table, vaddr, size); } /** @@ -111,30 +150,9 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { * using a VMA from the current process. */ static u8* GetPointerFromVMA(VAddr vaddr) { - return GetPointerFromVMA(*Kernel::g_current_process, vaddr); + return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); } -/** - * This function should only be called for virtual addreses with attribute `PageType::Special`. - */ -static MMIORegionPointer GetMMIOHandler(const PageTable& page_table, VAddr vaddr) { - for (const auto& region : page_table.special_regions) { - if (vaddr >= region.base && vaddr < (region.base + region.size)) { - return region.handler; - } - } - ASSERT_MSG(false, "Mapped IO page without a handler @ %08X", vaddr); - return nullptr; // Should never happen -} - -static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { - const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; - return GetMMIOHandler(page_table, vaddr); -} - -template <typename T> -T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr); - template <typename T> T Read(const VAddr vaddr) { const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; @@ -151,7 +169,7 @@ T Read(const VAddr vaddr) { PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { case PageType::Unmapped: - LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%llx", sizeof(T) * 8, vaddr); + LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%08X", sizeof(T) * 8, vaddr); return 0; case PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); @@ -163,21 +181,12 @@ T Read(const VAddr vaddr) { std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); return value; } - case PageType::Special: - return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); - case PageType::RasterizerCachedSpecial: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); - return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); - } default: UNREACHABLE(); } } template <typename T> -void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const T data); - -template <typename T> void Write(const VAddr vaddr, const T data) { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; if (page_pointer) { @@ -199,18 +208,10 @@ void Write(const VAddr vaddr, const T data) { ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); break; case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate); + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate); std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); break; } - case PageType::Special: - WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); - break; - case PageType::RasterizerCachedSpecial: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate); - WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); - break; - } default: UNREACHABLE(); } @@ -229,16 +230,11 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) return false; - MMIORegionPointer mmio_region = GetMMIOHandler(page_table, vaddr); - if (mmio_region) { - return mmio_region->IsValidAddress(vaddr); - } - return false; } bool IsValidVirtualAddress(const VAddr vaddr) { - return IsValidVirtualAddress(*Kernel::g_current_process, vaddr); + return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr); } bool IsValidPhysicalAddress(const PAddr paddr) { @@ -284,7 +280,6 @@ u8* GetPhysicalPointer(PAddr address) { {IO_AREA_PADDR, IO_AREA_SIZE}, {DSP_RAM_PADDR, DSP_RAM_SIZE}, {FCRAM_PADDR, FCRAM_N3DS_SIZE}, - {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE}, }; const auto area = @@ -293,12 +288,13 @@ u8* GetPhysicalPointer(PAddr address) { }); if (area == std::end(memory_areas)) { - LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%08X", address); + LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%016" PRIX64, address); return nullptr; } if (area->paddr_base == IO_AREA_PADDR) { - LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%08X", address); + LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%016" PRIX64, + address); return nullptr; } @@ -322,9 +318,6 @@ u8* GetPhysicalPointer(PAddr address) { } ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address"); break; - case N3DS_EXTRA_RAM_PADDR: - target_pointer = n3ds_extra_ram.data() + offset_into_region; - break; default: UNREACHABLE(); } @@ -332,35 +325,19 @@ u8* GetPhysicalPointer(PAddr address) { return target_pointer; } -void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) { +void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached) { if (start == 0) { return; } u64 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1; - PAddr paddr = start; - - for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) { - boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr); - // While the physical <-> virtual mapping is 1:1 for the regions supported by the cache, - // some games (like Pokemon Super Mystery Dungeon) will try to use textures that go beyond - // the end address of VRAM, causing the Virtual->Physical translation to fail when flushing - // parts of the texture. - if (!maybe_vaddr) { - LOG_ERROR(HW_Memory, - "Trying to flush a cached region to an invalid physical address %08X", paddr); - continue; - } - VAddr vaddr = *maybe_vaddr; + VAddr vaddr = start; - u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS]; - ASSERT_MSG(count_delta <= UINT8_MAX - res_count, - "Rasterizer resource cache counter overflow!"); - ASSERT_MSG(count_delta >= -res_count, "Rasterizer resource cache counter underflow!"); + for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { + PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; - // Switch page type to cached if now cached - if (res_count == 0) { - PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + if (cached) { + // Switch page type to cached if now cached switch (page_type) { case PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address @@ -370,19 +347,11 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) { page_type = PageType::RasterizerCachedMemory; current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; break; - case PageType::Special: - page_type = PageType::RasterizerCachedSpecial; - break; default: UNREACHABLE(); } - } - - res_count += count_delta; - - // Switch page type to uncached if now uncached - if (res_count == 0) { - PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + } else { + // Switch page type to uncached if now uncached switch (page_type) { case PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address @@ -391,7 +360,7 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) { case PageType::RasterizerCachedMemory: { u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); if (pointer == nullptr) { - // It's possible that this function has called been while updating the pagetable + // It's possible that this function has been called while updating the pagetable // after unmapping a VMA. In that case the underlying VMA will no longer exist, // and we should just leave the pagetable entry blank. page_type = PageType::Unmapped; @@ -401,9 +370,6 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) { } break; } - case PageType::RasterizerCachedSpecial: - page_type = PageType::Special; - break; default: UNREACHABLE(); } @@ -411,16 +377,41 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) { } } -void RasterizerFlushRegion(PAddr start, u64 size) {} - -void RasterizerFlushAndInvalidateRegion(PAddr start, u64 size) { - // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be - // null here -} - void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) { // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be // null here + if (VideoCore::g_renderer == nullptr) { + return; + } + + VAddr end = start + size; + + auto CheckRegion = [&](VAddr region_start, VAddr region_end) { + if (start >= region_end || end <= region_start) { + // No overlap with region + return; + } + + VAddr overlap_start = std::max(start, region_start); + VAddr overlap_end = std::min(end, region_end); + u64 overlap_size = overlap_end - overlap_start; + + auto* rasterizer = VideoCore::g_renderer->Rasterizer(); + switch (mode) { + case FlushMode::Flush: + rasterizer->FlushRegion(overlap_start, overlap_size); + break; + case FlushMode::Invalidate: + rasterizer->InvalidateRegion(overlap_start, overlap_size); + break; + case FlushMode::FlushAndInvalidate: + rasterizer->FlushAndInvalidateRegion(overlap_start, overlap_size); + break; + } + }; + + CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END); + CheckRegion(HEAP_VADDR, HEAP_VADDR_END); } u8 Read8(const VAddr addr) { @@ -448,12 +439,13 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { case PageType::Unmapped: { - LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0xllx, size = %zu)", + LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); break; @@ -465,26 +457,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ std::memcpy(dest_buffer, src_ptr, copy_amount); break; } - case PageType::Special: { - MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); - DEBUG_ASSERT(handler); - handler->ReadBlock(current_vaddr, dest_buffer, copy_amount); - break; - } case PageType::RasterizerCachedMemory: { RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), FlushMode::Flush); std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount); break; } - case PageType::RasterizerCachedSpecial: { - MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); - DEBUG_ASSERT(handler); - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Flush); - handler->ReadBlock(current_vaddr, dest_buffer, copy_amount); - break; - } default: UNREACHABLE(); } @@ -497,7 +475,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ } void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { - ReadBlock(*Kernel::g_current_process, src_addr, dest_buffer, size); + ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); } void Write8(const VAddr addr, const u8 data) { @@ -524,7 +502,8 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi size_t page_offset = dest_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { @@ -541,26 +520,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi std::memcpy(dest_ptr, src_buffer, copy_amount); break; } - case PageType::Special: { - MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); - DEBUG_ASSERT(handler); - handler->WriteBlock(current_vaddr, src_buffer, copy_amount); - break; - } case PageType::RasterizerCachedMemory: { RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::FlushAndInvalidate); + FlushMode::Invalidate); std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); break; } - case PageType::RasterizerCachedSpecial: { - MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); - DEBUG_ASSERT(handler); - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::FlushAndInvalidate); - handler->WriteBlock(current_vaddr, src_buffer, copy_amount); - break; - } default: UNREACHABLE(); } @@ -573,10 +538,11 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi } void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { - WriteBlock(*Kernel::g_current_process, dest_addr, src_buffer, size); + WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); } -void ZeroBlock(const VAddr dest_addr, const size_t size) { +void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = dest_addr >> PAGE_BITS; size_t page_offset = dest_addr & PAGE_MASK; @@ -584,39 +550,27 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { static const std::array<u8, PAGE_SIZE> zeros = {}; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { + switch (page_table.attributes[page_index]) { case PageType::Unmapped: { LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size); break; } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); + DEBUG_ASSERT(page_table.pointers[page_index]); - u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; + u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memset(dest_ptr, 0, copy_amount); break; } - case PageType::Special: { - DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); - break; - } case PageType::RasterizerCachedMemory: { RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::FlushAndInvalidate); - std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount); - break; - } - case PageType::RasterizerCachedSpecial: { - DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::FlushAndInvalidate); - GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); + FlushMode::Invalidate); + std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount); break; } default: @@ -629,50 +583,34 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { } } -void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { +void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) { + auto& page_table = process.vm_manager.page_table; size_t remaining_size = size; size_t page_index = src_addr >> PAGE_BITS; size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size); + const size_t copy_amount = + std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); - switch (current_page_table->attributes[page_index]) { + switch (page_table.attributes[page_index]) { case PageType::Unmapped: { LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size); - ZeroBlock(dest_addr, copy_amount); + ZeroBlock(process, dest_addr, copy_amount); break; } case PageType::Memory: { - DEBUG_ASSERT(current_page_table->pointers[page_index]); - const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; - WriteBlock(dest_addr, src_ptr, copy_amount); - break; - } - case PageType::Special: { - DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - std::vector<u8> buffer(copy_amount); - GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); - WriteBlock(dest_addr, buffer.data(), buffer.size()); + DEBUG_ASSERT(page_table.pointers[page_index]); + const u8* src_ptr = page_table.pointers[page_index] + page_offset; + WriteBlock(process, dest_addr, src_ptr, copy_amount); break; } case PageType::RasterizerCachedMemory: { RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), FlushMode::Flush); - WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount); - break; - } - case PageType::RasterizerCachedSpecial: { - DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Flush); - - std::vector<u8> buffer(copy_amount); - GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); - WriteBlock(dest_addr, buffer.data(), buffer.size()); + WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount); break; } default: @@ -687,46 +625,6 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { } } -template <> -u8 ReadMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr) { - return mmio_handler->Read8(addr); -} - -template <> -u16 ReadMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr) { - return mmio_handler->Read16(addr); -} - -template <> -u32 ReadMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr) { - return mmio_handler->Read32(addr); -} - -template <> -u64 ReadMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr) { - return mmio_handler->Read64(addr); -} - -template <> -void WriteMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr, const u8 data) { - mmio_handler->Write8(addr, data); -} - -template <> -void WriteMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr, const u16 data) { - mmio_handler->Write16(addr, data); -} - -template <> -void WriteMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr, const u32 data) { - mmio_handler->Write32(addr, data); -} - -template <> -void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data) { - mmio_handler->Write64(addr, data); -} - boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { if (addr == 0) { return 0; @@ -740,8 +638,6 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { return addr - DSP_RAM_VADDR + DSP_RAM_PADDR; } else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) { return addr - IO_AREA_VADDR + IO_AREA_PADDR; - } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) { - return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR; } return boost::none; @@ -750,7 +646,7 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { PAddr VirtualToPhysicalAddress(const VAddr addr) { auto paddr = TryVirtualToPhysicalAddress(addr); if (!paddr) { - LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); + LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%016" PRIX64, addr); // To help with debugging, set bit on address so that it's obviously invalid. return addr | 0x80000000; } @@ -763,13 +659,11 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) { } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { return addr - VRAM_PADDR + VRAM_VADDR; } else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) { - return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapAreaAddress(); + return addr - FCRAM_PADDR + Core::CurrentProcess()->GetLinearHeapAreaAddress(); } else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) { return addr - DSP_RAM_PADDR + DSP_RAM_VADDR; } else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) { return addr - IO_AREA_PADDR + IO_AREA_VADDR; - } else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) { - return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR; } return boost::none; diff --git a/src/core/memory.h b/src/core/memory.h index 7e554f394..e9b8ca873 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -8,10 +8,12 @@ #include <cstddef> #include <map> #include <string> +#include <tuple> #include <vector> +#include <boost/icl/interval_map.hpp> #include <boost/optional.hpp> #include "common/common_types.h" -#include "core/mmio.h" +#include "core/memory_hook.h" namespace Kernel { class Process; @@ -23,12 +25,13 @@ namespace Memory { * Page size used by the ARM architecture. This is the smallest granularity with which memory can * be mapped. */ -const int PAGE_BITS = 12; -const u64 PAGE_SIZE = 1 << PAGE_BITS; -const u64 PAGE_MASK = PAGE_SIZE - 1; -const size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (36 - PAGE_BITS); +constexpr size_t PAGE_BITS = 12; +constexpr u64 PAGE_SIZE = 1 << PAGE_BITS; +constexpr u64 PAGE_MASK = PAGE_SIZE - 1; +constexpr size_t ADDRESS_SPACE_BITS = 36; +constexpr size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (ADDRESS_SPACE_BITS - PAGE_BITS); -enum class PageType { +enum class PageType : u8 { /// Page is unmapped and should cause an access error. Unmapped, /// Page is mapped to regular memory. This is the only type you can get pointers to. @@ -38,22 +41,28 @@ enum class PageType { RasterizerCachedMemory, /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. Special, - /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and - /// invalidation - RasterizerCachedSpecial, }; struct SpecialRegion { - VAddr base; - u64 size; - MMIORegionPointer handler; + enum class Type { + DebugHook, + IODevice, + } type; + + MemoryHookPointer handler; + + bool operator<(const SpecialRegion& other) const { + return std::tie(type, handler) < std::tie(other.type, other.handler); + } + + bool operator==(const SpecialRegion& other) const { + return std::tie(type, handler) == std::tie(other.type, other.handler); + } }; /** * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely - * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and - * fetching requirements when accessing. In the usual case of an access to regular memory, it only - * requires an indexed fetch and a check for NULL. + * mimics the way a real CPU page table works. */ struct PageTable { /** @@ -66,19 +75,13 @@ struct PageTable { * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of * type `Special`. */ - std::vector<SpecialRegion> special_regions; + boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions; /** * Array of fine grained page attributes. If it is set to any value other than `Memory`, then * the corresponding entry in `pointers` MUST be set to null. */ std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes; - - /** - * Indicates the number of externally cached resources touching a page that should be - * flushed before the memory is accessed - */ - std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count; }; /// Physical memory regions as seen from the ARM11 @@ -98,12 +101,6 @@ enum : PAddr { VRAM_SIZE = 0x00600000, ///< VRAM size (6MB) VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE, - /// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by - /// applications and system modules if mapped via the ExHeader. - N3DS_EXTRA_RAM_PADDR = 0x1F000000, - N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB) - N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE, - /// DSP memory DSP_RAM_PADDR = 0x1FF00000, DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB) @@ -119,7 +116,6 @@ enum : PAddr { FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB) FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB) FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE, - FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE, }; /// Virtual user-space memory regions @@ -129,31 +125,12 @@ enum : VAddr { PROCESS_IMAGE_MAX_SIZE = 0x08000000, PROCESS_IMAGE_VADDR_END = PROCESS_IMAGE_VADDR + PROCESS_IMAGE_MAX_SIZE, - /// Area where IPC buffers are mapped onto. - IPC_MAPPING_VADDR = 0x04000000, - IPC_MAPPING_SIZE = 0x04000000, - IPC_MAPPING_VADDR_END = IPC_MAPPING_VADDR + IPC_MAPPING_SIZE, - - /// Application heap (includes stack). - HEAP_VADDR = 0x108000000, - HEAP_SIZE = 0xF0000000, - HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE, - - /// Area where shared memory buffers are mapped onto. - SHARED_MEMORY_VADDR = 0x10000000, - SHARED_MEMORY_SIZE = 0x04000000, - SHARED_MEMORY_VADDR_END = SHARED_MEMORY_VADDR + SHARED_MEMORY_SIZE, - /// Maps 1:1 to an offset in FCRAM. Used for HW allocations that need to be linear in physical /// memory. LINEAR_HEAP_VADDR = 0x14000000, LINEAR_HEAP_SIZE = 0x08000000, LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE, - /// Maps 1:1 to New 3DS additional memory - N3DS_EXTRA_RAM_VADDR = 0x1E800000, - N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE, - /// Maps 1:1 to the IO register area. IO_AREA_VADDR = 0x1EC00000, IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE, @@ -176,14 +153,40 @@ enum : VAddr { SHARED_PAGE_SIZE = 0x00001000, SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE, - /// Area where TLS (Thread-Local Storage) buffers are allocated. - TLS_AREA_VADDR = 0x228000000, - TLS_ENTRY_SIZE = 0x200, - /// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS. NEW_LINEAR_HEAP_VADDR = 0x30000000, NEW_LINEAR_HEAP_SIZE = 0x10000000, NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, + + /// Area where TLS (Thread-Local Storage) buffers are allocated. + TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END, + TLS_ENTRY_SIZE = 0x200, + TLS_AREA_SIZE = 0x10000000, + TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE, + + /// Application stack + STACK_AREA_VADDR = TLS_AREA_VADDR_END, + STACK_AREA_SIZE = 0x10000000, + STACK_AREA_VADDR_END = STACK_AREA_VADDR + STACK_AREA_SIZE, + DEFAULT_STACK_SIZE = 0x100000, + + /// Application heap + /// Size is confirmed to be a static value on fw 3.0.0 + HEAP_VADDR = 0x108000000, + HEAP_SIZE = 0x180000000, + HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE, + + /// New map region + /// Size is confirmed to be a static value on fw 3.0.0 + NEW_MAP_REGION_VADDR = HEAP_VADDR_END, + NEW_MAP_REGION_SIZE = 0x80000000, + NEW_MAP_REGION_VADDR_END = NEW_MAP_REGION_VADDR + NEW_MAP_REGION_SIZE, + + /// Map region + /// Size is confirmed to be a static value on fw 3.0.0 + MAP_REGION_VADDR = NEW_MAP_REGION_VADDR_END, + MAP_REGION_SIZE = 0x1000000000, + MAP_REGION_VADDR_END = MAP_REGION_VADDR + MAP_REGION_SIZE, }; /// Currently active page table @@ -243,30 +246,21 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr); */ u8* GetPhysicalPointer(PAddr address); -/** - * Adds the supplied value to the rasterizer resource cache counter of each - * page touching the region. - */ -void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta); - -/** - * Flushes any externally cached rasterizer resources touching the given region. - */ -void RasterizerFlushRegion(PAddr start, u64 size); - -/** - * Flushes and invalidates any externally cached rasterizer resources touching the given region. - */ -void RasterizerFlushAndInvalidateRegion(PAddr start, u64 size); - enum class FlushMode { /// Write back modified surfaces to RAM Flush, + /// Remove region from the cache + Invalidate, /// Write back modified surfaces to RAM, and also remove them from the cache FlushAndInvalidate, }; /** + * Mark each page touching the region as cached. + */ +void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached); + +/** * Flushes and invalidates any externally cached rasterizer resources touching the given virtual * address region. */ diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h new file mode 100644 index 000000000..feebd850a --- /dev/null +++ b/src/core/memory_hook.h @@ -0,0 +1,46 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <boost/optional.hpp> +#include "common/common_types.h" + +namespace Memory { + +/** + * Memory hooks have two purposes: + * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement + * texture forwarding and memory breakpoints for debugging. + * 2. To allow for the implementation of MMIO devices. + * + * A hook may be mapped to multiple regions of memory. + * + * If a boost::none or false is returned from a function, the read/write request is passed through + * to the underlying memory region. + */ +class MemoryHook { +public: + virtual ~MemoryHook() = default; + + virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0; + + virtual boost::optional<u8> Read8(VAddr addr) = 0; + virtual boost::optional<u16> Read16(VAddr addr) = 0; + virtual boost::optional<u32> Read32(VAddr addr) = 0; + virtual boost::optional<u64> Read64(VAddr addr) = 0; + + virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0; + + virtual bool Write8(VAddr addr, u8 data) = 0; + virtual bool Write16(VAddr addr, u16 data) = 0; + virtual bool Write32(VAddr addr, u32 data) = 0; + virtual bool Write64(VAddr addr, u64 data) = 0; + + virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0; +}; + +using MemoryHookPointer = std::shared_ptr<MemoryHook>; +} // namespace Memory diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index ff4dcc936..9a1a4f4be 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -5,7 +5,7 @@ #pragma once #include "common/common_types.h" -#include "core/mmio.h" +#include "core/memory_hook.h" namespace Memory { @@ -26,7 +26,11 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target); * @param size The amount of bytes to map. Must be page-aligned. * @param mmio_handler The handler that backs the mapping. */ -void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MMIORegionPointer mmio_handler); +void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler); void UnmapRegion(PageTable& page_table, VAddr base, u64 size); -} + +void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook); +void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook); + +} // namespace Memory diff --git a/src/core/mmio.h b/src/core/mmio.h deleted file mode 100644 index f45126da8..000000000 --- a/src/core/mmio.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "common/common_types.h" - -namespace Memory { - -/** - * Represents a device with memory mapped IO. - * A device may be mapped to multiple regions of memory. - */ -class MMIORegion { -public: - virtual ~MMIORegion() = default; - - virtual bool IsValidAddress(VAddr addr) = 0; - - virtual u8 Read8(VAddr addr) = 0; - virtual u16 Read16(VAddr addr) = 0; - virtual u32 Read32(VAddr addr) = 0; - virtual u64 Read64(VAddr addr) = 0; - - virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0; - - virtual void Write8(VAddr addr, u8 data) = 0; - virtual void Write16(VAddr addr, u16 data) = 0; - virtual void Write32(VAddr addr, u32 data) = 0; - virtual void Write64(VAddr addr, u64 data) = 0; - - virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0; -}; - -using MMIORegionPointer = std::shared_ptr<MMIORegion>; -}; diff --git a/src/core/settings.h b/src/core/settings.h index 56fb189ae..2c94caab7 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -100,16 +100,15 @@ enum Values { }; static const std::array<const char*, NumAnalogs> mapping = {{ - "lstick", "rstick", + "lstick", + "rstick", }}; } // namespace NativeAnalog -enum class CpuCore { - Unicorn, - Dynarmic, -}; - struct Values { + // System + bool use_docked_mode; + // Controls std::array<std::string, NativeButton::NumButtons> buttons; std::array<std::string, NativeAnalog::NumAnalogs> analogs; @@ -117,7 +116,7 @@ struct Values { std::string touch_device; // Core - CpuCore cpu_core; + bool use_cpu_jit; // Data Storage bool use_virtual_sd; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index a613889f0..b3ffcd443 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -7,13 +7,16 @@ #include "common/assert.h" #include "common/file_util.h" #include "common/scm_rev.h" +#ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" +#endif #include "core/core.h" #include "core/settings.h" #include "core/telemetry_session.h" namespace Core { +#ifdef ARCHITECTURE_x86_64 static const char* CpuVendorToStr(Common::CPUVendor vendor) { switch (vendor) { case Common::CPUVendor::INTEL: @@ -25,6 +28,7 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) { } UNREACHABLE(); } +#endif static u64 GenerateTelemetryId() { u64 telemetry_id{}; @@ -113,7 +117,8 @@ TelemetrySession::TelemetrySession() { AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date); AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name); - // Log user system information +// Log user system information +#ifdef ARCHITECTURE_x86_64 AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string); @@ -135,6 +140,9 @@ TelemetrySession::TelemetrySession() { Common::GetCPUCaps().sse4_1); AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2); +#else + AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other"); +#endif #ifdef __APPLE__ AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple"); #elif defined(_WIN32) @@ -146,12 +154,13 @@ TelemetrySession::TelemetrySession() { #endif // Log user configuration information - AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore", - static_cast<int>(Settings::values.cpu_core)); + AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", Settings::values.resolution_factor); AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit", Settings::values.toggle_framelimit); + AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode", + Settings::values.use_docked_mode); } TelemetrySession::~TelemetrySession() { diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h index 215f86359..21fdc127a 100644 --- a/src/core/tracer/citrace.h +++ b/src/core/tracer/citrace.h @@ -97,4 +97,4 @@ struct CTStreamElement { }; #pragma pack() -} +} // namespace CiTrace diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index 55b3b5efc..f3b0d6a8f 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -205,4 +205,4 @@ template void Recorder::RegisterWritten(u32, u8); template void Recorder::RegisterWritten(u32, u16); template void Recorder::RegisterWritten(u32, u32); template void Recorder::RegisterWritten(u32, u64); -} +} // namespace CiTrace diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h index 39e6ec4fd..629c2f6d2 100644 --- a/src/core/tracer/recorder.h +++ b/src/core/tracer/recorder.h @@ -63,9 +63,9 @@ private: CTStreamElement data; /** - * Extra data to store along "core" data. - * This is e.g. used for data used in MemoryUpdates. - */ + * Extra data to store along "core" data. + * This is e.g. used for data used in MemoryUpdates. + */ std::vector<u8> extra_data; /// Optional CRC hash (e.g. for hashing memory regions) @@ -84,4 +84,4 @@ private: std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions; }; -} // namespace +} // namespace CiTrace diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 92792a702..1c7db28c0 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,25 +1,18 @@ -set(SRCS - analog_from_button.cpp - keyboard.cpp - main.cpp - motion_emu.cpp - ) +add_library(input_common STATIC + analog_from_button.cpp + analog_from_button.h + keyboard.cpp + keyboard.h + main.cpp + main.h + motion_emu.cpp + motion_emu.h -set(HEADERS - analog_from_button.h - keyboard.h - main.h - motion_emu.h - ) + $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h> +) -if(SDL2_FOUND) - set(SRCS ${SRCS} sdl/sdl.cpp) - set(HEADERS ${HEADERS} sdl/sdl.h) -endif() - -create_directory_groups(${SRCS} ${HEADERS}) +create_target_directory_groups(input_common) -add_library(input_common STATIC ${SRCS} ${HEADERS}) target_link_libraries(input_common PUBLIC core PRIVATE common) if(SDL2_FOUND) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 95d40f09f..b12623d55 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -52,7 +52,8 @@ MotionEmu* GetMotionEmu() { std::string GenerateKeyboardParam(int key_code) { Common::ParamPackage param{ - {"engine", "keyboard"}, {"code", std::to_string(key_code)}, + {"engine", "keyboard"}, + {"code", std::to_string(key_code)}, }; return param.Serialize(); } diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index 88b557c5d..3b87d6b65 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -425,7 +425,7 @@ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers( pollers.push_back(std::make_unique<SDLButtonPoller>()); break; } - return std::move(pollers); + return pollers; } } // namespace Polling } // namespace SDL diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 1b8fb2a9f..12f1b93e0 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,20 +1,16 @@ -set(SRCS - common/param_package.cpp - core/arm/arm_test_common.cpp - core/core_timing.cpp - core/file_sys/path_parser.cpp - core/memory/memory.cpp - glad.cpp - tests.cpp - ) +add_executable(tests + common/param_package.cpp + core/arm/arm_test_common.cpp + core/arm/arm_test_common.h + core/core_timing.cpp + core/file_sys/path_parser.cpp + core/memory/memory.cpp + glad.cpp + tests.cpp +) -set(HEADERS - core/arm/arm_test_common.h - ) +create_target_directory_groups(tests) -create_directory_groups(${SRCS} ${HEADERS}) - -add_executable(tests ${SRCS} ${HEADERS}) target_link_libraries(tests PRIVATE common core) target_link_libraries(tests PRIVATE glad) # To support linker work-around target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads) diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp index efec2cc86..19d372236 100644 --- a/src/tests/common/param_package.cpp +++ b/src/tests/common/param_package.cpp @@ -10,7 +10,9 @@ namespace Common { TEST_CASE("ParamPackage", "[common]") { ParamPackage original{ - {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"}, + {"abc", "xyz"}, + {"def", "42"}, + {"jkl", "$$:1:$2$,3"}, }; original.Set("ghi", 3.14f); ParamPackage copy(original.Serialize()); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 2339bdfb8..7f9f27e19 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -15,12 +15,12 @@ static Memory::PageTable* page_table = nullptr; TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { - Kernel::g_current_process = Kernel::Process::Create(""); - page_table = &Kernel::g_current_process->vm_manager.page_table; + Core::CurrentProcess() = Kernel::Process::Create(""); + page_table = &Core::CurrentProcess()->vm_manager.page_table; page_table->pointers.fill(nullptr); + page_table->special_regions.clear(); page_table->attributes.fill(Memory::PageType::Unmapped); - page_table->cached_res_count.fill(0); Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); @@ -62,11 +62,11 @@ void TestEnvironment::ClearWriteRecords() { TestEnvironment::TestMemory::~TestMemory() {} -bool TestEnvironment::TestMemory::IsValidAddress(VAddr addr) { +boost::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) { return true; } -u8 TestEnvironment::TestMemory::Read8(VAddr addr) { +boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) { auto iter = data.find(addr); if (iter == data.end()) { return addr; // Some arbitrary data @@ -74,16 +74,16 @@ u8 TestEnvironment::TestMemory::Read8(VAddr addr) { return iter->second; } -u16 TestEnvironment::TestMemory::Read16(VAddr addr) { - return Read8(addr) | static_cast<u16>(Read8(addr + 1)) << 8; +boost::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) { + return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8; } -u32 TestEnvironment::TestMemory::Read32(VAddr addr) { - return Read16(addr) | static_cast<u32>(Read16(addr + 2)) << 16; +boost::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) { + return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16; } -u64 TestEnvironment::TestMemory::Read64(VAddr addr) { - return Read32(addr) | static_cast<u64>(Read32(addr + 4)) << 32; +boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) { + return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32; } bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) { @@ -91,34 +91,38 @@ bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, s u8* data = static_cast<u8*>(dest_buffer); for (size_t i = 0; i < size; i++, addr++, data++) { - *data = Read8(addr); + *data = *Read8(addr); } return true; } -void TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) { +bool TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) { env->write_records.emplace_back(8, addr, data); if (env->mutable_memory) env->SetMemory8(addr, data); + return true; } -void TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) { +bool TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) { env->write_records.emplace_back(16, addr, data); if (env->mutable_memory) env->SetMemory16(addr, data); + return true; } -void TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) { +bool TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) { env->write_records.emplace_back(32, addr, data); if (env->mutable_memory) env->SetMemory32(addr, data); + return true; } -void TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) { +bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) { env->write_records.emplace_back(64, addr, data); if (env->mutable_memory) env->SetMemory64(addr, data); + return true; } bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) { diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h index 592c28594..b66922d61 100644 --- a/src/tests/core/arm/arm_test_common.h +++ b/src/tests/core/arm/arm_test_common.h @@ -7,7 +7,7 @@ #include <vector> #include "common/common_types.h" -#include "core/mmio.h" +#include "core/memory_hook.h" namespace ArmTests { @@ -51,25 +51,25 @@ public: private: friend struct TestMemory; - struct TestMemory final : Memory::MMIORegion { + struct TestMemory final : Memory::MemoryHook { explicit TestMemory(TestEnvironment* env_) : env(env_) {} TestEnvironment* env; ~TestMemory() override; - bool IsValidAddress(VAddr addr) override; + boost::optional<bool> IsValidAddress(VAddr addr) override; - u8 Read8(VAddr addr) override; - u16 Read16(VAddr addr) override; - u32 Read32(VAddr addr) override; - u64 Read64(VAddr addr) override; + boost::optional<u8> Read8(VAddr addr) override; + boost::optional<u16> Read16(VAddr addr) override; + boost::optional<u32> Read32(VAddr addr) override; + boost::optional<u64> Read64(VAddr addr) override; bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) override; - void Write8(VAddr addr, u8 data) override; - void Write16(VAddr addr, u16 data) override; - void Write32(VAddr addr, u32 data) override; - void Write64(VAddr addr, u64 data) override; + bool Write8(VAddr addr, u8 data) override; + bool Write16(VAddr addr, u16 data) override; + bool Write32(VAddr addr, u32 data) override; + bool Write64(VAddr addr, u64 data) override; bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) override; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 3fd177c46..a710c4bc5 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,23 +1,50 @@ -set(SRCS - renderer_base.cpp - renderer_opengl/gl_shader_util.cpp - renderer_opengl/gl_state.cpp - renderer_opengl/renderer_opengl.cpp - video_core.cpp - ) +add_library(video_core STATIC + command_processor.cpp + command_processor.h + debug_utils/debug_utils.cpp + debug_utils/debug_utils.h + engines/fermi_2d.cpp + engines/fermi_2d.h + engines/maxwell_3d.cpp + engines/maxwell_3d.h + engines/maxwell_compute.cpp + engines/maxwell_compute.h + gpu.cpp + gpu.h + macro_interpreter.cpp + macro_interpreter.h + memory_manager.cpp + memory_manager.h + rasterizer_interface.h + renderer_base.cpp + renderer_base.h + renderer_opengl/gl_rasterizer.cpp + renderer_opengl/gl_rasterizer.h + renderer_opengl/gl_rasterizer_cache.cpp + renderer_opengl/gl_rasterizer_cache.h + renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_decompiler.cpp + renderer_opengl/gl_shader_decompiler.h + renderer_opengl/gl_shader_gen.cpp + renderer_opengl/gl_shader_gen.h + renderer_opengl/gl_shader_util.cpp + renderer_opengl/gl_shader_util.h + renderer_opengl/gl_state.cpp + renderer_opengl/gl_state.h + renderer_opengl/gl_stream_buffer.cpp + renderer_opengl/gl_stream_buffer.h + renderer_opengl/maxwell_to_gl.h + renderer_opengl/renderer_opengl.cpp + renderer_opengl/renderer_opengl.h + textures/decoders.cpp + textures/decoders.h + textures/texture.h + utils.h + video_core.cpp + video_core.h +) -set(HEADERS - renderer_base.h - renderer_opengl/gl_resource_manager.h - renderer_opengl/gl_shader_util.h - renderer_opengl/gl_state.h - renderer_opengl/renderer_opengl.h - utils.h - video_core.h - ) +create_target_directory_groups(video_core) -create_directory_groups(${SRCS} ${HEADERS}) - -add_library(video_core STATIC ${SRCS} ${HEADERS}) target_link_libraries(video_core PUBLIC common core) target_link_libraries(video_core PRIVATE glad) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp new file mode 100644 index 000000000..d4cdb4ab2 --- /dev/null +++ b/src/video_core/command_processor.cpp @@ -0,0 +1,149 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstddef> +#include <memory> +#include <utility> +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/vector_math.h" +#include "core/memory.h" +#include "core/tracer/recorder.h" +#include "video_core/command_processor.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/engines/maxwell_compute.h" +#include "video_core/gpu.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" + +namespace Tegra { + +enum class BufferMethods { + BindObject = 0, + SetGraphMacroCode = 0x45, + SetGraphMacroCodeArg = 0x46, + SetGraphMacroEntry = 0x47, + CountBufferMethods = 0x100, +}; + +void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) { + LOG_WARNING(HW_GPU, "Processing method %08X on subchannel %u value %08X remaining params %u", + method, subchannel, value, remaining_params); + + if (method == static_cast<u32>(BufferMethods::SetGraphMacroEntry)) { + // Prepare to upload a new macro, reset the upload counter. + LOG_DEBUG(HW_GPU, "Uploading GPU macro %08X", value); + current_macro_entry = value; + current_macro_code.clear(); + return; + } + + if (method == static_cast<u32>(BufferMethods::SetGraphMacroCodeArg)) { + // Append a new code word to the current macro. + current_macro_code.push_back(value); + + // There are no more params remaining, submit the code to the 3D engine. + if (remaining_params == 0) { + maxwell_3d->SubmitMacroCode(current_macro_entry, std::move(current_macro_code)); + current_macro_entry = InvalidGraphMacroEntry; + current_macro_code.clear(); + } + + return; + } + + if (method == static_cast<u32>(BufferMethods::BindObject)) { + // Bind the current subchannel to the desired engine id. + LOG_DEBUG(HW_GPU, "Binding subchannel %u to engine %u", subchannel, value); + ASSERT(bound_engines.find(subchannel) == bound_engines.end()); + bound_engines[subchannel] = static_cast<EngineID>(value); + return; + } + + if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) { + // TODO(Subv): Research and implement these methods. + LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); + return; + } + + ASSERT(bound_engines.find(subchannel) != bound_engines.end()); + + const EngineID engine = bound_engines[subchannel]; + + switch (engine) { + case EngineID::FERMI_TWOD_A: + fermi_2d->WriteReg(method, value); + break; + case EngineID::MAXWELL_B: + maxwell_3d->WriteReg(method, value, remaining_params); + break; + case EngineID::MAXWELL_COMPUTE_B: + maxwell_compute->WriteReg(method, value); + break; + default: + UNIMPLEMENTED(); + } +} + +void GPU::ProcessCommandList(GPUVAddr address, u32 size) { + // TODO(Subv): PhysicalToVirtualAddress is a misnomer, it converts a GPU VAddr into an + // application VAddr. + const VAddr head_address = memory_manager->PhysicalToVirtualAddress(address); + VAddr current_addr = head_address; + while (current_addr < head_address + size * sizeof(CommandHeader)) { + const CommandHeader header = {Memory::Read32(current_addr)}; + current_addr += sizeof(u32); + + switch (header.mode.Value()) { + case SubmissionMode::IncreasingOld: + case SubmissionMode::Increasing: { + // Increase the method value with each argument. + for (unsigned i = 0; i < header.arg_count; ++i) { + WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::NonIncreasingOld: + case SubmissionMode::NonIncreasing: { + // Use the same method value for all arguments. + for (unsigned i = 0; i < header.arg_count; ++i) { + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::IncreaseOnce: { + ASSERT(header.arg_count.Value() >= 1); + + // Use the original method for the first argument and then the next method for all other + // arguments. + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - 1); + current_addr += sizeof(u32); + + for (unsigned i = 1; i < header.arg_count; ++i) { + WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::Inline: { + // The register value is stored in the bits 16-28 as an immediate + WriteReg(header.method, header.subchannel, header.inline_data, 0); + break; + } + default: + UNIMPLEMENTED(); + } + } +} + +} // namespace Tegra diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h new file mode 100644 index 000000000..f7214ffec --- /dev/null +++ b/src/video_core/command_processor.h @@ -0,0 +1,37 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Tegra { + +enum class SubmissionMode : u32 { + IncreasingOld = 0, + Increasing = 1, + NonIncreasingOld = 2, + NonIncreasing = 3, + Inline = 4, + IncreaseOnce = 5 +}; + +union CommandHeader { + u32 hex; + + BitField<0, 13, u32> method; + BitField<13, 3, u32> subchannel; + + BitField<16, 13, u32> arg_count; + BitField<16, 13, u32> inline_data; + + BitField<29, 3, SubmissionMode> mode; +}; +static_assert(std::is_standard_layout<CommandHeader>::value == true, + "CommandHeader does not use standard layout"); +static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!"); + +} // namespace Tegra diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp new file mode 100644 index 000000000..22d44aab2 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -0,0 +1,64 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <algorithm> +#include <condition_variable> +#include <cstdint> +#include <cstring> +#include <fstream> +#include <map> +#include <mutex> +#include <string> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/debug_utils/debug_utils.h" + +namespace Tegra { + +void DebugContext::DoOnEvent(Event event, void* data) { + { + std::unique_lock<std::mutex> lock(breakpoint_mutex); + + // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will + // show on debug widgets + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::lock_guard<std::mutex> lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +} // namespace Tegra diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h new file mode 100644 index 000000000..bbba8e380 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h @@ -0,0 +1,163 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> +#include <condition_variable> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Tegra { + +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + MaxwellCommandLoaded = FirstEvent, + MaxwellCommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state + * access + * @todo Evaluate an alternative interface, in which there is only one managing observer and + * multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr<DebugContext> debug_context) + : context_weak(debug_context) { + std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock<std::mutex> lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper yuzu + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellBreakPointHit(Event event, void* data) {} + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellResume() {} + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr<DebugContext> context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr<DebugContext> Construct() { + return std::shared_ptr<DebugContext>(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until + * Resume() is called. + */ + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. + * Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + for (auto& bp : breakpoints) { + bp.enabled = false; + } + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::array<BreakPoint, (int)Event::NumEvents> breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list<BreakPointObserver*> breakpoint_observers; +}; + +} // namespace Tegra diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp new file mode 100644 index 000000000..7aab163dc --- /dev/null +++ b/src/video_core/engines/fermi_2d.cpp @@ -0,0 +1,13 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/engines/fermi_2d.h" + +namespace Tegra { +namespace Engines { + +void Fermi2D::WriteReg(u32 method, u32 value) {} + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h new file mode 100644 index 000000000..8967ddede --- /dev/null +++ b/src/video_core/engines/fermi_2d.h @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Tegra { +namespace Engines { + +class Fermi2D final { +public: + Fermi2D() = default; + ~Fermi2D() = default; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value); +}; + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp new file mode 100644 index 000000000..124753032 --- /dev/null +++ b/src/video_core/engines/maxwell_3d.cpp @@ -0,0 +1,303 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include "common/assert.h" +#include "core/core.h" +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/video_core.h" + +namespace Tegra { +namespace Engines { + +/// First register id that is actually a Macro call. +constexpr u32 MacroRegistersStart = 0xE00; + +Maxwell3D::Maxwell3D(MemoryManager& memory_manager) + : memory_manager(memory_manager), macro_interpreter(*this) {} + +void Maxwell3D::SubmitMacroCode(u32 entry, std::vector<u32> code) { + uploaded_macros[entry * 2 + MacroRegistersStart] = std::move(code); +} + +void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { + auto macro_code = uploaded_macros.find(method); + // The requested macro must have been uploaded already. + ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method); + + // Reset the current macro and execute it. + executing_macro = 0; + macro_interpreter.Execute(macro_code->second, std::move(parameters)); +} + +void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { + ASSERT_MSG(method < Regs::NUM_REGS, + "Invalid Maxwell3D register, increase the size of the Regs structure"); + + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + + // It is an error to write to a register other than the current macro's ARG register before it + // has finished execution. + if (executing_macro != 0) { + ASSERT(method == executing_macro + 1); + } + + // Methods after 0xE00 are special, they're actually triggers for some microcode that was + // uploaded to the GPU during initialization. + if (method >= MacroRegistersStart) { + // We're trying to execute a macro + if (executing_macro == 0) { + // A macro call must begin by writing the macro method's register, not its argument. + ASSERT_MSG((method % 2) == 0, + "Can't start macro execution by writing to the ARGS register"); + executing_macro = method; + } + + macro_params.push_back(value); + + // Call the macro when there are no more parameters in the command buffer + if (remaining_params == 0) { + CallMacroMethod(executing_macro, std::move(macro_params)); + } + return; + } + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); + } + + regs.reg_array[method] = value; + +#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) + + switch (method) { + case MAXWELL3D_REG_INDEX(code_address.code_address_high): + case MAXWELL3D_REG_INDEX(code_address.code_address_low): { + // Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS + // register, we do not currently know if that's intended or a bug, so we assert it lest + // stuff breaks in other places (like the shader address calculation). + ASSERT_MSG(regs.code_address.CodeAddress() == 0, "Unexpected CODE_ADDRESS register value."); + break; + } + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): + case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { + ProcessCBData(value); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): { + ProcessCBBind(Regs::ShaderStage::Vertex); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[1].raw_config): { + ProcessCBBind(Regs::ShaderStage::TesselationControl); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[2].raw_config): { + ProcessCBBind(Regs::ShaderStage::TesselationEval); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[3].raw_config): { + ProcessCBBind(Regs::ShaderStage::Geometry); + break; + } + case MAXWELL3D_REG_INDEX(cb_bind[4].raw_config): { + ProcessCBBind(Regs::ShaderStage::Fragment); + break; + } + case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): { + DrawArrays(); + break; + } + case MAXWELL3D_REG_INDEX(query.query_get): { + ProcessQueryGet(); + break; + } + default: + break; + } + +#undef MAXWELL3D_REG_INDEX + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); + } +} + +void Maxwell3D::ProcessQueryGet() { + GPUVAddr sequence_address = regs.query.QueryAddress(); + // Since the sequence address is given as a GPU VAddr, we have to convert it to an application + // VAddr before writing. + VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address); + + switch (regs.query.query_get.mode) { + case Regs::QueryMode::Write: { + // Write the current query sequence to the sequence address. + u32 sequence = regs.query.query_sequence; + Memory::Write32(address, sequence); + break; + } + default: + UNIMPLEMENTED_MSG("Query mode %u not implemented", + static_cast<u32>(regs.query.query_get.mode.Value())); + } +} + +void Maxwell3D::DrawArrays() { + LOG_DEBUG(HW_GPU, "called, topology=%d, count=%d", regs.draw.topology.Value(), + regs.vertex_buffer.count); + + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); + } + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } + + VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(false /*is_indexed*/); +} + +void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { + // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage. + auto& shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)]; + + auto& buffer = shader.const_buffers[bind_data.index]; + + buffer.enabled = bind_data.valid.Value() != 0; + buffer.index = bind_data.index; + buffer.address = regs.const_buffer.BufferAddress(); + buffer.size = regs.const_buffer.cb_size; +} + +void Maxwell3D::ProcessCBData(u32 value) { + // Write the input value to the current const buffer at the current position. + GPUVAddr buffer_address = regs.const_buffer.BufferAddress(); + ASSERT(buffer_address != 0); + + // Don't allow writing past the end of the buffer. + ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size); + + VAddr address = + memory_manager.PhysicalToVirtualAddress(buffer_address + regs.const_buffer.cb_pos); + + Memory::Write32(address, value); + + // Increment the current buffer position. + regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; +} + +Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry); + VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); + + Texture::TICEntry tic_entry; + Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + + ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear, + "TIC versions other than BlockLinear are unimplemented"); + + ASSERT_MSG(tic_entry.texture_type == Texture::TextureType::Texture2D, + "Texture types other than Texture2D are unimplemented"); + + auto r_type = tic_entry.r_type.Value(); + auto g_type = tic_entry.g_type.Value(); + auto b_type = tic_entry.b_type.Value(); + auto a_type = tic_entry.a_type.Value(); + + // TODO(Subv): Different data types for separate components are not supported + ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + return tic_entry; +} + +Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { + GPUVAddr tsc_base_address = regs.tsc.TSCAddress(); + + GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry); + VAddr tsc_address_cpu = memory_manager.PhysicalToVirtualAddress(tsc_address_gpu); + + Texture::TSCEntry tsc_entry; + Memory::ReadBlock(tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; +} + +std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const { + std::vector<Texture::FullTextureInfo> textures; + + auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; + + // Offset into the texture constbuffer where the texture info begins. + static constexpr size_t TextureInfoOffset = 0x20; + + for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; + current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) { + + Texture::TextureHandle tex_handle{ + Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; + + Texture::FullTextureInfo tex_info{}; + // TODO(Subv): Use the shader to determine which textures are actually accessed. + tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) / + sizeof(Texture::TextureHandle); + + // Load the TIC data. + if (tex_handle.tic_id != 0) { + tex_info.enabled = true; + + auto tic_entry = GetTICEntry(tex_handle.tic_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry)); + } + + // Load the TSC data + if (tex_handle.tsc_id != 0) { + auto tsc_entry = GetTSCEntry(tex_handle.tsc_id); + // TODO(Subv): Workaround for BitField's move constructor being deleted. + std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry)); + } + + if (tex_info.enabled) + textures.push_back(tex_info); + } + + return textures; +} + +u32 Maxwell3D::GetRegisterValue(u32 method) const { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register"); + return regs.reg_array[method]; +} + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h new file mode 100644 index 000000000..98b39b2ff --- /dev/null +++ b/src/video_core/engines/maxwell_3d.h @@ -0,0 +1,590 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <unordered_map> +#include <vector> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/macro_interpreter.h" +#include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Engines { + +class Maxwell3D final { +public: + explicit Maxwell3D(MemoryManager& memory_manager); + ~Maxwell3D() = default; + + /// Register structure of the Maxwell3D engine. + /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. + struct Regs { + static constexpr size_t NUM_REGS = 0xE36; + + static constexpr size_t NumRenderTargets = 8; + static constexpr size_t NumViewports = 16; + static constexpr size_t NumCBData = 16; + static constexpr size_t NumVertexArrays = 32; + static constexpr size_t NumVertexAttributes = 32; + static constexpr size_t MaxShaderProgram = 6; + static constexpr size_t MaxShaderStage = 5; + // Maximum number of const buffers per shader stage. + static constexpr size_t MaxConstBuffers = 16; + + enum class QueryMode : u32 { + Write = 0, + Sync = 1, + }; + + enum class ShaderProgram : u32 { + VertexA = 0, + VertexB = 1, + TesselationControl = 2, + TesselationEval = 3, + Geometry = 4, + Fragment = 5, + }; + + enum class ShaderStage : u32 { + Vertex = 0, + TesselationControl = 1, + TesselationEval = 2, + Geometry = 3, + Fragment = 4, + }; + + struct VertexAttribute { + enum class Size : u32 { + Size_32_32_32_32 = 0x01, + Size_32_32_32 = 0x02, + Size_16_16_16_16 = 0x03, + Size_32_32 = 0x04, + Size_16_16_16 = 0x05, + Size_8_8_8_8 = 0x0a, + Size_16_16 = 0x0f, + Size_32 = 0x12, + Size_8_8_8 = 0x13, + Size_8_8 = 0x18, + Size_16 = 0x1b, + Size_8 = 0x1d, + Size_10_10_10_2 = 0x30, + Size_11_11_10 = 0x31, + }; + + enum class Type : u32 { + SignedNorm = 1, + UnsignedNorm = 2, + SignedInt = 3, + UnsignedInt = 4, + UnsignedScaled = 5, + SignedScaled = 6, + Float = 7, + }; + + union { + BitField<0, 5, u32> buffer; + BitField<6, 1, u32> constant; + BitField<7, 14, u32> offset; + BitField<21, 6, Size> size; + BitField<27, 3, Type> type; + BitField<31, 1, u32> bgra; + }; + + u32 ComponentCount() const { + switch (size) { + case Size::Size_32_32_32_32: + return 4; + case Size::Size_32_32_32: + return 3; + case Size::Size_16_16_16_16: + return 4; + case Size::Size_32_32: + return 2; + case Size::Size_16_16_16: + return 3; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 2; + case Size::Size_32: + return 1; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 1; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 3; + default: + UNREACHABLE(); + } + } + + u32 SizeInBytes() const { + switch (size) { + case Size::Size_32_32_32_32: + return 16; + case Size::Size_32_32_32: + return 12; + case Size::Size_16_16_16_16: + return 8; + case Size::Size_32_32: + return 8; + case Size::Size_16_16_16: + return 6; + case Size::Size_8_8_8_8: + return 4; + case Size::Size_16_16: + return 4; + case Size::Size_32: + return 4; + case Size::Size_8_8_8: + return 3; + case Size::Size_8_8: + return 2; + case Size::Size_16: + return 2; + case Size::Size_8: + return 1; + case Size::Size_10_10_10_2: + return 4; + case Size::Size_11_11_10: + return 4; + default: + UNREACHABLE(); + } + } + + std::string SizeString() const { + switch (size) { + case Size::Size_32_32_32_32: + return "32_32_32_32"; + case Size::Size_32_32_32: + return "32_32_32"; + case Size::Size_16_16_16_16: + return "16_16_16_16"; + case Size::Size_32_32: + return "32_32"; + case Size::Size_16_16_16: + return "16_16_16"; + case Size::Size_8_8_8_8: + return "8_8_8_8"; + case Size::Size_16_16: + return "16_16"; + case Size::Size_32: + return "32"; + case Size::Size_8_8_8: + return "8_8_8"; + case Size::Size_8_8: + return "8_8"; + case Size::Size_16: + return "16"; + case Size::Size_8: + return "8"; + case Size::Size_10_10_10_2: + return "10_10_10_2"; + case Size::Size_11_11_10: + return "11_11_10"; + } + UNREACHABLE(); + return {}; + } + + std::string TypeString() const { + switch (type) { + case Type::SignedNorm: + return "SNORM"; + case Type::UnsignedNorm: + return "UNORM"; + case Type::SignedInt: + return "SINT"; + case Type::UnsignedInt: + return "UINT"; + case Type::UnsignedScaled: + return "USCALED"; + case Type::SignedScaled: + return "SSCALED"; + case Type::Float: + return "FLOAT"; + } + UNREACHABLE(); + return {}; + } + + bool IsNormalized() const { + return (type == Type::SignedNorm) || (type == Type::UnsignedNorm); + } + }; + + enum class PrimitiveTopology : u32 { + Points = 0x0, + Lines = 0x1, + LineLoop = 0x2, + LineStrip = 0x3, + Triangles = 0x4, + TriangleStrip = 0x5, + TriangleFan = 0x6, + Quads = 0x7, + QuadStrip = 0x8, + Polygon = 0x9, + LinesAdjacency = 0xa, + LineStripAdjacency = 0xb, + TrianglesAdjacency = 0xc, + TriangleStripAdjacency = 0xd, + Patches = 0xe, + }; + + union { + struct { + INSERT_PADDING_WORDS(0x200); + + struct { + u32 address_high; + u32 address_low; + u32 width; + u32 height; + Tegra::RenderTargetFormat format; + u32 block_dimensions; + u32 array_mode; + u32 layer_stride; + u32 base_layer; + INSERT_PADDING_WORDS(7); + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } rt[NumRenderTargets]; + + INSERT_PADDING_WORDS(0x80); + + struct { + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> width; + }; + union { + BitField<0, 16, u32> y; + BitField<16, 16, u32> height; + }; + float depth_range_near; + float depth_range_far; + + MathUtil::Rectangle<s32> GetRect() const { + return { + static_cast<s32>(x), // left + static_cast<s32>(y + height), // top + static_cast<s32>(x + width), // right + static_cast<s32>(y) // bottom + }; + }; + } viewport[NumViewports]; + + INSERT_PADDING_WORDS(0x1D); + + struct { + u32 first; + u32 count; + } vertex_buffer; + + INSERT_PADDING_WORDS(0x99); + + struct { + u32 address_high; + u32 address_low; + u32 format; + u32 block_dimensions; + u32 layer_stride; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } zeta; + + INSERT_PADDING_WORDS(0x5B); + + VertexAttribute vertex_attrib_format[NumVertexAttributes]; + + INSERT_PADDING_WORDS(0xF); + + struct { + union { + BitField<0, 4, u32> count; + }; + } rt_control; + + INSERT_PADDING_WORDS(0xCF); + + struct { + u32 tsc_address_high; + u32 tsc_address_low; + u32 tsc_limit; + + GPUVAddr TSCAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low); + } + } tsc; + + INSERT_PADDING_WORDS(0x3); + + struct { + u32 tic_address_high; + u32 tic_address_low; + u32 tic_limit; + + GPUVAddr TICAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low); + } + } tic; + + INSERT_PADDING_WORDS(0x22); + + struct { + u32 code_address_high; + u32 code_address_low; + + GPUVAddr CodeAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low); + } + } code_address; + INSERT_PADDING_WORDS(1); + + struct { + u32 vertex_end_gl; + union { + u32 vertex_begin_gl; + BitField<0, 16, PrimitiveTopology> topology; + }; + } draw; + + INSERT_PADDING_WORDS(0x139); + struct { + u32 query_address_high; + u32 query_address_low; + u32 query_sequence; + union { + u32 raw; + BitField<0, 2, QueryMode> mode; + BitField<4, 1, u32> fence; + BitField<12, 4, u32> unit; + } query_get; + + GPUVAddr QueryAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(query_address_high) << 32) | query_address_low); + } + } query; + + INSERT_PADDING_WORDS(0x3C); + + struct { + union { + BitField<0, 12, u32> stride; + BitField<12, 1, u32> enable; + }; + u32 start_high; + u32 start_low; + u32 divisor; + + GPUVAddr StartAddress() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(start_high) << 32) | + start_low); + } + } vertex_array[NumVertexArrays]; + + INSERT_PADDING_WORDS(0x40); + + struct { + u32 limit_high; + u32 limit_low; + + GPUVAddr LimitAddress() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) | + limit_low); + } + } vertex_array_limit[NumVertexArrays]; + + struct { + union { + BitField<0, 1, u32> enable; + BitField<4, 4, ShaderProgram> program; + }; + u32 start_id; + INSERT_PADDING_WORDS(1); + u32 gpr_alloc; + ShaderStage type; + INSERT_PADDING_WORDS(9); + } shader_config[MaxShaderProgram]; + + INSERT_PADDING_WORDS(0x8C); + + struct { + u32 cb_size; + u32 cb_address_high; + u32 cb_address_low; + u32 cb_pos; + u32 cb_data[NumCBData]; + + GPUVAddr BufferAddress() const { + return static_cast<GPUVAddr>( + (static_cast<GPUVAddr>(cb_address_high) << 32) | cb_address_low); + } + } const_buffer; + + INSERT_PADDING_WORDS(0x10); + + struct { + union { + u32 raw_config; + BitField<0, 1, u32> valid; + BitField<4, 5, u32> index; + }; + INSERT_PADDING_WORDS(7); + } cb_bind[MaxShaderStage]; + + INSERT_PADDING_WORDS(0x56); + + u32 tex_cb_index; + + INSERT_PADDING_WORDS(0x395); + + struct { + /// Compressed address of a buffer that holds information about bound SSBOs. + /// This address is usually bound to c0 in the shaders. + u32 buffer_address; + + GPUVAddr BufferAddress() const { + return static_cast<GPUVAddr>(buffer_address) << 8; + } + } ssbo_info; + + INSERT_PADDING_WORDS(0x11); + + struct { + u32 address[MaxShaderStage]; + u32 size[MaxShaderStage]; + } tex_info_buffers; + + INSERT_PADDING_WORDS(0x102); + }; + std::array<u32, NUM_REGS> reg_array; + }; + } regs{}; + + static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size"); + + struct State { + struct ConstBufferInfo { + GPUVAddr address; + u32 index; + u32 size; + bool enabled; + }; + + struct ShaderStageInfo { + std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers; + }; + + std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages; + }; + + State state{}; + + /// Reads a register value located at the input method address + u32 GetRegisterValue(u32 method) const; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value, u32 remaining_params); + + /// Uploads the code for a GPU macro program associated with the specified entry. + void SubmitMacroCode(u32 entry, std::vector<u32> code); + + /// Returns a list of enabled textures for the specified shader stage. + std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const; + +private: + MemoryManager& memory_manager; + + std::unordered_map<u32, std::vector<u32>> uploaded_macros; + + /// Macro method that is currently being executed / being fed parameters. + u32 executing_macro = 0; + /// Parameters that have been submitted to the macro call so far. + std::vector<u32> macro_params; + + /// Interpreter for the macro codes uploaded to the GPU. + MacroInterpreter macro_interpreter; + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; + + /** + * Call a macro on this engine. + * @param method Method to call + * @param parameters Arguments to the method call + */ + void CallMacroMethod(u32 method, std::vector<u32> parameters); + + /// Handles a write to the QUERY_GET register. + void ProcessQueryGet(); + + /// Handles a write to the CB_DATA[i] register. + void ProcessCBData(u32 value); + + /// Handles a write to the CB_BIND register. + void ProcessCBBind(Regs::ShaderStage stage); + + /// Handles a write to the VERTEX_END_GL register, triggering a draw. + void DrawArrays(); +}; + +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(rt, 0x200); +ASSERT_REG_POSITION(viewport, 0x300); +ASSERT_REG_POSITION(vertex_buffer, 0x35D); +ASSERT_REG_POSITION(zeta, 0x3F8); +ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458); +ASSERT_REG_POSITION(rt_control, 0x487); +ASSERT_REG_POSITION(tsc, 0x557); +ASSERT_REG_POSITION(tic, 0x55D); +ASSERT_REG_POSITION(code_address, 0x582); +ASSERT_REG_POSITION(draw, 0x585); +ASSERT_REG_POSITION(query, 0x6C0); +ASSERT_REG_POSITION(vertex_array[0], 0x700); +ASSERT_REG_POSITION(vertex_array_limit[0], 0x7C0); +ASSERT_REG_POSITION(shader_config[0], 0x800); +ASSERT_REG_POSITION(const_buffer, 0x8E0); +ASSERT_REG_POSITION(cb_bind[0], 0x904); +ASSERT_REG_POSITION(tex_cb_index, 0x982); +ASSERT_REG_POSITION(ssbo_info, 0xD18); +ASSERT_REG_POSITION(tex_info_buffers.address[0], 0xD2A); +ASSERT_REG_POSITION(tex_info_buffers.size[0], 0xD2F); + +#undef ASSERT_REG_POSITION + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp new file mode 100644 index 000000000..e4e5f9e5e --- /dev/null +++ b/src/video_core/engines/maxwell_compute.cpp @@ -0,0 +1,13 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/engines/maxwell_compute.h" + +namespace Tegra { +namespace Engines { + +void MaxwellCompute::WriteReg(u32 method, u32 value) {} + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h new file mode 100644 index 000000000..7262e1bcb --- /dev/null +++ b/src/video_core/engines/maxwell_compute.h @@ -0,0 +1,22 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Tegra { +namespace Engines { + +class MaxwellCompute final { +public: + MaxwellCompute() = default; + ~MaxwellCompute() = default; + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value); +}; + +} // namespace Engines +} // namespace Tegra diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp new file mode 100644 index 000000000..9463cd5d6 --- /dev/null +++ b/src/video_core/gpu.cpp @@ -0,0 +1,25 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/engines/maxwell_compute.h" +#include "video_core/gpu.h" + +namespace Tegra { + +GPU::GPU() { + memory_manager = std::make_unique<MemoryManager>(); + maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager); + fermi_2d = std::make_unique<Engines::Fermi2D>(); + maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); +} + +GPU::~GPU() = default; + +const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const { + return *maxwell_3d; +} + +} // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h new file mode 100644 index 000000000..71a8661b4 --- /dev/null +++ b/src/video_core/gpu.h @@ -0,0 +1,107 @@ +// Copyright 2018 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 <vector> +#include "common/common_types.h" +#include "core/hle/service/nvflinger/buffer_queue.h" +#include "video_core/memory_manager.h" + +namespace Tegra { + +enum class RenderTargetFormat : u32 { + NONE = 0x0, + RGBA8_UNORM = 0xD5, +}; + +class DebugContext; + +/** + * Struct describing framebuffer configuration + */ +struct FramebufferConfig { + enum class PixelFormat : u32 { + ABGR8 = 1, + }; + + /** + * Returns the number of bytes per pixel. + */ + static u32 BytesPerPixel(PixelFormat format) { + switch (format) { + case PixelFormat::ABGR8: + return 4; + } + + UNREACHABLE(); + } + + VAddr address; + u32 offset; + u32 width; + u32 height; + u32 stride; + PixelFormat pixel_format; + + using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags; + TransformFlags transform_flags; +}; + +namespace Engines { +class Fermi2D; +class Maxwell3D; +class MaxwellCompute; +} // namespace Engines + +enum class EngineID { + FERMI_TWOD_A = 0x902D, // 2D Engine + MAXWELL_B = 0xB197, // 3D Engine + MAXWELL_COMPUTE_B = 0xB1C0, + KEPLER_INLINE_TO_MEMORY_B = 0xA140, + MAXWELL_DMA_COPY_A = 0xB0B5, +}; + +class GPU final { +public: + GPU(); + ~GPU(); + + /// Processes a command list stored at the specified address in GPU memory. + void ProcessCommandList(GPUVAddr address, u32 size); + + /// Returns a reference to the Maxwell3D GPU engine. + const Engines::Maxwell3D& Get3DEngine() const; + + std::unique_ptr<MemoryManager> memory_manager; + + Engines::Maxwell3D& Maxwell3D() { + return *maxwell_3d; + } + +private: + static constexpr u32 InvalidGraphMacroEntry = 0xFFFFFFFF; + + /// Writes a single register in the engine bound to the specified subchannel + void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params); + + /// Mapping of command subchannels to their bound engine ids. + std::unordered_map<u32, EngineID> bound_engines; + + /// 3D engine + std::unique_ptr<Engines::Maxwell3D> maxwell_3d; + /// 2D engine + std::unique_ptr<Engines::Fermi2D> fermi_2d; + /// Compute engine + std::unique_ptr<Engines::MaxwellCompute> maxwell_compute; + + /// Entry of the macro that is currently being uploaded + u32 current_macro_entry = InvalidGraphMacroEntry; + /// Code being uploaded for the current macro + std::vector<u32> current_macro_code; +}; + +} // namespace Tegra diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp new file mode 100644 index 000000000..993a67746 --- /dev/null +++ b/src/video_core/macro_interpreter.cpp @@ -0,0 +1,257 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/macro_interpreter.h" + +namespace Tegra { + +MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} + +void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) { + Reset(); + registers[1] = parameters[0]; + this->parameters = std::move(parameters); + + // Execute the code until we hit an exit condition. + bool keep_executing = true; + while (keep_executing) { + keep_executing = Step(code, false); + } + + // Assert the the macro used all the input parameters + ASSERT(next_parameter_index == this->parameters.size()); +} + +void MacroInterpreter::Reset() { + registers = {}; + pc = 0; + delayed_pc = boost::none; + method_address.raw = 0; + parameters.clear(); + // The next parameter index starts at 1, because $r1 already has the value of the first + // parameter. + next_parameter_index = 1; +} + +bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) { + u32 base_address = pc; + + Opcode opcode = GetOpcode(code); + pc += 4; + + // Update the program counter if we were delayed + if (delayed_pc != boost::none) { + ASSERT(is_delay_slot); + pc = *delayed_pc; + delayed_pc = boost::none; + } + + switch (opcode.operation) { + case Operation::ALU: { + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), + GetRegister(opcode.src_b)); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::AddImmediate: { + ProcessResult(opcode.result_operation, opcode.dst, + GetRegister(opcode.src_a) + opcode.immediate); + break; + } + case Operation::ExtractInsert: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); + dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); + dst |= src << opcode.bf_dst_bit; + ProcessResult(opcode.result_operation, opcode.dst, dst); + break; + } + case Operation::ExtractShiftLeftImmediate: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::ExtractShiftLeftRegister: { + u32 dst = GetRegister(opcode.src_a); + u32 src = GetRegister(opcode.src_b); + + u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; + + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Read: { + u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, result); + break; + } + case Operation::Branch: { + ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); + u32 value = GetRegister(opcode.src_a); + bool taken = EvaluateBranchCondition(opcode.branch_condition, value); + if (taken) { + // Ignore the delay slot if the branch has the annul bit. + if (opcode.branch_annul) { + pc = base_address + (opcode.immediate << 2); + return true; + } + + delayed_pc = base_address + (opcode.immediate << 2); + // Execute one more instruction due to the delay slot. + return Step(code, true); + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented macro operation %u", + static_cast<u32>(opcode.operation.Value())); + } + + if (opcode.is_exit) { + // Exit has a delay slot, execute the next instruction + // Note: Executing an exit during a branch delay slot will cause the instruction at the + // branch target to be executed before exiting. + Step(code, true); + return false; + } + + return true; +} + +MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const { + ASSERT((pc % sizeof(u32)) == 0); + ASSERT(pc < code.size() * sizeof(u32)); + return {code[pc / sizeof(u32)]}; +} + +u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const { + switch (operation) { + case ALUOperation::Add: + return src_a + src_b; + // TODO(Subv): Implement AddWithCarry + case ALUOperation::Subtract: + return src_a - src_b; + // TODO(Subv): Implement SubtractWithBorrow + case ALUOperation::Xor: + return src_a ^ src_b; + case ALUOperation::Or: + return src_a | src_b; + case ALUOperation::And: + return src_a & src_b; + case ALUOperation::AndNot: + return src_a & ~src_b; + case ALUOperation::Nand: + return ~(src_a & src_b); + + default: + UNIMPLEMENTED_MSG("Unimplemented ALU operation %u", static_cast<u32>(operation)); + } +} + +void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) { + switch (operation) { + case ResultOperation::IgnoreAndFetch: + // Fetch parameter and ignore result. + SetRegister(reg, FetchParameter()); + break; + case ResultOperation::Move: + // Move result. + SetRegister(reg, result); + break; + case ResultOperation::MoveAndSetMethod: + // Move result and use as Method Address. + SetRegister(reg, result); + SetMethodAddress(result); + break; + case ResultOperation::FetchAndSend: + // Fetch parameter and send result. + SetRegister(reg, FetchParameter()); + Send(result); + break; + case ResultOperation::MoveAndSend: + // Move and send result. + SetRegister(reg, result); + Send(result); + break; + case ResultOperation::FetchAndSetMethod: + // Fetch parameter and use result as Method Address. + SetRegister(reg, FetchParameter()); + SetMethodAddress(result); + break; + case ResultOperation::MoveAndSetMethodFetchAndSend: + // Move result and use as Method Address, then fetch and send parameter. + SetRegister(reg, result); + SetMethodAddress(result); + Send(FetchParameter()); + break; + case ResultOperation::MoveAndSetMethodSend: + // Move result and use as Method Address, then send bits 12:17 of result. + SetRegister(reg, result); + SetMethodAddress(result); + Send((result >> 12) & 0b111111); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented result operation %u", static_cast<u32>(operation)); + } +} + +u32 MacroInterpreter::FetchParameter() { + ASSERT(next_parameter_index < parameters.size()); + return parameters[next_parameter_index++]; +} + +u32 MacroInterpreter::GetRegister(u32 register_id) const { + // Register 0 is supposed to always return 0. + if (register_id == 0) + return 0; + + ASSERT(register_id < registers.size()); + return registers[register_id]; +} + +void MacroInterpreter::SetRegister(u32 register_id, u32 value) { + // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero + // register. + if (register_id == 0) + return; + + ASSERT(register_id < registers.size()); + registers[register_id] = value; +} + +void MacroInterpreter::SetMethodAddress(u32 address) { + method_address.raw = address; +} + +void MacroInterpreter::Send(u32 value) { + maxwell3d.WriteReg(method_address.address, value, 0); + // Increment the method address by the method increment. + method_address.address.Assign(method_address.address.Value() + + method_address.increment.Value()); +} + +u32 MacroInterpreter::Read(u32 method) const { + return maxwell3d.GetRegisterValue(method); +} + +bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const { + switch (cond) { + case BranchCondition::Zero: + return value == 0; + case BranchCondition::NotZero: + return value != 0; + } + UNREACHABLE(); +} + +} // namespace Tegra diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h new file mode 100644 index 000000000..a71e359d8 --- /dev/null +++ b/src/video_core/macro_interpreter.h @@ -0,0 +1,164 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include <boost/optional.hpp> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Tegra { +namespace Engines { +class Maxwell3D; +} + +class MacroInterpreter final { +public: + explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d); + + /** + * Executes the macro code with the specified input parameters. + * @param code The macro byte code to execute + * @param parameters The parameters of the macro + */ + void Execute(const std::vector<u32>& code, std::vector<u32> parameters); + +private: + enum class Operation : u32 { + ALU = 0, + AddImmediate = 1, + ExtractInsert = 2, + ExtractShiftLeftImmediate = 3, + ExtractShiftLeftRegister = 4, + Read = 5, + Unused = 6, // This operation doesn't seem to be a valid encoding. + Branch = 7, + }; + + enum class ALUOperation : u32 { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + // Operations 4-7 don't seem to be valid encodings. + Xor = 8, + Or = 9, + And = 10, + AndNot = 11, + Nand = 12 + }; + + enum class ResultOperation : u32 { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMethod = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMethod = 5, + MoveAndSetMethodFetchAndSend = 6, + MoveAndSetMethodSend = 7 + }; + + enum class BranchCondition : u32 { + Zero = 0, + NotZero = 1, + }; + + union Opcode { + u32 raw; + BitField<0, 3, Operation> operation; + BitField<4, 3, ResultOperation> result_operation; + BitField<4, 1, BranchCondition> branch_condition; + BitField<5, 1, u32> + branch_annul; // If set on a branch, then the branch doesn't have a delay slot. + BitField<7, 1, u32> is_exit; + BitField<8, 3, u32> dst; + BitField<11, 3, u32> src_a; + BitField<14, 3, u32> src_b; + // The signed immediate overlaps the second source operand and the alu operation. + BitField<14, 18, s32> immediate; + + BitField<17, 5, ALUOperation> alu_operation; + + // Bitfield instructions data + BitField<17, 5, u32> bf_src_bit; + BitField<22, 5, u32> bf_size; + BitField<27, 5, u32> bf_dst_bit; + + u32 GetBitfieldMask() const { + return (1 << bf_size) - 1; + } + }; + + union MethodAddress { + u32 raw; + BitField<0, 12, u32> address; + BitField<12, 6, u32> increment; + }; + + /// Resets the execution engine state, zeroing registers, etc. + void Reset(); + + /** + * Executes a single macro instruction located at the current program counter. Returns whether + * the interpreter should keep running. + * @param code The macro code to execute. + * @param is_delay_slot Whether the current step is being executed due to a delay slot in a + * previous instruction. + */ + bool Step(const std::vector<u32>& code, bool is_delay_slot); + + /// Calculates the result of an ALU operation. src_a OP src_b; + u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const; + + /// Performs the result operation on the input result and stores it in the specified register + /// (if necessary). + void ProcessResult(ResultOperation operation, u32 reg, u32 result); + + /// Evaluates the branch condition and returns whether the branch should be taken or not. + bool EvaluateBranchCondition(BranchCondition cond, u32 value) const; + + /// Reads an opcode at the current program counter location. + Opcode GetOpcode(const std::vector<u32>& code) const; + + /// Returns the specified register's value. Register 0 is hardcoded to always return 0. + u32 GetRegister(u32 register_id) const; + + /// Sets the register to the input value. + void SetRegister(u32 register_id, u32 value); + + /// Sets the method address to use for the next Send instruction. + void SetMethodAddress(u32 address); + + /// Calls a GPU Engine method with the input parameter. + void Send(u32 value); + + /// Reads a GPU register located at the method address. + u32 Read(u32 method) const; + + /// Returns the next parameter in the parameter queue. + u32 FetchParameter(); + + Engines::Maxwell3D& maxwell3d; + + u32 pc; ///< Current program counter + boost::optional<u32> + delayed_pc; ///< Program counter to execute at after the delay slot is executed. + + static constexpr size_t NumMacroRegisters = 8; + + /// General purpose macro registers. + std::array<u32, NumMacroRegisters> registers = {}; + + /// Method address to use for the next Send instruction. + MethodAddress method_address = {}; + + /// Input parameters of the current macro. + std::vector<u32> parameters; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; +}; +} // namespace Tegra diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp new file mode 100644 index 000000000..2789a4ca1 --- /dev/null +++ b/src/video_core/memory_manager.cpp @@ -0,0 +1,110 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/memory_manager.h" + +namespace Tegra { + +PAddr MemoryManager::AllocateSpace(u64 size, u64 align) { + boost::optional<PAddr> paddr = FindFreeBlock(size, align); + ASSERT(paddr); + + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + PageSlot(*paddr + offset) = static_cast<u64>(PageStatus::Allocated); + } + + return *paddr; +} + +PAddr MemoryManager::AllocateSpace(PAddr paddr, u64 size, u64 align) { + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + if (IsPageMapped(paddr + offset)) { + return AllocateSpace(size, align); + } + } + + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + PageSlot(paddr + offset) = static_cast<u64>(PageStatus::Allocated); + } + + return paddr; +} + +PAddr MemoryManager::MapBufferEx(VAddr vaddr, u64 size) { + vaddr &= ~Memory::PAGE_MASK; + + boost::optional<PAddr> paddr = FindFreeBlock(size); + ASSERT(paddr); + + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + PageSlot(*paddr + offset) = vaddr + offset; + } + + return *paddr; +} + +PAddr MemoryManager::MapBufferEx(VAddr vaddr, PAddr paddr, u64 size) { + vaddr &= ~Memory::PAGE_MASK; + paddr &= ~Memory::PAGE_MASK; + + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + if (PageSlot(paddr + offset) != static_cast<u64>(PageStatus::Allocated)) { + return MapBufferEx(vaddr, size); + } + } + + for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) { + PageSlot(paddr + offset) = vaddr + offset; + } + + return paddr; +} + +boost::optional<PAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) { + PAddr paddr{}; + u64 free_space{}; + align = (align + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; + + while (paddr + free_space < MAX_ADDRESS) { + if (!IsPageMapped(paddr + free_space)) { + free_space += Memory::PAGE_SIZE; + if (free_space >= size) { + return paddr; + } + } else { + paddr += free_space + Memory::PAGE_SIZE; + free_space = 0; + const u64 remainder{paddr % align}; + if (!remainder) { + paddr = (paddr - remainder) + align; + } + } + } + + return {}; +} + +VAddr MemoryManager::PhysicalToVirtualAddress(PAddr paddr) { + VAddr base_addr = PageSlot(paddr); + ASSERT(base_addr != static_cast<u64>(PageStatus::Unmapped)); + return base_addr + (paddr & Memory::PAGE_MASK); +} + +bool MemoryManager::IsPageMapped(PAddr paddr) { + return PageSlot(paddr) != static_cast<u64>(PageStatus::Unmapped); +} + +VAddr& MemoryManager::PageSlot(PAddr paddr) { + auto& block = page_table[(paddr >> (Memory::PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]; + if (!block) { + block = std::make_unique<PageBlock>(); + for (unsigned index = 0; index < PAGE_BLOCK_SIZE; index++) { + (*block)[index] = static_cast<u64>(PageStatus::Unmapped); + } + } + return (*block)[(paddr >> Memory::PAGE_BITS) & PAGE_BLOCK_MASK]; +} + +} // namespace Tegra diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h new file mode 100644 index 000000000..47da7acd6 --- /dev/null +++ b/src/video_core/memory_manager.h @@ -0,0 +1,49 @@ +// 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> +#include "common/common_types.h" +#include "core/memory.h" + +namespace Tegra { + +/// Virtual addresses in the GPU's memory map are 64 bit. +using GPUVAddr = u64; + +class MemoryManager final { +public: + MemoryManager() = default; + + PAddr AllocateSpace(u64 size, u64 align); + PAddr AllocateSpace(PAddr paddr, u64 size, u64 align); + PAddr MapBufferEx(VAddr vaddr, u64 size); + PAddr MapBufferEx(VAddr vaddr, PAddr paddr, u64 size); + VAddr PhysicalToVirtualAddress(PAddr paddr); + +private: + boost::optional<PAddr> FindFreeBlock(u64 size, u64 align = 1); + bool IsPageMapped(PAddr paddr); + VAddr& PageSlot(PAddr paddr); + + enum class PageStatus : u64 { + Unmapped = 0xFFFFFFFFFFFFFFFFULL, + Allocated = 0xFFFFFFFFFFFFFFFEULL, + }; + + static constexpr u64 MAX_ADDRESS{0x10000000000ULL}; + static constexpr u64 PAGE_TABLE_BITS{14}; + static constexpr u64 PAGE_TABLE_SIZE{1 << PAGE_TABLE_BITS}; + static constexpr u64 PAGE_TABLE_MASK{PAGE_TABLE_SIZE - 1}; + static constexpr u64 PAGE_BLOCK_BITS{14}; + static constexpr u64 PAGE_BLOCK_SIZE{1 << PAGE_BLOCK_BITS}; + static constexpr u64 PAGE_BLOCK_MASK{PAGE_BLOCK_SIZE - 1}; + + using PageBlock = std::array<VAddr, PAGE_BLOCK_SIZE>; + std::array<std::unique_ptr<PageBlock>, PAGE_TABLE_SIZE> page_table{}; +}; + +} // namespace Tegra diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h new file mode 100644 index 000000000..8239f9aad --- /dev/null +++ b/src/video_core/rasterizer_interface.h @@ -0,0 +1,63 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "video_core/gpu.h" + +struct ScreenInfo; + +namespace VideoCore { + +class RasterizerInterface { +public: + virtual ~RasterizerInterface() {} + + /// Draw the current batch of vertex arrays + virtual void DrawArrays() = 0; + + /// Notify rasterizer that the specified Maxwell register has been changed + virtual void NotifyMaxwellRegisterChanged(u32 id) = 0; + + /// Notify rasterizer that all caches should be flushed to 3DS memory + virtual void FlushAll() = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory + virtual void FlushRegion(VAddr addr, u64 size) = 0; + + /// Notify rasterizer that any caches of the specified region should be invalidated + virtual void InvalidateRegion(VAddr addr, u64 size) = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory + /// and invalidated + virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; + + /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0 + virtual bool AccelerateDisplayTransfer(const void* config) { + return false; + } + + /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1 + virtual bool AccelerateTextureCopy(const void* config) { + return false; + } + + /// Attempt to use a faster method to fill a region + virtual bool AccelerateFill(const void* config) { + return false; + } + + /// Attempt to use a faster method to display the framebuffer to screen + virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, + ScreenInfo& screen_info) { + return false; + } + + virtual bool AccelerateDrawBatch(bool is_indexed) { + return false; + } +}; +} // namespace VideoCore diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 51e1d45f9..30075b23c 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -5,6 +5,11 @@ #include <atomic> #include <memory> #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/video_core.h" -void RendererBase::RefreshRasterizerSetting() {} +void RendererBase::RefreshRasterizerSetting() { + if (rasterizer == nullptr) { + rasterizer = std::make_unique<RasterizerOpenGL>(); + } +} diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 28893b181..89a960eaf 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -8,6 +8,8 @@ #include <boost/optional.hpp> #include "common/assert.h" #include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" class EmuWindow; @@ -16,39 +18,10 @@ public: /// Used to reference a framebuffer enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture }; - /** - * Struct describing framebuffer metadata - * TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet. - */ - struct FramebufferInfo { - enum class PixelFormat : u32 { - ABGR8 = 1, - }; - - /** - * Returns the number of bytes per pixel. - */ - static u32 BytesPerPixel(PixelFormat format) { - switch (format) { - case PixelFormat::ABGR8: - return 4; - } - - UNREACHABLE(); - } - - VAddr address; - u32 offset; - u32 width; - u32 height; - u32 stride; - PixelFormat pixel_format; - }; - virtual ~RendererBase() {} /// Swap buffers (render frame) - virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0; + virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0; /** * Set the emulator window to use for renderer @@ -73,12 +46,16 @@ public: return m_current_frame; } + VideoCore::RasterizerInterface* Rasterizer() const { + return rasterizer.get(); + } + void RefreshRasterizerSetting(); protected: + std::unique_ptr<VideoCore::RasterizerInterface> rasterizer; f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer int m_current_frame = 0; ///< Current frame, should be set by the renderer private: - bool opengl_rasterizer_active = false; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp new file mode 100644 index 000000000..911890f16 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -0,0 +1,578 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <glad/glad.h> +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "common/vector_math.h" +#include "core/core.h" +#include "core/hle/kernel/process.h" +#include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/maxwell_to_gl.h" +#include "video_core/renderer_opengl/renderer_opengl.h" + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; +using PixelFormat = SurfaceParams::PixelFormat; +using SurfaceType = SurfaceParams::SurfaceType; + +MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); +MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); + +enum class UniformBindings : GLuint { Common, VS, FS }; + +static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding, + size_t expected_size) { + GLuint ub_index = glGetUniformBlockIndex(shader, name); + if (ub_index != GL_INVALID_INDEX) { + GLint ub_size = 0; + glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); + ASSERT_MSG(ub_size == expected_size, + "Uniform block size did not match! Got %d, expected %zu", + static_cast<int>(ub_size), expected_size); + glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding)); + } +} + +static void SetShaderUniformBlockBindings(GLuint shader) { + SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common, + sizeof(RasterizerOpenGL::UniformData)); + SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS, + sizeof(RasterizerOpenGL::VSUniformData)); + SetShaderUniformBlockBinding(shader, "fs_config", UniformBindings::FS, + sizeof(RasterizerOpenGL::FSUniformData)); +} + +RasterizerOpenGL::RasterizerOpenGL() { + shader_dirty = true; + + has_ARB_buffer_storage = false; + has_ARB_direct_state_access = false; + has_ARB_separate_shader_objects = false; + has_ARB_vertex_attrib_binding = false; + + GLint ext_num; + glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num); + for (GLint i = 0; i < ext_num; i++) { + std::string extension{reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))}; + + if (extension == "GL_ARB_buffer_storage") { + has_ARB_buffer_storage = true; + } else if (extension == "GL_ARB_direct_state_access") { + has_ARB_direct_state_access = true; + } else if (extension == "GL_ARB_separate_shader_objects") { + has_ARB_separate_shader_objects = true; + } else if (extension == "GL_ARB_vertex_attrib_binding") { + has_ARB_vertex_attrib_binding = true; + } + } + + // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0 + state.clip_distance[0] = true; + + // Generate VBO, VAO and UBO + vertex_buffer = OGLStreamBuffer::MakeBuffer(GLAD_GL_ARB_buffer_storage, GL_ARRAY_BUFFER); + vertex_buffer->Create(VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE / 2); + sw_vao.Create(); + uniform_buffer.Create(); + + state.draw.vertex_array = sw_vao.handle; + state.draw.vertex_buffer = vertex_buffer->GetHandle(); + state.draw.uniform_buffer = uniform_buffer.handle; + state.Apply(); + + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), nullptr, GL_STATIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, uniform_buffer.handle); + + uniform_block_data.dirty = true; + + // Create render framebuffer + framebuffer.Create(); + + if (has_ARB_separate_shader_objects) { + hw_vao.Create(); + hw_vao_enabled_attributes.fill(false); + + stream_buffer = OGLStreamBuffer::MakeBuffer(has_ARB_buffer_storage, GL_ARRAY_BUFFER); + stream_buffer->Create(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE / 2); + state.draw.vertex_buffer = stream_buffer->GetHandle(); + + pipeline.Create(); + state.draw.program_pipeline = pipeline.handle; + state.draw.shader_program = 0; + state.draw.vertex_array = hw_vao.handle; + state.Apply(); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer->GetHandle()); + + vs_uniform_buffer.Create(); + glBindBuffer(GL_UNIFORM_BUFFER, vs_uniform_buffer.handle); + glBufferData(GL_UNIFORM_BUFFER, sizeof(VSUniformData), nullptr, GL_STREAM_COPY); + glBindBufferBase(GL_UNIFORM_BUFFER, 1, vs_uniform_buffer.handle); + } else { + UNREACHABLE(); + } + + accelerate_draw = AccelDraw::Disabled; + + glEnable(GL_BLEND); + + LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!"); +} + +RasterizerOpenGL::~RasterizerOpenGL() { + if (stream_buffer != nullptr) { + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + stream_buffer->Release(); + } +} + +void RasterizerOpenGL::AnalyzeVertexArray(bool is_indexed) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + if (is_indexed) { + UNREACHABLE(); + } + + // TODO(bunnei): Add support for 1+ vertex arrays + vs_input_size = regs.vertex_buffer.count * regs.vertex_array[0].stride; +} + +void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset) { + MICROPROFILE_SCOPE(OpenGL_VAO); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager; + + state.draw.vertex_array = hw_vao.handle; + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + // TODO(bunnei): Add support for 1+ vertex arrays + const auto& vertex_array{regs.vertex_array[0]}; + ASSERT_MSG(vertex_array.enable, "vertex array 0 is disabled?"); + ASSERT_MSG(!vertex_array.divisor, "vertex array 0 divisor is unimplemented!"); + for (unsigned index = 1; index < Maxwell::NumVertexArrays; ++index) { + ASSERT_MSG(!regs.vertex_array[index].enable, "vertex array %d is unimplemented!", index); + } + + // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. + // Enables the first 16 vertex attributes always, as we don't know which ones are actually used + // until shader time. Note, Tegra technically supports 32, but we're cappinig this to 16 for now + // to avoid OpenGL errors. + for (unsigned index = 0; index < 16; ++index) { + auto& attrib = regs.vertex_attrib_format[index]; + glVertexAttribPointer(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, vertex_array.stride, + reinterpret_cast<GLvoid*>(buffer_offset + attrib.offset)); + glEnableVertexAttribArray(index); + hw_vao_enabled_attributes[index] = true; + } + + // Copy vertex array data + const u32 data_size{vertex_array.stride * regs.vertex_buffer.count}; + const VAddr data_addr{memory_manager->PhysicalToVirtualAddress(vertex_array.StartAddress())}; + res_cache.FlushRegion(data_addr, data_size, nullptr); + Memory::ReadBlock(data_addr, array_ptr, data_size); + + array_ptr += data_size; + buffer_offset += data_size; +} + +void RasterizerOpenGL::SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset) { + MICROPROFILE_SCOPE(OpenGL_VS); + LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader."); + glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_shader->shader.handle); +} + +void RasterizerOpenGL::SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset) { + MICROPROFILE_SCOPE(OpenGL_FS); + UNREACHABLE(); +} + +bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { + if (!has_ARB_separate_shader_objects) { + UNREACHABLE(); + return false; + } + + accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; + DrawArrays(); + + return true; +} + +void RasterizerOpenGL::DrawArrays() { + if (accelerate_draw == AccelDraw::Disabled) + return; + + MICROPROFILE_SCOPE(OpenGL_Drawing); + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + + // TODO(bunnei): Implement these + const bool has_stencil = false; + const bool using_color_fb = true; + const bool using_depth_fb = false; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()}; + + const bool write_color_fb = + state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || + state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; + + const bool write_depth_fb = + (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) || + (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0); + + Surface color_surface; + Surface depth_surface; + MathUtil::Rectangle<u32> surfaces_rect; + std::tie(color_surface, depth_surface, surfaces_rect) = + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect); + + const u16 res_scale = color_surface != nullptr + ? color_surface->res_scale + : (depth_surface == nullptr ? 1u : depth_surface->res_scale); + + MathUtil::Rectangle<u32> draw_rect{ + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) + + viewport_rect.left * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Left + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + + viewport_rect.top * res_scale, + surfaces_rect.bottom, surfaces_rect.top)), // Top + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) + + viewport_rect.right * res_scale, + surfaces_rect.left, surfaces_rect.right)), // Right + static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + + viewport_rect.bottom * res_scale, + surfaces_rect.bottom, surfaces_rect.top))}; // Bottom + + // Bind the framebuffer surfaces + BindFramebufferSurfaces(color_surface, depth_surface, has_stencil); + + // Sync the viewport + SyncViewport(surfaces_rect, res_scale); + + // TODO(bunnei): Sync framebuffer_scale uniform here + // TODO(bunnei): Sync scissorbox uniform(s) here + // TODO(bunnei): Sync and bind the texture surfaces + + // Sync and bind the shader + if (shader_dirty) { + SetShader(); + shader_dirty = false; + } + + // Sync the uniform data + if (uniform_block_data.dirty) { + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformData), &uniform_block_data.data); + uniform_block_data.dirty = false; + } + + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable + // scissor test to prevent drawing outside of the framebuffer region + state.scissor.enabled = true; + state.scissor.x = draw_rect.left; + state.scissor.y = draw_rect.bottom; + state.scissor.width = draw_rect.GetWidth(); + state.scissor.height = draw_rect.GetHeight(); + state.Apply(); + + // Draw the vertex batch + const bool is_indexed = accelerate_draw == AccelDraw::Indexed; + AnalyzeVertexArray(is_indexed); + state.draw.vertex_buffer = stream_buffer->GetHandle(); + state.Apply(); + + size_t buffer_size = static_cast<size_t>(vs_input_size); + if (is_indexed) { + UNREACHABLE(); + } + buffer_size += sizeof(VSUniformData); + + size_t ptr_pos = 0; + u8* buffer_ptr; + GLintptr buffer_offset; + std::tie(buffer_ptr, buffer_offset) = + stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4); + + SetupVertexArray(buffer_ptr, buffer_offset); + ptr_pos += vs_input_size; + + GLintptr index_buffer_offset = 0; + if (is_indexed) { + UNREACHABLE(); + } + + SetupVertexShader(reinterpret_cast<VSUniformData*>(&buffer_ptr[ptr_pos]), + buffer_offset + static_cast<GLintptr>(ptr_pos)); + const GLintptr vs_ubo_offset = buffer_offset + static_cast<GLintptr>(ptr_pos); + ptr_pos += sizeof(VSUniformData); + + stream_buffer->Unmap(); + + const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) { + if (has_ARB_direct_state_access) { + glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size); + } else { + glBindBuffer(GL_COPY_WRITE_BUFFER, handle); + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size); + } + }; + + copy_buffer(vs_uniform_buffer.handle, vs_ubo_offset, sizeof(VSUniformData)); + + glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_shader->shader.handle); + + if (is_indexed) { + UNREACHABLE(); + } else { + glDrawArrays(MaxwellToGL::PrimitiveTopology(regs.draw.topology), 0, + regs.vertex_buffer.count); + } + + // Disable scissor test + state.scissor.enabled = false; + + accelerate_draw = AccelDraw::Disabled; + + // Unbind textures for potential future use as framebuffer attachments + for (auto& texture_unit : state.texture_units) { + texture_unit.texture_2d = 0; + } + state.Apply(); + + // Mark framebuffer surfaces as dirty + MathUtil::Rectangle<u32> draw_rect_unscaled{ + draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale, + draw_rect.bottom / res_scale}; + + if (color_surface != nullptr && write_color_fb) { + auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + color_surface); + } + if (depth_surface != nullptr && write_depth_fb) { + auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + depth_surface); + } +} + +void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 id) {} + +void RasterizerOpenGL::FlushAll() { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushAll(); +} + +void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushRegion(addr, size); +} + +void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.InvalidateRegion(addr, size, nullptr); +} + +void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + res_cache.FlushRegion(addr, size); + res_cache.InvalidateRegion(addr, size, nullptr); +} + +bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { + MICROPROFILE_SCOPE(OpenGL_Blits); + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) { + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateFill(const void* config) { + UNREACHABLE(); + return true; +} + +bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, + VAddr framebuffer_addr, u32 pixel_stride, + ScreenInfo& screen_info) { + if (framebuffer_addr == 0) { + return false; + } + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + + SurfaceParams src_params; + src_params.addr = framebuffer_addr; + src_params.width = std::min(framebuffer.width, pixel_stride); + src_params.height = framebuffer.height; + src_params.stride = pixel_stride; + src_params.is_tiled = false; + src_params.pixel_format = + SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format); + src_params.UpdateParams(); + + MathUtil::Rectangle<u32> src_rect; + Surface src_surface; + std::tie(src_surface, src_rect) = + res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true); + + if (src_surface == nullptr) { + return false; + } + + u32 scaled_width = src_surface->GetScaledWidth(); + u32 scaled_height = src_surface->GetScaledHeight(); + + screen_info.display_texcoords = MathUtil::Rectangle<float>( + (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width, + (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width); + + screen_info.display_texture = src_surface->texture.handle; + + return true; +} + +void RasterizerOpenGL::SetShader() { + // TODO(bunnei): The below sets up a static test shader for passing untransformed vertices to + // OpenGL for rendering. This should be removed/replaced when we start emulating Maxwell + // shaders. + + static constexpr char vertex_shader[] = R"( +#version 150 core + +in vec2 vert_position; +in vec2 vert_tex_coord; +out vec2 frag_tex_coord; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)) * vert_position + mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} +)"; + + static constexpr char fragment_shader[] = R"( +#version 150 core + +in vec2 frag_tex_coord; +out vec4 color; + +uniform sampler2D color_texture; + +void main() { + color = vec4(1.0, 0.0, 1.0, 0.0); +} +)"; + + if (current_shader) { + return; + } + + LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader."); + + current_shader = &test_shader; + if (has_ARB_separate_shader_objects) { + test_shader.shader.Create(vertex_shader, nullptr, fragment_shader, {}, true); + glActiveShaderProgram(pipeline.handle, test_shader.shader.handle); + } else { + UNREACHABLE(); + } + + state.draw.shader_program = test_shader.shader.handle; + state.Apply(); + + if (has_ARB_separate_shader_objects) { + state.draw.shader_program = 0; + state.Apply(); + } +} + +void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface, + const Surface& depth_surface, bool has_stencil) { + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + color_surface != nullptr ? color_surface->texture.handle : 0, 0); + if (depth_surface != nullptr) { + if (has_stencil) { + // attach both depth and stencil + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + } else { + // attach depth + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->texture.handle, 0); + // clear stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + } else { + // clear both depth and stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } +} + +void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()}; + + state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale; + state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale; + state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale); + state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale); +} + +void RasterizerOpenGL::SyncClipEnabled() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncClipCoef() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncCullMode() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncDepthScale() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncDepthOffset() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncBlendEnabled() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncBlendFuncs() { + UNREACHABLE(); +} + +void RasterizerOpenGL::SyncBlendColor() { + UNREACHABLE(); +} diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h new file mode 100644 index 000000000..fd53e94cd --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -0,0 +1,179 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <cstring> +#include <memory> +#include <unordered_map> +#include <vector> +#include <glad/glad.h> +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/hash.h" +#include "common/vector_math.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +struct ScreenInfo; + +class RasterizerOpenGL : public VideoCore::RasterizerInterface { +public: + RasterizerOpenGL(); + ~RasterizerOpenGL() override; + + void DrawArrays() override; + void NotifyMaxwellRegisterChanged(u32 id) override; + void FlushAll() override; + void FlushRegion(VAddr addr, u64 size) override; + void InvalidateRegion(VAddr addr, u64 size) override; + void FlushAndInvalidateRegion(VAddr addr, u64 size) override; + bool AccelerateDisplayTransfer(const void* config) override; + bool AccelerateTextureCopy(const void* config) override; + bool AccelerateFill(const void* config) override; + bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr, + u32 pixel_stride, ScreenInfo& screen_info) override; + bool AccelerateDrawBatch(bool is_indexed) override; + + /// OpenGL shader generated for a given Maxwell register state + struct MaxwellShader { + /// OpenGL shader resource + OGLShader shader; + }; + + struct VertexShader { + OGLShader shader; + }; + + struct FragmentShader { + OGLShader shader; + }; + + /// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned + // NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at + // the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not. + // Not following that rule will cause problems on some AMD drivers. + struct UniformData {}; + + // static_assert( + // sizeof(UniformData) == 0x460, + // "The size of the UniformData structure has changed, update the structure in the shader"); + static_assert(sizeof(UniformData) < 16384, + "UniformData structure must be less than 16kb as per the OpenGL spec"); + + struct VSUniformData {}; + // static_assert( + // sizeof(VSUniformData) == 1856, + // "The size of the VSUniformData structure has changed, update the structure in the + // shader"); + static_assert(sizeof(VSUniformData) < 16384, + "VSUniformData structure must be less than 16kb as per the OpenGL spec"); + + struct FSUniformData {}; + // static_assert( + // sizeof(FSUniformData) == 1856, + // "The size of the FSUniformData structure has changed, update the structure in the + // shader"); + static_assert(sizeof(FSUniformData) < 16384, + "FSUniformData structure must be less than 16kb as per the OpenGL spec"); + +private: + struct SamplerInfo {}; + + /// Binds the framebuffer color and depth surface + void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface, + bool has_stencil); + + /// Syncs the viewport to match the guest state + void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale); + + /// Syncs the clip enabled status to match the guest state + void SyncClipEnabled(); + + /// Syncs the clip coefficients to match the guest state + void SyncClipCoef(); + + /// Sets the OpenGL shader in accordance with the current guest state + void SetShader(); + + /// Syncs the cull mode to match the guest state + void SyncCullMode(); + + /// Syncs the depth scale to match the guest state + void SyncDepthScale(); + + /// Syncs the depth offset to match the guest state + void SyncDepthOffset(); + + /// Syncs the blend enabled status to match the guest state + void SyncBlendEnabled(); + + /// Syncs the blend functions to match the guest state + void SyncBlendFuncs(); + + /// Syncs the blend color to match the guest state + void SyncBlendColor(); + + bool has_ARB_buffer_storage; + bool has_ARB_direct_state_access; + bool has_ARB_separate_shader_objects; + bool has_ARB_vertex_attrib_binding; + + OpenGLState state; + + RasterizerCacheOpenGL res_cache; + + /// Shader used for test renderering - to be removed once we have emulated shaders + MaxwellShader test_shader{}; + + const MaxwellShader* current_shader{}; + bool shader_dirty{}; + + struct { + UniformData data; + bool dirty; + } uniform_block_data = {}; + + OGLPipeline pipeline; + OGLVertexArray sw_vao; + OGLVertexArray hw_vao; + std::array<bool, 16> hw_vao_enabled_attributes; + + std::array<SamplerInfo, 32> texture_samplers; + static constexpr size_t VERTEX_BUFFER_SIZE = 128 * 1024 * 1024; + std::unique_ptr<OGLStreamBuffer> vertex_buffer; + OGLBuffer uniform_buffer; + OGLFramebuffer framebuffer; + + static constexpr size_t STREAM_BUFFER_SIZE = 4 * 1024 * 1024; + std::unique_ptr<OGLStreamBuffer> stream_buffer; + + GLsizeiptr vs_input_size; + + void AnalyzeVertexArray(bool is_indexed); + void SetupVertexArray(u8* array_ptr, GLintptr buffer_offset); + + OGLBuffer vs_uniform_buffer; + std::unordered_map<GLShader::MaxwellVSConfig, VertexShader*> vs_shader_map; + std::unordered_map<std::string, VertexShader> vs_shader_cache; + OGLShader vs_default_shader; + + void SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset); + + OGLBuffer fs_uniform_buffer; + std::unordered_map<GLShader::MaxwellFSConfig, FragmentShader*> fs_shader_map; + std::unordered_map<std::string, FragmentShader> fs_shader_cache; + OGLShader fs_default_shader; + + void SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset); + + enum class AccelDraw { Disabled, Arrays, Indexed }; + AccelDraw accelerate_draw; +}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp new file mode 100644 index 000000000..2ffbd3bab --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -0,0 +1,1432 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <atomic> +#include <cstring> +#include <iterator> +#include <memory> +#include <unordered_set> +#include <utility> +#include <vector> +#include <boost/optional.hpp> +#include <boost/range/iterator_range.hpp> +#include <glad/glad.h> +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "common/vector_math.h" +#include "core/core.h" +#include "core/frontend/emu_window.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" +#include "core/memory.h" +#include "core/settings.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/utils.h" +#include "video_core/video_core.h" + +using SurfaceType = SurfaceParams::SurfaceType; +using PixelFormat = SurfaceParams::PixelFormat; + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; +}; + +static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{ + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8 + {GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8 + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1 + {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565 + {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4 +}}; + +static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{ + {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16 + {}, + {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24 + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8 +}}; + +static constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; + +static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { + const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); + if (type == SurfaceType::Color) { + ASSERT(static_cast<size_t>(pixel_format) < fb_format_tuples.size()); + return fb_format_tuples[static_cast<unsigned int>(pixel_format)]; + } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { + size_t tuple_idx = static_cast<size_t>(pixel_format) - 14; + ASSERT(tuple_idx < depth_format_tuples.size()); + return depth_format_tuples[tuple_idx]; + } + return tex_tuple; +} + +template <typename Map, typename Interval> +constexpr auto RangeFromInterval(Map& map, const Interval& interval) { + return boost::make_iterator_range(map.equal_range(interval)); +} + +static u16 GetResolutionScaleFactor() { + return static_cast<u16>(!Settings::values.resolution_factor + ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio() + : Settings::values.resolution_factor); +} + +template <bool morton_to_gl, PixelFormat format> +static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) { + constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8; + constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); + for (u32 y = 0; y < 8; ++y) { + for (u32 x = 0; x < 8; ++x) { + u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel; + u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * gl_bytes_per_pixel; + if (morton_to_gl) { + if (format == PixelFormat::D24S8) { + gl_ptr[0] = tile_ptr[3]; + std::memcpy(gl_ptr + 1, tile_ptr, 3); + } else { + std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel); + } + } else { + if (format == PixelFormat::D24S8) { + std::memcpy(tile_ptr, gl_ptr + 1, 3); + tile_ptr[3] = gl_ptr[0]; + } else { + std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel); + } + } + } + } +} + +template <bool morton_to_gl, PixelFormat format> +static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, VAddr base, VAddr start, VAddr end) { + constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8; + constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); + + // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check the + // configuration for this and perform more generic un/swizzle + LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); + VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel, + Memory::GetPointer(base), gl_buffer, morton_to_gl); +} + +static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> morton_to_gl_fns = { + MortonCopy<true, PixelFormat::RGBA8>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; + +static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> gl_to_morton_fns = { + MortonCopy<false, PixelFormat::RGBA8>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; + +// Allocate an uninitialized texture of appropriate size and format for the surface +static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, + u32 height) { + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Keep track of previous texture bindings + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = texture; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, + format_tuple.format, format_tuple.type, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Restore previous texture bindings + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); +} + +static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex, + const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type, + GLuint read_fb_handle, GLuint draw_fb_handle) { + OpenGLState state = OpenGLState::GetCurState(); + + OpenGLState prev_state = state; + SCOPE_EXIT({ prev_state.Apply(); }); + + // Make sure textures aren't bound to texture units, since going to bind them to framebuffer + // components + state.ResetTexture(src_tex); + state.ResetTexture(dst_tex); + + state.draw.read_framebuffer = read_fb_handle; + state.draw.draw_framebuffer = draw_fb_handle; + state.Apply(); + + u32 buffers = 0; + + if (type == SurfaceType::Color || type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, + 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, + 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + buffers = GL_COLOR_BUFFER_BIT; + } else if (type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_DEPTH_BUFFER_BIT; + } else if (type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + src_tex, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + dst_tex, 0); + + buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + + glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, dst_rect.left, + dst_rect.bottom, dst_rect.right, dst_rect.top, buffers, + buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); + + return true; +} + +static bool FillSurface(const Surface& surface, const u8* fill_data, + const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) { + UNREACHABLE(); + return {}; +} + +SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const { + SurfaceParams params = *this; + const u32 tiled_size = is_tiled ? 8 : 1; + const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size); + VAddr aligned_start = + addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes); + VAddr aligned_end = + addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes); + + if (aligned_end - aligned_start > stride_tiled_bytes) { + params.addr = aligned_start; + params.height = static_cast<u32>((aligned_end - aligned_start) / BytesInPixels(stride)); + } else { + // 1 row + ASSERT(aligned_end - aligned_start == stride_tiled_bytes); + const u64 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1); + aligned_start = + addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment); + aligned_end = + addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment); + params.addr = aligned_start; + params.width = static_cast<u32>(PixelsInBytes(aligned_end - aligned_start) / tiled_size); + params.stride = params.width; + params.height = tiled_size; + } + params.UpdateParams(); + + return params; +} + +SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const { + if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) { + return {}; + } + + if (is_tiled) { + unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8; + unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8; + unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8; + unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8; + } + + const u32 stride_tiled = !is_tiled ? stride : stride * 8; + + const u32 pixel_offset = + stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) + + unscaled_rect.left; + + const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth(); + + return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)}; +} + +MathUtil::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const { + const u32 begin_pixel_index = static_cast<u32>(PixelsInBytes(sub_surface.addr - addr)); + + if (is_tiled) { + const int x0 = (begin_pixel_index % (stride * 8)) / 8; + const int y0 = (begin_pixel_index / (stride * 8)) * 8; + // Top to bottom + return MathUtil::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width, + height - (y0 + sub_surface.height)); + } + + const int x0 = begin_pixel_index % stride; + const int y0 = begin_pixel_index / stride; + // Bottom to top + return MathUtil::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0); +} + +MathUtil::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const { + auto rect = GetSubRect(sub_surface); + rect.left = rect.left * res_scale; + rect.right = rect.right * res_scale; + rect.top = rect.top * res_scale; + rect.bottom = rect.bottom * res_scale; + return rect; +} + +bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const { + return std::tie(other_surface.addr, other_surface.width, other_surface.height, + other_surface.stride, other_surface.pixel_format, other_surface.is_tiled) == + std::tie(addr, width, height, stride, pixel_format, is_tiled) && + pixel_format != PixelFormat::Invalid; +} + +bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const { + return sub_surface.addr >= addr && sub_surface.end <= end && + sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid && + sub_surface.is_tiled == is_tiled && + (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && + (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) && + GetSubRect(sub_surface).left + sub_surface.width <= stride; +} + +bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const { + return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format && + addr <= expanded_surface.end && expanded_surface.addr <= end && + is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride && + (std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) % + BytesInPixels(stride * (is_tiled ? 8 : 1)) == + 0; +} + +bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const { + if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr || + end < texcopy_params.end) { + return false; + } + if (texcopy_params.width != texcopy_params.stride) { + const u32 tile_stride = static_cast<u32>(BytesInPixels(stride * (is_tiled ? 8 : 1))); + return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && + texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 && + (texcopy_params.height == 1 || texcopy_params.stride == tile_stride) && + ((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride; + } + return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval(); +} + +bool CachedSurface::CanFill(const SurfaceParams& dest_surface, + SurfaceInterval fill_interval) const { + if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && + boost::icl::first(fill_interval) >= addr && + boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range + dest_surface.FromInterval(fill_interval).GetInterval() == + fill_interval) { // make sure interval is a rectangle in dest surface + if (fill_size * 8 != dest_surface.GetFormatBpp()) { + // Check if bits repeat for our fill_size + const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u); + std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel); + + for (u32 i = 0; i < dest_bytes_per_pixel; ++i) + std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size); + + for (u32 i = 0; i < fill_size; ++i) + if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0], + dest_bytes_per_pixel) != 0) + return false; + + if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) + return false; + } + return true; + } + return false; +} + +bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, + SurfaceInterval copy_interval) const { + SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); + ASSERT(subrect_params.GetInterval() == copy_interval); + if (CanSubRect(subrect_params)) + return true; + + if (CanFill(dest_surface, copy_interval)) + return true; + + return false; +} + +SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const { + SurfaceInterval result{}; + const auto valid_regions = + SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions; + for (auto& valid_interval : valid_regions) { + const SurfaceInterval aligned_interval{ + addr + Common::AlignUp(boost::icl::first(valid_interval) - addr, + BytesInPixels(is_tiled ? 8 * 8 : 1)), + addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr, + BytesInPixels(is_tiled ? 8 * 8 : 1))}; + + if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) || + boost::icl::length(aligned_interval) == 0) { + continue; + } + + // Get the rectangle within aligned_interval + const u32 stride_bytes = static_cast<u32>(BytesInPixels(stride)) * (is_tiled ? 8 : 1); + SurfaceInterval rect_interval{ + addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes), + addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes), + }; + if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) { + // 1 row + rect_interval = aligned_interval; + } else if (boost::icl::length(rect_interval) == 0) { + // 2 rows that do not make a rectangle, return the larger one + const SurfaceInterval row1{boost::icl::first(aligned_interval), + boost::icl::first(rect_interval)}; + const SurfaceInterval row2{boost::icl::first(rect_interval), + boost::icl::last_next(aligned_interval)}; + rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2; + } + + if (boost::icl::length(rect_interval) > boost::icl::length(result)) { + result = rect_interval; + } + } + return result; +} + +void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, + SurfaceInterval copy_interval) { + SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval); + ASSERT(subrect_params.GetInterval() == copy_interval); + + ASSERT(src_surface != dst_surface); + + // This is only called when CanCopy is true, no need to run checks here + if (src_surface->type == SurfaceType::Fill) { + // FillSurface needs a 4 bytes buffer + const u64 fill_offset = + (boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size; + std::array<u8, 4> fill_buffer; + + u64 fill_buff_pos = fill_offset; + for (int i : {0, 1, 2, 3}) + fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size]; + + FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params), + draw_framebuffer.handle); + return; + } + if (src_surface->CanSubRect(subrect_params)) { + BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params), + dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params), + src_surface->type, read_framebuffer.handle, draw_framebuffer.handle); + return; + } + UNREACHABLE(); +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); +void CachedSurface::LoadGLBuffer(VAddr load_start, VAddr load_end) { + ASSERT(type != SurfaceType::Fill); + + u8* const texture_src_data = Memory::GetPointer(addr); + if (texture_src_data == nullptr) + return; + + if (gl_buffer == nullptr) { + gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); + gl_buffer.reset(new u8[gl_buffer_size]); + } + + MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); + + ASSERT(load_start >= addr && load_end <= end); + const u64 start_offset = load_start - addr; + + if (!is_tiled) { + ASSERT(type == SurfaceType::Color); + const u32 bytes_per_pixel{GetFormatBpp() >> 3}; + + // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check + // the configuration for this and perform more generic un/swizzle + LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); + VideoCore::MortonCopyPixels128(width, height, bytes_per_pixel, 4, + texture_src_data + start_offset, &gl_buffer[start_offset], + true); + } else { + morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, + load_start, load_end); + } +} + +MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); +void CachedSurface::FlushGLBuffer(VAddr flush_start, VAddr flush_end) { + u8* const dst_buffer = Memory::GetPointer(addr); + if (dst_buffer == nullptr) + return; + + ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); + + // TODO: Should probably be done in ::Memory:: and check for other regions too + // same as loadglbuffer() + if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END) + flush_end = Memory::VRAM_VADDR_END; + + if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR) + flush_start = Memory::VRAM_VADDR; + + MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); + + ASSERT(flush_start >= addr && flush_end <= end); + const u64 start_offset = flush_start - addr; + const u64 end_offset = flush_end - addr; + + if (type == SurfaceType::Fill) { + const u64 coarse_start_offset = start_offset - (start_offset % fill_size); + const u64 backup_bytes = start_offset % fill_size; + std::array<u8, 4> backup_data; + if (backup_bytes) + std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes); + + for (u64 offset = coarse_start_offset; offset < end_offset; offset += fill_size) { + std::memcpy(&dst_buffer[offset], &fill_data[0], + std::min(fill_size, end_offset - offset)); + } + + if (backup_bytes) + std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes); + } else if (!is_tiled) { + ASSERT(type == SurfaceType::Color); + std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], flush_end - flush_start); + } else { + gl_to_morton_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, + flush_start, flush_end); + } +} + +MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); +void CachedSurface::UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle) { + if (type == SurfaceType::Fill) + return; + + MICROPROFILE_SCOPE(OpenGL_TextureUL); + + ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); + + // Load data from memory to the surface + GLint x0 = static_cast<GLint>(rect.left); + GLint y0 = static_cast<GLint>(rect.bottom); + size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format); + + const FormatTuple& tuple = GetFormatTuple(pixel_format); + GLuint target_tex = texture.handle; + + // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in + // surface + OGLTexture unscaled_tex; + if (res_scale != 1) { + x0 = 0; + y0 = 0; + + unscaled_tex.Create(); + AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + target_tex = unscaled_tex.handle; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = target_tex; + cur_state.Apply(); + + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT + ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride)); + + glActiveTexture(GL_TEXTURE0); + glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()), + static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); + + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle, + scaled_rect, type, read_fb_handle, draw_fb_handle); + } +} + +MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); +void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle) { + if (type == SurfaceType::Fill) + return; + + MICROPROFILE_SCOPE(OpenGL_TextureDL); + + if (gl_buffer == nullptr) { + gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); + gl_buffer.reset(new u8[gl_buffer_size]); + } + + OpenGLState state = OpenGLState::GetCurState(); + OpenGLState prev_state = state; + SCOPE_EXIT({ prev_state.Apply(); }); + + const FormatTuple& tuple = GetFormatTuple(pixel_format); + + // Ensure no bad interactions with GL_PACK_ALIGNMENT + ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride)); + size_t buffer_offset = (rect.bottom * stride + rect.left) * GetGLBytesPerPixel(pixel_format); + + // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush + if (res_scale != 1) { + auto scaled_rect = rect; + scaled_rect.left *= res_scale; + scaled_rect.top *= res_scale; + scaled_rect.right *= res_scale; + scaled_rect.bottom *= res_scale; + + OGLTexture unscaled_tex; + unscaled_tex.Create(); + + MathUtil::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; + AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); + BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type, + read_fb_handle, draw_fb_handle); + + state.texture_units[0].texture_2d = unscaled_tex.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); + } else { + state.ResetTexture(texture.handle); + state.draw.read_framebuffer = read_fb_handle; + state.Apply(); + + if (type == SurfaceType::Color || type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + texture.handle, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); + } else if (type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + texture.handle, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } else { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + texture.handle, 0); + } + glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom), + static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), + tuple.format, tuple.type, &gl_buffer[buffer_offset]); + } + + glPixelStorei(GL_PACK_ROW_LENGTH, 0); +} + +enum MatchFlags { + Invalid = 1, // Flag that can be applied to other match types, invalid matches require + // validation before they can be used + Exact = 1 << 1, // Surfaces perfectly match + SubRect = 1 << 2, // Surface encompasses params + Copy = 1 << 3, // Surface we can copy from + Expand = 1 << 4, // Surface that can expand params + TexCopy = 1 << 5 // Surface that will match a display transfer "texture copy" parameters +}; + +constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) { + return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs)); +} + +/// Get the best surface match (and its match type) for the given flags +template <MatchFlags find_flags> +Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params, + ScaleMatch match_scale_type, + boost::optional<SurfaceInterval> validate_interval = boost::none) { + Surface match_surface = nullptr; + bool match_valid = false; + u32 match_scale = 0; + SurfaceInterval match_interval{}; + + for (auto& pair : RangeFromInterval(surface_cache, params.GetInterval())) { + for (auto& surface : pair.second) { + bool res_scale_matched = match_scale_type == ScaleMatch::Exact + ? (params.res_scale == surface->res_scale) + : (params.res_scale <= surface->res_scale); + // validity will be checked in GetCopyableInterval + bool is_valid = + find_flags & MatchFlags::Copy + ? true + : surface->IsRegionValid(validate_interval.value_or(params.GetInterval())); + + if (!(find_flags & MatchFlags::Invalid) && !is_valid) + continue; + + auto IsMatch_Helper = [&](auto check_type, auto match_fn) { + if (!(find_flags & check_type)) + return; + + bool matched; + SurfaceInterval surface_interval; + std::tie(matched, surface_interval) = match_fn(); + if (!matched) + return; + + if (!res_scale_matched && match_scale_type != ScaleMatch::Ignore && + surface->type != SurfaceType::Fill) + return; + + // Found a match, update only if this is better than the previous one + auto UpdateMatch = [&] { + match_surface = surface; + match_valid = is_valid; + match_scale = surface->res_scale; + match_interval = surface_interval; + }; + + if (surface->res_scale > match_scale) { + UpdateMatch(); + return; + } else if (surface->res_scale < match_scale) { + return; + } + + if (is_valid && !match_valid) { + UpdateMatch(); + return; + } else if (is_valid != match_valid) { + return; + } + + if (boost::icl::length(surface_interval) > boost::icl::length(match_interval)) { + UpdateMatch(); + } + }; + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Exact>{}, [&] { + return std::make_pair(surface->ExactMatch(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::SubRect>{}, [&] { + return std::make_pair(surface->CanSubRect(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Copy>{}, [&] { + auto copy_interval = + params.FromInterval(*validate_interval).GetCopyableInterval(surface); + bool matched = boost::icl::length(copy_interval & *validate_interval) != 0 && + surface->CanCopy(params, copy_interval); + return std::make_pair(matched, copy_interval); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] { + return std::make_pair(surface->CanExpand(params), surface->GetInterval()); + }); + IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] { + return std::make_pair(surface->CanTexCopy(params), surface->GetInterval()); + }); + } + } + return match_surface; +} + +RasterizerCacheOpenGL::RasterizerCacheOpenGL() { + read_framebuffer.Create(); + draw_framebuffer.Create(); + + attributeless_vao.Create(); + + d24s8_abgr_buffer.Create(); + d24s8_abgr_buffer_size = 0; + + const char* vs_source = R"( +#version 330 core +const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)); +void main() { + gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); +} +)"; + const char* fs_source = R"( +#version 330 core + +uniform samplerBuffer tbo; +uniform vec2 tbo_size; +uniform vec4 viewport; + +out vec4 color; + +void main() { + vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw; + int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x); + color = texelFetch(tbo, tbo_offset).rabg; +} +)"; + d24s8_abgr_shader.Create(vs_source, nullptr, fs_source); + + OpenGLState state = OpenGLState::GetCurState(); + GLuint old_program = state.draw.shader_program; + state.draw.shader_program = d24s8_abgr_shader.handle; + state.Apply(); + + GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo"); + ASSERT(tbo_u_id != -1); + glUniform1i(tbo_u_id, 0); + + state.draw.shader_program = old_program; + state.Apply(); + + d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size"); + ASSERT(d24s8_abgr_tbo_size_u_id != -1); + d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport"); + ASSERT(d24s8_abgr_viewport_u_id != -1); +} + +RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); +} + +bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, + const MathUtil::Rectangle<u32>& src_rect, + const Surface& dst_surface, + const MathUtil::Rectangle<u32>& dst_rect) { + if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) + return false; + + return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, + dst_rect, src_surface->type, read_framebuffer.handle, + draw_framebuffer.handle); +} + +void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex, + const MathUtil::Rectangle<u32>& src_rect, + GLuint dst_tex, + const MathUtil::Rectangle<u32>& dst_rect) { + OpenGLState prev_state = OpenGLState::GetCurState(); + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.draw.read_framebuffer = read_framebuffer.handle; + state.draw.draw_framebuffer = draw_framebuffer.handle; + state.Apply(); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); + + GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4; + if (target_pbo_size > d24s8_abgr_buffer_size) { + d24s8_abgr_buffer_size = target_pbo_size * 2; + glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY); + } + + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, + 0); + glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom), + static_cast<GLsizei>(src_rect.GetWidth()), + static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + 0); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + // PBO now contains src_tex in RABG format + state.draw.shader_program = d24s8_abgr_shader.handle; + state.draw.vertex_array = attributeless_vao.handle; + state.viewport.x = static_cast<GLint>(dst_rect.left); + state.viewport.y = static_cast<GLint>(dst_rect.bottom); + state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth()); + state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight()); + state.Apply(); + + OGLTexture tbo; + tbo.Create(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_BUFFER, tbo.handle); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle); + + glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()), + static_cast<GLfloat>(src_rect.GetHeight())); + glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x), + static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width), + static_cast<GLfloat>(state.viewport.height)); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindTexture(GL_TEXTURE_BUFFER, 0); +} + +Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return nullptr; + } + // Use GetSurfaceSubRect instead + ASSERT(params.width == params.stride); + + ASSERT(!params.is_tiled || (params.width % 8 == 0 && params.height % 8 == 0)); + + // Check for an exact match in existing surfaces + Surface surface = + FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale); + + if (surface == nullptr) { + u16 target_res_scale = params.res_scale; + if (match_res_scale != ScaleMatch::Exact) { + // This surface may have a subrect of another surface with a higher res_scale, find it + // to adjust our params + SurfaceParams find_params = params; + Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( + surface_cache, find_params, match_res_scale); + if (expandable != nullptr && expandable->res_scale > target_res_scale) { + target_res_scale = expandable->res_scale; + } + // Keep res_scale when reinterpreting d24s8 -> rgba8 + if (params.pixel_format == PixelFormat::RGBA8) { + find_params.pixel_format = PixelFormat::D24S8; + expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( + surface_cache, find_params, match_res_scale); + if (expandable != nullptr && expandable->res_scale > target_res_scale) { + target_res_scale = expandable->res_scale; + } + } + } + SurfaceParams new_params = params; + new_params.res_scale = target_res_scale; + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + + if (load_if_create) { + ValidateSurface(surface, params.addr, params.size); + } + + return surface; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params, + ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return std::make_tuple(nullptr, MathUtil::Rectangle<u32>{}); + } + + // Attempt to find encompassing surface + Surface surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, + match_res_scale); + + // Check if FindMatch failed because of res scaling + // If that's the case create a new surface with + // the dimensions of the lower res_scale surface + // to suggest it should not be used again + if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) { + surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, + ScaleMatch::Ignore); + if (surface != nullptr) { + ASSERT(surface->res_scale < params.res_scale); + SurfaceParams new_params = *surface; + new_params.res_scale = params.res_scale; + + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + } + + SurfaceParams aligned_params = params; + if (params.is_tiled) { + aligned_params.height = Common::AlignUp(params.height, 8); + aligned_params.width = Common::AlignUp(params.width, 8); + aligned_params.stride = Common::AlignUp(params.stride, 8); + aligned_params.UpdateParams(); + } + + // Check for a surface we can expand before creating a new one + if (surface == nullptr) { + surface = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(surface_cache, aligned_params, + match_res_scale); + if (surface != nullptr) { + aligned_params.width = aligned_params.stride; + aligned_params.UpdateParams(); + + SurfaceParams new_params = *surface; + new_params.addr = std::min(aligned_params.addr, surface->addr); + new_params.end = std::max(aligned_params.end, surface->end); + new_params.size = new_params.end - new_params.addr; + new_params.height = static_cast<u32>( + new_params.size / aligned_params.BytesInPixels(aligned_params.stride)); + ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0); + + Surface new_surface = CreateSurface(new_params); + DuplicateSurface(surface, new_surface); + + // Delete the expanded surface, this can't be done safely yet + // because it may still be in use + remove_surfaces.emplace(surface); + + surface = new_surface; + RegisterSurface(new_surface); + } + } + + // No subrect found - create and return a new surface + if (surface == nullptr) { + SurfaceParams new_params = aligned_params; + // Can't have gaps in a surface + new_params.width = aligned_params.stride; + new_params.UpdateParams(); + // GetSurface will create the new surface and possibly adjust res_scale if necessary + surface = GetSurface(new_params, match_res_scale, load_if_create); + } else if (load_if_create) { + ValidateSurface(surface, aligned_params.addr, aligned_params.size); + } + + return std::make_tuple(surface, surface->GetScaledSubRect(params)); +} + +Surface RasterizerCacheOpenGL::GetTextureSurface(const void* config) { + UNREACHABLE(); + return {}; +} + +SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( + bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) { + const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs; + const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager; + const auto& config = regs.rt[0]; + + // TODO(bunnei): This is hard corded to use just the first render buffer + LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!"); + + // update resolution_scale_factor and reset cache if changed + // TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We + // need to fix this before making the renderer multi-threaded. + static u16 resolution_scale_factor = GetResolutionScaleFactor(); + if (resolution_scale_factor != GetResolutionScaleFactor()) { + resolution_scale_factor = GetResolutionScaleFactor(); + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); + } + + MathUtil::Rectangle<u32> viewport_clamped{ + static_cast<u32>(MathUtil::Clamp(viewport.left, 0, static_cast<s32>(config.width))), + static_cast<u32>(MathUtil::Clamp(viewport.top, 0, static_cast<s32>(config.height))), + static_cast<u32>(MathUtil::Clamp(viewport.right, 0, static_cast<s32>(config.width))), + static_cast<u32>(MathUtil::Clamp(viewport.bottom, 0, static_cast<s32>(config.height)))}; + + // get color and depth surfaces + SurfaceParams color_params; + color_params.is_tiled = true; + color_params.res_scale = resolution_scale_factor; + color_params.width = config.width; + color_params.height = config.height; + SurfaceParams depth_params = color_params; + + color_params.addr = memory_manager->PhysicalToVirtualAddress(config.Address()); + color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format); + color_params.UpdateParams(); + + ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented"); + // depth_params.addr = config.GetDepthBufferPhysicalAddress(); + // depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format); + // depth_params.UpdateParams(); + + auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); + auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); + + // Make sure that framebuffers don't overlap if both color and depth are being used + if (using_color_fb && using_depth_fb && + boost::icl::length(color_vp_interval & depth_vp_interval)) { + LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " + "overlapping framebuffers not supported!"); + using_depth_fb = false; + } + + MathUtil::Rectangle<u32> color_rect{}; + Surface color_surface = nullptr; + if (using_color_fb) + std::tie(color_surface, color_rect) = + GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> depth_rect{}; + Surface depth_surface = nullptr; + if (using_depth_fb) + std::tie(depth_surface, depth_rect) = + GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle<u32> fb_rect{}; + if (color_surface != nullptr && depth_surface != nullptr) { + fb_rect = color_rect; + // Color and Depth surfaces must have the same dimensions and offsets + if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top || + color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) { + color_surface = GetSurface(color_params, ScaleMatch::Exact, false); + depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); + fb_rect = color_surface->GetScaledRect(); + } + } else if (color_surface != nullptr) { + fb_rect = color_rect; + } else if (depth_surface != nullptr) { + fb_rect = depth_rect; + } + + if (color_surface != nullptr) { + ValidateSurface(color_surface, boost::icl::first(color_vp_interval), + boost::icl::length(color_vp_interval)); + } + if (depth_surface != nullptr) { + ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), + boost::icl::length(depth_vp_interval)); + } + + return std::make_tuple(color_surface, depth_surface, fb_rect); +} + +Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) { + UNREACHABLE(); + return {}; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) { + MathUtil::Rectangle<u32> rect{}; + + Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>( + surface_cache, params, ScaleMatch::Ignore); + + if (match_surface != nullptr) { + ValidateSurface(match_surface, params.addr, params.size); + + SurfaceParams match_subrect; + if (params.width != params.stride) { + const u32 tiled_size = match_surface->is_tiled ? 8 : 1; + match_subrect = params; + match_subrect.width = + static_cast<u32>(match_surface->PixelsInBytes(params.width) / tiled_size); + match_subrect.stride = + static_cast<u32>(match_surface->PixelsInBytes(params.stride) / tiled_size); + match_subrect.height *= tiled_size; + } else { + match_subrect = match_surface->FromInterval(params.GetInterval()); + ASSERT(match_subrect.GetInterval() == params.GetInterval()); + } + + rect = match_surface->GetScaledSubRect(match_subrect); + } + + return std::make_tuple(match_surface, rect); +} + +void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, + const Surface& dest_surface) { + ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end); + + BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface, + dest_surface->GetScaledSubRect(*src_surface)); + + dest_surface->invalid_regions -= src_surface->GetInterval(); + dest_surface->invalid_regions += src_surface->invalid_regions; + + SurfaceRegions regions; + for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) { + if (pair.second == src_surface) { + regions += pair.first; + } + } + for (auto& interval : regions) { + dirty_regions.set({interval, dest_surface}); + } +} + +void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, VAddr addr, u64 size) { + if (size == 0) + return; + + const SurfaceInterval validate_interval(addr, addr + size); + + if (surface->type == SurfaceType::Fill) { + // Sanity check, fill surfaces will always be valid when used + ASSERT(surface->IsRegionValid(validate_interval)); + return; + } + + while (true) { + const auto it = surface->invalid_regions.find(validate_interval); + if (it == surface->invalid_regions.end()) + break; + + const auto interval = *it & validate_interval; + // Look for a valid surface to copy from + SurfaceParams params = surface->FromInterval(interval); + + Surface copy_surface = + FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); + if (copy_surface != nullptr) { + SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); + CopySurface(copy_surface, surface, copy_interval); + surface->invalid_regions.erase(copy_interval); + continue; + } + + // D24S8 to RGBA8 + if (surface->pixel_format == PixelFormat::RGBA8) { + params.pixel_format = PixelFormat::D24S8; + Surface reinterpret_surface = + FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); + if (reinterpret_surface != nullptr) { + ASSERT(reinterpret_surface->pixel_format == PixelFormat::D24S8); + + SurfaceInterval convert_interval = params.GetCopyableInterval(reinterpret_surface); + SurfaceParams convert_params = surface->FromInterval(convert_interval); + auto src_rect = reinterpret_surface->GetScaledSubRect(convert_params); + auto dest_rect = surface->GetScaledSubRect(convert_params); + + ConvertD24S8toABGR(reinterpret_surface->texture.handle, src_rect, + surface->texture.handle, dest_rect); + + surface->invalid_regions.erase(convert_interval); + continue; + } + } + + // Load data from 3DS memory + FlushRegion(params.addr, params.size); + surface->LoadGLBuffer(params.addr, params.end); + surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, + draw_framebuffer.handle); + surface->invalid_regions.erase(params.GetInterval()); + } +} + +void RasterizerCacheOpenGL::FlushRegion(VAddr addr, u64 size, Surface flush_surface) { + if (size == 0) + return; + + const SurfaceInterval flush_interval(addr, addr + size); + SurfaceRegions flushed_intervals; + + for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) { + // small sizes imply that this most likely comes from the cpu, flush the entire region + // the point is to avoid thousands of small writes every frame if the cpu decides to access + // that region, anything higher than 8 you're guaranteed it comes from a service + const auto interval = size <= 8 ? pair.first : pair.first & flush_interval; + auto& surface = pair.second; + + if (flush_surface != nullptr && surface != flush_surface) + continue; + + // Sanity check, this surface is the last one that marked this region dirty + ASSERT(surface->IsRegionValid(interval)); + + if (surface->type != SurfaceType::Fill) { + SurfaceParams params = surface->FromInterval(interval); + surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, + draw_framebuffer.handle); + } + surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval)); + flushed_intervals += interval; + } + // Reset dirty regions + dirty_regions -= flushed_intervals; +} + +void RasterizerCacheOpenGL::FlushAll() { + FlushRegion(0, Kernel::VMManager::MAX_ADDRESS); +} + +void RasterizerCacheOpenGL::InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner) { + if (size == 0) + return; + + const SurfaceInterval invalid_interval(addr, addr + size); + + if (region_owner != nullptr) { + ASSERT(region_owner->type != SurfaceType::Texture); + ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end); + // Surfaces can't have a gap + ASSERT(region_owner->width == region_owner->stride); + region_owner->invalid_regions.erase(invalid_interval); + } + + for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) { + for (auto& cached_surface : pair.second) { + if (cached_surface == region_owner) + continue; + + // If cpu is invalidating this region we want to remove it + // to (likely) mark the memory pages as uncached + if (region_owner == nullptr && size <= 8) { + FlushRegion(cached_surface->addr, cached_surface->size, cached_surface); + remove_surfaces.emplace(cached_surface); + continue; + } + + const auto interval = cached_surface->GetInterval() & invalid_interval; + cached_surface->invalid_regions.insert(interval); + + // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures + if (cached_surface->type == SurfaceType::Fill && + cached_surface->IsSurfaceFullyInvalid()) { + remove_surfaces.emplace(cached_surface); + } + } + } + + if (region_owner != nullptr) + dirty_regions.set({invalid_interval, region_owner}); + else + dirty_regions.erase(invalid_interval); + + for (auto& remove_surface : remove_surfaces) { + if (remove_surface == region_owner) { + Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>( + surface_cache, *region_owner, ScaleMatch::Ignore); + ASSERT(expanded_surface); + + if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) { + DuplicateSurface(region_owner, expanded_surface); + } else { + continue; + } + } + UnregisterSurface(remove_surface); + } + + remove_surfaces.clear(); +} + +Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) { + Surface surface = std::make_shared<CachedSurface>(); + static_cast<SurfaceParams&>(*surface) = params; + + surface->texture.Create(); + + surface->gl_buffer_size = 0; + surface->invalid_regions.insert(surface->GetInterval()); + AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format), + surface->GetScaledWidth(), surface->GetScaledHeight()); + + return surface; +} + +void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) { + if (surface->registered) { + return; + } + surface->registered = true; + surface_cache.add({surface->GetInterval(), SurfaceSet{surface}}); + UpdatePagesCachedCount(surface->addr, surface->size, 1); +} + +void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) { + if (!surface->registered) { + return; + } + surface->registered = false; + UpdatePagesCachedCount(surface->addr, surface->size, -1); + surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}}); +} + +void RasterizerCacheOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { + const u64 num_pages = + ((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1; + const u64 page_start = addr >> Memory::PAGE_BITS; + const u64 page_end = page_start + num_pages; + + // Interval maps will erase segments if count reaches 0, so if delta is negative we have to + // subtract after iterating + const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end); + if (delta > 0) + cached_pages.add({pages_interval, delta}); + + for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) { + const auto interval = pair.first & pages_interval; + const int count = pair.second; + + const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS; + const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS; + const u64 interval_size = interval_end_addr - interval_start_addr; + + if (delta > 0 && count == delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true); + else if (delta < 0 && count == -delta) + Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false); + else + ASSERT(count >= 0); + } + + if (delta < 0) + cached_pages.add({pages_interval, delta}); +} diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h new file mode 100644 index 000000000..1f660d30c --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -0,0 +1,369 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <set> +#include <tuple> +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#endif +#include <boost/icl/interval_map.hpp> +#include <boost/icl/interval_set.hpp> +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#include <glad/glad.h> +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/gpu.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +struct CachedSurface; +using Surface = std::shared_ptr<CachedSurface>; +using SurfaceSet = std::set<Surface>; + +using SurfaceRegions = boost::icl::interval_set<VAddr>; +using SurfaceMap = boost::icl::interval_map<VAddr, Surface>; +using SurfaceCache = boost::icl::interval_map<VAddr, SurfaceSet>; + +using SurfaceInterval = SurfaceCache::interval_type; +static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() && + std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(), + "incorrect interval types"); + +using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>; +using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>; + +using PageMap = boost::icl::interval_map<u64, int>; + +enum class ScaleMatch { + Exact, // only accept same res scale + Upscale, // only allow higher scale than params + Ignore // accept every scaled res +}; + +struct SurfaceParams { + enum class PixelFormat { + // First 5 formats are shared between textures and color buffers + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + + // Texture-only formats + IA8 = 5, + RG8 = 6, + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, + ETC1A4 = 13, + + // Depth buffer-only formats + D16 = 14, + // gap + D24 = 16, + D24S8 = 17, + + Invalid = 255, + }; + + enum class SurfaceType { + Color = 0, + Texture = 1, + Depth = 2, + DepthStencil = 3, + Fill = 4, + Invalid = 5 + }; + + static constexpr unsigned int GetFormatBpp(PixelFormat format) { + constexpr std::array<unsigned int, 18> bpp_table = { + 32, // RGBA8 + 24, // RGB8 + 16, // RGB5A1 + 16, // RGB565 + 16, // RGBA4 + 16, // IA8 + 16, // RG8 + 8, // I8 + 8, // A8 + 8, // IA4 + 4, // I4 + 4, // A4 + 4, // ETC1 + 8, // ETC1A4 + 16, // D16 + 0, + 24, // D24 + 32, // D24S8 + }; + + assert(static_cast<size_t>(format) < bpp_table.size()); + return bpp_table[static_cast<size_t>(format)]; + } + unsigned int GetFormatBpp() const { + return GetFormatBpp(pixel_format); + } + + static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) { + switch (format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return PixelFormat::RGBA8; + default: + UNREACHABLE(); + } + } + + static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) { + switch (format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: + return PixelFormat::RGBA8; + default: + UNREACHABLE(); + } + } + + static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { + SurfaceType a_type = GetFormatType(pixel_format_a); + SurfaceType b_type = GetFormatType(pixel_format_b); + + if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) && + (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) { + return true; + } + + if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { + return true; + } + + if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { + return true; + } + + return false; + } + + static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) { + if ((unsigned int)pixel_format < 5) { + return SurfaceType::Color; + } + + if ((unsigned int)pixel_format < 14) { + return SurfaceType::Texture; + } + + if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) { + return SurfaceType::Depth; + } + + if (pixel_format == PixelFormat::D24S8) { + return SurfaceType::DepthStencil; + } + + return SurfaceType::Invalid; + } + + /// Update the params "size", "end" and "type" from the already set "addr", "width", "height" + /// and "pixel_format" + void UpdateParams() { + if (stride == 0) { + stride = width; + } + type = GetFormatType(pixel_format); + size = !is_tiled ? BytesInPixels(stride * (height - 1) + width) + : BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8); + end = addr + size; + } + + SurfaceInterval GetInterval() const { + return SurfaceInterval::right_open(addr, end); + } + + // Returns the outer rectangle containing "interval" + SurfaceParams FromInterval(SurfaceInterval interval) const; + + SurfaceInterval GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const; + + // Returns the region of the biggest valid rectange within interval + SurfaceInterval GetCopyableInterval(const Surface& src_surface) const; + + u32 GetScaledWidth() const { + return width * res_scale; + } + + u32 GetScaledHeight() const { + return height * res_scale; + } + + MathUtil::Rectangle<u32> GetRect() const { + return {0, height, width, 0}; + } + + MathUtil::Rectangle<u32> GetScaledRect() const { + return {0, GetScaledHeight(), GetScaledWidth(), 0}; + } + + u64 PixelsInBytes(u64 size) const { + return size * CHAR_BIT / GetFormatBpp(pixel_format); + } + + u64 BytesInPixels(u64 pixels) const { + return pixels * GetFormatBpp(pixel_format) / CHAR_BIT; + } + + bool ExactMatch(const SurfaceParams& other_surface) const; + bool CanSubRect(const SurfaceParams& sub_surface) const; + bool CanExpand(const SurfaceParams& expanded_surface) const; + bool CanTexCopy(const SurfaceParams& texcopy_params) const; + + MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const; + MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const; + + VAddr addr = 0; + VAddr end = 0; + u64 size = 0; + + u32 width = 0; + u32 height = 0; + u32 stride = 0; + u16 res_scale = 1; + + bool is_tiled = false; + PixelFormat pixel_format = PixelFormat::Invalid; + SurfaceType type = SurfaceType::Invalid; +}; + +struct CachedSurface : SurfaceParams { + bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; + bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; + + bool IsRegionValid(SurfaceInterval interval) const { + return (invalid_regions.find(interval) == invalid_regions.end()); + } + + bool IsSurfaceFullyInvalid() const { + return (invalid_regions & GetInterval()) == SurfaceRegions(GetInterval()); + } + + bool registered = false; + SurfaceRegions invalid_regions; + + u64 fill_size = 0; /// Number of bytes to read from fill_data + std::array<u8, 4> fill_data; + + OGLTexture texture; + + static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) { + // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type + return format == PixelFormat::Invalid + ? 0 + : (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) + ? 4 + : SurfaceParams::GetFormatBpp(format) / 8; + } + + std::unique_ptr<u8[]> gl_buffer; + size_t gl_buffer_size = 0; + + // Read/Write data in Switch memory to/from gl_buffer + void LoadGLBuffer(VAddr load_start, VAddr load_end); + void FlushGLBuffer(VAddr flush_start, VAddr flush_end); + + // Upload/Download data in gl_buffer in/to this surface's texture + void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle); + void DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, + GLuint draw_fb_handle); +}; + +class RasterizerCacheOpenGL : NonCopyable { +public: + RasterizerCacheOpenGL(); + ~RasterizerCacheOpenGL(); + + /// Blit one surface's texture to another + bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect, + const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect); + + void ConvertD24S8toABGR(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, + GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect); + + /// Copy one surface's region to another + void CopySurface(const Surface& src_surface, const Surface& dst_surface, + SurfaceInterval copy_interval); + + /// Load a texture from 3DS memory to OpenGL and cache it (if not already cached) + Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create); + + /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from + /// 3DS memory to OpenGL and caches it (if not already cached) + SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create); + + /// Get a surface based on the texture configuration + Surface GetTextureSurface(const void* config); + + /// Get the color and depth surfaces based on the framebuffer configuration + SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, + const MathUtil::Rectangle<s32>& viewport); + + /// Get a surface that matches the fill config + Surface GetFillSurface(const void* config); + + /// Get a surface that matches a "texture copy" display transfer config + SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); + + /// Write any cached resources overlapping the region back to memory (if dirty) + void FlushRegion(VAddr addr, u64 size, Surface flush_surface = nullptr); + + /// Mark region as being invalidated by region_owner (nullptr if 3DS memory) + void InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner); + + /// Flush all cached resources tracked by this cache manager + void FlushAll(); + +private: + void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface); + + /// Update surface's texture for given region when necessary + void ValidateSurface(const Surface& surface, VAddr addr, u64 size); + + /// Create a new surface + Surface CreateSurface(const SurfaceParams& params); + + /// Register surface into the cache + void RegisterSurface(const Surface& surface); + + /// Remove surface from the cache + void UnregisterSurface(const Surface& surface); + + /// Increase/decrease the number of surface in pages touching the specified region + void UpdatePagesCachedCount(VAddr addr, u64 size, int delta); + + SurfaceCache surface_cache; + PageMap cached_pages; + SurfaceMap dirty_regions; + SurfaceSet remove_surfaces; + + OGLFramebuffer read_framebuffer; + OGLFramebuffer draw_framebuffer; + + OGLVertexArray attributeless_vao; + OGLBuffer d24s8_abgr_buffer; + GLsizeiptr d24s8_abgr_buffer_size; + OGLShader d24s8_abgr_shader; + GLint d24s8_abgr_tbo_size_u_id; + GLint d24s8_abgr_viewport_u_id; +}; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 13301ec9f..7da5e74d1 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -36,7 +36,7 @@ public: if (handle == 0) return; glDeleteTextures(1, &handle); - OpenGLState::ResetTexture(handle); + OpenGLState::GetCurState().ResetTexture(handle).Apply(); handle = 0; } @@ -69,7 +69,7 @@ public: if (handle == 0) return; glDeleteSamplers(1, &handle); - OpenGLState::ResetSampler(handle); + OpenGLState::GetCurState().ResetSampler(handle).Apply(); handle = 0; } @@ -91,10 +91,13 @@ public: } /// Creates a new internal OpenGL resource and stores the handle - void Create(const char* vert_shader, const char* frag_shader) { + void Create(const char* vert_shader, const char* geo_shader, const char* frag_shader, + const std::vector<const char*>& feedback_vars = {}, + bool separable_program = false) { if (handle != 0) return; - handle = GLShader::LoadProgram(vert_shader, frag_shader); + handle = GLShader::LoadProgram(vert_shader, geo_shader, frag_shader, feedback_vars, + separable_program); } /// Deletes the internal OpenGL resource @@ -102,7 +105,40 @@ public: if (handle == 0) return; glDeleteProgram(handle); - OpenGLState::ResetProgram(handle); + OpenGLState::GetCurState().ResetProgram(handle).Apply(); + handle = 0; + } + + GLuint handle = 0; +}; + +class OGLPipeline : private NonCopyable { +public: + OGLPipeline() = default; + OGLPipeline(OGLPipeline&& o) { + handle = std::exchange<GLuint>(o.handle, 0); + } + ~OGLPipeline() { + Release(); + } + OGLPipeline& operator=(OGLPipeline&& o) { + handle = std::exchange<GLuint>(o.handle, 0); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create() { + if (handle != 0) + return; + glGenProgramPipelines(1, &handle); + } + + /// Deletes the internal OpenGL resource + void Release() { + if (handle == 0) + return; + glDeleteProgramPipelines(1, &handle); + OpenGLState::GetCurState().ResetPipeline(handle).Apply(); handle = 0; } @@ -135,13 +171,46 @@ public: if (handle == 0) return; glDeleteBuffers(1, &handle); - OpenGLState::ResetBuffer(handle); + OpenGLState::GetCurState().ResetBuffer(handle).Apply(); handle = 0; } GLuint handle = 0; }; +class OGLSync : private NonCopyable { +public: + OGLSync() = default; + + OGLSync(OGLSync&& o) : handle(std::exchange(o.handle, nullptr)) {} + + ~OGLSync() { + Release(); + } + OGLSync& operator=(OGLSync&& o) { + Release(); + handle = std::exchange(o.handle, nullptr); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create() { + if (handle != 0) + return; + handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + + /// Deletes the internal OpenGL resource + void Release() { + if (handle == 0) + return; + glDeleteSync(handle); + handle = 0; + } + + GLsync handle = 0; +}; + class OGLVertexArray : private NonCopyable { public: OGLVertexArray() = default; @@ -168,7 +237,7 @@ public: if (handle == 0) return; glDeleteVertexArrays(1, &handle); - OpenGLState::ResetVertexArray(handle); + OpenGLState::GetCurState().ResetVertexArray(handle).Apply(); handle = 0; } @@ -201,7 +270,7 @@ public: if (handle == 0) return; glDeleteFramebuffers(1, &handle); - OpenGLState::ResetFramebuffer(handle); + OpenGLState::GetCurState().ResetFramebuffer(handle).Apply(); handle = 0; } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp new file mode 100644 index 000000000..564ea8f9e --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -0,0 +1,58 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <string> +#include <queue> +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_shader_decompiler.h" + +namespace Maxwell3D { +namespace Shader { +namespace Decompiler { + +constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; + +class Impl { +public: + Impl(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, u32 main_offset, + const std::function<std::string(u32)>& inputreg_getter, + const std::function<std::string(u32)>& outputreg_getter, bool sanitize_mul, + const std::string& emit_cb, const std::string& setemit_cb) + : program_code(program_code), swizzle_data(swizzle_data), main_offset(main_offset), + inputreg_getter(inputreg_getter), outputreg_getter(outputreg_getter), + sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {} + + std::string Decompile() { + UNREACHABLE(); + return {}; + } + +private: + const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code; + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data; + u32 main_offset; + const std::function<std::string(u32)>& inputreg_getter; + const std::function<std::string(u32)>& outputreg_getter; + bool sanitize_mul; + const std::string& emit_cb; + const std::string& setemit_cb; +}; + +std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, + u32 main_offset, + const std::function<std::string(u32)>& inputreg_getter, + const std::function<std::string(u32)>& outputreg_getter, + bool sanitize_mul, const std::string& emit_cb, + const std::string& setemit_cb) { + Impl impl(program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter, + sanitize_mul, emit_cb, setemit_cb); + return impl.Decompile(); +} + +} // namespace Decompiler +} // namespace Shader +} // namespace Maxwell3D diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h new file mode 100644 index 000000000..02ebfcbe8 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <functional> +#include <string> +#include "common/common_types.h" + +namespace Maxwell3D { +namespace Shader { +namespace Decompiler { + +constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100000}; +constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100000}; + +std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code, + const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, + u32 main_offset, + const std::function<std::string(u32)>& inputreg_getter, + const std::function<std::string(u32)>& outputreg_getter, + bool sanitize_mul, const std::string& emit_cb = "", + const std::string& setemit_cb = ""); + +} // namespace Decompiler +} // namespace Shader +} // namespace Maxwell3D diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp new file mode 100644 index 000000000..8f3c98800 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "video_core/renderer_opengl/gl_shader_gen.h" + +namespace GLShader { + +std::string GenerateVertexShader(const MaxwellVSConfig& config) { + UNREACHABLE(); + return {}; +} + +std::string GenerateFragmentShader(const MaxwellFSConfig& config) { + UNREACHABLE(); + return {}; +} + +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h new file mode 100644 index 000000000..5101e7d30 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -0,0 +1,66 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstring> +#include <string> +#include <type_traits> +#include "common/hash.h" + +namespace GLShader { + +enum Attributes { + ATTRIBUTE_POSITION, + ATTRIBUTE_COLOR, + ATTRIBUTE_TEXCOORD0, + ATTRIBUTE_TEXCOORD1, + ATTRIBUTE_TEXCOORD2, + ATTRIBUTE_TEXCOORD0_W, + ATTRIBUTE_NORMQUAT, + ATTRIBUTE_VIEW, +}; + +struct MaxwellShaderConfigCommon { + explicit MaxwellShaderConfigCommon(){}; +}; + +struct MaxwellVSConfig : MaxwellShaderConfigCommon { + explicit MaxwellVSConfig() : MaxwellShaderConfigCommon() {} + + bool operator==(const MaxwellVSConfig& o) const { + return std::memcmp(this, &o, sizeof(MaxwellVSConfig)) == 0; + }; +}; + +struct MaxwellFSConfig : MaxwellShaderConfigCommon { + explicit MaxwellFSConfig() : MaxwellShaderConfigCommon() {} + + bool operator==(const MaxwellFSConfig& o) const { + return std::memcmp(this, &o, sizeof(MaxwellFSConfig)) == 0; + }; +}; + +std::string GenerateVertexShader(const MaxwellVSConfig& config); +std::string GenerateFragmentShader(const MaxwellFSConfig& config); + +} // namespace GLShader + +namespace std { + +template <> +struct hash<GLShader::MaxwellVSConfig> { + size_t operator()(const GLShader::MaxwellVSConfig& k) const { + return Common::ComputeHash64(&k, sizeof(GLShader::MaxwellVSConfig)); + } +}; + +template <> +struct hash<GLShader::MaxwellFSConfig> { + size_t operator()(const GLShader::MaxwellFSConfig& k) const { + return Common::ComputeHash64(&k, sizeof(GLShader::MaxwellFSConfig)); + } +}; + +} // namespace std diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 4da241d83..a6c6204d5 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -10,53 +10,85 @@ namespace GLShader { -GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) { - +GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, + const char* fragment_shader, const std::vector<const char*>& feedback_vars, + bool separable_program) { // Create the shaders - GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); - GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); + GLuint vertex_shader_id = vertex_shader ? glCreateShader(GL_VERTEX_SHADER) : 0; + GLuint geometry_shader_id = geometry_shader ? glCreateShader(GL_GEOMETRY_SHADER) : 0; + GLuint fragment_shader_id = fragment_shader ? glCreateShader(GL_FRAGMENT_SHADER) : 0; GLint result = GL_FALSE; int info_log_length; - // Compile Vertex Shader - LOG_DEBUG(Render_OpenGL, "Compiling vertex shader..."); - - glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr); - glCompileShader(vertex_shader_id); - - // Check Vertex Shader - glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result); - glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); - - if (info_log_length > 1) { - std::vector<char> vertex_shader_error(info_log_length); - glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]); - if (result == GL_TRUE) { - LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]); - } else { - LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s", &vertex_shader_error[0]); + if (vertex_shader) { + // Compile Vertex Shader + LOG_DEBUG(Render_OpenGL, "Compiling vertex shader..."); + + glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr); + glCompileShader(vertex_shader_id); + + // Check Vertex Shader + glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result); + glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (info_log_length > 1) { + std::vector<char> vertex_shader_error(info_log_length); + glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]); + if (result == GL_TRUE) { + LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]); + } else { + LOG_CRITICAL(Render_OpenGL, "Error compiling vertex shader:\n%s", + &vertex_shader_error[0]); + } } } - // Compile Fragment Shader - LOG_DEBUG(Render_OpenGL, "Compiling fragment shader..."); - - glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr); - glCompileShader(fragment_shader_id); - - // Check Fragment Shader - glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result); - glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); + if (geometry_shader) { + // Compile Geometry Shader + LOG_DEBUG(Render_OpenGL, "Compiling geometry shader..."); + + glShaderSource(geometry_shader_id, 1, &geometry_shader, nullptr); + glCompileShader(geometry_shader_id); + + // Check Geometry Shader + glGetShaderiv(geometry_shader_id, GL_COMPILE_STATUS, &result); + glGetShaderiv(geometry_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (info_log_length > 1) { + std::vector<char> geometry_shader_error(info_log_length); + glGetShaderInfoLog(geometry_shader_id, info_log_length, nullptr, + &geometry_shader_error[0]); + if (result == GL_TRUE) { + LOG_DEBUG(Render_OpenGL, "%s", &geometry_shader_error[0]); + } else { + LOG_CRITICAL(Render_OpenGL, "Error compiling geometry shader:\n%s", + &geometry_shader_error[0]); + } + } + } - if (info_log_length > 1) { - std::vector<char> fragment_shader_error(info_log_length); - glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, &fragment_shader_error[0]); - if (result == GL_TRUE) { - LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]); - } else { - LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s", - &fragment_shader_error[0]); + if (fragment_shader) { + // Compile Fragment Shader + LOG_DEBUG(Render_OpenGL, "Compiling fragment shader..."); + + glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr); + glCompileShader(fragment_shader_id); + + // Check Fragment Shader + glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result); + glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (info_log_length > 1) { + std::vector<char> fragment_shader_error(info_log_length); + glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, + &fragment_shader_error[0]); + if (result == GL_TRUE) { + LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]); + } else { + LOG_CRITICAL(Render_OpenGL, "Error compiling fragment shader:\n%s", + &fragment_shader_error[0]); + } } } @@ -64,8 +96,25 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) { LOG_DEBUG(Render_OpenGL, "Linking program..."); GLuint program_id = glCreateProgram(); - glAttachShader(program_id, vertex_shader_id); - glAttachShader(program_id, fragment_shader_id); + if (vertex_shader) { + glAttachShader(program_id, vertex_shader_id); + } + if (geometry_shader) { + glAttachShader(program_id, geometry_shader_id); + } + if (fragment_shader) { + glAttachShader(program_id, fragment_shader_id); + } + + if (!feedback_vars.empty()) { + auto varyings = feedback_vars; + glTransformFeedbackVaryings(program_id, static_cast<GLsizei>(feedback_vars.size()), + &varyings[0], GL_INTERLEAVED_ATTRIBS); + } + + if (separable_program) { + glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE); + } glLinkProgram(program_id); @@ -79,19 +128,36 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) { if (result == GL_TRUE) { LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]); } else { - LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]); + LOG_CRITICAL(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]); } } // If the program linking failed at least one of the shaders was probably bad if (result == GL_FALSE) { - LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader); - LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader); + if (vertex_shader) { + LOG_CRITICAL(Render_OpenGL, "Vertex shader:\n%s", vertex_shader); + } + if (geometry_shader) { + LOG_CRITICAL(Render_OpenGL, "Geometry shader:\n%s", geometry_shader); + } + if (fragment_shader) { + LOG_CRITICAL(Render_OpenGL, "Fragment shader:\n%s", fragment_shader); + } } ASSERT_MSG(result == GL_TRUE, "Shader not linked"); - glDeleteShader(vertex_shader_id); - glDeleteShader(fragment_shader_id); + if (vertex_shader) { + glDetachShader(program_id, vertex_shader_id); + glDeleteShader(vertex_shader_id); + } + if (geometry_shader) { + glDetachShader(program_id, geometry_shader_id); + glDeleteShader(geometry_shader_id); + } + if (fragment_shader) { + glDetachShader(program_id, fragment_shader_id); + glDeleteShader(fragment_shader_id); + } return program_id; } diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h index c66e8acd3..fc7b5e080 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.h +++ b/src/video_core/renderer_opengl/gl_shader_util.h @@ -4,6 +4,7 @@ #pragma once +#include <vector> #include <glad/glad.h> namespace GLShader { @@ -11,9 +12,12 @@ namespace GLShader { /** * Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader) * @param vertex_shader String of the GLSL vertex shader program + * @param geometry_shader String of the GLSL geometry shader program * @param fragment_shader String of the GLSL fragment shader program * @returns Handle of the newly created OpenGL shader object */ -GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader); +GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader, + const char* fragment_shader, const std::vector<const char*>& feedback_vars = {}, + bool separable_program = false); -} // namespace +} // namespace GLShader diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 5770ae08f..1d396728b 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -33,7 +33,7 @@ OpenGLState::OpenGLState() { stencil.action_depth_pass = GL_KEEP; stencil.action_stencil_fail = GL_KEEP; - blend.enabled = false; + blend.enabled = true; blend.rgb_equation = GL_FUNC_ADD; blend.a_equation = GL_FUNC_ADD; blend.src_rgb_func = GL_ONE; @@ -68,6 +68,18 @@ OpenGLState::OpenGLState() { draw.vertex_buffer = 0; draw.uniform_buffer = 0; draw.shader_program = 0; + draw.program_pipeline = 0; + + scissor.enabled = false; + scissor.x = 0; + scissor.y = 0; + scissor.width = 0; + scissor.height = 0; + + viewport.x = 0; + viewport.y = 0; + viewport.width = 0; + viewport.height = 0; clip_distance = {}; } @@ -148,9 +160,6 @@ void OpenGLState::Apply() const { if (blend.enabled != cur_state.blend.enabled) { if (blend.enabled) { glEnable(GL_BLEND); - - cur_state.logic_op = GL_COPY; - glLogicOp(cur_state.logic_op); glDisable(GL_COLOR_LOGIC_OP); } else { glDisable(GL_BLEND); @@ -196,7 +205,7 @@ void OpenGLState::Apply() const { // Lighting LUTs if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) { glActiveTexture(TextureUnits::LightingLUT.Enum()); - glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer); + glBindTexture(GL_TEXTURE_BUFFER, lighting_lut.texture_buffer); } // Fog LUT @@ -263,6 +272,31 @@ void OpenGLState::Apply() const { glUseProgram(draw.shader_program); } + // Program pipeline + if (draw.program_pipeline != cur_state.draw.program_pipeline) { + glBindProgramPipeline(draw.program_pipeline); + } + + // Scissor test + if (scissor.enabled != cur_state.scissor.enabled) { + if (scissor.enabled) { + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } + } + + if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y || + scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) { + glScissor(scissor.x, scissor.y, scissor.width, scissor.height); + } + + if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y || + viewport.width != cur_state.viewport.width || + viewport.height != cur_state.viewport.height) { + glViewport(viewport.x, viewport.y, viewport.width, viewport.height); + } + // Clip distance for (size_t i = 0; i < clip_distance.size(); ++i) { if (clip_distance[i] != cur_state.clip_distance[i]) { @@ -277,62 +311,75 @@ void OpenGLState::Apply() const { cur_state = *this; } -void OpenGLState::ResetTexture(GLuint handle) { - for (auto& unit : cur_state.texture_units) { +OpenGLState& OpenGLState::ResetTexture(GLuint handle) { + for (auto& unit : texture_units) { if (unit.texture_2d == handle) { unit.texture_2d = 0; } } - if (cur_state.lighting_lut.texture_buffer == handle) - cur_state.lighting_lut.texture_buffer = 0; - if (cur_state.fog_lut.texture_buffer == handle) - cur_state.fog_lut.texture_buffer = 0; - if (cur_state.proctex_noise_lut.texture_buffer == handle) - cur_state.proctex_noise_lut.texture_buffer = 0; - if (cur_state.proctex_color_map.texture_buffer == handle) - cur_state.proctex_color_map.texture_buffer = 0; - if (cur_state.proctex_alpha_map.texture_buffer == handle) - cur_state.proctex_alpha_map.texture_buffer = 0; - if (cur_state.proctex_lut.texture_buffer == handle) - cur_state.proctex_lut.texture_buffer = 0; - if (cur_state.proctex_diff_lut.texture_buffer == handle) - cur_state.proctex_diff_lut.texture_buffer = 0; + if (lighting_lut.texture_buffer == handle) + lighting_lut.texture_buffer = 0; + if (fog_lut.texture_buffer == handle) + fog_lut.texture_buffer = 0; + if (proctex_noise_lut.texture_buffer == handle) + proctex_noise_lut.texture_buffer = 0; + if (proctex_color_map.texture_buffer == handle) + proctex_color_map.texture_buffer = 0; + if (proctex_alpha_map.texture_buffer == handle) + proctex_alpha_map.texture_buffer = 0; + if (proctex_lut.texture_buffer == handle) + proctex_lut.texture_buffer = 0; + if (proctex_diff_lut.texture_buffer == handle) + proctex_diff_lut.texture_buffer = 0; + return *this; } -void OpenGLState::ResetSampler(GLuint handle) { - for (auto& unit : cur_state.texture_units) { +OpenGLState& OpenGLState::ResetSampler(GLuint handle) { + for (auto& unit : texture_units) { if (unit.sampler == handle) { unit.sampler = 0; } } + return *this; +} + +OpenGLState& OpenGLState::ResetProgram(GLuint handle) { + if (draw.shader_program == handle) { + draw.shader_program = 0; + } + return *this; } -void OpenGLState::ResetProgram(GLuint handle) { - if (cur_state.draw.shader_program == handle) { - cur_state.draw.shader_program = 0; +OpenGLState& OpenGLState::ResetPipeline(GLuint handle) { + if (draw.program_pipeline == handle) { + draw.program_pipeline = 0; } + return *this; } -void OpenGLState::ResetBuffer(GLuint handle) { - if (cur_state.draw.vertex_buffer == handle) { - cur_state.draw.vertex_buffer = 0; +OpenGLState& OpenGLState::ResetBuffer(GLuint handle) { + if (draw.vertex_buffer == handle) { + draw.vertex_buffer = 0; } - if (cur_state.draw.uniform_buffer == handle) { - cur_state.draw.uniform_buffer = 0; + if (draw.uniform_buffer == handle) { + draw.uniform_buffer = 0; } + return *this; } -void OpenGLState::ResetVertexArray(GLuint handle) { - if (cur_state.draw.vertex_array == handle) { - cur_state.draw.vertex_array = 0; +OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) { + if (draw.vertex_array == handle) { + draw.vertex_array = 0; } + return *this; } -void OpenGLState::ResetFramebuffer(GLuint handle) { - if (cur_state.draw.read_framebuffer == handle) { - cur_state.draw.read_framebuffer = 0; +OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { + if (draw.read_framebuffer == handle) { + draw.read_framebuffer = 0; } - if (cur_state.draw.draw_framebuffer == handle) { - cur_state.draw.draw_framebuffer = 0; + if (draw.draw_framebuffer == handle) { + draw.draw_framebuffer = 0; } + return *this; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 437fe34c4..c1f4efc8c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -85,7 +85,7 @@ public: struct { GLuint texture_2d; // GL_TEXTURE_BINDING_2D GLuint sampler; // GL_SAMPLER_BINDING - } texture_units[3]; + } texture_units[32]; struct { GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER @@ -122,27 +122,44 @@ public: GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM + GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING } draw; + struct { + bool enabled; // GL_SCISSOR_TEST + GLint x; + GLint y; + GLsizei width; + GLsizei height; + } scissor; + + struct { + GLint x; + GLint y; + GLsizei width; + GLsizei height; + } viewport; + std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE OpenGLState(); /// Get the currently active OpenGL state - static const OpenGLState& GetCurState() { + static OpenGLState GetCurState() { return cur_state; } /// Apply this state as the current OpenGL state void Apply() const; - /// Resets and unbinds any references to the given resource in the current OpenGL state - static void ResetTexture(GLuint handle); - static void ResetSampler(GLuint handle); - static void ResetProgram(GLuint handle); - static void ResetBuffer(GLuint handle); - static void ResetVertexArray(GLuint handle); - static void ResetFramebuffer(GLuint handle); + /// Resets any references to the given resource + OpenGLState& ResetTexture(GLuint handle); + OpenGLState& ResetSampler(GLuint handle); + OpenGLState& ResetProgram(GLuint handle); + OpenGLState& ResetPipeline(GLuint handle); + OpenGLState& ResetBuffer(GLuint handle); + OpenGLState& ResetVertexArray(GLuint handle); + OpenGLState& ResetFramebuffer(GLuint handle); private: static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp new file mode 100644 index 000000000..a2713e9f0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -0,0 +1,182 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <deque> +#include <vector> +#include "common/alignment.h" +#include "common/assert.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +class OrphanBuffer : public OGLStreamBuffer { +public: + explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~OrphanBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override; + void Unmap() override; + + std::vector<u8> data; +}; + +class StorageBuffer : public OGLStreamBuffer { +public: + explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {} + ~StorageBuffer() override; + +private: + void Create(size_t size, size_t sync_subdivide) override; + void Release() override; + + std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override; + void Unmap() override; + + struct Fence { + OGLSync sync; + size_t offset; + }; + std::deque<Fence> head; + std::deque<Fence> tail; + + u8* mapped_ptr; +}; + +OGLStreamBuffer::OGLStreamBuffer(GLenum target) { + gl_target = target; +} + +GLuint OGLStreamBuffer::GetHandle() const { + return gl_buffer.handle; +} + +std::unique_ptr<OGLStreamBuffer> OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) { + if (storage_buffer) { + return std::make_unique<StorageBuffer>(target); + } + return std::make_unique<OrphanBuffer>(target); +} + +OrphanBuffer::~OrphanBuffer() { + Release(); +} + +void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) { + buffer_pos = 0; + buffer_size = size; + data.resize(buffer_size); + + if (gl_buffer.handle == 0) { + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + } + + glBufferData(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, GL_STREAM_DRAW); +} + +void OrphanBuffer::Release() { + gl_buffer.Release(); +} + +std::pair<u8*, GLintptr> OrphanBuffer::Map(size_t size, size_t alignment) { + buffer_pos = Common::AlignUp(buffer_pos, alignment); + + if (buffer_pos + size > buffer_size) { + Create(std::max(buffer_size, size), 0); + } + + mapped_size = size; + return std::make_pair(&data[buffer_pos], static_cast<GLintptr>(buffer_pos)); +} + +void OrphanBuffer::Unmap() { + glBufferSubData(gl_target, static_cast<GLintptr>(buffer_pos), + static_cast<GLsizeiptr>(mapped_size), &data[buffer_pos]); + buffer_pos += mapped_size; +} + +StorageBuffer::~StorageBuffer() { + Release(); +} + +void StorageBuffer::Create(size_t size, size_t sync_subdivide) { + if (gl_buffer.handle != 0) + return; + + buffer_pos = 0; + buffer_size = size; + buffer_sync_subdivide = std::max<size_t>(sync_subdivide, 1); + + gl_buffer.Create(); + glBindBuffer(gl_target, gl_buffer.handle); + + glBufferStorage(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); + mapped_ptr = reinterpret_cast<u8*>( + glMapBufferRange(gl_target, 0, static_cast<GLsizeiptr>(buffer_size), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); +} + +void StorageBuffer::Release() { + if (gl_buffer.handle == 0) + return; + + glUnmapBuffer(gl_target); + + gl_buffer.Release(); + head.clear(); + tail.clear(); +} + +std::pair<u8*, GLintptr> StorageBuffer::Map(size_t size, size_t alignment) { + ASSERT(size <= buffer_size); + + OGLSync sync; + + buffer_pos = Common::AlignUp(buffer_pos, alignment); + size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide); + + if (!head.empty() && + (effective_offset > head.back().offset || buffer_pos + size > buffer_size)) { + ASSERT(head.back().sync.handle == 0); + head.back().sync.Create(); + } + + if (buffer_pos + size > buffer_size) { + if (!tail.empty()) { + std::swap(sync, tail.back().sync); + tail.clear(); + } + std::swap(tail, head); + buffer_pos = 0; + effective_offset = 0; + } + + while (!tail.empty() && buffer_pos + size > tail.front().offset) { + std::swap(sync, tail.front().sync); + tail.pop_front(); + } + + if (sync.handle != 0) { + glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + sync.Release(); + } + + if (head.empty() || effective_offset > head.back().offset) { + head.emplace_back(); + head.back().offset = effective_offset; + } + + mapped_size = size; + return std::make_pair(&mapped_ptr[buffer_pos], static_cast<GLintptr>(buffer_pos)); +} + +void StorageBuffer::Unmap() { + glFlushMappedBufferRange(gl_target, static_cast<GLintptr>(buffer_pos), + static_cast<GLsizeiptr>(mapped_size)); + buffer_pos += mapped_size; +} diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h new file mode 100644 index 000000000..4bc2f52e0 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -0,0 +1,34 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <glad/glad.h> +#include "common/common_types.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +class OGLStreamBuffer : private NonCopyable { +public: + explicit OGLStreamBuffer(GLenum target); + virtual ~OGLStreamBuffer() = default; + +public: + static std::unique_ptr<OGLStreamBuffer> MakeBuffer(bool storage_buffer, GLenum target); + + virtual void Create(size_t size, size_t sync_subdivide) = 0; + virtual void Release() {} + + GLuint GetHandle() const; + + virtual std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) = 0; + virtual void Unmap() = 0; + +protected: + OGLBuffer gl_buffer; + GLenum gl_target; + + size_t buffer_pos = 0; + size_t buffer_size = 0; + size_t buffer_sync_subdivide = 0; + size_t mapped_size = 0; +}; diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h new file mode 100644 index 000000000..d847317ac --- /dev/null +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <glad/glad.h> +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/maxwell_3d.h" + +namespace MaxwellToGL { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +inline GLenum VertexType(Maxwell::VertexAttribute attrib) { + switch (attrib.type) { + case Maxwell::VertexAttribute::Type::UnsignedNorm: { + + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_8_8_8_8: + return GL_UNSIGNED_BYTE; + } + + LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size=%s", attrib.SizeString().c_str()); + UNREACHABLE(); + return {}; + } + + case Maxwell::VertexAttribute::Type::Float: + return GL_FLOAT; + } + + LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type=%s", attrib.TypeString().c_str()); + UNREACHABLE(); + return {}; +} + +inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) { + switch (topology) { + case Maxwell::PrimitiveTopology::TriangleStrip: + return GL_TRIANGLE_STRIP; + } + LOG_CRITICAL(Render_OpenGL, "Unimplemented primitive topology=%d", topology); + UNREACHABLE(); + return {}; +} + +} // namespace MaxwellToGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 50396b5c1..78b50b227 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -20,6 +20,7 @@ #include "core/settings.h" #include "core/tracer/recorder.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/utils.h" #include "video_core/video_core.h" static const char vertex_shader[] = R"( @@ -98,195 +99,88 @@ RendererOpenGL::RendererOpenGL() = default; RendererOpenGL::~RendererOpenGL() = default; /// Swap buffers (render frame) -void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) { +void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) { + Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); - if (framebuffer_info != boost::none) { - // If framebuffer_info is provided, reload it from memory to a texture - if (screen_info.texture.width != (GLsizei)framebuffer_info->width || - screen_info.texture.height != (GLsizei)framebuffer_info->height || - screen_info.texture.pixel_format != framebuffer_info->pixel_format) { + if (framebuffer != boost::none) { + // If framebuffer is provided, reload it from memory to a texture + if (screen_info.texture.width != (GLsizei)framebuffer->width || + screen_info.texture.height != (GLsizei)framebuffer->height || + screen_info.texture.pixel_format != framebuffer->pixel_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info); + ConfigureFramebufferTexture(screen_info.texture, *framebuffer); } - LoadFBToScreenInfo(*framebuffer_info, screen_info); - } - - DrawScreens(); - Core::System::GetInstance().perf_stats.EndSystemFrame(); + // Load the framebuffer from memory, draw it to the screen, and swap buffers + LoadFBToScreenInfo(*framebuffer, screen_info); + DrawScreen(); + render_window->SwapBuffers(); + } - // Swap buffers render_window->PollEvents(); - render_window->SwapBuffers(); Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs()); Core::System::GetInstance().perf_stats.BeginSystemFrame(); + // Restore the rasterizer state prev_state.Apply(); RefreshRasterizerSetting(); } -static inline u32 MortonInterleave128(u32 x, u32 y) { - // 128x128 Z-Order coordinate from 2D coordinates - static constexpr u32 xlut[] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, - 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, - 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, - 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, - 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, - 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, - 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, - 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, - 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, - 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, - 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, - 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, - 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, - 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, - 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, - 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, - 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, - 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, - 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, - 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, - 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, - 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, - 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, - 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, - 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, - 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, - 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, - 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, - 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, - 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, - 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, - 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, - 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, - 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, - }; - static constexpr u32 ylut[] = { - 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, - 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, - 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, - 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, - 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, - 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, - 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, - 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, - 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, - 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, - 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, - 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, - 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, - 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, - 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, - 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, - 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, - 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, - 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, - 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, - 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, - 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, - 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, - 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, - 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, - 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, - 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, - 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, - 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, - 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, - 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, - 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, - 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, - 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, - 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, - }; - return xlut[x % 128] + ylut[y % 128]; -} - -static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { - // Calculates the offset of the position of the pixel in Morton order - // Framebuffer images are split into 128x128 tiles. - - const unsigned int block_height = 128; - const unsigned int coarse_x = x & ~127; - - u32 i = MortonInterleave128(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - -static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, - u8* morton_data, u8* gl_data, bool morton_to_gl) { - u8* data_ptrs[2]; - for (unsigned y = 0; y < height; ++y) { - for (unsigned x = 0; x < width; ++x) { - const u32 coarse_y = y & ~127; - u32 morton_offset = - GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; - u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; - - data_ptrs[morton_to_gl] = morton_data + morton_offset; - data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; - - memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - /** * Loads framebuffer from emulated memory into the active OpenGL texture. */ -void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, +void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info) { - const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)}; - const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp}; + const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)}; + const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; + const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; - MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4, - Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(), - true); - - LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes, - framebuffer_info.address, framebuffer_info.width, framebuffer_info.height, - (int)framebuffer_info.pixel_format); + // Framebuffer orientation handling + framebuffer_transform_flags = framebuffer.transform_flags; // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default // only allows rows to have a memory alignement of 4. - ASSERT(framebuffer_info.stride % 4 == 0); + ASSERT(framebuffer.stride % 4 == 0); - // Reset the screen info's display texture to its own permanent texture - screen_info.display_texture = screen_info.texture.resource.handle; - screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride, + screen_info)) { + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; + screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); - Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes); + Rasterizer()->FlushRegion(framebuffer_addr, size_in_bytes); - state.texture_units[0].texture_2d = screen_info.texture.resource.handle; - state.Apply(); + VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4, + Memory::GetPointer(framebuffer_addr), + gl_framebuffer_data.data(), true); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride); + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that - // they differ from the LCD resolution. - // TODO: Applications could theoretically crash Citra here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height, - screen_info.texture.gl_format, screen_info.texture.gl_type, - gl_framebuffer_data.data()); + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that + // they differ from the LCD resolution. + // TODO: Applications could theoretically crash yuzu here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, + gl_framebuffer_data.data()); - state.texture_units[0].texture_2d = 0; - state.Apply(); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + state.texture_units[0].texture_2d = 0; + state.Apply(); + } } /** @@ -316,7 +210,7 @@ void RendererOpenGL::InitOpenGLObjects() { 0.0f); // Link shaders and get variable locations - shader.Create(vertex_shader, fragment_shader); + shader.Create(vertex_shader, nullptr, fragment_shader); state.draw.shader_program = shader.handle; state.Apply(); uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); @@ -370,14 +264,14 @@ void RendererOpenGL::InitOpenGLObjects() { } void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, - const FramebufferInfo& framebuffer_info) { + const Tegra::FramebufferConfig& framebuffer) { - texture.width = framebuffer_info.width; - texture.height = framebuffer_info.height; + texture.width = framebuffer.width; + texture.height = framebuffer.height; GLint internal_format; - switch (framebuffer_info.pixel_format) { - case FramebufferInfo::PixelFormat::ABGR8: + switch (framebuffer.pixel_format) { + case Tegra::FramebufferConfig::PixelFormat::ABGR8: // Use RGBA8 and swap in the fragment shader internal_format = GL_RGBA; texture.gl_format = GL_RGBA; @@ -385,7 +279,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, gl_framebuffer_data.resize(texture.width * texture.height * 4); break; default: - UNIMPLEMENTED(); + UNREACHABLE(); } state.texture_units[0].texture_2d = texture.resource.handle; @@ -399,15 +293,28 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, state.Apply(); } -void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, - float h) { - auto& texcoords = screen_info.display_texcoords; +void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, + float h) { + const auto& texcoords = screen_info.display_texcoords; + auto left = texcoords.left; + auto right = texcoords.right; + if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset) + if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) { + // Flip the framebuffer vertically + left = texcoords.right; + right = texcoords.left; + } else { + // Other transformations are unsupported + LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags=%d", + framebuffer_transform_flags); + UNIMPLEMENTED(); + } std::array<ScreenRectVertex, 4> vertices = {{ - ScreenRectVertex(x, y, texcoords.top, texcoords.right), - ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right), - ScreenRectVertex(x, y + h, texcoords.top, texcoords.left), - ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.left), + ScreenRectVertex(x, y, texcoords.top, right), + ScreenRectVertex(x + w, y, texcoords.bottom, right), + ScreenRectVertex(x, y + h, texcoords.top, left), + ScreenRectVertex(x + w, y + h, texcoords.bottom, left), }}; state.texture_units[0].texture_2d = screen_info.display_texture; @@ -423,7 +330,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl /** * Draws the emulated screens to the emulator window. */ -void RendererOpenGL::DrawScreens() { +void RendererOpenGL::DrawScreen() { const auto& layout = render_window->GetFramebufferLayout(); const auto& screen = layout.screen; @@ -439,8 +346,8 @@ void RendererOpenGL::DrawScreens() { glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(), - (float)screen.GetHeight()); + DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top, + (float)screen.GetWidth(), (float)screen.GetHeight()); m_current_frame++; } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index db6c355a5..fffd0f9f4 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -21,7 +21,7 @@ struct TextureInfo { GLsizei height; GLenum gl_format; GLenum gl_type; - RendererBase::FramebufferInfo::PixelFormat pixel_format; + Tegra::FramebufferConfig::PixelFormat pixel_format; }; /// Structure used for storing information about the display target for each 3DS screen @@ -37,7 +37,7 @@ public: ~RendererOpenGL() override; /// Swap buffers (render frame) - void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override; + void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override; /** * Set the emulator window to use for renderer @@ -53,13 +53,14 @@ public: private: void InitOpenGLObjects(); - void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info); - void DrawScreens(); - void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h); + void ConfigureFramebufferTexture(TextureInfo& texture, + const Tegra::FramebufferConfig& framebuffer); + void DrawScreen(); + void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); // Loads framebuffer from emulated memory into the display information structure - void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info); + void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info); // Fills active OpenGL texture with the given RGBA color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture); @@ -86,4 +87,7 @@ private: // Shader attribute input indices GLuint attrib_position; GLuint attrib_tex_coord; + + /// Used for transforming the framebuffer orientation + Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; }; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp new file mode 100644 index 000000000..2e87281eb --- /dev/null +++ b/src/video_core/textures/decoders.cpp @@ -0,0 +1,105 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Calculates the offset of an (x, y) position within a swizzled texture. + * Taken from the Tegra X1 TRM. + */ +static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { + u32 image_width_in_gobs = image_width * bytes_per_pixel / 64; + u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (x * bytes_per_pixel / 64) * 512 * block_height + + (y % (8 * block_height) / 8) * 512; + x *= bytes_per_pixel; + u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); + + return address; +} + +static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, + u32 block_height) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); + u32 pixel_index = (x + y * width) * out_bytes_per_pixel; + + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +u32 BytesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::DXT1: + // In this case a 'pixel' actually refers to a 4x4 tile. + return 8; + case TextureFormat::A8R8G8B8: + return 4; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } +} + +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) { + u8* data = Memory::GetPointer(address); + u32 bytes_per_pixel = BytesPerPixel(format); + + static constexpr u32 DefaultBlockHeight = 16; + + std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); + + switch (format) { + case TextureFormat::DXT1: + // In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values. + CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; + case TextureFormat::A8R8G8B8: + CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return unswizzled_data; +} + +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height) { + std::vector<u8> rgba_data; + + // TODO(Subv): Implement. + switch (format) { + case TextureFormat::DXT1: + case TextureFormat::A8R8G8B8: + // TODO(Subv): For the time being just forward the same data without any decoding. + rgba_data = texture_data; + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return rgba_data; +} + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h new file mode 100644 index 000000000..0c21694ff --- /dev/null +++ b/src/video_core/textures/decoders.h @@ -0,0 +1,26 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Unswizzles a swizzled texture without changing its format. + */ +std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height); + +/** + * Decodes an unswizzled texture into a A8R8G8B8 texture. + */ +std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, + u32 height); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h new file mode 100644 index 000000000..07936f8a3 --- /dev/null +++ b/src/video_core/textures/texture.h @@ -0,0 +1,137 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +namespace Texture { + +enum class TextureFormat : u32 { + A8R8G8B8 = 8, + DXT1 = 0x24, +}; + +enum class TextureType : u32 { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCubemap = 3, + Texture1DArray = 4, + Texture2DArray = 5, + Texture1DBuffer = 6, + Texture2DNoMipmap = 7, + TextureCubeArray = 8, +}; + +enum class TICHeaderVersion : u32 { + OneDBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4, +}; + +union TextureHandle { + u32 raw; + BitField<0, 20, u32> tic_id; + BitField<20, 12, u32> tsc_id; +}; +static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); + +struct TICEntry { + union { + u32 raw; + BitField<0, 7, TextureFormat> format; + BitField<7, 3, u32> r_type; + BitField<10, 3, u32> g_type; + BitField<13, 3, u32> b_type; + BitField<16, 3, u32> a_type; + }; + u32 address_low; + union { + BitField<0, 16, u32> address_high; + BitField<21, 3, TICHeaderVersion> header_version; + }; + INSERT_PADDING_BYTES(4); + union { + BitField<0, 16, u32> width_minus_1; + BitField<23, 4, TextureType> texture_type; + }; + u16 height_minus_1; + INSERT_PADDING_BYTES(10); + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); + } + + u32 Width() const { + return width_minus_1 + 1; + } + + u32 Height() const { + return height_minus_1 + 1; + } +}; +static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); + +enum class WrapMode : u32 { + Wrap = 0, + Mirror = 1, + ClampToEdge = 2, + Border = 3, + ClampOGL = 4, + MirrorOnceClampToEdge = 5, + MirrorOnceBorder = 6, + MirrorOnceClampOGL = 7, +}; + +enum class TextureFilter : u32 { + Nearest = 1, + Linear = 2, +}; + +enum class TextureMipmapFilter : u32 { + None = 1, + Nearest = 2, + Linear = 3, +}; + +struct TSCEntry { + union { + BitField<0, 3, WrapMode> wrap_u; + BitField<3, 3, WrapMode> wrap_v; + BitField<6, 3, WrapMode> wrap_p; + BitField<9, 1, u32> depth_compare_enabled; + BitField<10, 3, u32> depth_compare_func; + }; + union { + BitField<0, 2, TextureFilter> mag_filter; + BitField<4, 2, TextureFilter> min_filter; + BitField<6, 2, TextureMipmapFilter> mip_filter; + }; + INSERT_PADDING_BYTES(8); + u32 border_color_r; + u32 border_color_g; + u32 border_color_b; + u32 border_color_a; +}; +static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size"); + +struct FullTextureInfo { + u32 index; + TICEntry tic; + TSCEntry tsc; + bool enabled; +}; + +/// Returns the number of bytes per pixel of the input texture format. +u32 BytesPerPixel(TextureFormat format); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/utils.h b/src/video_core/utils.h index d8567f314..be0f7e22b 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { return (i + offset) * bytes_per_pixel; } -} // namespace +static inline u32 MortonInterleave128(u32 x, u32 y) { + // 128x128 Z-Order coordinate from 2D coordinates + static constexpr u32 xlut[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, + 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, + 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, + 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, + 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, + 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, + 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, + 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, + 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, + 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, + 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, + 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, + 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, + 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, + 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, + 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, + 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, + 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, + 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, + 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, + 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, + 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, + 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, + 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, + 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, + 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, + 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, + 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, + 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, + 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, + 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, + 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, + 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, + 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, + }; + static constexpr u32 ylut[] = { + 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, + 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, + 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, + 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, + 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, + 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, + 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, + 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, + 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, + 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, + 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, + 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, + 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, + 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, + 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, + 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, + 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, + 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, + 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, + 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, + 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, + 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, + 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, + 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, + 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, + 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, + 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, + 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, + 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, + 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, + 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, + 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, + 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, + 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, + 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, + }; + return xlut[x % 128] + ylut[y % 128]; +} + +static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { + // Calculates the offset of the position of the pixel in Morton order + // Framebuffer images are split into 128x128 tiles. + + const unsigned int block_height = 128; + const unsigned int coarse_x = x & ~127; + + u32 i = MortonInterleave128(x, y); + + const unsigned int offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + +static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, + u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, + bool morton_to_gl) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~127; + u32 morton_offset = + GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +} // namespace VideoCore diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 106d62562..289140f31 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -26,7 +26,7 @@ bool Init(EmuWindow* emu_window) { if (g_renderer->Init()) { LOG_DEBUG(Render, "initialized OK"); } else { - LOG_ERROR(Render, "initialization failed !"); + LOG_CRITICAL(Render, "initialization failed !"); return false; } return true; @@ -39,4 +39,4 @@ void Shutdown() { LOG_DEBUG(Render, "shutdown OK"); } -} // namespace +} // namespace VideoCore diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index 0b8785898..37da62436 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -15,6 +15,8 @@ class RendererBase; namespace VideoCore { +enum class Renderer { Software, OpenGL }; + extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin extern EmuWindow* g_emu_window; ///< Emu window @@ -31,4 +33,4 @@ bool Init(EmuWindow* emu_window); /// Shutdown the video core void Shutdown(); -} // namespace +} // namespace VideoCore diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c52d5627a..5af3154d7 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -3,83 +3,91 @@ set(CMAKE_AUTORCC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) -set(SRCS - about_dialog.cpp - configuration/config.cpp - configuration/configure_debug.cpp - configuration/configure_dialog.cpp - configuration/configure_general.cpp - configuration/configure_graphics.cpp - configuration/configure_input.cpp - configuration/configure_system.cpp - debugger/profiler.cpp - debugger/registers.cpp - debugger/wait_tree.cpp - util/spinbox.cpp - util/util.cpp - bootmanager.cpp - game_list.cpp - hotkeys.cpp - main.cpp - ui_settings.cpp - yuzu.rc - Info.plist - ) - -set(HEADERS - about_dialog.h - configuration/config.h - configuration/configure_debug.h - configuration/configure_dialog.h - configuration/configure_general.h - configuration/configure_graphics.h - configuration/configure_input.h - configuration/configure_system.h - debugger/profiler.h - debugger/registers.h - debugger/wait_tree.h - util/spinbox.h - util/util.h - bootmanager.h - game_list.h - game_list_p.h - hotkeys.h - main.h - ui_settings.h - ) +add_executable(yuzu + Info.plist + about_dialog.cpp + about_dialog.h + bootmanager.cpp + bootmanager.h + configuration/config.cpp + configuration/config.h + configuration/configure_debug.cpp + configuration/configure_debug.h + configuration/configure_dialog.cpp + configuration/configure_dialog.h + configuration/configure_general.cpp + configuration/configure_general.h + configuration/configure_graphics.cpp + configuration/configure_graphics.h + configuration/configure_input.cpp + configuration/configure_input.h + configuration/configure_system.cpp + configuration/configure_system.h + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_surface.h + debugger/profiler.cpp + debugger/profiler.h + debugger/registers.cpp + debugger/registers.h + debugger/wait_tree.cpp + debugger/wait_tree.h + game_list.cpp + game_list.h + game_list_p.h + hotkeys.cpp + hotkeys.h + main.cpp + main.h + ui_settings.cpp + ui_settings.h + util/spinbox.cpp + util/spinbox.h + util/util.cpp + util/util.h + yuzu.rc +) set(UIS - aboutdialog.ui - configuration/configure.ui - configuration/configure_debug.ui - configuration/configure_general.ui - configuration/configure_graphics.ui - configuration/configure_input.ui - configuration/configure_system.ui - debugger/registers.ui - hotkeys.ui - main.ui - ) + aboutdialog.ui + configuration/configure.ui + configuration/configure_debug.ui + configuration/configure_general.ui + configuration/configure_graphics.ui + configuration/configure_input.ui + configuration/configure_system.ui + debugger/registers.ui + hotkeys.ui + main.ui +) file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) -create_directory_groups(${SRCS} ${HEADERS} ${UIS}) +qt5_wrap_ui(UI_HDRS ${UIS}) -if (Qt5_FOUND) - qt5_wrap_ui(UI_HDRS ${UIS}) -else() - qt4_wrap_ui(UI_HDRS ${UIS}) -endif() +target_sources(yuzu + PRIVATE + ${ICONS} + ${THEMES} + ${UI_HDRS} + ${UIS} +) if (APPLE) set(MACOSX_ICON "../../dist/yuzu.icns") set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - add_executable(yuzu MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${MACOSX_ICON} ${ICONS}) + target_sources(yuzu PRIVATE ${MACOSX_ICON}) + set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) -else() - add_executable(yuzu ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS}) endif() + +create_target_directory_groups(yuzu) + target_link_libraries(yuzu PRIVATE common core input_common video_core) target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index da3429822..d6647eeea 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QIcon> #include "common/scm_rev.h" #include "ui_aboutdialog.h" #include "yuzu/about_dialog.h" AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); + ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 843ac6ad7..469988d63 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,12 +1,8 @@ #include <QApplication> #include <QHBoxLayout> #include <QKeyEvent> - -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) -// Required for screen DPI information #include <QScreen> #include <QWindow> -#endif #include "common/microprofile.h" #include "common/scm_rev.h" @@ -19,8 +15,7 @@ #include "input_common/motion_emu.h" #include "yuzu/bootmanager.h" -EmuThread::EmuThread(GRenderWindow* render_window) - : exec_step(false), running(false), stop_run(false), render_window(render_window) {} +EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} void EmuThread::run() { render_window->MakeCurrent(); @@ -120,15 +115,13 @@ GRenderWindow::~GRenderWindow() { void GRenderWindow::moveContext() { DoneCurrent(); -// We need to move GL context to the swapping thread in Qt5 -#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) + // If the thread started running, move the GL Context to the new thread. Otherwise, move it // back. auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) ? emu_thread : qApp->thread(); child->context()->moveToThread(thread); -#endif } void GRenderWindow::SwapBuffers() { @@ -191,12 +184,8 @@ QByteArray GRenderWindow::saveGeometry() { } qreal GRenderWindow::windowPixelRatio() { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) // windowHandle() might not be accessible until the window is displayed to screen. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; -#else - return 1.0f; -#endif } void GRenderWindow::closeEvent(QCloseEvent* event) { @@ -299,9 +288,7 @@ void GRenderWindow::OnEmulationStopping() { void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) // windowHandle() is not initialized until the Window is shown, so we connect it here. - connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, - SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection); -#endif + connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, + Qt::UniqueConnection); } diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 6974edcbb..130bc613b 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -58,7 +58,7 @@ public: * @return True if the emulation thread is running, otherwise false * @note This function is thread-safe */ - bool IsRunning() { + bool IsRunning() const { return running; } @@ -68,12 +68,12 @@ public: void RequestStop() { stop_run = true; SetRunning(false); - }; + } private: - bool exec_step; - bool running; - std::atomic<bool> stop_run; + bool exec_step = false; + bool running = false; + std::atomic<bool> stop_run{false}; std::mutex running_mutex; std::condition_variable running_cv; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 6a40f035c..8843f2078 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -26,10 +26,18 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ { - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_E, + Qt::Key_Up, + Qt::Key_Down, + Qt::Key_Left, + Qt::Key_Right, + Qt::Key_E, }, { - Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_R, + Qt::Key_I, + Qt::Key_K, + Qt::Key_J, + Qt::Key_L, + Qt::Key_R, }, }}; @@ -69,8 +77,7 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt()); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -86,6 +93,10 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); + qt_config->beginGroup("System"); + Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); qt_config->endGroup(); @@ -96,6 +107,8 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("UI"); + UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -163,7 +176,7 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core)); + qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); qt_config->endGroup(); qt_config->beginGroup("Renderer"); @@ -180,6 +193,10 @@ void Config::SaveValues() { qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); + qt_config->beginGroup("System"); + qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->endGroup(); + qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index babd583a2..c5303851c 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>740</width> + <width>461</width> <height>500</height> </rect> </property> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 92fd6ab02..2d73fc5aa 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,9 +13,14 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->setupUi(this); + for (auto theme : UISettings::themes) { + ui->theme_combobox->addItem(theme.first, theme.second); + } + this->setConfiguration(); - ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } ConfigureGeneral::~ConfigureGeneral() {} @@ -23,13 +28,18 @@ ConfigureGeneral::~ConfigureGeneral() {} void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); - ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core)); + ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex()); + UISettings::values.theme = + ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + + Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 573c4cb0e..1775c4d40 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -13,17 +13,17 @@ <property name="windowTitle"> <string>Form</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="HorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="VerticalLayout"> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="GeneralGroupBox"> <property name="title"> <string>General</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> @@ -44,40 +44,80 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>CPU Core</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QComboBox" name="cpu_core_combobox"> - <item> - <property name="text"> - <string>Unicorn</string> - </property> - </item> - <item> - <property name="text"> - <string>Dynarmic</string> - </property> - </item> - </widget> - </item> - </layout> - </item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Performance</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_cpu_jit"> + <property name="text"> + <string>Enable CPU JIT</string> + </property> + </widget> + </item> </layout> - </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="EmulationGroupBox"> + <property name="title"> + <string>Emulation</string> + </property> + <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="EmulationVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Enable docked mode</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> + </item> + </layout> + </item> + </layout> + </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> + <widget class="QGroupBox" name="HotKeysGroupBox"> <property name="title"> <string>Hotkeys</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <layout class="QHBoxLayout" name="HotKeysHorizontalLayout"> <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="HotKeysVerticalLayout"> <item> <widget class="GHotkeysDialog" name="widget" native="true"/> </item> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 10043e6e8..78559e2bb 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -14,7 +14,11 @@ const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> ConfigureInput::analog_sub_buttons{{ - "up", "down", "left", "right", "modifier", + "up", + "down", + "left", + "right", + "modifier", }}; static QString getKeyName(int key_code) { @@ -36,7 +40,8 @@ static void SetAnalogButton(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, const std::string& button_name) { if (analog_param.Get("engine", "") != "analog_from_button") { analog_param = { - {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, + {"engine", "analog_from_button"}, + {"modifier_scale", "0.5"}, }; } analog_param.Set(button_name, input_param.Serialize()); @@ -107,11 +112,17 @@ ConfigureInput::ConfigureInput(QWidget* parent) analog_map_buttons = {{ { - ui->buttonLStickUp, ui->buttonLStickDown, ui->buttonLStickLeft, ui->buttonLStickRight, + ui->buttonLStickUp, + ui->buttonLStickDown, + ui->buttonLStickLeft, + ui->buttonLStickRight, ui->buttonLStickMod, }, { - ui->buttonRStickUp, ui->buttonRStickDown, ui->buttonRStickLeft, ui->buttonRStickRight, + ui->buttonRStickUp, + ui->buttonRStickDown, + ui->buttonRStickLeft, + ui->buttonRStickRight, ui->buttonRStickMod, }, }}; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index c162ca02c..377b79c77 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -15,9 +15,9 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> - <layout class="QGridLayout" name="gridLayout_7"> + <layout class="QGridLayout" name="buttons"> <item row="3" column="1"> - <widget class="QGroupBox" name="faceButtons_6"> + <widget class="QGroupBox" name="misc"> <property name="title"> <string>Misc.</string> </property> @@ -29,9 +29,9 @@ </property> <layout class="QGridLayout" name="gridLayout_6"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_25"> + <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> <item> - <widget class="QLabel" name="label_29"> + <widget class="QLabel" name="labelPlus"> <property name="text"> <string>Plus:</string> </property> @@ -47,9 +47,9 @@ </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_26"> + <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> <item> - <widget class="QLabel" name="label_30"> + <widget class="QLabel" name="labelMinus"> <property name="text"> <string>Minus:</string> </property> @@ -65,9 +65,9 @@ </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_27"> + <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> <item> - <widget class="QLabel" name="label_31"> + <widget class="QLabel" name="labelHome"> <property name="text"> <string>Home:</string> </property> @@ -83,9 +83,9 @@ </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_28"> + <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> <item> - <widget class="QLabel" name="label_11"> + <widget class="QLabel" name="labelScrCap"> <property name="text"> <string>Screen Capture:</string> @@ -130,9 +130,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="labelA"> <property name="text"> <string>A:</string> </property> @@ -148,9 +148,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> <item> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="labelB"> <property name="text"> <string>B:</string> </property> @@ -166,9 +166,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> <item> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="labelX"> <property name="text"> <string>X:</string> </property> @@ -184,9 +184,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> <item> - <widget class="QLabel" name="label_4"> + <widget class="QLabel" name="labelY"> <property name="text"> <string>Y:</string> </property> @@ -205,7 +205,7 @@ Capture:</string> </widget> </item> <item row="0" column="1"> - <widget class="QGroupBox" name="faceButtons_2"> + <widget class="QGroupBox" name="Dpad"> <property name="title"> <string>Directional Pad</string> </property> @@ -217,9 +217,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_2"> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_12"> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> <item> - <widget class="QLabel" name="label_34"> + <widget class="QLabel" name="labelDpadUp"> <property name="text"> <string>Up:</string> </property> @@ -235,9 +235,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_9"> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> <item> - <widget class="QLabel" name="label_35"> + <widget class="QLabel" name="labelDpadDown"> <property name="text"> <string>Down:</string> </property> @@ -253,9 +253,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_10"> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_32"> + <widget class="QLabel" name="labelDpadLeft"> <property name="text"> <string>Left:</string> </property> @@ -271,9 +271,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_11"> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> <item> - <widget class="QLabel" name="label_33"> + <widget class="QLabel" name="labelDpadRight"> <property name="text"> <string>Right:</string> </property> @@ -292,7 +292,7 @@ Capture:</string> </widget> </item> <item row="3" column="0"> - <widget class="QGroupBox" name="faceButtons_3"> + <widget class="QGroupBox" name="shoulderButtons"> <property name="title"> <string>Shoulder Buttons</string> </property> @@ -304,9 +304,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_13"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> <item> - <widget class="QLabel" name="label_17"> + <widget class="QLabel" name="labelL"> <property name="text"> <string>L:</string> </property> @@ -322,9 +322,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_14"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> <item> - <widget class="QLabel" name="label_19"> + <widget class="QLabel" name="labelR"> <property name="text"> <string>R:</string> </property> @@ -340,9 +340,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_15"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> <item> - <widget class="QLabel" name="label_20"> + <widget class="QLabel" name="labelZL"> <property name="text"> <string>ZL:</string> </property> @@ -358,9 +358,9 @@ Capture:</string> </layout> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_16"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> <item> - <widget class="QLabel" name="label_18"> + <widget class="QLabel" name="labelZR"> <property name="text"> <string>ZR:</string> </property> @@ -376,9 +376,9 @@ Capture:</string> </layout> </item> <item row="2" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_8"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> <item> - <widget class="QLabel" name="label_7"> + <widget class="QLabel" name="labelSL"> <property name="text"> <string>SL:</string> </property> @@ -394,9 +394,9 @@ Capture:</string> </layout> </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_29"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> <item> - <widget class="QLabel" name="label_8"> + <widget class="QLabel" name="labelSR"> <property name="text"> <string>SR:</string> </property> @@ -415,7 +415,7 @@ Capture:</string> </widget> </item> <item row="1" column="1"> - <widget class="QGroupBox" name="faceButtons_5"> + <widget class="QGroupBox" name="RStick"> <property name="title"> <string>Right Stick</string> </property> @@ -430,9 +430,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_5"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_24"> + <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> <item> - <widget class="QLabel" name="label_26"> + <widget class="QLabel" name="labelRStickDown"> <property name="text"> <string>Down:</string> </property> @@ -448,9 +448,9 @@ Capture:</string> </layout> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_22"> + <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> <item> - <widget class="QLabel" name="label_27"> + <widget class="QLabel" name="labelRStickRight"> <property name="text"> <string>Right:</string> </property> @@ -473,9 +473,9 @@ Capture:</string> </widget> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_21"> + <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_25"> + <widget class="QLabel" name="labelRStickLeft"> <property name="text"> <string>Left:</string> </property> @@ -491,9 +491,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_25"> + <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> <item> - <widget class="QLabel" name="label_28"> + <widget class="QLabel" name="labelRStickUp"> <property name="text"> <string>Up:</string> </property> @@ -509,9 +509,9 @@ Capture:</string> </layout> </item> <item row="2" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> <item> - <widget class="QLabel" name="label_5"> + <widget class="QLabel" name="labelRStickPressed"> <property name="text"> <string>Pressed:</string> </property> @@ -527,9 +527,9 @@ Capture:</string> </layout> </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_32"> + <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> <item> - <widget class="QLabel" name="label_10"> + <widget class="QLabel" name="labelRStickMod"> <property name="text"> <string>Modifier:</string> </property> @@ -548,7 +548,7 @@ Capture:</string> </widget> </item> <item row="1" column="0"> - <widget class="QGroupBox" name="faceButtons_4"> + <widget class="QGroupBox" name="LStick"> <property name="title"> <string>Left Stick</string> </property> @@ -560,9 +560,9 @@ Capture:</string> </property> <layout class="QGridLayout" name="gridLayout_4"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_20"> + <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> <item> - <widget class="QLabel" name="label_22"> + <widget class="QLabel" name="labelLStickDown"> <property name="text"> <string>Down:</string> </property> @@ -585,9 +585,9 @@ Capture:</string> </widget> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_18"> + <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> <item> - <widget class="QLabel" name="label_23"> + <widget class="QLabel" name="labelLStickRight"> <property name="text"> <string>Right:</string> </property> @@ -603,9 +603,9 @@ Capture:</string> </layout> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_17"> + <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> <item> - <widget class="QLabel" name="label_21"> + <widget class="QLabel" name="labelLStickLeft"> <property name="text"> <string>Left:</string> </property> @@ -621,9 +621,9 @@ Capture:</string> </layout> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_19"> + <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> <item> - <widget class="QLabel" name="label_24"> + <widget class="QLabel" name="labelLStickUp"> <property name="text"> <string>Up:</string> </property> @@ -639,9 +639,9 @@ Capture:</string> </layout> </item> <item row="3" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_31"> + <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> <item> - <widget class="QLabel" name="label_9"> + <widget class="QLabel" name="labelLStickMod"> <property name="text"> <string>Modifier:</string> </property> @@ -657,9 +657,9 @@ Capture:</string> </layout> </item> <item row="3" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_7" stretch="0,0"> + <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> <item> - <widget class="QLabel" name="label_6"> + <widget class="QLabel" name="labelLStickPressed"> <property name="text"> <string>Pressed:</string> </property> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index d198e38ae..d09505a0f 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -9,7 +9,18 @@ #include "yuzu/ui_settings.h" static const std::array<int, 12> days_in_month = {{ - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + 31, + 29, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, }}; ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { @@ -72,5 +83,6 @@ void ConfigureSystem::refreshConsoleID() { if (reply == QMessageBox::No) return; u64 console_id{}; - ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); + ui->label_console_id->setText( + tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..d6d61a739 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp @@ -0,0 +1,27 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMetaType> +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) { + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnMaxwellResume() { + emit Resumed(); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h new file mode 100644 index 000000000..9d05493cf --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. + * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, + protected Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +private slots: + virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp new file mode 100644 index 000000000..f98cc8152 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -0,0 +1,212 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_breakpoints_p.h" + +BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context, + QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) {} + +int BreakPointModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const { + return static_cast<int>(Tegra::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::DisplayRole: { + if (index.column() == 0) { + static const std::map<Tegra::DebugContext::Event, QString> map = { + {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, + {Tegra::DebugContext::Event::MaxwellCommandProcessed, + tr("Maxwell command processed")}, + {Tegra::DebugContext::Event::IncomingPrimitiveBatch, + tr("Incoming primitive batch")}, + {Tegra::DebugContext::Event::FinishedPrimitiveBatch, + tr("Finished primitive batch")}, + }; + + DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); + return (map.find(event) != map.end()) ? map.at(event) : QString(); + } + + break; + } + + case Qt::CheckStateRole: { + if (index.column() == 0) + return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; + break; + } + + case Qt::BackgroundRole: { + if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: { + auto context = context_weak.lock(); + return context && context->breakpoints[(int)event].enabled; + } + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { + if (!index.isValid()) + return 0; + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.column() == 0) + flags |= Qt::ItemIsUserCheckable; + return flags; +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { + const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::CheckStateRole: { + if (index.column() != 0) + return false; + + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[(int)event].enabled = value == Qt::Checked; + QModelIndex changed_index = createIndex(index.row(), 0); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + +void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(event), 0), + createIndex(static_cast<int>(event), 0)); +} + +void BreakPointModel::OnResumed() { + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), + createIndex(static_cast<int>(active_breakpoint), 0)); + active_breakpoint = context->active_breakpoint; +} + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( + std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) + : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( + debug_context) { + setObjectName("TegraBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setRootIsDecorated(false); + breakpoint_list->setHeaderHidden(true); + breakpoint_list->setModel(breakpoint_model); + + qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); + + connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, + SLOT(OnItemDoubleClicked(const QModelIndex&))); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnMaxwellResume() { + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() { + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() { + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { + if (!index.isValid()) + return; + + QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); + QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); + QVariant new_state = Qt::Unchecked; + if (enabled == Qt::Unchecked) + new_state = Qt::Checked; + breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); +} diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h new file mode 100644 index 000000000..ae0ede2e8 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h @@ -0,0 +1,46 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDockWidget> +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + +public: + explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnItemDoubleClicked(const QModelIndex&); + void OnResumeRequested(); + void OnResumed(); + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + QLabel* status_text; + QPushButton* resume_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h new file mode 100644 index 000000000..35a6876ae --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h @@ -0,0 +1,36 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QAbstractListModel> +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr<Tegra::DebugContext> context_weak; + bool at_breakpoint; + Tegra::DebugContext::Event active_breakpoint; +}; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..1e4844b57 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,451 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QFileDialog> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QScrollArea> +#include <QSpinBox> +#include "core/core.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/utils.h" +#include "yuzu/debugger/graphics/graphics_surface.h" +#include "yuzu/util/spinbox.h" + +static Tegra::Texture::TextureFormat ConvertToTextureFormat( + Tegra::RenderTargetFormat render_target_format) { + switch (render_target_format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return Tegra::Texture::TextureFormat::A8R8G8B8; + default: + UNIMPLEMENTED_MSG("Unimplemented RT format"); + } +} + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) + : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +void SurfacePicture::mousePressEvent(QMouseEvent* event) { + // Only do something while the left mouse button is held down + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (pixmap() == nullptr) + return; + + if (surface_widget) + surface_widget->Pick(event->x() * pixmap()->width() / width(), + event->y() * pixmap()->height() / height()); +} + +void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { + // We also want to handle the event if the user moves the mouse while holding down the LMB + mousePressEvent(event); +} + +GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), + surface_source(Source::RenderTarget0) { + setObjectName("MaxwellSurface"); + + surface_source_list = new QComboBox; + surface_source_list->addItem(tr("Render Target 0")); + surface_source_list->addItem(tr("Render Target 1")); + surface_source_list->addItem(tr("Render Target 2")); + surface_source_list->addItem(tr("Render Target 3")); + surface_source_list->addItem(tr("Render Target 4")); + surface_source_list->addItem(tr("Render Target 5")); + surface_source_list->addItem(tr("Render Target 6")); + surface_source_list->addItem(tr("Render Target 7")); + surface_source_list->addItem(tr("Z Buffer")); + surface_source_list->addItem(tr("Custom")); + surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); + + surface_address_control = new CSpinBox; + surface_address_control->SetBase(16); + surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); + surface_address_control->SetPrefix("0x"); + + unsigned max_dimension = 16384; // TODO: Find actual maximum + + surface_width_control = new QSpinBox; + surface_width_control->setRange(0, max_dimension); + + surface_height_control = new QSpinBox; + surface_height_control->setRange(0, max_dimension); + + surface_picker_x_control = new QSpinBox; + surface_picker_x_control->setRange(0, max_dimension - 1); + + surface_picker_y_control = new QSpinBox; + surface_picker_y_control->setRange(0, max_dimension - 1); + + surface_format_control = new QComboBox; + + // Color formats sorted by Maxwell texture format index + surface_format_control->addItem(tr("None")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("A8R8G8B8")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("DXT1")); + surface_format_control->addItem(tr("DXT23")); + surface_format_control->addItem(tr("DXT45")); + surface_format_control->addItem(tr("DXN1")); + surface_format_control->addItem(tr("DXN2")); + + surface_info_label = new QLabel(); + surface_info_label->setWordWrap(true); + + surface_picture_label = new SurfacePicture(0, this); + surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + surface_picture_label->setScaledContents(false); + + auto scroll_area = new QScrollArea(); + scroll_area->setBackgroundRole(QPalette::Dark); + scroll_area->setWidgetResizable(false); + scroll_area->setWidget(surface_picture_label); + + save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceSourceChanged(int))); + connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, + SLOT(OnSurfaceAddressChanged(qint64))); + connect(surface_width_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceWidthChanged(int))); + connect(surface_height_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceHeightChanged(int))); + connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceFormatChanged(int))); + connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerXChanged(int))); + connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerYChanged(int))); + connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(surface_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("GPU Address:"))); + sub_layout->addWidget(surface_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(surface_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(surface_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(surface_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(scroll_area); + + auto info_layout = new QHBoxLayout; + { + auto xy_layout = new QVBoxLayout; + { + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("X:"))); + sub_layout->addWidget(surface_picker_x_control); + xy_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Y:"))); + sub_layout->addWidget(surface_picker_y_control); + xy_layout->addLayout(sub_layout); + } + } + info_layout->addLayout(xy_layout); + surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + info_layout->addWidget(surface_info_label); + } + main_layout->addLayout(info_layout); + + main_layout->addWidget(save_surface); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + if (debug_context && debug_context->at_breakpoint) { + emit Update(); + widget()->setEnabled(debug_context->at_breakpoint); + } else { + widget()->setEnabled(false); + } +} + +void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsSurfaceWidget::OnResumed() { + widget()->setEnabled(false); +} + +void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { + surface_source = static_cast<Source>(new_value); + emit Update(); +} + +void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { + if (surface_address != new_value) { + surface_address = static_cast<Tegra::GPUVAddr>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { + if (surface_width != static_cast<unsigned>(new_value)) { + surface_width = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { + if (surface_height != static_cast<unsigned>(new_value)) { + surface_height = static_cast<unsigned>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { + if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { + surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); + + surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { + if (surface_picker_x != new_value) { + surface_picker_x = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { + if (surface_picker_y != new_value) { + surface_picker_y = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::Pick(int x, int y) { + surface_picker_x_control->setValue(x); + surface_picker_y_control->setValue(y); + + if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || + y >= static_cast<int>(surface_height)) { + surface_info_label->setText(tr("Pixel out of bounds")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + return; + } + + surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); +} + +void GraphicsSurfaceWidget::OnUpdate() { + auto& gpu = Core::System::GetInstance().GPU(); + + QPixmap pixmap; + + switch (surface_source) { + case Source::RenderTarget0: + case Source::RenderTarget1: + case Source::RenderTarget2: + case Source::RenderTarget3: + case Source::RenderTarget4: + case Source::RenderTarget5: + case Source::RenderTarget6: + case Source::RenderTarget7: { + // TODO: Store a reference to the registers in the debug context instead of accessing them + // directly... + + auto& registers = gpu.Get3DEngine().regs; + auto& rt = registers.rt[static_cast<size_t>(surface_source) - + static_cast<size_t>(Source::RenderTarget0)]; + + surface_address = rt.Address(); + surface_width = rt.width; + surface_height = rt.height; + if (rt.format != Tegra::RenderTargetFormat::NONE) { + surface_format = ConvertToTextureFormat(rt.format); + } + + break; + } + + case Source::Custom: { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast<int>(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); + + if (surface_address == 0) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + auto unswizzled_data = + Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height); + + auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, + surface_width, surface_height); + + surface_picture_label->show(); + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + Math::Vec4<u8> color; + color[0] = texture_data[x + y * surface_width + 0]; + color[1] = texture_data[x + y * surface_width + 1]; + color[2] = texture_data[x + y * surface_width + 2]; + color[3] = texture_data[x + y * surface_width + 3]; + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + pixmap = QPixmap::fromImage(decoded_image); + surface_picture_label->setPixmap(pixmap); + surface_picture_label->resize(pixmap.size()); + + // Update the info with pixel data + surface_picker_x_control->setEnabled(true); + surface_picker_y_control->setEnabled(true); + Pick(surface_picker_x, surface_picker_y); + + // Enable saving the converted pixmap to file + save_surface->setEnabled(true); +} + +void GraphicsSurfaceWidget::SaveSurface() { + QString png_filter = tr("Portable Network Graphic (*.png)"); + QString bin_filter = tr("Binary data (*.bin)"); + + QString selectedFilter; + QString filename = QFileDialog::getSaveFileName( + this, tr("Save Surface"), + QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), + QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + + if (filename.isEmpty()) { + // If the user canceled the dialog, don't save anything. + return; + } + + if (selectedFilter == png_filter) { + const QPixmap* pixmap = surface_picture_label->pixmap(); + ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + if (pixmap) + pixmap->save(&file, "PNG"); + } else if (selectedFilter == bin_filter) { + auto& gpu = Core::System::GetInstance().GPU(); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + const u8* buffer = Memory::GetPointer(address); + ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); + QByteArray data(reinterpret_cast<const char*>(buffer), size); + file.write(data); + } else { + UNREACHABLE_MSG("Unhandled filter selected"); + } +} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h new file mode 100644 index 000000000..6a344bdfc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -0,0 +1,97 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QLabel> +#include <QPushButton> +#include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +class QComboBox; +class QSpinBox; +class CSpinBox; + +class GraphicsSurfaceWidget; + +class SurfacePicture : public QLabel { + Q_OBJECT + +public: + explicit SurfacePicture(QWidget* parent = nullptr, + GraphicsSurfaceWidget* surface_widget = nullptr); + ~SurfacePicture(); + +protected slots: + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + GraphicsSurfaceWidget* surface_widget; +}; + +class GraphicsSurfaceWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + + enum class Source { + RenderTarget0 = 0, + RenderTarget1 = 1, + RenderTarget2 = 2, + RenderTarget3 = 3, + RenderTarget4 = 4, + RenderTarget5 = 5, + RenderTarget6 = 6, + RenderTarget7 = 7, + ZBuffer = 8, + Custom = 9, + }; + +public: + explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, + QWidget* parent = nullptr); + void Pick(int x, int y); + +public slots: + void OnSurfaceSourceChanged(int new_value); + void OnSurfaceAddressChanged(qint64 new_value); + void OnSurfaceWidthChanged(int new_value); + void OnSurfaceHeightChanged(int new_value); + void OnSurfaceFormatChanged(int new_value); + void OnSurfacePickerXChanged(int new_value); + void OnSurfacePickerYChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnResumed() override; + + void SaveSurface(); + +signals: + void Update(); + +private: + QComboBox* surface_source_list; + CSpinBox* surface_address_control; + QSpinBox* surface_width_control; + QSpinBox* surface_height_control; + QComboBox* surface_format_control; + + SurfacePicture* surface_picture_label; + QSpinBox* surface_picker_x_control; + QSpinBox* surface_picker_y_control; + QLabel* surface_info_label; + QPushButton* save_surface; + + Source surface_source; + Tegra::GPUVAddr surface_address; + unsigned surface_width; + unsigned surface_height; + Tegra::Texture::TextureFormat surface_format; + int surface_picker_x = 0; + int surface_picker_y = 0; +}; diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index cc9babe84..8b30e0a85 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -74,7 +74,7 @@ QAction* MicroProfileDialog::toggleViewAction() { toggle_view_action = new QAction(windowTitle(), this); toggle_view_action->setCheckable(true); toggle_view_action->setChecked(isVisible()); - connect(toggle_view_action, SIGNAL(toggled(bool)), SLOT(setVisible(bool))); + connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible); } return toggle_view_action; @@ -107,7 +107,8 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { MicroProfileSetDisplayMode(1); // Timers screen MicroProfileInitUI(); - connect(&update_timer, SIGNAL(timeout()), SLOT(update())); + connect(&update_timer, &QTimer::timeout, this, + static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update)); } void MicroProfileWidget::paintEvent(QPaintEvent* ev) { diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 70471923e..cae2864e5 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -5,6 +5,7 @@ #include "yuzu/debugger/wait_tree.h" #include "yuzu/util/util.h" +#include "core/core.h" #include "core/hle/kernel/condition_variable.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" @@ -50,7 +51,7 @@ std::size_t WaitTreeItem::Row() const { } std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { - const auto& threads = Kernel::GetThreadList(); + const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList(); std::vector<std::unique_ptr<WaitTreeThread>> item_list; item_list.reserve(threads.size()); for (std::size_t i = 0; i < threads.size(); ++i) { @@ -149,8 +150,8 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_READY: status = tr("ready"); break; - case THREADSTATUS_WAIT_ARB: - status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0')); + case THREADSTATUS_WAIT_HLE_EVENT: + status = tr("waiting for HLE return"); break; case THREADSTATUS_WAIT_SLEEP: status = tr("sleeping"); @@ -168,7 +169,7 @@ QString WaitTreeThread::GetText() const { } QString pc_info = tr(" PC = 0x%1 LR = 0x%2") .arg(thread.context.pc, 8, 16, QLatin1Char('0')) - .arg(thread.context.cpu_registers[31], 8, 16, QLatin1Char('0')); + .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0')); return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; } @@ -179,7 +180,7 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkGreen); case THREADSTATUS_READY: return QColor(Qt::GlobalColor::darkBlue); - case THREADSTATUS_WAIT_ARB: + case THREADSTATUS_WAIT_HLE_EVENT: return QColor(Qt::GlobalColor::darkRed); case THREADSTATUS_WAIT_SLEEP: return QColor(Qt::GlobalColor::darkYellow); diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 4034e909b..e538174eb 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -20,7 +20,7 @@ class Mutex; class ConditionVariable; class Thread; class Timer; -} +} // namespace Kernel class WaitTreeThread; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 679c89828..76ced4de4 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -114,8 +114,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { edit_filter->setPlaceholderText(tr("Enter pattern to filter")); edit_filter->installEventFilter(keyReleaseEater); edit_filter->setClearButtonEnabled(true); - connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, - SLOT(onTextChanged(const QString&))); + connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged); label_filter_result = new QLabel; button_filter_close = new QToolButton(this); button_filter_close->setText("X"); @@ -124,7 +123,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { "#000000; font-weight: bold; background: #F0F0F0; }" "QToolButton:hover{ border: none; padding: 0px; color: " "#EEEEEE; font-weight: bold; background: #E81123}"); - connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); + connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked); layout_filter->setSpacing(10); layout_filter->addWidget(label_filter); layout_filter->addWidget(edit_filter); @@ -137,8 +136,8 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { * Checks if all words separated by spaces are contained in another string * This offers a word order insensitive search function * - * @param String that gets checked if it contains all words of the userinput string - * @param String containing all words getting checked + * @param haystack String that gets checked if it contains all words of the userinput string + * @param userinput String containing all words getting checked * @return true if the haystack contains all words of userinput */ bool GameList::containsAllWords(QString haystack, QString userinput) { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 4823a1296..7aff597b7 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -49,7 +49,7 @@ public: QString edit_filter_text_old; protected: - bool eventFilter(QObject* obj, QEvent* event); + bool eventFilter(QObject* obj, QEvent* event) override; }; QHBoxLayout* layout_filter = nullptr; QTreeView* tree_view = nullptr; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 42f026464..61acb38ee 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -5,6 +5,7 @@ #include <map> #include <QKeySequence> #include <QShortcut> +#include <QTreeWidgetItem> #include <QtGlobal> #include "yuzu/hotkeys.h" #include "yuzu/ui_settings.h" diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 31f2825ee..265502c2a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -18,7 +18,6 @@ #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/microprofile.h" -#include "common/platform.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" @@ -26,10 +25,13 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" @@ -69,10 +71,16 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + + debug_context = Tegra::DebugContext::Construct(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); + default_theme_paths = QIcon::themeSearchPaths(); + UpdateUITheme(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -161,6 +169,16 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + + graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); + graphicsSurfaceWidget->hide(); + debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); + waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); @@ -175,7 +193,7 @@ void GMainWindow::InitializeRecentFileMenuActions() { for (int i = 0; i < max_recent_files_item; ++i) { actions_recent_files[i] = new QAction(this); actions_recent_files[i]->setVisible(false); - connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile())); + connect(actions_recent_files[i], &QAction::triggered, this, &GMainWindow::OnMenuRecentFile); ui.menu_recent_files->addAction(actions_recent_files[i]); } @@ -187,13 +205,14 @@ void GMainWindow::InitializeHotkeys() { RegisterHotkey("Main Window", "Load File", QKeySequence::Open); RegisterHotkey("Main Window", "Start Emulation"); RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); - RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence::Cancel, Qt::ApplicationShortcut); + RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), + Qt::ApplicationShortcut); LoadHotkeys(); - connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this, - SLOT(OnMenuLoadFile())); - connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this, - SLOT(OnStartGame())); + connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this, + &GMainWindow::OnMenuLoadFile); + connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this, + &GMainWindow::OnStartGame); connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger); connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously, @@ -245,13 +264,14 @@ void GMainWindow::RestoreUIState() { } void GMainWindow::ConnectWidgetEvents() { - connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString))); - connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, - SLOT(OnGameListOpenSaveFolder(u64))); + connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); + connect(game_list, &GameList::OpenSaveFolderRequested, this, + &GMainWindow::OnGameListOpenSaveFolder); - connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window, - SLOT(OnEmulationStarting(EmuThread*))); - connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping())); + connect(this, &GMainWindow::EmulationStarting, render_window, + &GRenderWindow::OnEmulationStarting); + connect(this, &GMainWindow::EmulationStopping, render_window, + &GRenderWindow::OnEmulationStopping); connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } @@ -323,6 +343,8 @@ bool GMainWindow::LoadROM(const QString& filename) { Core::System& system{Core::System::GetInstance()}; + system.SetGPUDebugContext(debug_context); + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); @@ -398,17 +420,17 @@ void GMainWindow::BootGame(const QString& filename) { render_window->moveContext(); emu_thread->start(); - connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); + connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, - SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget, - SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget, + &RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget, + &WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget, + &RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); + connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget, + &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection); // Update the GUI registersWidget->OnDebugModeEntered(); @@ -437,7 +459,7 @@ void GMainWindow::ShutdownGame() { emu_thread = nullptr; // The emulation is stopped, so closing the window or not does not matter anymore - disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); + disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); // Update the GUI ui.action_Start->setEnabled(false); @@ -548,8 +570,7 @@ void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); - connect(emu_thread.get(), SIGNAL(ErrorThrown(Core::System::ResultStatus, std::string)), this, - SLOT(OnCoreError(Core::System::ResultStatus, std::string))); + connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); ui.action_Start->setText(tr("Continue")); @@ -635,6 +656,7 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); + UpdateUITheme(); config->Save(); } } @@ -815,6 +837,32 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +void GMainWindow::UpdateUITheme() { + QStringList theme_paths(default_theme_paths); + if (UISettings::values.theme != UISettings::themes[0].second && + !UISettings::values.theme.isEmpty()) { + QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + QFile f(theme_uri); + if (!f.exists()) { + LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); + } else { + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + GMainWindow::setStyleSheet(ts.readAll()); + } + theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); + QIcon::setThemeName(":/icons/" + UISettings::values.theme); + } else { + qApp->setStyleSheet(""); + GMainWindow::setStyleSheet(""); + theme_paths.append(QStringList{":/icons/default"}); + QIcon::setThemeName(":/icons/default"); + } + QIcon::setThemeSearchPaths(theme_paths); + emit UpdateThemedIcons(); +} + #ifdef main #undef main #endif diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..20ff65314 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -2,8 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#ifndef _CITRA_QT_MAIN_HXX_ -#define _CITRA_QT_MAIN_HXX_ +#pragma once #include <memory> #include <QMainWindow> @@ -15,17 +14,18 @@ class Config; class EmuThread; class GameList; class GImageInfo; -class GPUCommandStreamWidget; -class GPUCommandListWidget; class GraphicsBreakPointsWidget; -class GraphicsTracingWidget; -class GraphicsVertexShaderWidget; +class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; class RegistersWidget; class WaitTreeWidget; +namespace Tegra { +class DebugContext; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -64,6 +64,9 @@ signals: */ void EmulationStopping(); + // Signal that tells widgets to update icons to use the current theme + void UpdateThemedIcons(); + private: void InitializeWidgets(); void InitializeDebugWidgets(); @@ -138,6 +141,8 @@ private: Ui::MainWindow ui; + std::shared_ptr<Tegra::DebugContext> debug_context; + GRenderWindow* render_window; GameList* game_list; @@ -150,7 +155,7 @@ private: std::unique_ptr<Config> config; - // Whether emulation is currently running in Citra. + // Whether emulation is currently running in yuzu. bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; @@ -158,14 +163,17 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; + // stores default icon theme search paths for the platform + QStringList default_theme_paths; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; }; - -#endif // _CITRA_QT_MAIN_HXX_ diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index d093da641..8e215a002 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,6 +15,10 @@ namespace UISettings { using ContextualShortcut = std::pair<QString, int>; using Shortcut = std::pair<QString, ContextualShortcut>; +static const std::array<std::pair<QString, QString>, 2> themes = { + {std::make_pair(QString("Default"), QString("default")), + std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; + struct Values { QByteArray geometry; QByteArray state; @@ -50,4 +54,4 @@ struct Values { }; extern Values values; -} +} // namespace UISettings diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp index 92753ec1c..14ef1e884 100644 --- a/src/yuzu/util/spinbox.cpp +++ b/src/yuzu/util/spinbox.cpp @@ -39,7 +39,7 @@ CSpinBox::CSpinBox(QWidget* parent) // TODO: Might be nice to not immediately call the slot. // Think of an address that is being replaced by a different one, in which case a lot // invalid intermediate addresses would be read from during editing. - connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished())); + connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished); UpdateText(); } diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 433e210b0..297dab653 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -1,21 +1,18 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) -set(SRCS - emu_window/emu_window_sdl2.cpp - config.cpp - yuzu.cpp - yuzu.rc - ) -set(HEADERS - emu_window/emu_window_sdl2.h - config.h - default_ini.h - resource.h - ) +add_executable(yuzu-cmd + config.cpp + config.h + default_ini.h + emu_window/emu_window_sdl2.cpp + emu_window/emu_window_sdl2.h + resource.h + yuzu.cpp + yuzu.rc +) -create_directory_groups(${SRCS} ${HEADERS}) +create_target_directory_groups(yuzu-cmd) -add_executable(yuzu-cmd ${SRCS} ${HEADERS}) target_link_libraries(yuzu-cmd PRIVATE common core input_common) target_link_libraries(yuzu-cmd PRIVATE inih glad) if (MSVC) diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index e5be72213..8b479bc6d 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -49,10 +49,18 @@ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{ { - SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D, + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_D, }, { - SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D, + SDL_SCANCODE_I, + SDL_SCANCODE_K, + SDL_SCANCODE_J, + SDL_SCANCODE_L, + SDL_SCANCODE_D, }, }}; @@ -82,8 +90,7 @@ void Config::ReadValues() { sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); // Core - Settings::values.cpu_core = - static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 0)); + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); // Renderer Settings::values.resolution_factor = @@ -99,6 +106,9 @@ void Config::ReadValues() { Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + // System + Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true); + // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 5a2f539b1..cf157bf27 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -76,9 +76,9 @@ motion_device= touch_device= [Core] -# Which CPU core to use for CPU emulation -# 0 (default): Unicorn (slow), 1: Dynarmic (faster) -cpu_core = +# Whether to use the Just-In-Time (JIT) compiler for CPU emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_cpu_jit = [Renderer] # Whether to use software or hardware rendering. @@ -124,8 +124,8 @@ custom_bottom_top = custom_bottom_right = custom_bottom_bottom = -#Whether to toggle frame limiter on or off. -# 0: Off , 1 (default): On +# Whether to toggle frame limiter on or off. +# 0: Off, 1 (default): On toggle_framelimit = # Swaps the prominent screen with the other screen. @@ -154,9 +154,9 @@ output_device = use_virtual_sd = [System] -# The system model that Citra will try to emulate -# 0: Old 3DS (default), 1: New 3DS -is_new_3ds = +# Whether the system is docked +# 1 (default): Yes, 0: No +use_docked_mode = # The system region that Citra will use during emulation # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc index 7cb8a14e1..7de8ef3d9 100644 --- a/src/yuzu_cmd/yuzu.rc +++ b/src/yuzu_cmd/yuzu.rc @@ -6,7 +6,7 @@ // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -CITRA_ICON ICON "../../dist/yuzu.ico" +YUZU_ICON ICON "../../dist/yuzu.ico" ///////////////////////////////////////////////////////////////////////////// |