diff options
Diffstat (limited to '')
153 files changed, 5614 insertions, 3546 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 32cfa8580..ab18275d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,90 +104,18 @@ endif() message(STATUS "Target architecture: ${ARCHITECTURE}") -# Configure compilation flags +# Configure C++ standard # =========================== set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if (NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - - if (MINGW) - add_definitions(-DMINGW_HAS_SECURE_API) - - if (MINGW_STATIC_BUILD) - add_definitions(-DQT_STATICPLUGIN) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - endif() - endif() -else() - # Silence "deprecation" warnings - add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS) - # Avoid windows.h junk - add_definitions(/DNOMINMAX) - # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. - add_definitions(/DWIN32_LEAN_AND_MEAN) - - set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) - - # Tweak optimization settings - # As far as I can tell, there's no way to override the CMake defaults while leaving user - # changes intact, so we'll just clobber everything and say sorry. - message(STATUS "Cache compiler flags ignored, please edit CMakeLists.txt to change the flags.") - - # /W3 - Level 3 warnings - # /MP - Multi-threaded compilation - # /Zi - Output debugging information - # /Zo - enhanced debug info for optimized builds - # /permissive- - enables stricter C++ standards conformance checks - set(CMAKE_C_FLAGS "/W3 /MP /Zi /Zo /permissive-" CACHE STRING "" FORCE) - # /EHsc - C++-only exception handling semantics - # /Zc:throwingNew - let codegen assume `operator new` will never return null - # /Zc:inline - let codegen omit inline functions in object files - set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} /EHsc /std:c++latest /Zc:throwingNew,inline" CACHE STRING "" FORCE) - - # /MDd - Multi-threaded Debug Runtime DLL - set(CMAKE_C_FLAGS_DEBUG "/Od /MDd" CACHE STRING "" FORCE) - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" CACHE STRING "" FORCE) - - # /O2 - Optimization level 2 - # /GS- - No stack buffer overflow checks - # /MD - Multi-threaded runtime DLL - set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE) - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE) - - set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) -endif() - -# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic) -# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1 -if (CMAKE_COMPILER_IS_GNUCC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching") -endif() - -# Set file offset size to 64 bits. -# -# On modern Unixes, this is typically already the case. The lone exception is -# glibc, which may default to 32 bits. glibc allows this to be configured -# by setting _FILE_OFFSET_BITS. -if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) - add_definitions(-D_FILE_OFFSET_BITS=64) -endif() - -# CMake seems to only define _DEBUG on Windows -set_property(DIRECTORY APPEND PROPERTY - COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) - # System imported libraries # ====================== -find_package(Boost 1.63.0 QUIET) +find_package(Boost 1.66.0 QUIET) if (NOT Boost_FOUND) - message(STATUS "Boost 1.63.0 or newer not found, falling back to externals") + message(STATUS "Boost 1.66.0 or newer not found, falling back to externals") set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost") set(Boost_NO_SYSTEM_PATHS OFF) @@ -332,25 +260,21 @@ endif() # Platform-specific library requirements # ====================================== -IF (APPLE) - find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related +if (APPLE) + # Umbrella framework for everything GUI-related + find_library(COCOA_LIBRARY Cocoa) set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) - - if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") - endif() -ELSEIF (WIN32) +elseif (WIN32) # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) set(PLATFORM_LIBRARIES winmm ws2_32) - IF (MINGW) + if (MINGW) # PSAPI is the Process Status API set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) - ENDIF (MINGW) -ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") + endif() +elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") set(PLATFORM_LIBRARIES rt) -ENDIF (APPLE) +endif() # Setup a custom clang-format target (if clang-format can be found) that will run # against all the src files. This should be used before making a pull request. diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake index 78728e08b..08315a1f1 100644 --- a/CMakeModules/GenerateSCMRev.cmake +++ b/CMakeModules/GenerateSCMRev.cmake @@ -73,6 +73,7 @@ set(HASH_FILES "${VIDEO_CORE}/shader/decode/integer_set.cpp" "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/memory.cpp" + "${VIDEO_CORE}/shader/decode/texture.cpp" "${VIDEO_CORE}/shader/decode/other.cpp" "${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/predicate_set_register.cpp" diff --git a/externals/opus b/externals/opus -Subproject b2871922a12abb49579512d604cabc471a59ad9 +Subproject 562f8ba555c4181e1b57e82e496e4a959b9c019 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f69d00a2b..6c99dd5e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,18 +1,79 @@ # Enable modules to include each other's files include_directories(.) +# CMake seems to only define _DEBUG on Windows +set_property(DIRECTORY APPEND PROPERTY + COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) + +# Set compilation flags +if (MSVC) + set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) + + # Silence "deprecation" warnings + add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS) + + # Avoid windows.h junk + add_definitions(-DNOMINMAX) + + # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. + add_definitions(-DWIN32_LEAN_AND_MEAN) + + # /W3 - Level 3 warnings + # /MP - Multi-threaded compilation + # /Zi - Output debugging information + # /Zo - enhanced debug info for optimized builds + # /permissive- - enables stricter C++ standards conformance checks + # /EHsc - C++-only exception handling semantics + # /Zc:throwingNew - let codegen assume `operator new` will never return null + # /Zc:inline - let codegen omit inline functions in object files + add_compile_options(/W3 /MP /Zi /Zo /permissive- /EHsc /std:c++latest /Zc:throwingNew,inline) + + # /GS- - No stack buffer overflow checks + add_compile_options("$<$<CONFIG:Release>:/GS->") + + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) +else() + add_compile_options("-Wno-attributes") + + if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) + add_compile_options("-stdlib=libc++") + endif() + + # Set file offset size to 64 bits. + # + # On modern Unixes, this is typically already the case. The lone exception is + # glibc, which may default to 32 bits. glibc allows this to be configured + # by setting _FILE_OFFSET_BITS. + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) + add_definitions(-D_FILE_OFFSET_BITS=64) + endif() + + if (MINGW) + add_definitions(-DMINGW_HAS_SECURE_API) + + if (MINGW_STATIC_BUILD) + add_definitions(-DQT_STATICPLUGIN) + add_compile_options("-static") + endif() + endif() +endif() + add_subdirectory(common) add_subdirectory(core) add_subdirectory(audio_core) add_subdirectory(video_core) add_subdirectory(input_common) add_subdirectory(tests) + if (ENABLE_SDL2) add_subdirectory(yuzu_cmd) endif() + if (ENABLE_QT) add_subdirectory(yuzu) endif() + if (ENABLE_WEB_SERVICE) add_subdirectory(web_service) endif() diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 1da0b9f2a..7047ed9cf 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -12,7 +12,7 @@ #include "common/ring_buffer.h" #include "core/settings.h" -#ifdef _MSC_VER +#ifdef _WIN32 #include <objbase.h> #endif @@ -113,7 +113,7 @@ private: CubebSink::CubebSink(std::string_view target_device_name) { // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows -#ifdef _MSC_VER +#ifdef _WIN32 com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); #endif @@ -152,7 +152,7 @@ CubebSink::~CubebSink() { cubeb_destroy(ctx); -#ifdef _MSC_VER +#ifdef _WIN32 if (SUCCEEDED(com_init_result)) { CoUninitialize(); } diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 511df7bb1..7ce850f47 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -26,7 +26,7 @@ private: cubeb_devid output_device{}; std::vector<SinkStreamPtr> sink_streams; -#ifdef _MSC_VER +#ifdef _WIN32 u32 com_init_result = 0; #endif }; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bdd885273..43ae8a9e7 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp "${VIDEO_CORE}/shader/decode/integer_set.cpp" "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/memory.cpp" + "${VIDEO_CORE}/shader/decode/texture.cpp" "${VIDEO_CORE}/shader/decode/other.cpp" "${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/predicate_set_register.cpp" @@ -91,10 +92,14 @@ add_library(common STATIC logging/text_formatter.cpp logging/text_formatter.h math_util.h + memory_hook.cpp + memory_hook.h microprofile.cpp microprofile.h microprofileui.h misc.cpp + page_table.cpp + page_table.h param_package.cpp param_package.h quaternion.h @@ -113,6 +118,8 @@ add_library(common STATIC threadsafe_queue.h timer.cpp timer.h + uint128.cpp + uint128.h vector_math.h web_result.h ) diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 7433c39ba..902e668e3 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -34,6 +34,7 @@ #include <limits> #include <type_traits> #include "common/common_funcs.h" +#include "common/swap.h" /* * Abstract bitfield class @@ -108,7 +109,7 @@ * symptoms. */ #pragma pack(1) -template <std::size_t Position, std::size_t Bits, typename T> +template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag> struct BitField { private: // UnderlyingType is T for non-enum types and the underlying type of T if @@ -121,6 +122,8 @@ private: // We store the value as the unsigned type to avoid undefined behaviour on value shifting using StorageType = std::make_unsigned_t<UnderlyingType>; + using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type; + public: /// Constants to allow limited introspection of fields if needed static constexpr std::size_t position = Position; @@ -170,7 +173,7 @@ public: } constexpr FORCE_INLINE void Assign(const T& value) { - storage = (storage & ~mask) | FormatValue(value); + storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value); } constexpr T Value() const { @@ -182,7 +185,7 @@ public: } private: - StorageType storage; + StorageTypeWithEndian storage; static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range"); @@ -193,3 +196,6 @@ private: static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField"); }; #pragma pack() + +template <std::size_t Position, std::size_t Bits, typename T> +using BitFieldBE = BitField<Position, Bits, T, BETag>; diff --git a/src/common/common_types.h b/src/common/common_types.h index 6b1766dca..4cec89fbd 100644 --- a/src/common/common_types.h +++ b/src/common/common_types.h @@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int using f32 = float; ///< 32-bit floating point using f64 = double; ///< 64-bit floating point -// TODO: It would be nice to eventually replace these with strong types that prevent accidental -// conversion between each other. -using VAddr = u64; ///< Represents a pointer in the userspace virtual address space. -using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space. +using VAddr = u64; ///< Represents a pointer in the userspace virtual address space. +using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space. +using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space. using u128 = std::array<std::uint64_t, 2>; static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide"); diff --git a/src/core/memory_hook.cpp b/src/common/memory_hook.cpp index c61c6c1fb..3986986d6 100644 --- a/src/core/memory_hook.cpp +++ b/src/common/memory_hook.cpp @@ -2,10 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/memory_hook.h" +#include "common/memory_hook.h" -namespace Memory { +namespace Common { MemoryHook::~MemoryHook() = default; -} // namespace Memory +} // namespace Common diff --git a/src/core/memory_hook.h b/src/common/memory_hook.h index 940777107..adaa4c2c5 100644 --- a/src/core/memory_hook.h +++ b/src/common/memory_hook.h @@ -9,7 +9,7 @@ #include "common/common_types.h" -namespace Memory { +namespace Common { /** * Memory hooks have two purposes: @@ -44,4 +44,4 @@ public: }; using MemoryHookPointer = std::shared_ptr<MemoryHook>; -} // namespace Memory +} // namespace Common diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp new file mode 100644 index 000000000..69b7abc54 --- /dev/null +++ b/src/common/page_table.cpp @@ -0,0 +1,31 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/page_table.h" + +namespace Common { + +PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {} + +PageTable::~PageTable() = default; + +void PageTable::Resize(std::size_t address_space_width_in_bits) { + const std::size_t num_page_table_entries = 1ULL + << (address_space_width_in_bits - page_size_in_bits); + + pointers.resize(num_page_table_entries); + attributes.resize(num_page_table_entries); + backing_addr.resize(num_page_table_entries); + + // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the + // vector size is subsequently decreased (via resize), the vector might not automatically + // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for + // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use. + + pointers.shrink_to_fit(); + attributes.shrink_to_fit(); + backing_addr.shrink_to_fit(); +} + +} // namespace Common diff --git a/src/common/page_table.h b/src/common/page_table.h new file mode 100644 index 000000000..8b8ff0bb8 --- /dev/null +++ b/src/common/page_table.h @@ -0,0 +1,84 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <boost/icl/interval_map.hpp> +#include "common/common_types.h" +#include "common/memory_hook.h" + +namespace Common { + +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. + Memory, + /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and + /// invalidation + RasterizerCachedMemory, + /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. + Special, + /// Page is allocated for use. + Allocated, +}; + +struct SpecialRegion { + 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. + */ +struct PageTable { + explicit PageTable(std::size_t page_size_in_bits); + ~PageTable(); + + /** + * Resizes the page table to be able to accomodate enough pages within + * a given address space. + * + * @param address_space_width_in_bits The address size width in bits. + */ + void Resize(std::size_t address_space_width_in_bits); + + /** + * Vector of memory pointers backing each page. An entry can only be non-null if the + * corresponding entry in the `attributes` vector is of type `Memory`. + */ + std::vector<u8*> pointers; + + /** + * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is + * of type `Special`. + */ + boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions; + + /** + * Vector 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::vector<PageType> attributes; + + std::vector<u64> backing_addr; + + const std::size_t page_size_in_bits{}; +}; + +} // namespace Common diff --git a/src/common/swap.h b/src/common/swap.h index 0e219747f..b3eab1324 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -17,6 +17,8 @@ #pragma once +#include <type_traits> + #if defined(_MSC_VER) #include <cstdlib> #elif defined(__linux__) @@ -170,7 +172,7 @@ struct swap_struct_t { using swapped_t = swap_struct_t; protected: - T value = T(); + T value; static T swap(T v) { return F::swap(v); @@ -605,52 +607,154 @@ struct swap_double_t { } }; -#if COMMON_LITTLE_ENDIAN -using u16_le = u16; -using u32_le = u32; -using u64_le = u64; +template <typename T> +struct swap_enum_t { + static_assert(std::is_enum_v<T>); + using base = std::underlying_type_t<T>; + +public: + swap_enum_t() = default; + swap_enum_t(const T& v) : value(swap(v)) {} + + swap_enum_t& operator=(const T& v) { + value = swap(v); + return *this; + } + + operator T() const { + return swap(value); + } + + explicit operator base() const { + return static_cast<base>(swap(value)); + } -using s16_le = s16; -using s32_le = s32; -using s64_le = s64; +protected: + T value{}; + // clang-format off + using swap_t = std::conditional_t< + std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t< + std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t< + std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t< + std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t< + std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t< + std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>; + // clang-format on + static T swap(T x) { + return static_cast<T>(swap_t::swap(static_cast<base>(x))); + } +}; -using float_le = float; -using double_le = double; +struct SwapTag {}; // Use the different endianness from the system +struct KeepTag {}; // Use the same endianness as the system -using u64_be = swap_struct_t<u64, swap_64_t<u64>>; -using s64_be = swap_struct_t<s64, swap_64_t<s64>>; +template <typename T, typename Tag> +struct AddEndian; -using u32_be = swap_struct_t<u32, swap_32_t<u32>>; -using s32_be = swap_struct_t<s32, swap_32_t<s32>>; +// KeepTag specializations -using u16_be = swap_struct_t<u16, swap_16_t<u16>>; -using s16_be = swap_struct_t<s16, swap_16_t<s16>>; +template <typename T> +struct AddEndian<T, KeepTag> { + using type = T; +}; -using float_be = swap_struct_t<float, swap_float_t<float>>; -using double_be = swap_struct_t<double, swap_double_t<double>>; -#else +// SwapTag specializations + +template <> +struct AddEndian<u8, SwapTag> { + using type = u8; +}; + +template <> +struct AddEndian<u16, SwapTag> { + using type = swap_struct_t<u16, swap_16_t<u16>>; +}; + +template <> +struct AddEndian<u32, SwapTag> { + using type = swap_struct_t<u32, swap_32_t<u32>>; +}; -using u64_le = swap_struct_t<u64, swap_64_t<u64>>; -using s64_le = swap_struct_t<s64, swap_64_t<s64>>; +template <> +struct AddEndian<u64, SwapTag> { + using type = swap_struct_t<u64, swap_64_t<u64>>; +}; + +template <> +struct AddEndian<s8, SwapTag> { + using type = s8; +}; -using u32_le = swap_struct_t<u32, swap_32_t<u32>>; -using s32_le = swap_struct_t<s32, swap_32_t<s32>>; +template <> +struct AddEndian<s16, SwapTag> { + using type = swap_struct_t<s16, swap_16_t<s16>>; +}; -using u16_le = swap_struct_t<u16, swap_16_t<u16>>; -using s16_le = swap_struct_t<s16, swap_16_t<s16>>; +template <> +struct AddEndian<s32, SwapTag> { + using type = swap_struct_t<s32, swap_32_t<s32>>; +}; + +template <> +struct AddEndian<s64, SwapTag> { + using type = swap_struct_t<s64, swap_64_t<s64>>; +}; + +template <> +struct AddEndian<float, SwapTag> { + using type = swap_struct_t<float, swap_float_t<float>>; +}; + +template <> +struct AddEndian<double, SwapTag> { + using type = swap_struct_t<double, swap_double_t<double>>; +}; + +template <typename T> +struct AddEndian<T, SwapTag> { + static_assert(std::is_enum_v<T>); + using type = swap_enum_t<T>; +}; -using float_le = swap_struct_t<float, swap_float_t<float>>; -using double_le = swap_struct_t<double, swap_double_t<double>>; +// Alias LETag/BETag as KeepTag/SwapTag depending on the system +#if COMMON_LITTLE_ENDIAN -using u16_be = u16; -using u32_be = u32; -using u64_be = u64; +using LETag = KeepTag; +using BETag = SwapTag; -using s16_be = s16; -using s32_be = s32; -using s64_be = s64; +#else -using float_be = float; -using double_be = double; +using BETag = KeepTag; +using LETag = SwapTag; #endif + +// Aliases for LE types +using u16_le = AddEndian<u16, LETag>::type; +using u32_le = AddEndian<u32, LETag>::type; +using u64_le = AddEndian<u64, LETag>::type; + +using s16_le = AddEndian<s16, LETag>::type; +using s32_le = AddEndian<s32, LETag>::type; +using s64_le = AddEndian<s64, LETag>::type; + +template <typename T> +using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>; + +using float_le = AddEndian<float, LETag>::type; +using double_le = AddEndian<double, LETag>::type; + +// Aliases for BE types +using u16_be = AddEndian<u16, BETag>::type; +using u32_be = AddEndian<u32, BETag>::type; +using u64_be = AddEndian<u64, BETag>::type; + +using s16_be = AddEndian<s16, BETag>::type; +using s32_be = AddEndian<s32, BETag>::type; +using s64_be = AddEndian<s64, BETag>::type; + +template <typename T> +using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>; + +using float_be = AddEndian<float, BETag>::type; +using double_be = AddEndian<double, BETag>::type; diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index e7594db68..791f99a8c 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -6,7 +6,6 @@ #include <array> #include <deque> -#include <boost/range/algorithm_ext/erase.hpp> namespace Common { @@ -111,8 +110,9 @@ struct ThreadQueueList { } void remove(Priority priority, const T& thread_id) { - Queue* cur = &queues[priority]; - boost::remove_erase(cur->data, thread_id); + Queue* const cur = &queues[priority]; + const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id); + cur->data.erase(iter, cur->data.end()); } void rotate(Priority priority) { diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp new file mode 100644 index 000000000..32bf56730 --- /dev/null +++ b/src/common/uint128.cpp @@ -0,0 +1,45 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef _MSC_VER +#include <intrin.h> + +#pragma intrinsic(_umul128) +#endif +#include <cstring> +#include "common/uint128.h" + +namespace Common { + +u128 Multiply64Into128(u64 a, u64 b) { + u128 result; +#ifdef _MSC_VER + result[0] = _umul128(a, b, &result[1]); +#else + unsigned __int128 tmp = a; + tmp *= b; + std::memcpy(&result, &tmp, sizeof(u128)); +#endif + return result; +} + +std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) { + u64 remainder = dividend[0] % divisor; + u64 accum = dividend[0] / divisor; + if (dividend[1] == 0) + return {accum, remainder}; + // We ignore dividend[1] / divisor as that overflows + const u64 first_segment = (dividend[1] % divisor) << 32; + accum += (first_segment / divisor) << 32; + const u64 second_segment = (first_segment % divisor) << 32; + accum += (second_segment / divisor); + remainder += second_segment % divisor; + if (remainder >= divisor) { + accum++; + remainder -= divisor; + } + return {accum, remainder}; +} + +} // namespace Common diff --git a/src/common/uint128.h b/src/common/uint128.h new file mode 100644 index 000000000..a3be2a2cb --- /dev/null +++ b/src/common/uint128.h @@ -0,0 +1,19 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <utility> +#include "common/common_types.h" + +namespace Common { + +// This function multiplies 2 u64 values and produces a u128 value; +u128 Multiply64Into128(u64 a, u64 b); + +// This function divides a u128 by a u32 value and produces two u64 values: +// the result of division and the remainder +std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor); + +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3f855dcb7..f156bca40 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,6 +31,8 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h + file_sys/cheat_engine.cpp + file_sys/cheat_engine.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -109,6 +111,8 @@ add_library(core STATIC hle/kernel/client_port.h hle/kernel/client_session.cpp hle/kernel/client_session.h + hle/kernel/code_set.cpp + hle/kernel/code_set.h hle/kernel/errors.h hle/kernel/handle_table.cpp hle/kernel/handle_table.h @@ -421,8 +425,6 @@ add_library(core STATIC loader/deconstructed_rom_directory.h loader/elf.cpp loader/elf.h - loader/linker.cpp - loader/linker.h loader/loader.cpp loader/loader.h loader/nax.cpp @@ -439,8 +441,6 @@ add_library(core STATIC loader/xci.h memory.cpp memory.h - memory_hook.cpp - memory_hook.h memory_setup.h perf_stats.cpp perf_stats.h diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 9b7ca4030..4fdc12f11 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -12,6 +12,7 @@ #include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" +#include "core/core_timing_util.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/svc.h" @@ -119,7 +120,7 @@ public: return std::max(parent.core_timing.GetDowncount(), 0); } u64 GetCNTPCT() override { - return parent.core_timing.GetTicks(); + return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks()); } ARM_Dynarmic& parent; @@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { config.tpidr_el0 = &cb->tpidr_el0; config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; - config.cntfrq_el0 = 19200000; // Value from fusee. + config.cntfrq_el0 = Timing::CNTFREQ; // Unpredictable instructions config.define_unpredictable_behaviour = true; diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 6cc458296..aada1e862 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -12,7 +12,7 @@ #include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" -namespace Memory { +namespace Common { struct PageTable; } @@ -70,7 +70,7 @@ private: Timing::CoreTiming& core_timing; DynarmicExclusiveMonitor& exclusive_monitor; - Memory::PageTable* current_page_table = nullptr; + Common::PageTable* current_page_table = nullptr; }; class DynarmicExclusiveMonitor final : public ExclusiveMonitor { diff --git a/src/core/core.cpp b/src/core/core.cpp index eba2177d1..4fe77c25b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -32,6 +32,7 @@ #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "file_sys/cheat_engine.h" #include "frontend/applets/profile_select.h" #include "frontend/applets/software_keyboard.h" #include "frontend/applets/web_browser.h" @@ -116,7 +117,7 @@ struct System::Impl { if (web_browser == nullptr) web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); - auto main_process = Kernel::Process::Create(kernel, "main"); + auto main_process = Kernel::Process::Create(system, "main"); kernel.MakeCurrentProcess(main_process.get()); telemetry_session = std::make_unique<Core::TelemetrySession>(); @@ -205,6 +206,7 @@ struct System::Impl { GDBStub::Shutdown(); Service::Shutdown(); service_manager.reset(); + cheat_engine.reset(); telemetry_session.reset(); gpu_core.reset(); @@ -255,6 +257,8 @@ struct System::Impl { CpuCoreManager cpu_core_manager; bool is_powered_on = false; + std::unique_ptr<FileSys::CheatEngine> cheat_engine; + /// Frontend applets std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; @@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { return impl->debug_context.get(); } +void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, + const std::string& build_id, VAddr code_region_start, + VAddr code_region_end) { + impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id, + code_region_start, code_region_end); +} + void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { impl->virtual_filesystem = std::move(vfs); } diff --git a/src/core/core.h b/src/core/core.h index ba76a41d8..4d83b93cc 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -20,6 +20,7 @@ class WebBrowserApplet; } // namespace Core::Frontend namespace FileSys { +class CheatList; class VfsFilesystem; } // namespace FileSys @@ -253,6 +254,9 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, + VAddr code_region_start, VAddr code_region_end); + void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); const Frontend::ProfileSelectApplet& GetProfileSelector() const; diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp index 54aa21a3a..1eefed6d0 100644 --- a/src/core/core_cpu.cpp +++ b/src/core/core_cpu.cpp @@ -11,6 +11,7 @@ #endif #include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" +#include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" #include "core/hle/kernel/scheduler.h" @@ -49,9 +50,9 @@ bool CpuBarrier::Rendezvous() { return false; } -Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor, - CpuBarrier& cpu_barrier, std::size_t core_index) - : cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} { +Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, + std::size_t core_index) + : cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} { if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index); @@ -63,7 +64,7 @@ Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor, arm_interface = std::make_unique<ARM_Unicorn>(core_timing); } - scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface); + scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface); } Cpu::~Cpu() = default; diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h index e2204c6b0..7589beb8c 100644 --- a/src/core/core_cpu.h +++ b/src/core/core_cpu.h @@ -15,6 +15,10 @@ namespace Kernel { class Scheduler; } +namespace Core { +class System; +} + namespace Core::Timing { class CoreTiming; } @@ -45,8 +49,8 @@ private: class Cpu { public: - Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor, - CpuBarrier& cpu_barrier, std::size_t core_index); + Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, + std::size_t core_index); ~Cpu(); void RunLoop(bool tight_loop = true); diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp index 88ff70233..7942f30d6 100644 --- a/src/core/core_timing_util.cpp +++ b/src/core/core_timing_util.cpp @@ -7,6 +7,7 @@ #include <cinttypes> #include <limits> #include "common/logging/log.h" +#include "common/uint128.h" namespace Core::Timing { @@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) { return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000; } +u64 CpuCyclesToClockCycles(u64 ticks) { + const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ); + return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first; +} + } // namespace Core::Timing diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h index 513cfac1b..679aa3123 100644 --- a/src/core/core_timing_util.h +++ b/src/core/core_timing_util.h @@ -11,6 +11,7 @@ namespace Core::Timing { // The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz // The exact value used is of course unverified. constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked +constexpr u64 CNTFREQ = 19200000; // Value from fusee. inline s64 msToCycles(int ms) { // since ms is int there is no way to overflow @@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) { return cycles * 1000 / BASE_CLOCK_RATE; } +u64 CpuCyclesToClockCycles(u64 ticks); + } // namespace Core::Timing diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp index 2ddb3610d..93bc5619c 100644 --- a/src/core/cpu_core_manager.cpp +++ b/src/core/cpu_core_manager.cpp @@ -27,8 +27,7 @@ void CpuCoreManager::Initialize(System& system) { exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size()); for (std::size_t index = 0; index < cores.size(); ++index) { - cores[index] = - std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index); + cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index); } // Create threads for CPU cores 1-3, and build thread_to_cpu map diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp new file mode 100644 index 000000000..247fbc864 --- /dev/null +++ b/src/core/file_sys/cheat_engine.cpp @@ -0,0 +1,490 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <locale> +#include "common/hex_util.h" +#include "common/microprofile.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/file_sys/cheat_engine.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace FileSys { + +constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); +constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; + +u64 Cheat::Address() const { + u64 out; + std::memcpy(&out, raw.data(), sizeof(u64)); + return Common::swap64(out) & 0xFFFFFFFFFF; +} + +u64 Cheat::ValueWidth(u64 offset) const { + return Value(offset, width); +} + +u64 Cheat::Value(u64 offset, u64 width) const { + u64 out; + std::memcpy(&out, raw.data() + offset, sizeof(u64)); + out = Common::swap64(out); + if (width == 8) + return out; + return out & ((1ull << (width * CHAR_BIT)) - 1); +} + +u32 Cheat::KeypadValue() const { + u32 out; + std::memcpy(&out, raw.data(), sizeof(u32)); + return Common::swap32(out) & 0x0FFFFFFF; +} + +void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, + VAddr heap_end, MemoryWriter writer, MemoryReader reader) { + this->main_region_begin = main_begin; + this->main_region_end = main_end; + this->heap_region_begin = heap_begin; + this->heap_region_end = heap_end; + this->writer = writer; + this->reader = reader; +} + +MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); + +void CheatList::Execute() { + MICROPROFILE_SCOPE(Cheat_Engine); + + std::fill(scratch.begin(), scratch.end(), 0); + in_standard = false; + for (std::size_t i = 0; i < master_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); + current_block = i; + ExecuteBlock(master_list[i].second); + } + + in_standard = true; + for (std::size_t i = 0; i < standard_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); + current_block = i; + ExecuteBlock(standard_list[i].second); + } +} + +CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) + : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} + +bool CheatList::EvaluateConditional(const Cheat& cheat) const { + using ComparisonFunction = bool (*)(u64, u64); + constexpr std::array<ComparisonFunction, 6> comparison_functions{ + [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, + [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, + [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, + }; + + if (cheat.type == CodeType::ConditionalInput) { + const auto applet_resource = + system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); + if (applet_resource == nullptr) { + LOG_WARNING( + Common_Filesystem, + "Attempted to evaluate input conditional, but applet resource is not initialized!"); + return false; + } + + const auto press_state = + applet_resource + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) + .GetAndResetPressState(); + return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; + } + + ASSERT(cheat.type == CodeType::Conditional); + + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); + auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; + const auto addr = cheat.Address() + offset; + + return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); +} + +void CheatList::ProcessBlockPairs(const Block& block) { + block_pairs.clear(); + + u64 scope = 0; + std::map<u64, u64> pairs; + + for (std::size_t i = 0; i < block.size(); ++i) { + const auto& cheat = block[i]; + + switch (cheat.type) { + case CodeType::Conditional: + case CodeType::ConditionalInput: + pairs.insert_or_assign(scope, i); + ++scope; + break; + case CodeType::EndConditional: { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + break; + } + case CodeType::Loop: { + if (cheat.end_of_loop) { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + } else { + pairs.insert_or_assign(scope, i); + ++scope; + } + break; + } + } + } +} + +void CheatList::WriteImmediate(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + const auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = cheat.Address() + offset + register_3; + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, + cheat.Value(8, cheat.width)); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); +} + +void CheatList::BeginConditional(const Cheat& cheat) { + if (EvaluateConditional(cheat)) { + return; + } + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; +} + +void CheatList::EndConditional(const Cheat& cheat) { + LOG_DEBUG(Common_Filesystem, "Ending conditional block."); +} + +void CheatList::Loop(const Cheat& cheat) { + if (cheat.end_of_loop.Value()) + ASSERT(!cheat.end_of_loop.Value()); + + auto& register_3 = scratch.at(cheat.register_3); + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + ASSERT(iter->first < iter->second); + + const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); + for (s32 i = initial_value; i >= 0; --i) { + register_3 = static_cast<u64>(i); + for (std::size_t c = iter->first + 1; c < iter->second; ++c) { + current_index = c; + ExecuteSingleCheat( + (in_standard ? standard_list : master_list)[current_block].second[c]); + } + } + + current_index = iter->second; +} + +void CheatList::LoadImmediate(const Cheat& cheat) { + auto& register_3 = scratch.at(cheat.register_3); + + LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, + cheat.Value(4, 8)); + register_3 = cheat.Value(4, 8); +} + +void CheatList::LoadIndexed(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); + LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", + cheat.register_3, addr); + register_3 = reader(cheat.width, SanitizeAddress(addr)); +} + +void CheatList::StoreIndexed(const Cheat& cheat) { + const auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = + register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", + cheat.Value(4, cheat.width), addr); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); +} + +void CheatList::RegisterArithmetic(const Cheat& cheat) { + using ArithmeticFunction = u64 (*)(u64, u64); + constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ + [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, + [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, + [](u64 a, u64 b) { return a >> b; }, + }; + + using ArithmeticOverflowCheck = bool (*)(u64, u64); + constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b + [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b + [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b + [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b + }; + + static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), + "Missing or have extra arithmetic overflow checks compared to functions!"); + + auto& register_3 = scratch.at(cheat.register_3); + + ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); + auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; + auto* overflow_function = + arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; + LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", + cheat.register_3, cheat.ValueWidth(4)); + + if (overflow_function(register_3, cheat.ValueWidth(4))) { + LOG_WARNING(Common_Filesystem, + "overflow will occur when performing arithmetic operation={:02X} with operands " + "a={:016X}, b={:016X}!", + static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); + } + + register_3 = function(register_3, cheat.ValueWidth(4)); +} + +void CheatList::BeginConditionalInput(const Cheat& cheat) { + if (EvaluateConditional(cheat)) + return; + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; +} + +VAddr CheatList::SanitizeAddress(VAddr in) const { + if ((in < main_region_begin || in >= main_region_end) && + (in < heap_region_begin || in >= heap_region_end)) { + LOG_ERROR(Common_Filesystem, + "Cheat attempting to access memory at invalid address={:016X}, if this persists, " + "the cheat may be incorrect. However, this may be normal early in execution if " + "the game has not properly set up yet.", + in); + return 0; ///< Invalid addresses will hard crash + } + + return in; +} + +void CheatList::ExecuteSingleCheat(const Cheat& cheat) { + using CheatOperationFunction = void (CheatList::*)(const Cheat&); + constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ + &CheatList::WriteImmediate, &CheatList::BeginConditional, + &CheatList::EndConditional, &CheatList::Loop, + &CheatList::LoadImmediate, &CheatList::LoadIndexed, + &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, + &CheatList::BeginConditionalInput, + }; + + const auto index = static_cast<u8>(cheat.type.Value()); + ASSERT(index < sizeof(cheat_operation_functions)); + const auto op = cheat_operation_functions[index]; + (this->*op)(cheat); +} + +void CheatList::ExecuteBlock(const Block& block) { + encountered_loops.clear(); + + ProcessBlockPairs(block); + for (std::size_t i = 0; i < block.size(); ++i) { + current_index = i; + ExecuteSingleCheat(block[i]); + i = current_index; + } +} + +CheatParser::~CheatParser() = default; + +CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const { + return {system, std::move(master), std::move(standard)}; +} + +TextCheatParser::~TextCheatParser() = default; + +CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { + std::stringstream ss; + ss.write(reinterpret_cast<const char*>(data.data()), data.size()); + + std::vector<std::string> lines; + std::string stream_line; + while (std::getline(ss, stream_line)) { + // Remove a trailing \r + if (!stream_line.empty() && stream_line.back() == '\r') + stream_line.pop_back(); + lines.push_back(std::move(stream_line)); + } + + CheatList::ProgramSegment master_list; + CheatList::ProgramSegment standard_list; + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + const auto master = line[0] == '{'; + const auto begin = master ? line.find('{') : line.find('['); + const auto end = master ? line.rfind('}') : line.rfind(']'); + + ASSERT(begin != std::string::npos && end != std::string::npos); + + const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; + CheatList::Block block{}; + + while (i < lines.size() - 1) { + line = lines[++i]; + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + --i; + break; + } + + if (line.size() < 8) + continue; + + Cheat out{}; + out.raw = ParseSingleLineCheat(line); + block.push_back(out); + } + + (master ? master_list : standard_list).emplace_back(patch_name, block); + } + } + + return MakeCheatList(system, master_list, standard_list); +} + +std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { + std::array<u8, 16> out{}; + + if (line.size() < 8) + return out; + + const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); + std::memcpy(out.data(), word1.data(), sizeof(u32)); + + if (line.size() < 17 || line[8] != ' ') + return out; + + const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); + std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); + + if (line.size() < 26 || line[17] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); + if (type == CodeType::Loop || type == CodeType::LoadImmediate || + type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { + std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); + std::memset(out.data() + 4, 0, sizeof(u32)); + } + + return out; + } + + const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); + std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); + + if (line.size() < 35 || line[26] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); + if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { + std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); + std::memset(out.data() + 8, 0, sizeof(u32)); + } + + return out; + } + + const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); + std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); + + return out; +} + +u64 MemoryReadImpl(u32 width, VAddr addr) { + switch (width) { + case 1: + return Memory::Read8(addr); + case 2: + return Memory::Read16(addr); + case 4: + return Memory::Read32(addr); + case 8: + return Memory::Read64(addr); + default: + UNREACHABLE(); + return 0; + } +} + +void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { + switch (width) { + case 1: + Memory::Write8(addr, static_cast<u8>(value)); + break; + case 2: + Memory::Write16(addr, static_cast<u16>(value)); + break; + case 4: + Memory::Write32(addr, static_cast<u32>(value)); + break; + case 8: + Memory::Write64(addr, value); + break; + default: + UNREACHABLE(); + } +} + +CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, + const std::string& build_id, VAddr code_region_start, + VAddr code_region_end) + : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { + event = core_timing.RegisterEvent( + "CheatEngine::FrameCallback::" + build_id, + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); + + const auto& vm_manager = system.CurrentProcess()->VMManager(); + for (auto& list : this->cheats) { + list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), + code_region_end, vm_manager.GetHeapRegionEndAddress(), + &MemoryWriteImpl, &MemoryReadImpl); + } +} + +CheatEngine::~CheatEngine() { + core_timing.UnscheduleEvent(event, 0); +} + +void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { + for (auto& list : cheats) { + list.Execute(); + } + + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); +} + +} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h new file mode 100644 index 000000000..ac22a82cb --- /dev/null +++ b/src/core/file_sys/cheat_engine.h @@ -0,0 +1,234 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <set> +#include <vector> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Core { +class System; +} + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace FileSys { + +enum class CodeType : u32 { + // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Writes a T sized value Y to the address A added to the value of register R in memory domain M + WriteImmediate = 0, + + // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Compares the T sized value Y to the value at address A in memory domain M using the + // conditional function C. If success, continues execution. If failure, jumps to the matching + // EndConditional statement. + Conditional = 1, + + // 20000000 + // Terminates a Conditional or ConditionalInput block. + EndConditional = 2, + + // 300R0000 VVVVVVVV + // Starts looping V times, storing the current count in register R. + // Loop block is terminated with a matching 310R0000. + Loop = 3, + + // 400R0000 VVVVVVVV VVVVVVVV + // Sets the value of register R to the value V. + LoadImmediate = 4, + + // 5TMRI0AA AAAAAAAA + // Sets the value of register R to the value of width T at address A in memory domain M, with + // the current value of R added to the address if I == 1. + LoadIndexed = 5, + + // 6T0RIFG0 VVVVVVVV VVVVVVVV + // Writes the value V of width T to the memory address stored in register R. Adds the value of + // register G to the final calculation if F is nonzero. Increments the value of register R by T + // after operation if I is nonzero. + StoreIndexed = 6, + + // 7T0RA000 VVVVVVVV + // Performs the arithmetic operation A on the value in register R and the value V of width T, + // storing the result in register R. + RegisterArithmetic = 7, + + // 8KKKKKKK + // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, + // execution continues. If none are, execution skips to the next EndConditional command. + ConditionalInput = 8, +}; + +enum class MemoryType : u32 { + // Addressed relative to start of main NSO + MainNSO = 0, + + // Addressed relative to start of heap + Heap = 1, +}; + +enum class ArithmeticOp : u32 { + Add = 0, + Sub = 1, + Mult = 2, + LShift = 3, + RShift = 4, +}; + +enum class ComparisonOp : u32 { + GreaterThan = 1, + GreaterThanEqual = 2, + LessThan = 3, + LessThanEqual = 4, + Equal = 5, + Inequal = 6, +}; + +union Cheat { + std::array<u8, 16> raw; + + BitField<4, 4, CodeType> type; + BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. + BitField<0, 4, u32> end_of_loop; + BitField<12, 4, MemoryType> memory_type; + BitField<8, 4, u32> register_3; + BitField<8, 4, ComparisonOp> comparison_op; + BitField<20, 4, u32> load_from_register; + BitField<20, 4, u32> increment_register; + BitField<20, 4, ArithmeticOp> arithmetic_op; + BitField<16, 4, u32> add_additional_register; + BitField<28, 4, u32> register_6; + + u64 Address() const; + u64 ValueWidth(u64 offset) const; + u64 Value(u64 offset, u64 width) const; + u32 KeypadValue() const; +}; + +class CheatParser; + +// Represents a full collection of cheats for a game. The Execute function should be called every +// interval that all cheats should be executed. Clients should not directly instantiate this class +// (hence private constructor), they should instead receive an instance from CheatParser, which +// guarantees the list is always in an acceptable state. +class CheatList { +public: + friend class CheatParser; + + using Block = std::vector<Cheat>; + using ProgramSegment = std::vector<std::pair<std::string, Block>>; + + // (width in bytes, address, value) + using MemoryWriter = void (*)(u32, VAddr, u64); + // (width in bytes, address) -> value + using MemoryReader = u64 (*)(u32, VAddr); + + void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, + MemoryWriter writer, MemoryReader reader); + + void Execute(); + +private: + CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); + + void ProcessBlockPairs(const Block& block); + void ExecuteSingleCheat(const Cheat& cheat); + + void ExecuteBlock(const Block& block); + + bool EvaluateConditional(const Cheat& cheat) const; + + // Individual cheat operations + void WriteImmediate(const Cheat& cheat); + void BeginConditional(const Cheat& cheat); + void EndConditional(const Cheat& cheat); + void Loop(const Cheat& cheat); + void LoadImmediate(const Cheat& cheat); + void LoadIndexed(const Cheat& cheat); + void StoreIndexed(const Cheat& cheat); + void RegisterArithmetic(const Cheat& cheat); + void BeginConditionalInput(const Cheat& cheat); + + VAddr SanitizeAddress(VAddr in) const; + + // Master Codes are defined as codes that cannot be disabled and are run prior to all + // others. + ProgramSegment master_list; + // All other codes + ProgramSegment standard_list; + + bool in_standard = false; + + // 16 (0x0-0xF) scratch registers that can be used by cheats + std::array<u64, 16> scratch{}; + + MemoryWriter writer = nullptr; + MemoryReader reader = nullptr; + + u64 main_region_begin{}; + u64 heap_region_begin{}; + u64 main_region_end{}; + u64 heap_region_end{}; + + u64 current_block{}; + // The current index of the cheat within the current Block + u64 current_index{}; + + // The 'stack' of the program. When a conditional or loop statement is encountered, its index is + // pushed onto this queue. When a end block is encountered, the condition is checked. + std::map<u64, u64> block_pairs; + + std::set<u64> encountered_loops; + + const Core::System* system; +}; + +// Intermediary class that parses a text file or other disk format for storing cheats into a +// CheatList object, that can be used for execution. +class CheatParser { +public: + virtual ~CheatParser(); + + virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; + +protected: + CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const; +}; + +// CheatParser implementation that parses text files +class TextCheatParser final : public CheatParser { +public: + ~TextCheatParser() override; + + CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; + +private: + std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; +}; + +// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming +class CheatEngine final { +public: + CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, + VAddr code_region_start, VAddr code_region_end); + ~CheatEngine(); + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + + std::vector<CheatList> cheats; + + Core::Timing::EventType* event; + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 5d4d05c82..15b9e6624 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -24,13 +24,26 @@ namespace FileSys { union NCASectionHeader; +/// Describes the type of content within an NCA archive. enum class NCAContentType : u8 { + /// Executable-related data Program = 0, + + /// Metadata. Meta = 1, + + /// Access control data. Control = 2, + + /// Information related to the game manual + /// e.g. Legal information, etc. Manual = 3, + + /// System data. Data = 4, - Data_Unknown5 = 5, ///< Seems to be used on some system archives + + /// Data that can be accessed by applications. + PublicData = 5, }; enum class NCASectionCryptoType : u8 { diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 61706966e..e11217708 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -7,6 +7,7 @@ #include <cstddef> #include <cstring> +#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "core/file_sys/content_archive.h" @@ -19,6 +20,7 @@ #include "core/file_sys/vfs_vector.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#include "core/loader/nso.h" #include "core/settings.h" namespace FileSys { @@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", }; -struct NSOBuildHeader { - u32_le magic; - INSERT_PADDING_BYTES(0x3C); - std::array<u8, 0x20> build_id; - INSERT_PADDING_BYTES(0xA0); -}; -static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size."); - std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { std::array<u8, sizeof(u32)> bytes{}; bytes[0] = version % SINGLE_BYTE_MODULUS; @@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD } std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { - if (nso.size() < 0x100) + if (nso.size() < sizeof(Loader::NSOHeader)) { return nso; + } - NSOBuildHeader header; - std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader)); + Loader::NSOHeader header; + std::memcpy(&header, nso.data(), sizeof(header)); - if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { return nso; + } const auto build_id_raw = Common::HexArrayToString(header.build_id); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); @@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { } } - if (out.size() < 0x100) + if (out.size() < sizeof(Loader::NSOHeader)) { return nso; - std::memcpy(out.data(), &header, sizeof(NSOBuildHeader)); + } + + std::memcpy(out.data(), &header, sizeof(header)); return out; } @@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } +static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, + const std::array<u8, 0x20>& build_id_, + const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexArrayToString(build_id_, upper); + const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); + const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); + + if (file == nullptr) { + LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + std::vector<u8> data(file->GetSize()); + if (file->Read(data.data(), data.size()) != data.size()) { + LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + TextCheatParser parser; + return parser.Parse(system, data); +} + +std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, + const std::array<u8, 32>& build_id_) const { + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + auto patch_dirs = load_dir->GetSubdirectories(); + std::sort(patch_dirs.begin(), patch_dirs.end(), + [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + + std::vector<CheatList> out; + out.reserve(patch_dirs.size()); + for (const auto& subdir : patch_dirs) { + auto cheats_dir = subdir->GetSubdirectory("cheats"); + if (cheats_dir != nullptr) { + auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); + if (res.has_value()) { + out.push_back(std::move(*res)); + continue; + } + + res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); + if (res.has_value()) + out.push_back(std::move(*res)); + } + } + + return out; +} + static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || @@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); + if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats"))) + AppendCommaIfNotEmpty(types, "Cheats"); if (types.empty()) continue; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index b8a1652fd..de2672c76 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,14 @@ #include <memory> #include <string> #include "common/common_types.h" +#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" +namespace Core { +class System; +} + namespace FileSys { class NCA; @@ -45,6 +50,10 @@ public: // Used to prevent expensive copies in NSO loader. bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; + // Creates a CheatList object with all + std::vector<CheatList> CreateCheatList(const Core::System& system, + const std::array<u8, 0x20>& build_id) const; + // Currently tracked RomFS patches: // - Game Updates // - LayeredFS diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 128199063..1c6bacace 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { case NCAContentType::Control: return ContentRecordType::Control; case NCAContentType::Data: - case NCAContentType::Data_Unknown5: + case NCAContentType::PublicData: return ContentRecordType::Data; case NCAContentType::Manual: // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 455d1f346..fae54bcc7 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -39,10 +39,10 @@ struct CommandHeader { union { u32_le raw_low; BitField<0, 16, CommandType> type; - BitField<16, 4, u32_le> num_buf_x_descriptors; - BitField<20, 4, u32_le> num_buf_a_descriptors; - BitField<24, 4, u32_le> num_buf_b_descriptors; - BitField<28, 4, u32_le> num_buf_w_descriptors; + BitField<16, 4, u32> num_buf_x_descriptors; + BitField<20, 4, u32> num_buf_a_descriptors; + BitField<24, 4, u32> num_buf_b_descriptors; + BitField<28, 4, u32> num_buf_w_descriptors; }; enum class BufferDescriptorCFlag : u32 { @@ -53,28 +53,28 @@ struct CommandHeader { union { u32_le raw_high; - BitField<0, 10, u32_le> data_size; + BitField<0, 10, u32> data_size; BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags; - BitField<31, 1, u32_le> enable_handle_descriptor; + BitField<31, 1, u32> enable_handle_descriptor; }; }; static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect"); union HandleDescriptorHeader { u32_le raw_high; - BitField<0, 1, u32_le> send_current_pid; - BitField<1, 4, u32_le> num_handles_to_copy; - BitField<5, 4, u32_le> num_handles_to_move; + BitField<0, 1, u32> send_current_pid; + BitField<1, 4, u32> num_handles_to_copy; + BitField<5, 4, u32> num_handles_to_move; }; static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect"); struct BufferDescriptorX { union { - BitField<0, 6, u32_le> counter_bits_0_5; - BitField<6, 3, u32_le> address_bits_36_38; - BitField<9, 3, u32_le> counter_bits_9_11; - BitField<12, 4, u32_le> address_bits_32_35; - BitField<16, 16, u32_le> size; + BitField<0, 6, u32> counter_bits_0_5; + BitField<6, 3, u32> address_bits_36_38; + BitField<9, 3, u32> counter_bits_9_11; + BitField<12, 4, u32> address_bits_32_35; + BitField<16, 16, u32> size; }; u32_le address_bits_0_31; @@ -103,10 +103,10 @@ struct BufferDescriptorABW { u32_le address_bits_0_31; union { - BitField<0, 2, u32_le> flags; - BitField<2, 3, u32_le> address_bits_36_38; - BitField<24, 4, u32_le> size_bits_32_35; - BitField<28, 4, u32_le> address_bits_32_35; + BitField<0, 2, u32> flags; + BitField<2, 3, u32> address_bits_36_38; + BitField<24, 4, u32> size_bits_32_35; + BitField<28, 4, u32> address_bits_32_35; }; VAddr Address() const { @@ -128,8 +128,8 @@ struct BufferDescriptorC { u32_le address_bits_0_31; union { - BitField<0, 16, u32_le> address_bits_32_47; - BitField<16, 16, u32_le> size; + BitField<0, 16, u32> address_bits_32_47; + BitField<16, 16, u32> size; }; VAddr Address() const { @@ -167,8 +167,8 @@ struct DomainMessageHeader { struct { union { BitField<0, 8, CommandType> command; - BitField<8, 8, u32_le> input_object_count; - BitField<16, 16, u32_le> size; + BitField<8, 8, u32> input_object_count; + BitField<16, 16, u32> size; }; u32_le object_id; INSERT_PADDING_WORDS(2); diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 079283830..68406eb63 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -19,9 +19,12 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/result.h" namespace IPC { +constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301}; + class RequestHelperBase { protected: Kernel::HLERequestContext* context = nullptr; @@ -272,6 +275,20 @@ inline void ResponseBuilder::Push(u64 value) { } template <> +inline void ResponseBuilder::Push(float value) { + u32 integral; + std::memcpy(&integral, &value, sizeof(u32)); + Push(integral); +} + +template <> +inline void ResponseBuilder::Push(double value) { + u64 integral; + std::memcpy(&integral, &value, sizeof(u64)); + Push(integral); +} + +template <> inline void ResponseBuilder::Push(bool value) { Push(static_cast<u8>(value)); } @@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() { return cmdbuf[index++]; } +template <> +inline s32 RequestParser::Pop() { + return static_cast<s32>(Pop<u32>()); +} + template <typename T> void RequestParser::PopRaw(T& value) { std::memcpy(&value, cmdbuf + index, sizeof(T)); @@ -393,11 +415,37 @@ inline u64 RequestParser::Pop() { } template <> +inline s8 RequestParser::Pop() { + return static_cast<s8>(Pop<u8>()); +} + +template <> +inline s16 RequestParser::Pop() { + return static_cast<s16>(Pop<u16>()); +} + +template <> inline s64 RequestParser::Pop() { return static_cast<s64>(Pop<u64>()); } template <> +inline float RequestParser::Pop() { + const u32 value = Pop<u32>(); + float real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +template <> +inline double RequestParser::Pop() { + const u64 value = Pop<u64>(); + float real; + std::memcpy(&real, &value, sizeof(real)); + return real; +} + +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 index 9780a7849..352190da8 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -42,7 +42,21 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_ AddressArbiter::AddressArbiter(Core::System& system) : system{system} {} AddressArbiter::~AddressArbiter() = default; -ResultCode AddressArbiter::SignalToAddress(VAddr address, s32 num_to_wake) { +ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value, + s32 num_to_wake) { + switch (type) { + case SignalType::Signal: + return SignalToAddressOnly(address, num_to_wake); + case SignalType::IncrementAndSignalIfEqual: + return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake); + case SignalType::ModifyByWaitingCountAndSignalIfEqual: + return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake); + default: + return ERR_INVALID_ENUM_VALUE; + } +} + +ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) { const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address); WakeThreads(waiting_threads, num_to_wake); return RESULT_SUCCESS; @@ -60,7 +74,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 } Memory::Write32(address, static_cast<u32>(value + 1)); - return SignalToAddress(address, num_to_wake); + return SignalToAddressOnly(address, num_to_wake); } ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, @@ -92,6 +106,20 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a return RESULT_SUCCESS; } +ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value, + s64 timeout_ns) { + switch (type) { + case ArbitrationType::WaitIfLessThan: + return WaitForAddressIfLessThan(address, value, timeout_ns, false); + case ArbitrationType::DecrementAndWaitIfLessThan: + return WaitForAddressIfLessThan(address, value, timeout_ns, true); + case ArbitrationType::WaitIfEqual: + return WaitForAddressIfEqual(address, value, timeout_ns); + default: + return ERR_INVALID_ENUM_VALUE; + } +} + ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) { // Ensure that we can read the address. @@ -113,7 +141,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6 return RESULT_TIMEOUT; } - return WaitForAddress(address, timeout); + return WaitForAddressImpl(address, timeout); } ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) { @@ -130,10 +158,10 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t return RESULT_TIMEOUT; } - return WaitForAddress(address, timeout); + return WaitForAddressImpl(address, timeout); } -ResultCode AddressArbiter::WaitForAddress(VAddr address, s64 timeout) { +ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) { SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread(); current_thread->SetArbiterWaitAddress(address); current_thread->SetStatus(ThreadStatus::WaitArb); diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h index e0c36f2e3..ed0d0e69f 100644 --- a/src/core/hle/kernel/address_arbiter.h +++ b/src/core/hle/kernel/address_arbiter.h @@ -4,8 +4,10 @@ #pragma once +#include <vector> + #include "common/common_types.h" -#include "core/hle/kernel/address_arbiter.h" +#include "core/hle/kernel/object.h" union ResultCode; @@ -40,8 +42,15 @@ public: AddressArbiter(AddressArbiter&&) = default; AddressArbiter& operator=(AddressArbiter&&) = delete; + /// Signals an address being waited on with a particular signaling type. + ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake); + + /// Waits on an address with a particular arbitration type. + ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns); + +private: /// Signals an address being waited on. - ResultCode SignalToAddress(VAddr address, s32 num_to_wake); + ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake); /// Signals an address being waited on and increments its value if equal to the value argument. ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake); @@ -59,9 +68,8 @@ public: /// Waits on an address if the value passed is equal to the argument value. ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout); -private: // Waits on the given address with a timeout in nanoseconds - ResultCode WaitForAddress(VAddr address, s64 timeout); + ResultCode WaitForAddressImpl(VAddr address, s64 timeout); // Gets the threads waiting on an address. std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const; diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index d4c91d529..aa432658e 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { // Create a new session pair, let the created sessions inherit the parent port's HLE handler. auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this); - if (server_port->hle_handler) - server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); - else - server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions)); + if (server_port->HasHLEHandler()) { + server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); + } else { + server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions)); + } // Wake the threads waiting on the ServerPort server_port->WakeupAllWaitingThreads(); diff --git a/src/core/hle/kernel/code_set.cpp b/src/core/hle/kernel/code_set.cpp new file mode 100644 index 000000000..1f434e9af --- /dev/null +++ b/src/core/hle/kernel/code_set.cpp @@ -0,0 +1,12 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/code_set.h" + +namespace Kernel { + +CodeSet::CodeSet() = default; +CodeSet::~CodeSet() = default; + +} // namespace Kernel diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h new file mode 100644 index 000000000..834fd23d2 --- /dev/null +++ b/src/core/hle/kernel/code_set.h @@ -0,0 +1,90 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> +#include <vector> + +#include "common/common_types.h" + +namespace Kernel { + +/** + * Represents executable data that may be loaded into a kernel process. + * + * A code set consists of three basic segments: + * - A code (AKA text) segment, + * - A read-only data segment (rodata) + * - A data segment + * + * The code segment is the portion of the object file that contains + * executable instructions. + * + * The read-only data segment in the portion of the object file that + * contains (as one would expect) read-only data, such as fixed constant + * values and data structures. + * + * The data segment is similar to the read-only data segment -- it contains + * variables and data structures that have predefined values, however, + * entities within this segment can be modified. + */ +struct CodeSet final { + /// A single segment within a code set. + struct Segment final { + /// The byte offset that this segment is located at. + std::size_t offset = 0; + + /// The address to map this segment to. + VAddr addr = 0; + + /// The size of this segment in bytes. + u32 size = 0; + }; + + explicit CodeSet(); + ~CodeSet(); + + CodeSet(const CodeSet&) = delete; + CodeSet& operator=(const CodeSet&) = delete; + + CodeSet(CodeSet&&) = default; + CodeSet& operator=(CodeSet&&) = default; + + Segment& CodeSegment() { + return segments[0]; + } + + const Segment& CodeSegment() const { + return segments[0]; + } + + Segment& RODataSegment() { + return segments[1]; + } + + const Segment& RODataSegment() const { + return segments[1]; + } + + Segment& DataSegment() { + return segments[2]; + } + + const Segment& DataSegment() const { + return segments[2]; + } + + /// The overall data that backs this code set. + std::shared_ptr<std::vector<u8>> memory; + + /// The segments that comprise this code set. + std::array<Segment, 3> segments; + + /// The entry point address for this code set. + VAddr entrypoint = 0; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 04ea9349e..4d224d01d 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -87,7 +87,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_ } struct KernelCore::Impl { - explicit Impl(Core::System& system) : address_arbiter{system}, system{system} {} + explicit Impl(Core::System& system) : system{system} {} void Initialize(KernelCore& kernel) { Shutdown(); @@ -138,8 +138,6 @@ struct KernelCore::Impl { std::vector<SharedPtr<Process>> process_list; Process* current_process = nullptr; - Kernel::AddressArbiter address_arbiter; - SharedPtr<ResourceLimit> system_resource_limit; Core::Timing::EventType* thread_wakeup_event_type = nullptr; @@ -192,14 +190,6 @@ const Process* KernelCore::CurrentProcess() const { return impl->current_process; } -AddressArbiter& KernelCore::AddressArbiter() { - return impl->address_arbiter; -} - -const AddressArbiter& KernelCore::AddressArbiter() const { - return impl->address_arbiter; -} - void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) { impl->named_ports.emplace(std::move(name), std::move(port)); } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 4d292aca9..ff17ff865 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -75,12 +75,6 @@ public: /// Retrieves a const pointer to the current process. const Process* CurrentProcess() const; - /// Provides a reference to the kernel's address arbiter. - Kernel::AddressArbiter& AddressArbiter(); - - /// Provides a const reference to the kernel's address arbiter. - const Kernel::AddressArbiter& AddressArbiter() const; - /// Adds a port to the named port table void AddNamedPort(std::string name, SharedPtr<ClientPort> port); diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 0743670ad..98e87313b 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <map> #include <utility> #include <vector> @@ -10,8 +9,11 @@ #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/mutex.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" #include "core/memory.h" @@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t } } -ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle, +Mutex::Mutex(Core::System& system) : system{system} {} +Mutex::~Mutex() = default; + +ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, Handle requesting_thread_handle) { // The mutex address must be 4-byte aligned if ((address % sizeof(u32)) != 0) { return ERR_INVALID_ADDRESS; } + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + Thread* const current_thread = system.CurrentScheduler().GetCurrentThread(); SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle); SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle); // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another // thread. - ASSERT(requesting_thread == GetCurrentThread()); + ASSERT(requesting_thread == current_thread); - u32 addr_value = Memory::Read32(address); + const u32 addr_value = Memory::Read32(address); // If the mutex isn't being held, just return success. if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) { return RESULT_SUCCESS; } - if (holding_thread == nullptr) + if (holding_thread == nullptr) { return ERR_INVALID_HANDLE; + } // Wait until the mutex is released - GetCurrentThread()->SetMutexWaitAddress(address); - GetCurrentThread()->SetWaitHandle(requesting_thread_handle); + current_thread->SetMutexWaitAddress(address); + current_thread->SetWaitHandle(requesting_thread_handle); - GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex); - GetCurrentThread()->InvalidateWakeupCallback(); + current_thread->SetStatus(ThreadStatus::WaitMutex); + current_thread->InvalidateWakeupCallback(); // Update the lock holder thread's priority to prevent priority inversion. - holding_thread->AddMutexWaiter(GetCurrentThread()); + holding_thread->AddMutexWaiter(current_thread); - Core::System::GetInstance().PrepareReschedule(); + system.PrepareReschedule(); return RESULT_SUCCESS; } @@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) { return ERR_INVALID_ADDRESS; } - auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); + auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); + auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address); // There are no more threads waiting for the mutex, release it completely. if (thread == nullptr) { @@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) { } // Transfer the ownership of the mutex from the previous owner to the new one. - TransferMutexOwnership(address, GetCurrentThread(), thread); + TransferMutexOwnership(address, current_thread, thread); u32 mutex_value = thread->GetWaitHandle(); diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 81e62d497..b904de2e8 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -5,32 +5,34 @@ #pragma once #include "common/common_types.h" -#include "core/hle/kernel/object.h" union ResultCode; -namespace Kernel { +namespace Core { +class System; +} -class HandleTable; -class Thread; +namespace Kernel { class Mutex final { public: + explicit Mutex(Core::System& system); + ~Mutex(); + /// Flag that indicates that a mutex still has threads waiting for it. static constexpr u32 MutexHasWaitersFlag = 0x40000000; /// Mask of the bits in a mutex address value that contain the mutex owner. static constexpr u32 MutexOwnerMask = 0xBFFFFFFF; /// Attempts to acquire a mutex at the specified address. - static ResultCode TryAcquire(HandleTable& handle_table, VAddr address, - Handle holding_thread_handle, Handle requesting_thread_handle); + ResultCode TryAcquire(VAddr address, Handle holding_thread_handle, + Handle requesting_thread_handle); /// Releases the mutex at the specified address. - static ResultCode Release(VAddr address); + ResultCode Release(VAddr address); private: - Mutex() = default; - ~Mutex() = default; + Core::System& system; }; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 8009150e0..0d782e4ba 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -31,7 +32,7 @@ namespace { */ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) { // Setup page table so we can write to memory - SetCurrentPageTable(&owner_process.VMManager().page_table); + Memory::SetCurrentPageTable(&owner_process.VMManager().page_table); // Initialize new "main" thread const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); @@ -50,12 +51,10 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi } } // Anonymous namespace -CodeSet::CodeSet() = default; -CodeSet::~CodeSet() = default; - -SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { - SharedPtr<Process> process(new Process(kernel)); +SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) { + auto& kernel = system.Kernel(); + SharedPtr<Process> process(new Process(system)); process->name = std::move(name); process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; @@ -132,7 +131,7 @@ void Process::PrepareForTermination() { if (thread->GetOwnerProcess() != this) continue; - if (thread == GetCurrentThread()) + if (thread == system.CurrentScheduler().GetCurrentThread()) continue; // TODO(Subv): When are the other running/ready threads terminated? @@ -144,7 +143,6 @@ void Process::PrepareForTermination() { } }; - const auto& system = Core::System::GetInstance(); stop_threads(system.Scheduler(0).GetThreadList()); stop_threads(system.Scheduler(1).GetThreadList()); stop_threads(system.Scheduler(2).GetThreadList()); @@ -212,7 +210,7 @@ void Process::FreeTLSSlot(VAddr tls_address) { } void Process::LoadModule(CodeSet module_, VAddr base_addr) { - const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, + const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { const auto vma = vm_manager .MapMemoryBlock(segment.addr + base_addr, module_.memory, @@ -222,19 +220,18 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) { }; // Map CodeSet segments - MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic); - MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable); - MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable); + MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code); + MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData); + MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData); // Clear instruction cache in CPU JIT - Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + system.InvalidateCpuInstructionCaches(); } -Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {} -Kernel::Process::~Process() {} +Process::Process(Core::System& system) + : WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {} + +Process::~Process() = default; void Process::Acquire(Thread* thread) { ASSERT_MSG(!ShouldWait(thread), "Object unavailable!"); diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index dcc57ae9f..1bd7bf5c1 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -7,17 +7,22 @@ #include <array> #include <bitset> #include <cstddef> -#include <memory> #include <string> #include <vector> #include <boost/container/static_vector.hpp> #include "common/common_types.h" +#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process_capability.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/kernel/wait_object.h" #include "core/hle/result.h" +namespace Core { +class System; +} + namespace FileSys { class ProgramMetadata; } @@ -28,6 +33,8 @@ class KernelCore; class ResourceLimit; class Thread; +struct CodeSet; + struct AddressMapping { // Address and size must be page-aligned VAddr address; @@ -60,46 +67,6 @@ enum class ProcessStatus { DebugBreak, }; -struct CodeSet final { - struct Segment { - std::size_t offset = 0; - VAddr addr = 0; - u32 size = 0; - }; - - explicit CodeSet(); - ~CodeSet(); - - Segment& CodeSegment() { - return segments[0]; - } - - const Segment& CodeSegment() const { - return segments[0]; - } - - Segment& RODataSegment() { - return segments[1]; - } - - const Segment& RODataSegment() const { - return segments[1]; - } - - Segment& DataSegment() { - return segments[2]; - } - - const Segment& DataSegment() const { - return segments[2]; - } - - std::shared_ptr<std::vector<u8>> memory; - - std::array<Segment, 3> segments; - VAddr entrypoint = 0; -}; - class Process final : public WaitObject { public: enum : u64 { @@ -116,7 +83,7 @@ public: static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; - static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name); + static SharedPtr<Process> Create(Core::System& system, std::string&& name); std::string GetTypeName() const override { return "Process"; @@ -150,6 +117,26 @@ public: return handle_table; } + /// Gets a reference to the process' address arbiter. + AddressArbiter& GetAddressArbiter() { + return address_arbiter; + } + + /// Gets a const reference to the process' address arbiter. + const AddressArbiter& GetAddressArbiter() const { + return address_arbiter; + } + + /// Gets a reference to the process' mutex lock. + Mutex& GetMutex() { + return mutex; + } + + /// Gets a const reference to the process' mutex lock + const Mutex& GetMutex() const { + return mutex; + } + /// Gets the current status of the process ProcessStatus GetStatus() const { return status; @@ -251,7 +238,7 @@ public: void FreeTLSSlot(VAddr tls_address); private: - explicit Process(KernelCore& kernel); + explicit Process(Core::System& system); ~Process() override; /// Checks if the specified thread should wait until this process is available. @@ -309,9 +296,21 @@ private: /// Per-process handle table for storing created object handles in. HandleTable handle_table; + /// Per-process address arbiter. + AddressArbiter address_arbiter; + + /// The per-process mutex lock instance used for handling various + /// forms of services, such as lock arbitration, and condition + /// variable related facilities. + Mutex mutex; + /// Random values for svcGetInfo RandomEntropy std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy; + /// System context + Core::System& system; + + /// Name of this process std::string name; }; diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 44f30d070..cc189cc64 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -19,7 +19,8 @@ namespace Kernel { std::mutex Scheduler::scheduler_mutex; -Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {} +Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core) + : cpu_core{cpu_core}, system{system} {} Scheduler::~Scheduler() { for (auto& thread : thread_list) { @@ -61,7 +62,7 @@ Thread* Scheduler::PopNextReadyThread() { void Scheduler::SwitchContext(Thread* new_thread) { Thread* const previous_thread = GetCurrentThread(); - Process* const previous_process = Core::CurrentProcess(); + Process* const previous_process = system.Kernel().CurrentProcess(); UpdateLastContextSwitchTime(previous_thread, previous_process); @@ -94,8 +95,8 @@ void Scheduler::SwitchContext(Thread* new_thread) { auto* const thread_owner_process = current_thread->GetOwnerProcess(); if (previous_process != thread_owner_process) { - Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process); - SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); + system.Kernel().MakeCurrentProcess(thread_owner_process); + Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table); } cpu_core.LoadContext(new_thread->GetContext()); @@ -111,7 +112,7 @@ void Scheduler::SwitchContext(Thread* new_thread) { void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = Core::System::GetInstance().CoreTiming().GetTicks(); + const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks(); const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; if (thread != nullptr) { @@ -198,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) { ASSERT(thread->GetPriority() < THREADPRIO_COUNT); // Yield this thread -- sleep for zero time and force reschedule to different thread - WaitCurrentThread_Sleep(); - GetCurrentThread()->WakeAfterDelay(0); + GetCurrentThread()->Sleep(0); } void Scheduler::YieldWithLoadBalancing(Thread* thread) { @@ -214,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) { ASSERT(priority < THREADPRIO_COUNT); // Sleep for zero time to be able to force reschedule to different thread - WaitCurrentThread_Sleep(); - GetCurrentThread()->WakeAfterDelay(0); + GetCurrentThread()->Sleep(0); Thread* suggested_thread = nullptr; @@ -223,8 +222,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) { // Take the first non-nullptr one for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) { const auto res = - Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread( - core, priority); + system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority); // If scheduler provides a suggested thread if (res != nullptr) { diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index 97ced4dfc..1c5bf57d9 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -13,7 +13,8 @@ namespace Core { class ARM_Interface; -} +class System; +} // namespace Core namespace Kernel { @@ -21,7 +22,7 @@ class Process; class Scheduler final { public: - explicit Scheduler(Core::ARM_Interface& cpu_core); + explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core); ~Scheduler(); /// Returns whether there are any threads that are ready to run. @@ -162,6 +163,7 @@ private: Core::ARM_Interface& cpu_core; u64 last_context_switch_time = 0; + Core::System& system; static std::mutex scheduler_mutex; }; diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index d6ceeb2da..0e1515c89 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -26,6 +26,10 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() { return MakeResult(std::move(session)); } +void ServerPort::AppendPendingSession(SharedPtr<ServerSession> pending_session) { + pending_sessions.push_back(std::move(pending_session)); +} + bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.empty(); diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index e52f8245f..9bc667cf2 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -22,6 +22,8 @@ class SessionRequestHandler; class ServerPort final : public WaitObject { public: + using HLEHandler = std::shared_ptr<SessionRequestHandler>; + /** * Creates a pair of ServerPort and an associated ClientPort. * @@ -51,22 +53,27 @@ public: */ ResultVal<SharedPtr<ServerSession>> Accept(); + /// Whether or not this server port has an HLE handler available. + bool HasHLEHandler() const { + return hle_handler != nullptr; + } + + /// Gets the HLE handler for this port. + HLEHandler GetHLEHandler() const { + return hle_handler; + } + /** * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port * will inherit a reference to this handler. */ - void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { + void SetHleHandler(HLEHandler hle_handler_) { hle_handler = std::move(hle_handler_); } - std::string name; ///< Name of port (optional) - - /// ServerSessions waiting to be accepted by the port - std::vector<SharedPtr<ServerSession>> pending_sessions; - - /// This session's HLE request handler template (optional) - /// ServerSessions created from this port inherit a reference to this handler. - std::shared_ptr<SessionRequestHandler> hle_handler; + /// Appends a ServerSession to the collection of ServerSessions + /// waiting to be accepted by this port. + void AppendPendingSession(SharedPtr<ServerSession> pending_session); bool ShouldWait(Thread* thread) const override; void Acquire(Thread* thread) override; @@ -74,6 +81,16 @@ public: private: explicit ServerPort(KernelCore& kernel); ~ServerPort() override; + + /// ServerSessions waiting to be accepted by the port + std::vector<SharedPtr<ServerSession>> pending_sessions; + + /// This session's HLE request handler template (optional) + /// ServerSessions created from this port inherit a reference to this handler. + HLEHandler hle_handler; + + /// Name of the port (optional) + std::string name; }; } // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 7f5c0cc86..a6a17efe7 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -551,9 +551,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, return ERR_INVALID_ADDRESS; } - auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle, - requesting_thread_handle); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle, + requesting_thread_handle); } /// Unlock a mutex @@ -571,7 +571,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return ERR_INVALID_ADDRESS; } - return Mutex::Release(mutex_addr); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + return current_process->GetMutex().Release(mutex_addr); } enum class BreakType : u32 { @@ -1284,10 +1285,14 @@ static ResultCode StartThread(Handle thread_handle) { /// Called when a thread exits static void ExitThread() { - LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC()); + auto& system = Core::System::GetInstance(); - ExitCurrentThread(); - Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); + + auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); + current_thread->Stop(); + system.CurrentScheduler().RemoveThread(current_thread); + system.PrepareReschedule(); } /// Sleep the current thread @@ -1300,32 +1305,32 @@ static void SleepThread(s64 nanoseconds) { YieldAndWaitForLoadBalancing = -2, }; + auto& system = Core::System::GetInstance(); + auto& scheduler = system.CurrentScheduler(); + auto* const current_thread = scheduler.GetCurrentThread(); + if (nanoseconds <= 0) { - auto& scheduler{Core::System::GetInstance().CurrentScheduler()}; switch (static_cast<SleepType>(nanoseconds)) { case SleepType::YieldWithoutLoadBalancing: - scheduler.YieldWithoutLoadBalancing(GetCurrentThread()); + scheduler.YieldWithoutLoadBalancing(current_thread); break; case SleepType::YieldWithLoadBalancing: - scheduler.YieldWithLoadBalancing(GetCurrentThread()); + scheduler.YieldWithLoadBalancing(current_thread); break; case SleepType::YieldAndWaitForLoadBalancing: - scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread()); + scheduler.YieldAndWaitForLoadBalancing(current_thread); break; default: UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } else { - // Sleep current thread and check for next thread to schedule - WaitCurrentThread_Sleep(); - - // Create an event to wake the thread up after the specified nanosecond delay has passed - GetCurrentThread()->WakeAfterDelay(nanoseconds); + current_thread->Sleep(nanoseconds); } // Reschedule all CPU cores - for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) - Core::System::GetInstance().CpuCore(i).PrepareReschedule(); + for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) { + system.CpuCore(i).PrepareReschedule(); + } } /// Wait process wide key atomic @@ -1336,11 +1341,15 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}", mutex_addr, condition_variable_addr, thread_handle, nano_seconds); - const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess(); + const auto& handle_table = current_process->GetHandleTable(); SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); ASSERT(thread); - CASCADE_CODE(Mutex::Release(mutex_addr)); + const auto release_result = current_process->GetMutex().Release(mutex_addr); + if (release_result.IsError()) { + return release_result; + } SharedPtr<Thread> current_thread = GetCurrentThread(); current_thread->SetCondVarWaitAddress(condition_variable_addr); @@ -1479,21 +1488,10 @@ static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout return ERR_INVALID_ADDRESS; } - auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter(); - switch (static_cast<AddressArbiter::ArbitrationType>(type)) { - case AddressArbiter::ArbitrationType::WaitIfLessThan: - return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, false); - case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan: - return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, true); - case AddressArbiter::ArbitrationType::WaitIfEqual: - return address_arbiter.WaitForAddressIfEqual(address, value, timeout); - default: - LOG_ERROR(Kernel_SVC, - "Invalid arbitration type, expected WaitIfLessThan, DecrementAndWaitIfLessThan " - "or WaitIfEqual but got {}", - type); - return ERR_INVALID_ENUM_VALUE; - } + const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type); + auto& address_arbiter = + Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter(); + return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout); } // Signals to an address (via Address Arbiter) @@ -1511,22 +1509,10 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to return ERR_INVALID_ADDRESS; } - auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter(); - switch (static_cast<AddressArbiter::SignalType>(type)) { - case AddressArbiter::SignalType::Signal: - return address_arbiter.SignalToAddress(address, num_to_wake); - case AddressArbiter::SignalType::IncrementAndSignalIfEqual: - return address_arbiter.IncrementAndSignalToAddressIfEqual(address, value, num_to_wake); - case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual: - return address_arbiter.ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, - num_to_wake); - default: - LOG_ERROR(Kernel_SVC, - "Invalid signal type, expected Signal, IncrementAndSignalIfEqual " - "or ModifyByWaitingCountAndSignalIfEqual but got {}", - type); - return ERR_INVALID_ENUM_VALUE; - } + const auto signal_type = static_cast<AddressArbiter::SignalType>(type); + auto& address_arbiter = + Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter(); + return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake); } /// This returns the total CPU ticks elapsed since the CPU was powered-on diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index eb54d6651..3b22e8e0d 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -7,8 +7,6 @@ #include <optional> #include <vector> -#include <boost/range/algorithm_ext/erase.hpp> - #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" @@ -68,17 +66,6 @@ void Thread::Stop() { owner_process->FreeTLSSlot(tls_address); } -void WaitCurrentThread_Sleep() { - Thread* thread = GetCurrentThread(); - thread->SetStatus(ThreadStatus::WaitSleep); -} - -void ExitCurrentThread() { - Thread* thread = GetCurrentThread(); - thread->Stop(); - Core::System::GetInstance().CurrentScheduler().RemoveThread(thread); -} - void Thread::WakeAfterDelay(s64 nanoseconds) { // Don't schedule a wakeup if the thread wants to wait forever if (nanoseconds == -1) @@ -269,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { if (thread->lock_owner == this) { // If the thread is already waiting for this thread to release the mutex, ensure that the // waiters list is consistent and return without doing anything. - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr != wait_mutex_threads.end()); + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter != wait_mutex_threads.end()); return; } @@ -278,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { ASSERT(thread->lock_owner == nullptr); // Ensure that the thread is not already in the list of mutex waiters - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr == wait_mutex_threads.end()); - + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter == wait_mutex_threads.end()); + + // Keep the list in an ordered fashion + const auto insertion_point = std::find_if( + wait_mutex_threads.begin(), wait_mutex_threads.end(), + [&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); }); + wait_mutex_threads.insert(insertion_point, thread); thread->lock_owner = this; - wait_mutex_threads.emplace_back(std::move(thread)); + UpdatePriority(); } @@ -290,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) { ASSERT(thread->lock_owner == this); // Ensure that the thread is in the list of mutex waiters - auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); - ASSERT(itr != wait_mutex_threads.end()); + const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread); + ASSERT(iter != wait_mutex_threads.end()); + + wait_mutex_threads.erase(iter); - boost::remove_erase(wait_mutex_threads, thread); thread->lock_owner = nullptr; UpdatePriority(); } void Thread::UpdatePriority() { - // Find the highest priority among all the threads that are waiting for this thread's lock + // If any of the threads waiting on the mutex have a higher priority + // (taking into account priority inheritance), then this thread inherits + // that thread's priority. u32 new_priority = nominal_priority; - for (const auto& thread : wait_mutex_threads) { - if (thread->nominal_priority < new_priority) - new_priority = thread->nominal_priority; + if (!wait_mutex_threads.empty()) { + if (wait_mutex_threads.front()->current_priority < new_priority) { + new_priority = wait_mutex_threads.front()->current_priority; + } } - if (new_priority == current_priority) + if (new_priority == current_priority) { return; + } scheduler->SetThreadPriority(this, new_priority); - current_priority = new_priority; + if (!lock_owner) { + return; + } + + // Ensure that the thread is within the correct location in the waiting list. + auto old_owner = lock_owner; + lock_owner->RemoveMutexWaiter(this); + old_owner->AddMutexWaiter(this); + // Recursively update the priority of the thread that depends on the priority of this one. - if (lock_owner) - lock_owner->UpdatePriority(); + lock_owner->UpdatePriority(); } void Thread::ChangeCore(u32 core, u64 mask) { @@ -391,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) { } } +void Thread::Sleep(s64 nanoseconds) { + // Sleep current thread and check for next thread to schedule + SetStatus(ThreadStatus::WaitSleep); + + // Create an event to wake the thread up after the specified nanosecond delay has passed + WakeAfterDelay(nanoseconds); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c48b21aba..faad5f391 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -383,6 +383,9 @@ public: void SetActivity(ThreadActivity value); + /// Sleeps this thread for the given amount of nanoseconds. + void Sleep(s64 nanoseconds); + private: explicit Thread(KernelCore& kernel); ~Thread() override; @@ -398,8 +401,14 @@ private: VAddr entry_point = 0; VAddr stack_top = 0; - u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application - u32 current_priority = 0; ///< Current thread priority, can be temporarily changed + /// Nominal thread priority, as set by the emulated application. + /// The nominal priority is the thread priority without priority + /// inheritance taken into account. + u32 nominal_priority = 0; + + /// Current thread priority. This may change over the course of the + /// thread's lifetime in order to facilitate priority inheritance. + u32 current_priority = 0; u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks. u64 last_running_ticks = 0; ///< CPU tick when thread was last running @@ -460,14 +469,4 @@ private: */ Thread* GetCurrentThread(); -/** - * Waits the current thread on a sleep - */ -void WaitCurrentThread_Sleep(); - -/** - * Stops the current thread and removes it from the thread_list - */ -void ExitCurrentThread(); - } // namespace Kernel diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 05c59af34..22bf55ce7 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -7,29 +7,29 @@ #include <utility> #include "common/assert.h" #include "common/logging/log.h" +#include "common/memory_hook.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" #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" namespace Kernel { namespace { const char* GetMemoryStateName(MemoryState state) { static constexpr const char* names[] = { - "Unmapped", "Io", - "Normal", "CodeStatic", - "CodeMutable", "Heap", - "Shared", "Unknown1", - "ModuleCodeStatic", "ModuleCodeMutable", - "IpcBuffer0", "Stack", - "ThreadLocal", "TransferMemoryIsolated", - "TransferMemory", "ProcessMemory", - "Inaccessible", "IpcBuffer1", - "IpcBuffer3", "KernelStack", + "Unmapped", "Io", + "Normal", "Code", + "CodeData", "Heap", + "Shared", "Unknown1", + "ModuleCode", "ModuleCodeData", + "IpcBuffer0", "Stack", + "ThreadLocal", "TransferMemoryIsolated", + "TransferMemory", "ProcessMemory", + "Inaccessible", "IpcBuffer1", + "IpcBuffer3", "KernelStack", }; return names[ToSvcMemoryState(state)]; @@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const { ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state, - Memory::MemoryHookPointer mmio_handler) { + Common::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; @@ -624,7 +624,7 @@ void VMManager::ClearPageTable() { std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr); page_table.special_regions.clear(); std::fill(page_table.attributes.begin(), page_table.attributes.end(), - Memory::PageType::Unmapped); + Common::PageType::Unmapped); } VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask, diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 88e0b3c02..7cdff6094 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -9,9 +9,10 @@ #include <tuple> #include <vector> #include "common/common_types.h" +#include "common/memory_hook.h" +#include "common/page_table.h" #include "core/hle/result.h" #include "core/memory.h" -#include "core/memory_hook.h" namespace FileSys { enum class ProgramAddressSpaceType : u8; @@ -164,12 +165,12 @@ enum class MemoryState : u32 { Unmapped = 0x00, Io = 0x01 | FlagMapped, Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed, - CodeStatic = 0x03 | CodeFlags | FlagMapProcess, - CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory, + Code = 0x03 | CodeFlags | FlagMapProcess, + CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory, Heap = 0x05 | DataFlags | FlagCodeMemory, Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated, - ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess, - ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory, + ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess, + ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory, IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated | IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned, @@ -290,7 +291,7 @@ struct VirtualMemoryArea { // Settings for type = MMIO /// Physical address of the register area this VMA maps to. PAddr paddr = 0; - Memory::MemoryHookPointer mmio_handler = nullptr; + Common::MemoryHookPointer mmio_handler = nullptr; /// Tests if this area can be merged to the right with `next`. bool CanBeMergedWith(const VirtualMemoryArea& next) const; @@ -368,7 +369,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::MemoryHookPointer mmio_handler); + Common::MemoryHookPointer mmio_handler); /// Unmaps a range of addresses, splitting VMAs as necessary. ResultCode UnmapRange(VAddr target, u64 size); @@ -509,7 +510,7 @@ public: /// Each VMManager has its own page table, which is set as the main one when the owning process /// is scheduled. - Memory::PageTable page_table; + Common::PageTable page_table{Memory::PAGE_BITS}; private: using VMAIter = VMAMap::iterator; @@ -616,6 +617,9 @@ private: VAddr new_map_region_base = 0; VAddr new_map_region_end = 0; + VAddr main_code_region_base = 0; + VAddr main_code_region_end = 0; + VAddr tls_io_region_base = 0; VAddr tls_io_region_end = 0; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 1ed144481..ab84f5ddc 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -13,14 +13,6 @@ // All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes /** - * Detailed description of the error. Code 0 always means success. - */ -enum class ErrorDescription : u32 { - Success = 0, - RemoteProcessDead = 301, -}; - -/** * Identifies the module which caused the error. Error codes can be propagated through a call * chain, meaning that this doesn't always correspond to the module where the API call made is * contained. @@ -120,7 +112,7 @@ enum class ErrorModule : u32 { ShopN = 811, }; -/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields. +/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields. union ResultCode { u32 raw; @@ -133,17 +125,9 @@ union ResultCode { constexpr explicit ResultCode(u32 raw) : raw(raw) {} - constexpr ResultCode(ErrorModule module, ErrorDescription description) - : ResultCode(module, static_cast<u32>(description)) {} - constexpr ResultCode(ErrorModule module_, u32 description_) : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} - constexpr ResultCode& operator=(const ResultCode& o) { - raw = o.raw; - return *this; - } - constexpr bool IsSuccess() const { return raw == 0; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3f009d2b7..9c44e27c6 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2,10 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> #include <cinttypes> #include <cstring> -#include <stack> #include "audio_core/audio_renderer.h" #include "core/core.h" #include "core/file_sys/savedata_factory.h" @@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) } IAudioController::IAudioController() : ServiceFramework("IAudioController") { + // clang-format off static const FunctionInfo functions[] = { {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"}, - {1, &IAudioController::GetMainAppletExpectedMasterVolume, - "GetMainAppletExpectedMasterVolume"}, - {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, - "GetLibraryAppletExpectedMasterVolume"}, - {3, nullptr, "ChangeMainAppletMasterVolume"}, - {4, nullptr, "SetTransparentVolumeRate"}, + {1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"}, + {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"}, + {3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"}, + {4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"}, }; + // clang-format on + RegisterHandlers(functions); } IAudioController::~IAudioController() = default; void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const float main_applet_volume_tmp = rp.Pop<float>(); + const float library_applet_volume_tmp = rp.Pop<float>(); + + LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}", + main_applet_volume_tmp, library_applet_volume_tmp); + + // Ensure the volume values remain within the 0-100% range + main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume); + library_applet_volume = + std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(volume); + rb.Push(main_applet_volume); } void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(volume); + rb.Push(library_applet_volume); +} + +void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) { + struct Parameters { + float volume; + s64 fade_time_ns; + }; + static_assert(sizeof(Parameters) == 16); + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw<Parameters>(); + + LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume, + parameters.fade_time_ns); + + main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume); + fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns}; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const float transparent_volume_rate_tmp = rp.Pop<float>(); + + LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp); + + // Clamp volume range to 0-100%. + transparent_volume_rate = + std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { @@ -169,7 +215,21 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController" IDisplayController::~IDisplayController() = default; -IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {} +IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "NotifyMessageToHomeMenuForDebug"}, + {1, nullptr, "OpenMainApplication"}, + {10, nullptr, "EmulateButtonEvent"}, + {20, nullptr, "InvalidateTransitionLayer"}, + {30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"}, + {40, nullptr, "GetAppletResourceUsageInfo"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + IDebugFunctions::~IDebugFunctions() = default; ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index b6113cfdd..565dd8e9e 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -4,6 +4,7 @@ #pragma once +#include <chrono> #include <memory> #include <queue> #include "core/hle/kernel/writable_event.h" @@ -81,8 +82,21 @@ private: void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx); void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx); + void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx); + void SetTransparentAudioRate(Kernel::HLERequestContext& ctx); - u32 volume{100}; + static constexpr float min_allowed_volume = 0.0f; + static constexpr float max_allowed_volume = 1.0f; + + float main_applet_volume{0.25f}; + float library_applet_volume{max_allowed_volume}; + float transparent_volume_rate{min_allowed_volume}; + + // Volume transition fade time in nanoseconds. + // e.g. If the main applet volume was 0% and was changed to 50% + // with a fade of 50ns, then over the course of 50ns, + // the volume will gradually fade up to 50% + std::chrono::nanoseconds fade_time_ns{0}; }; class IDisplayController final : public ServiceFramework<IDisplayController> { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 11eba4a12..cb4a1160d 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -8,44 +8,34 @@ #include <vector> #include <opus.h> +#include <opus_multistream.h> -#include "common/common_funcs.h" +#include "common/assert.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/hwopus.h" namespace Service::Audio { - +namespace { struct OpusDeleter { - void operator()(void* ptr) const { - operator delete(ptr); + void operator()(OpusMSDecoder* ptr) const { + opus_multistream_decoder_destroy(ptr); } }; -class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { -public: - IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate, - u32 channel_count) - : ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)), - sample_rate(sample_rate), channel_count(channel_count) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, - {1, nullptr, "SetContext"}, - {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, - {3, nullptr, "SetContextForMultiStream"}, - {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, - {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, - {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, - {7, nullptr, "DecodeInterleavedForMultiStream"}, - }; - // clang-format on +using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; - RegisterHandlers(functions); - } +struct OpusPacketHeader { + // Packet size in bytes. + u32_be size; + // Indicates the final range of the codec's entropy coder. + u32_be final_range; +}; +static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); -private: +class OpusDecoderState { +public: /// Describes extra behavior that may be asked of the decoding context. enum class ExtraBehavior { /// No extra behavior. @@ -55,30 +45,27 @@ private: ResetContext, }; - void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - - DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None); - } - - void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None); - } - - void DecodeInterleaved(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - - IPC::RequestParser rp{ctx}; - const auto extra_behavior = - rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None; + enum class PerfTime { + Disabled, + Enabled, + }; - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, extra_behavior); + explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count) + : decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {} + + // Decodes interleaved Opus packets. Optionally allows reporting time taken to + // perform the decoding, as well as any relevant extra behavior. + void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time, + ExtraBehavior extra_behavior) { + if (perf_time == PerfTime::Disabled) { + DecodeInterleavedHelper(ctx, nullptr, extra_behavior); + } else { + u64 performance = 0; + DecodeInterleavedHelper(ctx, &performance, extra_behavior); + } } +private: void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance, ExtraBehavior extra_behavior) { u32 consumed = 0; @@ -89,8 +76,7 @@ private: ResetDecoderContext(); } - if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples, - performance)) { + if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { LOG_ERROR(Audio, "Failed to decode opus data"); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code @@ -109,27 +95,27 @@ private: ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); } - bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, - std::vector<opus_int16>& output, u64* out_performance_time) { + bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input, + std::vector<opus_int16>& output, u64* out_performance_time) const { const auto start_time = std::chrono::high_resolution_clock::now(); const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusHeader) > input.size()) { + if (sizeof(OpusPacketHeader) > input.size()) { LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", - sizeof(OpusHeader), input.size()); + sizeof(OpusPacketHeader), input.size()); return false; } - OpusHeader hdr{}; - std::memcpy(&hdr, input.data(), sizeof(OpusHeader)); - if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) { + OpusPacketHeader hdr{}; + std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); + if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", - sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size()); + sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); return false; } - const auto frame = input.data() + sizeof(OpusHeader); + const auto frame = input.data() + sizeof(OpusPacketHeader); const auto decoded_sample_count = opus_packet_get_nb_samples( - frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)), + frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), static_cast<opus_int32>(sample_rate)); if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { LOG_ERROR( @@ -141,18 +127,18 @@ private: const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); const auto out_sample_count = - opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0); + opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); if (out_sample_count < 0) { LOG_ERROR(Audio, "Incorrect sample count received from opus_decode, " "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", - out_sample_count, frame_size, static_cast<u32>(hdr.sz)); + out_sample_count, frame_size, static_cast<u32>(hdr.size)); return false; } const auto end_time = std::chrono::high_resolution_clock::now() - start_time; sample_count = out_sample_count; - consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); + consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); if (out_performance_time != nullptr) { *out_performance_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); @@ -164,25 +150,86 @@ private: void ResetDecoderContext() { ASSERT(decoder != nullptr); - opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); } - struct OpusHeader { - u32_be sz; // Needs to be BE for some odd reason - INSERT_PADDING_WORDS(1); - }; - static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size"); - - std::unique_ptr<OpusDecoder, OpusDeleter> decoder; + OpusDecoderPtr decoder; u32 sample_rate; u32 channel_count; }; -static std::size_t WorkerBufferSize(u32 channel_count) { +class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { +public: + explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state) + : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, + {1, nullptr, "SetContext"}, + {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, + {3, nullptr, "SetContextForMultiStream"}, + {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, + {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, + {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, + {7, nullptr, "DecodeInterleavedForMultiStream"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Audio, "called"); + + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, + OpusDecoderState::ExtraBehavior::None); + } + + void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Audio, "called"); + + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, + OpusDecoderState::ExtraBehavior::None); + } + + void DecodeInterleaved(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Audio, "called"); + + IPC::RequestParser rp{ctx}; + const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext + : OpusDecoderState::ExtraBehavior::None; + + decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + } + + OpusDecoderState decoder_state; +}; + +std::size_t WorkerBufferSize(u32 channel_count) { ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - return opus_decoder_get_size(static_cast<int>(channel_count)); + constexpr int num_streams = 1; + const int num_stereo_streams = channel_count == 2 ? 1 : 0; + return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); } +// Creates the mapping table that maps the input channels to the particular +// output channels. In the stereo case, we map the left and right input channels +// to the left and right output channels respectively. +// +// However, in the monophonic case, we only map the one available channel +// to the sole output channel. We specify 255 for the would-be right channel +// as this is a special value defined by Opus to indicate to the decoder to +// ignore that channel. +std::array<u8, 2> CreateMappingTable(u32 channel_count) { + if (channel_count == 2) { + return {{0, 1}}; + } + + return {{0, 255}}; +} +} // Anonymous namespace + void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto sample_rate = rp.Pop<u32>(); @@ -220,10 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { const std::size_t worker_sz = WorkerBufferSize(channel_count); ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ - static_cast<OpusDecoder*>(operator new(worker_sz))}; - if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { - LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); + const int num_stereo_streams = channel_count == 2 ? 1 : 0; + const auto mapping_table = CreateMappingTable(channel_count); + + int error = 0; + OpusDecoderPtr decoder{ + opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, + num_stereo_streams, mapping_table.data(), &error)}; + if (error != OPUS_OK || decoder == nullptr) { + LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code rb.Push(ResultCode(-1)); @@ -232,8 +284,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate, - channel_count); + rb.PushIpcInterface<IHardwareOpusDecoderManager>( + OpusDecoderState{std::move(decoder), sample_rate, channel_count}); } HwOpus::HwOpus() : ServiceFramework("hwopus") { diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 54959edd8..f03fb629c 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { FSP_SRV::~FSP_SRV() = default; void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + current_process_id = rp.Pop<u64>(); + + LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 3a5f4e200..d7572ba7a 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -32,6 +32,7 @@ private: void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); FileSys::VirtualFile romfs; + u64 current_process_id = 0; }; } // namespace Service::FileSystem diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 929035034..e584b92ec 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -41,20 +41,20 @@ private: struct PadState { union { u32_le raw{}; - BitField<0, 1, u32_le> a; - BitField<1, 1, u32_le> b; - BitField<2, 1, u32_le> x; - BitField<3, 1, u32_le> y; - BitField<4, 1, u32_le> l; - BitField<5, 1, u32_le> r; - BitField<6, 1, u32_le> zl; - BitField<7, 1, u32_le> zr; - BitField<8, 1, u32_le> plus; - BitField<9, 1, u32_le> minus; - BitField<10, 1, u32_le> d_left; - BitField<11, 1, u32_le> d_up; - BitField<12, 1, u32_le> d_right; - BitField<13, 1, u32_le> d_down; + BitField<0, 1, u32> a; + BitField<1, 1, u32> b; + BitField<2, 1, u32> x; + BitField<3, 1, u32> y; + BitField<4, 1, u32> l; + BitField<5, 1, u32> r; + BitField<6, 1, u32> zl; + BitField<7, 1, u32> zr; + BitField<8, 1, u32> plus; + BitField<9, 1, u32> minus; + BitField<10, 1, u32> d_left; + BitField<11, 1, u32> d_up; + BitField<12, 1, u32> d_right; + BitField<13, 1, u32> d_down; }; }; static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size"); @@ -62,7 +62,7 @@ private: struct Attributes { union { u32_le raw{}; - BitField<0, 1, u32_le> connected; + BitField<0, 1, u32> connected; }; }; static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 18c7a94e6..4ff50b3cd 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -39,13 +39,13 @@ public: union { u32_le raw{}; - BitField<0, 1, u32_le> pro_controller; - BitField<1, 1, u32_le> handheld; - BitField<2, 1, u32_le> joycon_dual; - BitField<3, 1, u32_le> joycon_left; - BitField<4, 1, u32_le> joycon_right; + BitField<0, 1, u32> pro_controller; + BitField<1, 1, u32> handheld; + BitField<2, 1, u32> joycon_dual; + BitField<3, 1, u32> joycon_left; + BitField<4, 1, u32> joycon_right; - BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible + BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible }; }; static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size"); @@ -150,43 +150,43 @@ private: union { u64_le raw{}; // Button states - BitField<0, 1, u64_le> a; - BitField<1, 1, u64_le> b; - BitField<2, 1, u64_le> x; - BitField<3, 1, u64_le> y; - BitField<4, 1, u64_le> l_stick; - BitField<5, 1, u64_le> r_stick; - BitField<6, 1, u64_le> l; - BitField<7, 1, u64_le> r; - BitField<8, 1, u64_le> zl; - BitField<9, 1, u64_le> zr; - BitField<10, 1, u64_le> plus; - BitField<11, 1, u64_le> minus; + BitField<0, 1, u64> a; + BitField<1, 1, u64> b; + BitField<2, 1, u64> x; + BitField<3, 1, u64> y; + BitField<4, 1, u64> l_stick; + BitField<5, 1, u64> r_stick; + BitField<6, 1, u64> l; + BitField<7, 1, u64> r; + BitField<8, 1, u64> zl; + BitField<9, 1, u64> zr; + BitField<10, 1, u64> plus; + BitField<11, 1, u64> minus; // D-Pad - BitField<12, 1, u64_le> d_left; - BitField<13, 1, u64_le> d_up; - BitField<14, 1, u64_le> d_right; - BitField<15, 1, u64_le> d_down; + BitField<12, 1, u64> d_left; + BitField<13, 1, u64> d_up; + BitField<14, 1, u64> d_right; + BitField<15, 1, u64> d_down; // Left JoyStick - BitField<16, 1, u64_le> l_stick_left; - BitField<17, 1, u64_le> l_stick_up; - BitField<18, 1, u64_le> l_stick_right; - BitField<19, 1, u64_le> l_stick_down; + BitField<16, 1, u64> l_stick_left; + BitField<17, 1, u64> l_stick_up; + BitField<18, 1, u64> l_stick_right; + BitField<19, 1, u64> l_stick_down; // Right JoyStick - BitField<20, 1, u64_le> r_stick_left; - BitField<21, 1, u64_le> r_stick_up; - BitField<22, 1, u64_le> r_stick_right; - BitField<23, 1, u64_le> r_stick_down; + BitField<20, 1, u64> r_stick_left; + BitField<21, 1, u64> r_stick_up; + BitField<22, 1, u64> r_stick_right; + BitField<23, 1, u64> r_stick_down; // Not always active? - BitField<24, 1, u64_le> left_sl; - BitField<25, 1, u64_le> left_sr; + BitField<24, 1, u64> left_sl; + BitField<25, 1, u64> left_sr; - BitField<26, 1, u64_le> right_sl; - BitField<27, 1, u64_le> right_sr; + BitField<26, 1, u64> right_sl; + BitField<27, 1, u64> right_sr; }; }; static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); @@ -200,12 +200,12 @@ private: struct ConnectionState { union { u32_le raw{}; - BitField<0, 1, u32_le> IsConnected; - BitField<1, 1, u32_le> IsWired; - BitField<2, 1, u32_le> IsLeftJoyConnected; - BitField<3, 1, u32_le> IsLeftJoyWired; - BitField<4, 1, u32_le> IsRightJoyConnected; - BitField<5, 1, u32_le> IsRightJoyWired; + BitField<0, 1, u32> IsConnected; + BitField<1, 1, u32> IsWired; + BitField<2, 1, u32> IsLeftJoyConnected; + BitField<3, 1, u32> IsLeftJoyWired; + BitField<4, 1, u32> IsRightJoyConnected; + BitField<5, 1, u32> IsRightJoyWired; }; }; static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); @@ -240,23 +240,23 @@ private: struct NPadProperties { union { s64_le raw{}; - BitField<11, 1, s64_le> is_vertical; - BitField<12, 1, s64_le> is_horizontal; - BitField<13, 1, s64_le> use_plus; - BitField<14, 1, s64_le> use_minus; + BitField<11, 1, s64> is_vertical; + BitField<12, 1, s64> is_horizontal; + BitField<13, 1, s64> use_plus; + BitField<14, 1, s64> use_minus; }; }; struct NPadDevice { union { u32_le raw{}; - BitField<0, 1, s32_le> pro_controller; - BitField<1, 1, s32_le> handheld; - BitField<2, 1, s32_le> handheld_left; - BitField<3, 1, s32_le> handheld_right; - BitField<4, 1, s32_le> joycon_left; - BitField<5, 1, s32_le> joycon_right; - BitField<6, 1, s32_le> pokeball; + BitField<0, 1, s32> pro_controller; + BitField<1, 1, s32> handheld; + BitField<2, 1, s32> handheld_left; + BitField<3, 1, s32> handheld_right; + BitField<4, 1, s32> joycon_left; + BitField<5, 1, s32> joycon_right; + BitField<6, 1, s32> pokeball; }; }; diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 012b6e0dd..76fc340e9 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -33,8 +33,8 @@ private: struct Attributes { union { u32 raw{}; - BitField<0, 1, u32_le> start_touch; - BitField<1, 1, u32_le> end_touch; + BitField<0, 1, u32> start_touch; + BitField<1, 1, u32> end_touch; }; }; static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7cc58db4c..498602de5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,6 +4,9 @@ #pragma once +#include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/service.h" + #include "controllers/controller_base.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 9df7ac50f..d65693fc7 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -319,15 +319,14 @@ public: } ASSERT(vm_manager - .MirrorMemory(*map_address, nro_addr, nro_size, - Kernel::MemoryState::ModuleCodeStatic) + .MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess()); if (bss_size > 0) { ASSERT(vm_manager .MirrorMemory(*map_address + nro_size, bss_addr, bss_size, - Kernel::MemoryState::ModuleCodeStatic) + Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess()); } @@ -388,8 +387,7 @@ public: const auto& nro_size = iter->second.size; ASSERT(vm_manager - .MirrorMemory(heap_addr, mapped_addr, nro_size, - Kernel::MemoryState::ModuleCodeStatic) + .MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess()); diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 1f462e087..2a61593e2 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -42,7 +42,7 @@ private: union { BitField<0, 16, Flags> flags; BitField<16, 8, Severity> severity; - BitField<24, 8, u32_le> verbosity; + BitField<24, 8, u32> verbosity; }; u32_le payload_size; diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 0f02a1a18..4f6042b00 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -19,11 +19,11 @@ public: 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; + BitField<0, 8, u32> cmd; + BitField<8, 8, u32> group; + BitField<16, 14, u32> length; + BitField<30, 1, u32> is_in; + BitField<31, 1, u32> is_out; }; /** 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 a34b9e753..af62d33d2 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -10,6 +10,7 @@ #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" #include "core/hle/service/nvdrv/devices/nvmap.h" +#include "core/memory.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" @@ -88,7 +89,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) for (const auto& entry : entries) { LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", entry.offset, entry.nvmap_handle, entry.pages); - Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10; + GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10; auto object = nvmap_dev->GetObject(entry.nvmap_handle); if (!object) { LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle); @@ -101,7 +102,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) u64 size = static_cast<u64>(entry.pages) << 0x10; ASSERT(size <= object->size); - Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size); + GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size); ASSERT(returned == offset); } std::memcpy(output.data(), entries.data(), output.size()); @@ -172,16 +173,8 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou return 0; } - auto& system_instance = Core::System::GetInstance(); - - // Remove this memory region from the rasterizer cache. - auto& gpu = system_instance.GPU(); - auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset); - ASSERT(cpu_addr); - gpu.FlushAndInvalidateRegion(*cpu_addr, itr->second.size); - - params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size); - + params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset, + itr->second.size); buffer_mappings.erase(itr->second.offset); std::memcpy(output.data(), ¶ms, output.size()); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 117f87a45..00806b0ed 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -11,7 +11,6 @@ #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" @@ -76,7 +75,8 @@ namespace Service { * Creates a function string for logging, complete with the name (or header code, depending * on what's passed in) the port name, and all the cmd_buff arguments. */ -[[maybe_unused]] static std::string MakeFunctionString(const char* name, const char* port_name, +[[maybe_unused]] static std::string MakeFunctionString(std::string_view name, + std::string_view port_name, const u32* cmd_buff) { // Number of params == bits 0-5 + bits 6-11 int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); @@ -158,9 +158,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { return ReportUnimplementedFunction(ctx, info); } - LOG_TRACE( - Service, "{}", - MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str()); + LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer())); handler_invoker(this, info->handler_callback, ctx); } @@ -169,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co case IPC::CommandType::Close: { IPC::ResponseBuilder rb{context, 2}; rb.Push(RESULT_SUCCESS); - return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead); + return IPC::ERR_REMOTE_PROCESS_DEAD; } case IPC::CommandType::ControlWithContext: case IPC::CommandType::Control: { diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index bef25433e..b9d6381b4 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -67,7 +67,7 @@ public: if (port == nullptr) { return nullptr; } - return std::static_pointer_cast<T>(port->hle_handler); + return std::static_pointer_cast<T>(port->GetHLEHandler()); } void InvokeControlRequest(Kernel::HLERequestContext& context); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index a975767bb..566cd6006 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -24,6 +24,7 @@ #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/service.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" #include "core/hle/service/vi/vi_s.h" @@ -33,6 +34,7 @@ namespace Service::VI { constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; +constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5}; constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6}; constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7}; @@ -1203,26 +1205,40 @@ IApplicationDisplayService::IApplicationDisplayService( RegisterHandlers(functions); } -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)) {} +static bool IsValidServiceAccess(Permission permission, Policy policy) { + if (permission == Permission::User) { + return policy == Policy::User; + } + + if (permission == Permission::System || permission == Permission::Manager) { + return policy == Policy::User || policy == Policy::Compositor; + } -Module::Interface::~Interface() = default; + return false; +} -void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_VI, "(STUBBED) called"); +void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, + Permission permission) { + IPC::RequestParser rp{ctx}; + const auto policy = rp.PopEnum<Policy>(); + + if (!IsValidServiceAccess(permission, policy)) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_PERMISSION_DENIED); + return; + } IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger); + rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger)); } 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); + std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager); + std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager); } } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index e3963502a..6b66f8b81 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -4,12 +4,21 @@ #pragma once -#include "core/hle/service/service.h" +#include <memory> +#include "common/common_types.h" + +namespace Kernel { +class HLERequestContext; +} namespace Service::NVFlinger { class NVFlinger; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::VI { enum class DisplayResolution : u32 { @@ -19,22 +28,25 @@ enum class DisplayResolution : u32 { UndockedHeight = 720, }; -class Module final { -public: - class Interface : public ServiceFramework<Interface> { - public: - explicit Interface(std::shared_ptr<Module> module, const char* name, - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); - ~Interface() override; - - void GetDisplayService(Kernel::HLERequestContext& ctx); +/// Permission level for a particular VI service instance +enum class Permission { + User, + System, + Manager, +}; - protected: - std::shared_ptr<Module> module; - std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; - }; +/// A policy type that may be requested via GetDisplayService and +/// GetDisplayServiceWithProxyNameExchange +enum class Policy { + User, + Compositor, }; +namespace detail { +void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission); +} // namespace detail + /// Registers all VI services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index 207c06b16..06070087f 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" namespace Service::VI { -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)) { +VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {2, &VI_M::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_M::~VI_M() = default; +void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index 487d58d50..290e06689 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_M final : public Module::Interface { +class VI_M final : public ServiceFramework<VI_M> { public: - explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_M() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 920e6a1f6..57c596cc4 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_s.h" namespace Service::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)) { +VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {1, &VI_S::GetDisplayService, "GetDisplayService"}, {3, nullptr, "GetDisplayServiceWithProxyNameExchange"}, @@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_S::~VI_S() = default; +void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index bbc31148f..47804dc0b 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_S final : public Module::Interface { +class VI_S final : public ServiceFramework<VI_S> { public: - explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_S() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index d81e410d6..9d5ceb608 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_u.h" namespace Service::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)) { +VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) + : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} { static const FunctionInfo functions[] = { {0, &VI_U::GetDisplayService, "GetDisplayService"}, }; @@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> VI_U::~VI_U() = default; +void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_VI, "called"); + + detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index b92f28c92..19bdb73b0 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -4,14 +4,27 @@ #pragma once -#include "core/hle/service/vi/vi.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class HLERequestContext; +} + +namespace Service::NVFlinger { +class NVFlinger; +} namespace Service::VI { -class VI_U final : public Module::Interface { +class VI_U final : public ServiceFramework<VI_U> { public: - explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); ~VI_U() override; + +private: + void GetDisplayService(Kernel::HLERequestContext& ctx); + + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; } // namespace Service::VI diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 6057c7f26..8b1920f22 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/elf.h" diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp deleted file mode 100644 index 57ca8c3ee..000000000 --- a/src/core/loader/linker.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <vector> - -#include "common/common_funcs.h" -#include "common/logging/log.h" -#include "common/swap.h" -#include "core/loader/linker.h" -#include "core/memory.h" - -namespace Loader { - -enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 }; - -enum DynamicType : u32 { - DT_NULL = 0, - DT_PLTRELSZ = 2, - DT_STRTAB = 5, - DT_SYMTAB = 6, - DT_RELA = 7, - DT_RELASZ = 8, - DT_STRSZ = 10, - DT_JMPREL = 23, -}; - -struct Elf64_Rela { - u64_le offset; - RelocationType type; - u32_le symbol; - s64_le addend; -}; -static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size."); - -struct Elf64_Dyn { - u64_le tag; - u64_le value; -}; -static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size."); - -struct Elf64_Sym { - u32_le name; - INSERT_PADDING_BYTES(0x2); - u16_le shndx; - u64_le value; - u64_le size; -}; -static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size."); - -void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols, - u64 relocation_offset, u64 size, VAddr load_base) { - for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) { - Elf64_Rela rela; - std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela)); - - const Symbol& symbol = symbols[rela.symbol]; - switch (rela.type) { - case RelocationType::RELATIVE: { - const u64 value = load_base + rela.addend; - if (!symbol.name.empty()) { - exports[symbol.name] = value; - } - std::memcpy(&program_image[rela.offset], &value, sizeof(u64)); - break; - } - case RelocationType::JUMP_SLOT: - case RelocationType::GLOB_DAT: - if (!symbol.value) { - imports[symbol.name] = {rela.offset + load_base, 0}; - } else { - exports[symbol.name] = symbol.value; - std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64)); - } - break; - case RelocationType::ABS64: - if (!symbol.value) { - imports[symbol.name] = {rela.offset + load_base, rela.addend}; - } else { - const u64 value = symbol.value + rela.addend; - exports[symbol.name] = value; - std::memcpy(&program_image[rela.offset], &value, sizeof(u64)); - } - break; - default: - LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type)); - break; - } - } -} - -void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) { - std::map<u64, u64> dynamic; - while (dynamic_section_offset < program_image.size()) { - Elf64_Dyn dyn; - std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn)); - dynamic_section_offset += sizeof(Elf64_Dyn); - - if (dyn.tag == DT_NULL) { - break; - } - dynamic[dyn.tag] = dyn.value; - } - - u64 offset = dynamic[DT_SYMTAB]; - std::vector<Symbol> symbols; - while (offset < program_image.size()) { - Elf64_Sym sym; - std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym)); - offset += sizeof(Elf64_Sym); - - if (sym.name >= dynamic[DT_STRSZ]) { - break; - } - - std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]); - if (sym.value) { - exports[name] = load_base + sym.value; - symbols.emplace_back(std::move(name), load_base + sym.value); - } else { - symbols.emplace_back(std::move(name), 0); - } - } - - if (dynamic.find(DT_RELA) != dynamic.end()) { - WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base); - } - - if (dynamic.find(DT_JMPREL) != dynamic.end()) { - WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ], - load_base); - } -} - -void Linker::ResolveImports() { - // Resolve imports - for (const auto& import : imports) { - const auto& search = exports.find(import.first); - if (search != exports.end()) { - Memory::Write64(import.second.ea, search->second + import.second.addend); - } else { - LOG_ERROR(Loader, "Unresolved import: {}", import.first); - } - } -} - -} // namespace Loader diff --git a/src/core/loader/linker.h b/src/core/loader/linker.h deleted file mode 100644 index 107625837..000000000 --- a/src/core/loader/linker.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <string> -#include "common/common_types.h" - -namespace Loader { - -class Linker { -protected: - struct Symbol { - Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {} - std::string name; - u64 value; - }; - - struct Import { - VAddr ea; - s64 addend; - }; - - void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols, - u64 relocation_offset, u64 size, VAddr load_base); - void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base); - - void ResolveImports(); - - std::map<std::string, Import> imports; - std::map<std::string, VAddr> exports; -}; - -} // namespace Loader diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 4fad0c0dd..5de02a94b 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_offset.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/service/filesystem/filesystem.h" diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 013d629c0..85b0ed644 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -4,10 +4,10 @@ #pragma once +#include <memory> #include <string> #include <vector> #include "common/common_types.h" -#include "core/loader/linker.h" #include "core/loader/loader.h" namespace FileSys { @@ -21,7 +21,7 @@ class Process; namespace Loader { /// Loads an NRO file -class AppLoader_NRO final : public AppLoader, Linker { +class AppLoader_NRO final : public AppLoader { public: explicit AppLoader_NRO(FileSys::VirtualFile file); ~AppLoader_NRO() override; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 6ded0b707..714d85a59 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -7,10 +7,13 @@ #include <lz4.h> #include "common/common_funcs.h" #include "common/file_util.h" +#include "common/hex_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nso.h" @@ -18,36 +21,8 @@ #include "core/settings.h" namespace Loader { - -struct NsoSegmentHeader { - u32_le offset; - u32_le location; - u32_le size; - union { - u32_le alignment; - u32_le bss_size; - }; -}; -static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); - -struct NsoHeader { - u32_le magic; - u32_le version; - INSERT_PADDING_WORDS(1); - u8 flags; - std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order) - std::array<u8, 0x20> build_id; - std::array<u32_le, 3> segments_compressed_size; - - bool IsSegmentCompressed(size_t segment_num) const { - ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); - return ((flags >> segment_num) & 1); - } -}; -static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); -static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable."); - -struct ModHeader { +namespace { +struct MODHeader { u32_le magic; u32_le dynamic_offset; u32_le bss_start_offset; @@ -56,25 +31,10 @@ struct ModHeader { u32_le eh_frame_hdr_end_offset; u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base }; -static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size."); - -AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} +static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size."); -FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { - u32 magic = 0; - if (file->ReadObject(&magic) != sizeof(magic)) { - return FileType::Error; - } - - if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { - return FileType::Error; - } - - return FileType::NSO; -} - -static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, - const NsoSegmentHeader& header) { +std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, + const NSOSegmentHeader& header) { std::vector<u8> uncompressed_data(header.size); const int bytes_uncompressed = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()), @@ -88,23 +48,47 @@ static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, return uncompressed_data; } -static constexpr u32 PageAlignSize(u32 size) { +constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } +} // Anonymous namespace + +bool NSOHeader::IsSegmentCompressed(size_t segment_num) const { + ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num); + return ((flags >> segment_num) & 1) != 0; +} + +AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} + +FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) { + u32 magic = 0; + if (file->ReadObject(&magic) != sizeof(magic)) { + return FileType::Error; + } + + if (Common::MakeMagic('N', 'S', 'O', '0') != magic) { + return FileType::Error; + } + + return FileType::NSO; +} std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments, std::optional<FileSys::PatchManager> pm) { - if (file.GetSize() < sizeof(NsoHeader)) + if (file.GetSize() < sizeof(NSOHeader)) { return {}; + } - NsoHeader nso_header{}; - if (sizeof(NsoHeader) != file.ReadObject(&nso_header)) + NSOHeader nso_header{}; + if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { return {}; + } - if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { return {}; + } // Build program image Kernel::CodeSet codeset; @@ -140,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); // Read MOD header - ModHeader mod_header{}; + MODHeader mod_header{}; // Default .bss to size in segment header if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; - std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); + std::memcpy(&mod_header, program_image.data() + module_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 @@ -155,13 +139,25 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply patches if necessary if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { - std::vector<u8> pi_header(program_image.size() + 0x100); - std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader)); - std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); + std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); + pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header), + reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader)); + pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(), + program_image.end()); pi_header = pm->PatchNSO(pi_header); - std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); + std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin()); + } + + // Apply cheats if they exist and the program has a valid title ID + if (pm) { + auto& system = Core::System::GetInstance(); + const auto cheats = pm->CreateCheatList(system, nso_header.build_id); + if (!cheats.empty()) { + system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id), + load_base, load_base + program_image.size()); + } } // Load codeset for current process diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 135b6ea5a..4674c3724 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -4,10 +4,12 @@ #pragma once +#include <array> #include <optional> +#include <type_traits> #include "common/common_types.h" +#include "common/swap.h" #include "core/file_sys/patch_manager.h" -#include "core/loader/linker.h" #include "core/loader/loader.h" namespace Kernel { @@ -16,6 +18,43 @@ class Process; namespace Loader { +struct NSOSegmentHeader { + u32_le offset; + u32_le location; + u32_le size; + union { + u32_le alignment; + u32_le bss_size; + }; +}; +static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size."); + +struct NSOHeader { + using SHA256Hash = std::array<u8, 0x20>; + + struct RODataRelativeExtent { + u32_le data_offset; + u32_le size; + }; + + u32_le magic; + u32_le version; + u32 reserved; + u32_le flags; + std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order) + std::array<u8, 0x20> build_id; + std::array<u32_le, 3> segments_compressed_size; + std::array<u8, 0x1C> padding; + RODataRelativeExtent api_info_extent; + RODataRelativeExtent dynstr_extent; + RODataRelativeExtent dynsyn_extent; + std::array<SHA256Hash, 3> segment_hashes; + + bool IsSegmentCompressed(size_t segment_num) const; +}; +static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); +static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); + constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; struct NSOArgumentHeader { @@ -26,7 +65,7 @@ struct NSOArgumentHeader { static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size."); /// Loads an NSO file -class AppLoader_NSO final : public AppLoader, Linker { +class AppLoader_NSO final : public AppLoader { public: explicit AppLoader_NSO(FileSys::VirtualFile file); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 6591c45d2..332c1037c 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/page_table.h" #include "common/swap.h" #include "core/arm/arm_interface.h" #include "core/core.h" @@ -18,13 +19,14 @@ #include "core/hle/lock.h" #include "core/memory.h" #include "core/memory_setup.h" +#include "video_core/gpu.h" #include "video_core/renderer_base.h" namespace Memory { -static PageTable* current_page_table = nullptr; +static Common::PageTable* current_page_table = nullptr; -void SetCurrentPageTable(PageTable* page_table) { +void SetCurrentPageTable(Common::PageTable* page_table) { current_page_table = page_table; auto& system = Core::System::GetInstance(); @@ -36,39 +38,20 @@ void SetCurrentPageTable(PageTable* page_table) { } } -PageTable* GetCurrentPageTable() { +Common::PageTable* GetCurrentPageTable() { return current_page_table; } -PageTable::PageTable() = default; - -PageTable::PageTable(std::size_t address_space_width_in_bits) { - Resize(address_space_width_in_bits); -} - -PageTable::~PageTable() = default; - -void PageTable::Resize(std::size_t address_space_width_in_bits) { - const std::size_t num_page_table_entries = 1ULL << (address_space_width_in_bits - PAGE_BITS); - - pointers.resize(num_page_table_entries); - attributes.resize(num_page_table_entries); - - // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the - // vector size is subsequently decreased (via resize), the vector might not automatically - // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for - // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use. - - pointers.shrink_to_fit(); - attributes.shrink_to_fit(); -} - -static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) { +static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory, + Common::PageType type) { LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE, (base + size) * PAGE_SIZE); - RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, - FlushMode::FlushAndInvalidate); + // During boot, current_page_table might not be set yet, in which case we need not flush + if (Core::System::GetInstance().IsPoweredOn()) { + Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS, + size * PAGE_SIZE); + } VAddr end = base + size; ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", @@ -88,41 +71,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa } } -void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) { +void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); } -void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) { +void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer mmio_handler) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special); auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)}; - page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)}; + page_table.special_regions.add( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } -void UnmapRegion(PageTable& page_table, VAddr base, u64 size) { +void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::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) { +void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook) { auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)}; - page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)}; + page_table.special_regions.add( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } -void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) { +void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook) { auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1); - SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)}; - page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region})); + Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)}; + page_table.special_regions.subtract( + std::make_pair(interval, std::set<Common::SpecialRegion>{region})); } /** @@ -171,19 +160,19 @@ T Read(const VAddr vaddr) { return value; } - PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr); return 0; - case PageType::Memory: + case Common::PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); break; - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); - + case Common::PageType::RasterizerCachedMemory: { + auto host_ptr{GetPointerFromVMA(vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T)); T value; - std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); + std::memcpy(&value, host_ptr, sizeof(T)); return value; } default: @@ -201,18 +190,19 @@ void Write(const VAddr vaddr, const T data) { return; } - PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; switch (type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, static_cast<u32>(data), vaddr); return; - case PageType::Memory: + case Common::PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr); break; - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate); - std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); + case Common::PageType::RasterizerCachedMemory: { + auto host_ptr{GetPointerFromVMA(vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T)); + std::memcpy(host_ptr, &data, sizeof(T)); break; } default: @@ -227,10 +217,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { if (page_pointer) return true; - if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) + if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory) return true; - if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) + if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special) return false; return false; @@ -250,7 +240,8 @@ u8* GetPointer(const VAddr vaddr) { return page_pointer + (vaddr & PAGE_MASK); } - if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) { + if (current_page_table->attributes[vaddr >> PAGE_BITS] == + Common::PageType::RasterizerCachedMemory) { return GetPointerFromVMA(vaddr); } @@ -284,20 +275,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { - PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; + Common::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: + case Common::PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address // space, for example, a system module need not have a VRAM mapping. break; - case PageType::Memory: - page_type = PageType::RasterizerCachedMemory; + case Common::PageType::Memory: + page_type = Common::PageType::RasterizerCachedMemory; current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; break; - case PageType::RasterizerCachedMemory: + case Common::PageType::RasterizerCachedMemory: // There can be more than one GPU region mapped per CPU region, so it's common that // this area is already marked as cached. break; @@ -307,23 +298,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { } else { // Switch page type to uncached if now uncached switch (page_type) { - case PageType::Unmapped: + case Common::PageType::Unmapped: // It is not necessary for a process to have this region mapped into its address // space, for example, a system module need not have a VRAM mapping. break; - case PageType::Memory: + case Common::PageType::Memory: // There can be more than one GPU region mapped per CPU region, so it's common that // this area is already unmarked as cached. break; - case PageType::RasterizerCachedMemory: { + case Common::PageType::RasterizerCachedMemory: { u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); if (pointer == nullptr) { // 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; + page_type = Common::PageType::Unmapped; } else { - page_type = PageType::Memory; + page_type = Common::PageType::Memory; current_page_table->pointers[vaddr >> PAGE_BITS] = pointer; } break; @@ -335,47 +326,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { } } -void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) { - auto& system_instance = Core::System::GetInstance(); - - // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be - // null here - if (!system_instance.IsPoweredOn()) { - return; - } - - const VAddr end = start + size; - - const auto CheckRegion = [&](VAddr region_start, VAddr region_end) { - if (start >= region_end || end <= region_start) { - // No overlap with region - return; - } - - const VAddr overlap_start = std::max(start, region_start); - const VAddr overlap_end = std::min(end, region_end); - const VAddr overlap_size = overlap_end - overlap_start; - - auto& gpu = system_instance.GPU(); - switch (mode) { - case FlushMode::Flush: - gpu.FlushRegion(overlap_start, overlap_size); - break; - case FlushMode::Invalidate: - gpu.InvalidateRegion(overlap_start, overlap_size); - break; - case FlushMode::FlushAndInvalidate: - gpu.FlushAndInvalidateRegion(overlap_start, overlap_size); - break; - } - }; - - const auto& vm_manager = Core::CurrentProcess()->VMManager(); - - CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress()); - CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress()); -} - u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -406,24 +356,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); const u8* src_ptr = page_table.pointers[page_index] + page_offset; std::memcpy(dest_buffer, src_ptr, 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); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount); + std::memcpy(dest_buffer, host_ptr, copy_amount); break; } default: @@ -470,23 +420,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memcpy(dest_ptr, src_buffer, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Invalidate); - std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount); + std::memcpy(host_ptr, src_buffer, copy_amount); break; } default: @@ -516,23 +466,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std: const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); break; } - case PageType::Memory: { + case Common::PageType::Memory: { DEBUG_ASSERT(page_table.pointers[page_index]); u8* dest_ptr = page_table.pointers[page_index] + page_offset; std::memset(dest_ptr, 0, copy_amount); break; } - case PageType::RasterizerCachedMemory: { - RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), - FlushMode::Invalidate); - std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount); + std::memset(host_ptr, 0, copy_amount); break; } default: @@ -558,23 +508,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { - case PageType::Unmapped: { + case Common::PageType::Unmapped: { LOG_ERROR(HW_Memory, "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); ZeroBlock(process, dest_addr, copy_amount); break; } - case PageType::Memory: { + case Common::PageType::Memory: { 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(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount); + case Common::PageType::RasterizerCachedMemory: { + const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)}; + Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount); + WriteBlock(process, dest_addr, host_ptr, copy_amount); break; } default: diff --git a/src/core/memory.h b/src/core/memory.h index 1acf5ce8c..1d38cdca8 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -6,11 +6,11 @@ #include <cstddef> #include <string> -#include <tuple> -#include <vector> -#include <boost/icl/interval_map.hpp> #include "common/common_types.h" -#include "core/memory_hook.h" + +namespace Common { +struct PageTable; +} namespace Kernel { class Process; @@ -26,71 +26,6 @@ constexpr std::size_t PAGE_BITS = 12; constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS; constexpr u64 PAGE_MASK = PAGE_SIZE - 1; -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. - Memory, - /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and - /// invalidation - RasterizerCachedMemory, - /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. - Special, -}; - -struct SpecialRegion { - 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. - */ -struct PageTable { - explicit PageTable(); - explicit PageTable(std::size_t address_space_width_in_bits); - ~PageTable(); - - /** - * Resizes the page table to be able to accomodate enough pages within - * a given address space. - * - * @param address_space_width_in_bits The address size width in bits. - */ - void Resize(std::size_t address_space_width_in_bits); - - /** - * Vector of memory pointers backing each page. An entry can only be non-null if the - * corresponding entry in the `attributes` vector is of type `Memory`. - */ - std::vector<u8*> pointers; - - /** - * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is - * of type `Special`. - */ - boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions; - - /** - * Vector 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::vector<PageType> attributes; -}; - /// Virtual user-space memory regions enum : VAddr { /// Read-only page containing kernel and system configuration values. @@ -116,8 +51,8 @@ enum : VAddr { }; /// Currently active page table -void SetCurrentPageTable(PageTable* page_table); -PageTable* GetCurrentPageTable(); +void SetCurrentPageTable(Common::PageTable* page_table); +Common::PageTable* GetCurrentPageTable(); /// Determines if the given VAddr is valid for the specified process. bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr); @@ -161,10 +96,4 @@ enum class FlushMode { */ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached); -/** - * Flushes and invalidates any externally cached rasterizer resources touching the given virtual - * address region. - */ -void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode); - } // namespace Memory diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 9a1a4f4be..5225ee8e2 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -5,7 +5,11 @@ #pragma once #include "common/common_types.h" -#include "core/memory_hook.h" +#include "common/memory_hook.h" + +namespace Common { +struct PageTable; +} namespace Memory { @@ -17,7 +21,7 @@ namespace Memory { * @param size The amount of bytes to map. Must be page-aligned. * @param target Buffer with the memory backing the mapping. Must be of length at least `size`. */ -void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target); +void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target); /** * Maps a region of the emulated process address space as a IO region. @@ -26,11 +30,14 @@ 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, MemoryHookPointer mmio_handler); +void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer mmio_handler); -void UnmapRegion(PageTable& page_table, VAddr base, u64 size); +void UnmapRegion(Common::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); +void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook); +void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size, + Common::MemoryHookPointer hook); } // namespace Memory diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 1c7db28c0..5b4e032bd 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,15 +7,18 @@ add_library(input_common STATIC main.h motion_emu.cpp motion_emu.h - - $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h> + sdl/sdl.cpp + sdl/sdl.h ) -create_target_directory_groups(input_common) - -target_link_libraries(input_common PUBLIC core PRIVATE common) - if(SDL2_FOUND) + target_sources(input_common PRIVATE + sdl/sdl_impl.cpp + sdl/sdl_impl.h + ) target_link_libraries(input_common PRIVATE SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() + +create_target_directory_groups(input_common) +target_link_libraries(input_common PUBLIC core PRIVATE common) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 37f572853..8e66c1b15 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -17,10 +17,7 @@ namespace InputCommon { static std::shared_ptr<Keyboard> keyboard; static std::shared_ptr<MotionEmu> motion_emu; - -#ifdef HAVE_SDL2 -static std::thread poll_thread; -#endif +static std::unique_ptr<SDL::State> sdl; void Init() { keyboard = std::make_shared<Keyboard>(); @@ -30,15 +27,7 @@ void Init() { motion_emu = std::make_shared<MotionEmu>(); Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); -#ifdef HAVE_SDL2 - SDL::Init(); -#endif -} - -void StartJoystickEventHandler() { -#ifdef HAVE_SDL2 - poll_thread = std::thread(SDL::PollLoop); -#endif + sdl = SDL::Init(); } void Shutdown() { @@ -47,11 +36,7 @@ void Shutdown() { Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); motion_emu.reset(); - -#ifdef HAVE_SDL2 - SDL::Shutdown(); - poll_thread.join(); -#endif + sdl.reset(); } Keyboard* GetKeyboard() { @@ -88,7 +73,7 @@ namespace Polling { std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { #ifdef HAVE_SDL2 - return SDL::Polling::GetPollers(type); + return sdl->GetPollers(type); #else return {}; #endif diff --git a/src/input_common/main.h b/src/input_common/main.h index 9eb13106e..77a0ce90b 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -20,8 +20,6 @@ void Init(); /// Deregisters all built-in input device factories and shuts them down. void Shutdown(); -void StartJoystickEventHandler(); - class Keyboard; /// Gets the keyboard button device factory. diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index faf3c1fa3..644db3448 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -1,631 +1,19 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> -#include <atomic> -#include <cmath> -#include <functional> -#include <iterator> -#include <mutex> -#include <string> -#include <thread> -#include <tuple> -#include <unordered_map> -#include <utility> -#include <vector> -#include <SDL.h> -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/math_util.h" -#include "common/param_package.h" -#include "common/threadsafe_queue.h" -#include "input_common/main.h" #include "input_common/sdl/sdl.h" +#ifdef HAVE_SDL2 +#include "input_common/sdl/sdl_impl.h" +#endif -namespace InputCommon { +namespace InputCommon::SDL { -namespace SDL { - -class SDLJoystick; -class SDLButtonFactory; -class SDLAnalogFactory; - -/// Map of GUID of a list of corresponding virtual Joysticks -static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; -static std::mutex joystick_map_mutex; - -static std::shared_ptr<SDLButtonFactory> button_factory; -static std::shared_ptr<SDLAnalogFactory> analog_factory; - -/// Used by the Pollers during config -static std::atomic<bool> polling; -static Common::SPSCQueue<SDL_Event> event_queue; - -static std::atomic<bool> initialized = false; - -static std::string GetGUID(SDL_Joystick* joystick) { - SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; -} - -class SDLJoystick { -public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} - - void SetButton(int button, bool value) { - std::lock_guard<std::mutex> lock(mutex); - state.buttons[button] = value; - } - - bool GetButton(int button) const { - std::lock_guard<std::mutex> lock(mutex); - return state.buttons.at(button); - } - - void SetAxis(int axis, Sint16 value) { - std::lock_guard<std::mutex> lock(mutex); - state.axes[axis] = value; - } - - float GetAxis(int axis) const { - std::lock_guard<std::mutex> lock(mutex); - return state.axes.at(axis) / 32767.0f; - } - - std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); - y = -y; // 3DS uses an y-axis inverse from SDL - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return std::make_tuple(x, y); - } - - void SetHat(int hat, Uint8 direction) { - std::lock_guard<std::mutex> lock(mutex); - state.hats[hat] = direction; - } - - bool GetHatDirection(int hat, Uint8 direction) const { - std::lock_guard<std::mutex> lock(mutex); - return (state.hats.at(hat) & direction) != 0; - } - /** - * The guid of the joystick - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_Joystick* GetSDLJoystick() const { - return sdl_joystick.get(); - } - - void SetSDLJoystick(SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { - sdl_joystick = - std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter); - } - -private: - struct State { - std::unordered_map<int, bool> buttons; - std::unordered_map<int, Sint16> axes; - std::unordered_map<int, Uint8> hats; - } state; - std::string guid; - int port; - std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; - mutable std::mutex mutex; -}; - -/** - * Get the nth joystick with the corresponding GUID - */ -static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) { - std::lock_guard<std::mutex> lock(joystick_map_mutex); - const auto it = joystick_map.find(guid); - if (it != joystick_map.end()) { - while (it->second.size() <= port) { - auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr, - [](SDL_Joystick*) {}); - it->second.emplace_back(std::move(joystick)); - } - return it->second[port]; - } - auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -/** - * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie - * it to a SDLJoystick with the same guid and that port - */ -static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - std::lock_guard<std::mutex> lock(joystick_map_mutex); - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); - auto map_it = joystick_map.find(guid); - if (map_it != joystick_map.end()) { - auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { - return sdl_joystick == joystick->GetSDLJoystick(); - }); - if (vec_it != map_it->second.end()) { - // This is the common case: There is already an existing SDL_Joystick maped to a - // SDLJoystick. return the SDLJoystick - return *vec_it; - } - // Search for a SDLJoystick without a mapped SDL_Joystick... - auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [](const std::shared_ptr<SDLJoystick>& joystick) { - return !joystick->GetSDLJoystick(); - }); - if (nullptr_it != map_it->second.end()) { - // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick); - return *nullptr_it; - } - // There is no SDLJoystick without a mapped SDL_Joystick - // Create a new SDLJoystick - auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick); - return map_it->second.emplace_back(std::move(joystick)); - } - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -void InitJoystick(int joystick_index) { - std::lock_guard<std::mutex> lock(joystick_map_mutex); - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - if (!sdl_joystick) { - LOG_ERROR(Input, "failed to open joystick {}", joystick_index); - return; - } - std::string guid = GetGUID(sdl_joystick); - if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); - joystick_map[guid].emplace_back(std::move(joystick)); - return; - } - auto& joystick_guid_list = joystick_map[guid]; - const auto it = std::find_if( - joystick_guid_list.begin(), joystick_guid_list.end(), - [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); - if (it != joystick_guid_list.end()) { - (*it)->SetSDLJoystick(sdl_joystick); - return; - } - auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick); - joystick_guid_list.emplace_back(std::move(joystick)); -} - -void CloseJoystick(SDL_Joystick* sdl_joystick) { - std::lock_guard<std::mutex> lock(joystick_map_mutex); - std::string guid = GetGUID(sdl_joystick); - // This call to guid is save since the joystick is guranteed to be in that map - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); -} - -void HandleGameControllerEvent(const SDL_Event& event) { - switch (event.type) { - case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { - joystick->SetButton(event.jbutton.button, false); - } - break; - } - case SDL_JOYBUTTONDOWN: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { - joystick->SetButton(event.jbutton.button, true); - } - break; - } - case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); - if (joystick) { - joystick->SetHat(event.jhat.hat, event.jhat.value); - } - break; - } - case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - if (joystick) { - joystick->SetAxis(event.jaxis.axis, event.jaxis.value); - } - break; - } - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); - break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); - break; - } -} - -void CloseSDLJoysticks() { - std::lock_guard<std::mutex> lock(joystick_map_mutex); - joystick_map.clear(); -} - -void PollLoop() { - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - return; - } - - SDL_Event event; - while (initialized) { - // Wait for 10 ms or until an event happens - if (SDL_WaitEventTimeout(&event, 10)) { - // Don't handle the event if we are configuring - if (polling) { - event_queue.Push(event); - } else { - HandleGameControllerEvent(event); - } - } - } - CloseSDLJoysticks(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); -} - -class SDLButton final : public Input::ButtonDevice { -public: - explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) - : joystick(std::move(joystick_)), button(button_) {} - - bool GetStatus() const override { - return joystick->GetButton(button); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int button; -}; - -class SDLDirectionButton final : public Input::ButtonDevice { -public: - explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - bool GetStatus() const override { - return joystick->GetHatDirection(hat, direction); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisButton final : public Input::ButtonDevice { -public: - explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - bool GetStatus() const override { - float axis_value = joystick->GetAxis(axis); - if (trigger_if_greater) - return axis_value > threshold; - return axis_value < threshold; - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLAnalog final : public Input::AnalogDevice { -public: - SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} - - std::tuple<float, float> GetStatus() const override { - return joystick->GetAnalog(axis_x, axis_y); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int axis_x; - int axis_y; -}; - -/// A button device factory that creates button devices from SDL joystick -class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { -public: - /** - * Creates a button device from a joystick button - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type to bind - * - "button"(optional): the index of the button to bind - * - "hat"(optional): the index of the hat to bind as direction buttons - * - "axis"(optional): the index of the axis to bind - * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", - * "down", "left" or "right" - * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is - * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis - * value is greater than the threshold; "-" means the button is triggered when the axis - * value is smaller than the threshold - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = GetSDLJoystickByGUID(guid, port); - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique<SDLDirectionButton>(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.5f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction '{}'", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->SetAxis(axis, 0); - return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->SetButton(button, false); - return std::make_unique<SDLButton>(joystick, button); - } -}; - -/// An analog device factory that creates analog devices from SDL joystick -class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { -public: - /** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - - auto joystick = GetSDLJoystickByGUID(guid, port); - - // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->SetAxis(axis_x, 0); - joystick->SetAxis(axis_y, 0); - return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y); - } -}; - -void Init() { - using namespace Input; - RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); - RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); - polling = false; - initialized = true; -} - -void Shutdown() { - if (initialized) { - using namespace Input; - UnregisterFactory<ButtonDevice>("sdl"); - UnregisterFactory<AnalogDevice>("sdl"); - initialized = false; - } -} - -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { - Common::ParamPackage params({{"engine", "sdl"}}); - switch (event.type) { - case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis", event.jaxis.axis); - if (event.jaxis.value > 0) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.5"); - } - break; - } - case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("button", event.jbutton.button); - break; - } - case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("hat", event.jhat.hat); - switch (event.jhat.value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - break; - } - } - return params; -} - -namespace Polling { - -class SDLPoller : public InputCommon::Polling::DevicePoller { -public: - void Start() override { - event_queue.Clear(); - polling = true; - } - - void Stop() override { - polling = false; - } -}; - -class SDLButtonPoller final : public SDLPoller { -public: - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (event_queue.Pop(event)) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(event); - } - } - return {}; - } -}; - -class SDLAnalogPoller final : public SDLPoller { -public: - void Start() override { - SDLPoller::Start(); - - // Reset stored axes - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - } - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second SDL event. The axes also must be from the same joystick. - int axis = event.jaxis.axis; - if (analog_xaxis == -1) { - analog_xaxis = axis; - analog_axes_joystick = event.jaxis.which; - } else if (analog_yaxis == -1 && analog_xaxis != axis && - analog_axes_joystick == event.jaxis.which) { - analog_yaxis = axis; - } - } - Common::ParamPackage params; - if (analog_xaxis != -1 && analog_yaxis != -1) { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("engine", "sdl"); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis_x", analog_xaxis); - params.Set("axis_y", analog_yaxis); - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - return params; - } - return params; - } - -private: - int analog_xaxis = -1; - int analog_yaxis = -1; - SDL_JoystickID analog_axes_joystick = -1; -}; - -std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers( - InputCommon::Polling::DeviceType type) { - std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers; - switch (type) { - case InputCommon::Polling::DeviceType::Analog: - pollers.push_back(std::make_unique<SDLAnalogPoller>()); - break; - case InputCommon::Polling::DeviceType::Button: - pollers.push_back(std::make_unique<SDLButtonPoller>()); - break; - } - return pollers; +std::unique_ptr<State> Init() { +#ifdef HAVE_SDL2 + return std::make_unique<SDLState>(); +#else + return std::make_unique<NullState>(); +#endif } -} // namespace Polling -} // namespace SDL -} // namespace InputCommon +} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 0206860d3..d7f24c68a 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,45 +7,38 @@ #include <memory> #include <vector> #include "core/frontend/input.h" +#include "input_common/main.h" union SDL_Event; + namespace Common { class ParamPackage; -} -namespace InputCommon { -namespace Polling { +} // namespace Common + +namespace InputCommon::Polling { class DevicePoller; enum class DeviceType; -} // namespace Polling -} // namespace InputCommon - -namespace InputCommon { -namespace SDL { - -/// Initializes and registers SDL device factories -void Init(); - -/// Unresisters SDL device factories and shut them down. -void Shutdown(); +} // namespace InputCommon::Polling -/// Needs to be called before SDL_QuitSubSystem. -void CloseSDLJoysticks(); +namespace InputCommon::SDL { -/// Handle SDL_Events for joysticks from SDL_PollEvent -void HandleGameControllerEvent(const SDL_Event& event); +class State { +public: + using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>; -/// A Loop that calls HandleGameControllerEvent until Shutdown is called -void PollLoop(); + /// Unregisters SDL device factories and shut them down. + virtual ~State() = default; -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); + virtual Pollers GetPollers(Polling::DeviceType type) = 0; +}; -namespace Polling { +class NullState : public State { +public: + Pollers GetPollers(Polling::DeviceType type) override { + return {}; + } +}; -/// Get all DevicePoller that use the SDL backend for a specific device type -std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers( - InputCommon::Polling::DeviceType type); +std::unique_ptr<State> Init(); -} // namespace Polling -} // namespace SDL -} // namespace InputCommon +} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp new file mode 100644 index 000000000..b132d77f5 --- /dev/null +++ b/src/input_common/sdl/sdl_impl.cpp @@ -0,0 +1,669 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <atomic> +#include <cmath> +#include <functional> +#include <iterator> +#include <mutex> +#include <string> +#include <thread> +#include <tuple> +#include <unordered_map> +#include <utility> +#include <vector> +#include <SDL.h> +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.h" +#include "common/threadsafe_queue.h" +#include "core/frontend/input.h" +#include "input_common/sdl/sdl_impl.h" + +namespace InputCommon { + +namespace SDL { + +static std::string GetGUID(SDL_Joystick* joystick) { + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + char guid_str[33]; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); + return guid_str; +} + +/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice +static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); + +static int SDLEventWatcher(void* userdata, SDL_Event* event) { + SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata); + // Don't handle the event if we are configuring + if (sdl_state->polling) { + sdl_state->event_queue.Push(*event); + } else { + sdl_state->HandleGameControllerEvent(*event); + } + return 0; +} + +class SDLJoystick { +public: + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} + + void SetButton(int button, bool value) { + std::lock_guard<std::mutex> lock(mutex); + state.buttons[button] = value; + } + + bool GetButton(int button) const { + std::lock_guard<std::mutex> lock(mutex); + return state.buttons.at(button); + } + + void SetAxis(int axis, Sint16 value) { + std::lock_guard<std::mutex> lock(mutex); + state.axes[axis] = value; + } + + float GetAxis(int axis) const { + std::lock_guard<std::mutex> lock(mutex); + return state.axes.at(axis) / 32767.0f; + } + + std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { + float x = GetAxis(axis_x); + float y = GetAxis(axis_y); + y = -y; // 3DS uses an y-axis inverse from SDL + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return std::make_tuple(x, y); + } + + void SetHat(int hat, Uint8 direction) { + std::lock_guard<std::mutex> lock(mutex); + state.hats[hat] = direction; + } + + bool GetHatDirection(int hat, Uint8 direction) const { + std::lock_guard<std::mutex> lock(mutex); + return (state.hats.at(hat) & direction) != 0; + } + /** + * The guid of the joystick + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); + } + + void SetSDLJoystick(SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { + sdl_joystick = + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter); + } + +private: + struct State { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, Sint16> axes; + std::unordered_map<int, Uint8> hats; + } state; + std::string guid; + int port; + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; + mutable std::mutex mutex; +}; + +/** + * Get the nth joystick with the corresponding GUID + */ +std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + const auto it = joystick_map.find(guid); + if (it != joystick_map.end()) { + while (it->second.size() <= port) { + auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr, + [](SDL_Joystick*) {}); + it->second.emplace_back(std::move(joystick)); + } + return it->second[port]; + } + auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +/** + * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie + * it to a SDLJoystick with the same guid and that port + */ +std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const std::string guid = GetGUID(sdl_joystick); + std::lock_guard<std::mutex> lock(joystick_map_mutex); + auto map_it = joystick_map.find(guid); + if (map_it != joystick_map.end()) { + auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { + return sdl_joystick == joystick->GetSDLJoystick(); + }); + if (vec_it != map_it->second.end()) { + // This is the common case: There is already an existing SDL_Joystick maped to a + // SDLJoystick. return the SDLJoystick + return *vec_it; + } + // Search for a SDLJoystick without a mapped SDL_Joystick... + auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [](const std::shared_ptr<SDLJoystick>& joystick) { + return !joystick->GetSDLJoystick(); + }); + if (nullptr_it != map_it->second.end()) { + // ... and map it + (*nullptr_it)->SetSDLJoystick(sdl_joystick); + return *nullptr_it; + } + // There is no SDLJoystick without a mapped SDL_Joystick + // Create a new SDLJoystick + auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick); + return map_it->second.emplace_back(std::move(joystick)); + } + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +void SDLState::InitJoystick(int joystick_index) { + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + if (!sdl_joystick) { + LOG_ERROR(Input, "failed to open joystick {}", joystick_index); + return; + } + std::string guid = GetGUID(sdl_joystick); + std::lock_guard<std::mutex> lock(joystick_map_mutex); + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + auto& joystick_guid_list = joystick_map[guid]; + const auto it = std::find_if( + joystick_guid_list.begin(), joystick_guid_list.end(), + [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); + if (it != joystick_guid_list.end()) { + (*it)->SetSDLJoystick(sdl_joystick); + return; + } + auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { + std::string guid = GetGUID(sdl_joystick); + std::shared_ptr<SDLJoystick> joystick; + { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + // This call to guid is safe since the joystick is guaranteed to be in the map + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + joystick = *joystick_it; + } + // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback + // which locks the mutex again + joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); +} + +void SDLState::HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + joystick->SetButton(event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + joystick->SetButton(event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { + joystick->SetHat(event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { + joystick->SetAxis(event.jaxis.axis, event.jaxis.value); + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void SDLState::CloseJoysticks() { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + joystick_map.clear(); +} + +class SDLButton final : public Input::ButtonDevice { +public: + explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) + : joystick(std::move(joystick_)), button(button_) {} + + bool GetStatus() const override { + return joystick->GetButton(button); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int button; +}; + +class SDLDirectionButton final : public Input::ButtonDevice { +public: + explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) + : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} + + bool GetStatus() const override { + return joystick->GetHatDirection(hat, direction); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int hat; + Uint8 direction; +}; + +class SDLAxisButton final : public Input::ButtonDevice { +public: + explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, + bool trigger_if_greater_) + : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), + trigger_if_greater(trigger_if_greater_) {} + + bool GetStatus() const override { + float axis_value = joystick->GetAxis(axis); + if (trigger_if_greater) + return axis_value > threshold; + return axis_value < threshold; + } + +private: + std::shared_ptr<SDLJoystick> joystick; + int axis; + float threshold; + bool trigger_if_greater; +}; + +class SDLAnalog final : public Input::AnalogDevice { +public: + SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) + : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)); + } + return std::make_tuple<float, float>(0.0f, 0.0f); + } + +private: + std::shared_ptr<SDLJoystick> joystick; + const int axis_x; + const int axis_y; + const float deadzone; +}; + +/// A button device factory that creates button devices from SDL joystick +class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit SDLButtonFactory(SDLState& state_) : state(state_) {} + + /** + * Creates a button device from a joystick button + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type to bind + * - "button"(optional): the index of the button to bind + * - "hat"(optional): the index of the hat to bind as direction buttons + * - "axis"(optional): the index of the axis to bind + * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", + * "down", "left" or "right" + * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is + * triggered if the axis value crosses + * - "direction"(only used for axis): "+" means the button is triggered when the axis + * value is greater than the threshold; "-" means the button is triggered when the axis + * value is smaller than the threshold + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + + auto joystick = state.GetSDLJoystickByGUID(guid, port); + + if (params.Has("hat")) { + const int hat = params.Get("hat", 0); + const std::string direction_name = params.Get("direction", ""); + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + // This is necessary so accessing GetHat with hat won't crash + joystick->SetHat(hat, SDL_HAT_CENTERED); + return std::make_unique<SDLDirectionButton>(joystick, hat, direction); + } + + if (params.Has("axis")) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.5f); + const std::string direction_name = params.Get("direction", ""); + bool trigger_if_greater; + if (direction_name == "+") { + trigger_if_greater = true; + } else if (direction_name == "-") { + trigger_if_greater = false; + } else { + trigger_if_greater = true; + LOG_ERROR(Input, "Unknown direction {}", direction_name); + } + // This is necessary so accessing GetAxis with axis won't crash + joystick->SetAxis(axis, 0); + return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); + } + + const int button = params.Get("button", 0); + // This is necessary so accessing GetButton with button won't crash + joystick->SetButton(button, false); + return std::make_unique<SDLButton>(joystick, button); + } + +private: + SDLState& state; +}; + +/// An analog device factory that creates analog devices from SDL joystick +class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} + /** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); + + auto joystick = state.GetSDLJoystickByGUID(guid, port); + + // This is necessary so accessing GetAxis with axis_x and axis_y won't crash + joystick->SetAxis(axis_x, 0); + joystick->SetAxis(axis_y, 0); + return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); + } + +private: + SDLState& state; +}; + +SDLState::SDLState() { + using namespace Input; + RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); + RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); + + // If the frontend is going to manage the event loop, then we dont start one here + start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); + if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); + return; + } + if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { + LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError()); + } + + SDL_AddEventWatch(&SDLEventWatcher, this); + + initialized = true; + if (start_thread) { + poll_thread = std::thread([this] { + using namespace std::chrono_literals; + while (initialized) { + SDL_PumpEvents(); + std::this_thread::sleep_for(10ms); + } + }); + } + // Because the events for joystick connection happens before we have our event watcher added, we + // can just open all the joysticks right here + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + InitJoystick(i); + } +} + +SDLState::~SDLState() { + using namespace Input; + UnregisterFactory<ButtonDevice>("sdl"); + UnregisterFactory<AnalogDevice>("sdl"); + + CloseJoysticks(); + SDL_DelEventWatch(&SDLEventWatcher, this); + + initialized = false; + if (start_thread) { + poll_thread.join(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { + Common::ParamPackage params({{"engine", "sdl"}}); + + switch (event.type) { + case SDL_JOYAXISMOTION: { + auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("axis", event.jaxis.axis); + if (event.jaxis.value > 0) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.5"); + } + break; + } + case SDL_JOYBUTTONUP: { + auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("button", event.jbutton.button); + break; + } + case SDL_JOYHATMOTION: { + auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("hat", event.jhat.hat); + switch (event.jhat.value) { + case SDL_HAT_UP: + params.Set("direction", "up"); + break; + case SDL_HAT_DOWN: + params.Set("direction", "down"); + break; + case SDL_HAT_LEFT: + params.Set("direction", "left"); + break; + case SDL_HAT_RIGHT: + params.Set("direction", "right"); + break; + default: + return {}; + } + break; + } + } + return params; +} + +namespace Polling { + +class SDLPoller : public InputCommon::Polling::DevicePoller { +public: + explicit SDLPoller(SDLState& state_) : state(state_) {} + + void Start() override { + state.event_queue.Clear(); + state.polling = true; + } + + void Stop() override { + state.polling = false; + } + +protected: + SDLState& state; +}; + +class SDLButtonPoller final : public SDLPoller { +public: + explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} + + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (state.event_queue.Pop(event)) { + switch (event.type) { + case SDL_JOYAXISMOTION: + if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + break; + } + case SDL_JOYBUTTONUP: + case SDL_JOYHATMOTION: + return SDLEventToButtonParamPackage(state, event); + } + } + return {}; + } +}; + +class SDLAnalogPoller final : public SDLPoller { +public: + explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} + + void Start() override { + SDLPoller::Start(); + + // Reset stored axes + analog_xaxis = -1; + analog_yaxis = -1; + analog_axes_joystick = -1; + } + + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (state.event_queue.Pop(event)) { + if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second SDL event. The axes also must be from the same joystick. + int axis = event.jaxis.axis; + if (analog_xaxis == -1) { + analog_xaxis = axis; + analog_axes_joystick = event.jaxis.which; + } else if (analog_yaxis == -1 && analog_xaxis != axis && + analog_axes_joystick == event.jaxis.which) { + analog_yaxis = axis; + } + } + Common::ParamPackage params; + if (analog_xaxis != -1 && analog_yaxis != -1) { + auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); + params.Set("engine", "sdl"); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("axis_x", analog_xaxis); + params.Set("axis_y", analog_yaxis); + analog_xaxis = -1; + analog_yaxis = -1; + analog_axes_joystick = -1; + return params; + } + return params; + } + +private: + int analog_xaxis = -1; + int analog_yaxis = -1; + SDL_JoystickID analog_axes_joystick = -1; +}; +} // namespace Polling + +SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { + Pollers pollers; + + switch (type) { + case InputCommon::Polling::DeviceType::Analog: + pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); + break; + case InputCommon::Polling::DeviceType::Button: + pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); + break; + } + + return pollers; +} + +} // namespace SDL +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h new file mode 100644 index 000000000..2579741d6 --- /dev/null +++ b/src/input_common/sdl/sdl_impl.h @@ -0,0 +1,63 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <memory> +#include <thread> +#include "common/threadsafe_queue.h" +#include "input_common/sdl/sdl.h" + +union SDL_Event; +using SDL_Joystick = struct _SDL_Joystick; +using SDL_JoystickID = s32; + +namespace InputCommon::SDL { + +class SDLJoystick; +class SDLButtonFactory; +class SDLAnalogFactory; + +class SDLState : public State { +public: + /// Initializes and registers SDL device factories + SDLState(); + + /// Unregisters SDL device factories and shut them down. + ~SDLState() override; + + /// Handle SDL_Events for joysticks from SDL_PollEvent + void HandleGameControllerEvent(const SDL_Event& event); + + std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); + + /// Get all DevicePoller that use the SDL backend for a specific device type + Pollers GetPollers(Polling::DeviceType type) override; + + /// Used by the Pollers during config + std::atomic<bool> polling = false; + Common::SPSCQueue<SDL_Event> event_queue; + +private: + void InitJoystick(int joystick_index); + void CloseJoystick(SDL_Joystick* sdl_joystick); + + /// Needs to be called before SDL_QuitSubSystem. + void CloseJoysticks(); + + /// Map of GUID of a list of corresponding virtual Joysticks + std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; + std::mutex joystick_map_mutex; + + std::shared_ptr<SDLButtonFactory> button_factory; + std::shared_ptr<SDLAnalogFactory> analog_factory; + + bool start_thread = false; + std::atomic<bool> initialized = false; + + std::thread poll_thread; +}; +} // namespace InputCommon::SDL diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 37f09ce5f..d0284bdf4 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(tests + common/bit_field.cpp common/param_package.cpp common/ring_buffer.cpp core/arm/arm_test_common.cpp diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp new file mode 100644 index 000000000..8ca1889f9 --- /dev/null +++ b/src/tests/common/bit_field.cpp @@ -0,0 +1,90 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstring> +#include <type_traits> +#include <catch2/catch.hpp> +#include "common/bit_field.h" + +TEST_CASE("BitField", "[common]") { + enum class TestEnum : u32 { + A = 0b10111101, + B = 0b10101110, + C = 0b00001111, + }; + + union LEBitField { + u32_le raw; + BitField<0, 6, u32> a; + BitField<6, 4, s32> b; + BitField<10, 8, TestEnum> c; + BitField<18, 14, u32> d; + } le_bitfield; + + union BEBitField { + u32_be raw; + BitFieldBE<0, 6, u32> a; + BitFieldBE<6, 4, s32> b; + BitFieldBE<10, 8, TestEnum> c; + BitFieldBE<18, 14, u32> d; + } be_bitfield; + + static_assert(sizeof(LEBitField) == sizeof(u32)); + static_assert(sizeof(BEBitField) == sizeof(u32)); + static_assert(std::is_trivially_copyable_v<LEBitField>); + static_assert(std::is_trivially_copyable_v<BEBitField>); + + std::array<u8, 4> raw{{ + 0b01101100, + 0b11110110, + 0b10111010, + 0b11101100, + }}; + + std::memcpy(&le_bitfield, &raw, sizeof(raw)); + std::memcpy(&be_bitfield, &raw, sizeof(raw)); + + // bit fields: 11101100101110'10111101'1001'101100 + REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100); + REQUIRE(le_bitfield.a == 0b101100); + REQUIRE(le_bitfield.b == -7); // 1001 as two's complement + REQUIRE(le_bitfield.c == TestEnum::A); + REQUIRE(le_bitfield.d == 0b11101100101110); + + le_bitfield.a.Assign(0b000111); + le_bitfield.b.Assign(-1); + le_bitfield.c.Assign(TestEnum::C); + le_bitfield.d.Assign(0b01010101010101); + std::memcpy(&raw, &le_bitfield, sizeof(raw)); + // bit fields: 01010101010101'00001111'1111'000111 + REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111); + REQUIRE(raw == std::array<u8, 4>{{ + 0b11000111, + 0b00111111, + 0b01010100, + 0b01010101, + }}); + + // bit fields: 01101100111101'10101110'1011'101100 + REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100); + REQUIRE(be_bitfield.a == 0b101100); + REQUIRE(be_bitfield.b == -5); // 1011 as two's complement + REQUIRE(be_bitfield.c == TestEnum::B); + REQUIRE(be_bitfield.d == 0b01101100111101); + + be_bitfield.a.Assign(0b000111); + be_bitfield.b.Assign(-1); + be_bitfield.c.Assign(TestEnum::C); + be_bitfield.d.Assign(0b01010101010101); + std::memcpy(&raw, &be_bitfield, sizeof(raw)); + // bit fields: 01010101010101'00001111'1111'000111 + REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111); + REQUIRE(raw == std::array<u8, 4>{{ + 0b01010101, + 0b01010100, + 0b00111111, + 0b11000111, + }}); +} diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index ea27ef90d..3e1a735c3 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -4,6 +4,7 @@ #include <algorithm> +#include "common/page_table.h" #include "core/core.h" #include "core/hle/kernel/process.h" #include "core/memory.h" @@ -15,14 +16,14 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} { - auto process = Kernel::Process::Create(kernel, ""); + auto process = Kernel::Process::Create(Core::System::GetInstance(), ""); kernel.MakeCurrentProcess(process.get()); page_table = &process->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); page_table->special_regions.clear(); std::fill(page_table->attributes.begin(), page_table->attributes.end(), - Memory::PageType::Unmapped); + Common::PageType::Unmapped); Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h index 0b7539601..d145dbfcc 100644 --- a/src/tests/core/arm/arm_test_common.h +++ b/src/tests/core/arm/arm_test_common.h @@ -9,10 +9,10 @@ #include <vector> #include "common/common_types.h" +#include "common/memory_hook.h" #include "core/hle/kernel/kernel.h" -#include "core/memory_hook.h" -namespace Memory { +namespace Common { struct PageTable; } @@ -58,7 +58,7 @@ public: private: friend struct TestMemory; - struct TestMemory final : Memory::MemoryHook { + struct TestMemory final : Common::MemoryHook { explicit TestMemory(TestEnvironment* env_) : env(env_) {} TestEnvironment* env; @@ -86,7 +86,7 @@ private: bool mutable_memory; std::shared_ptr<TestMemory> test_memory; std::vector<WriteRecord> write_records; - Memory::PageTable* page_table = nullptr; + Common::PageTable* page_table = nullptr; Kernel::KernelCore kernel; }; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index dac992d44..14b76680f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -80,6 +80,7 @@ add_library(video_core STATIC shader/decode/hfma2.cpp shader/decode/conversion.cpp shader/decode/memory.cpp + shader/decode/texture.cpp shader/decode/float_set_predicate.cpp shader/decode/integer_set_predicate.cpp shader/decode/half_set_predicate.cpp @@ -122,6 +123,8 @@ if (ENABLE_VULKAN) renderer_vulkan/vk_memory_manager.h renderer_vulkan/vk_resource_manager.cpp renderer_vulkan/vk_resource_manager.h + renderer_vulkan/vk_sampler_cache.cpp + renderer_vulkan/vk_sampler_cache.h renderer_vulkan/vk_scheduler.cpp renderer_vulkan/vk_scheduler.h renderer_vulkan/vk_stream_buffer.cpp diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index bff1a37ff..8b1bea1ae 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -55,12 +55,9 @@ bool DmaPusher::Step() { } // Push buffer non-empty, read a word - const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get); - ASSERT_MSG(address, "Invalid GPU address"); - command_headers.resize(command_list_header.size); - - Memory::ReadBlock(*address, command_headers.data(), command_list_header.size * sizeof(u32)); + gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(), + command_list_header.size * sizeof(u32)); for (const CommandHeader& command_header : command_headers) { diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 27a36348c..6ab06518f 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -9,7 +9,6 @@ #include "common/bit_field.h" #include "common/common_types.h" -#include "video_core/memory_manager.h" namespace Tegra { diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index aae2a4019..e259bf46b 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -9,6 +9,7 @@ #include "video_core/engines/kepler_memory.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" namespace Tegra::Engines { @@ -40,17 +41,13 @@ void KeplerMemory::ProcessData(u32 data) { ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported"); ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0); - const GPUVAddr address = regs.dest.Address(); - const auto dest_address = - memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32)); - ASSERT_MSG(dest_address, "Invalid GPU address"); - // We have to invalidate the destination region to evict any outdated surfaces from the cache. - // We do this before actually writing the new data because the destination address might contain - // a dirty surface that will have to be written back to memory. - Core::System::GetInstance().GPU().InvalidateRegion(*dest_address, sizeof(u32)); + // We do this before actually writing the new data because the destination address might + // contain a dirty surface that will have to be written back to memory. + const GPUVAddr address{regs.dest.Address() + state.write_offset * sizeof(u32)}; + rasterizer.InvalidateRegion(ToCacheAddr(memory_manager.GetPointer(address)), sizeof(u32)); + memory_manager.Write<u32>(address, data); - Memory::Write32(*dest_address, data); system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); state.write_offset++; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 144e7fa82..defcfbd3f 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -270,11 +270,9 @@ void Maxwell3D::ProcessMacroBind(u32 data) { } void Maxwell3D::ProcessQueryGet() { - GPUVAddr sequence_address = regs.query.QueryAddress(); + const 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. - const auto address = memory_manager.GpuToCpuAddress(sequence_address); - ASSERT_MSG(address, "Invalid GPU address"); // TODO(Subv): Support the other query units. ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop, @@ -309,7 +307,7 @@ void Maxwell3D::ProcessQueryGet() { // Write the current query sequence to the sequence address. // TODO(Subv): Find out what happens if you use a long query type but mark it as a short // query. - Memory::Write32(*address, sequence); + memory_manager.Write<u32>(sequence_address, sequence); } else { // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast // GPU, this command may actually take a while to complete in real hardware due to GPU @@ -318,7 +316,7 @@ void Maxwell3D::ProcessQueryGet() { query_result.value = result; // TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming query_result.timestamp = system.CoreTiming().GetTicks(); - Memory::WriteBlock(*address, &query_result, sizeof(query_result)); + memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result)); } dirty_flags.OnMemoryWrite(); break; @@ -393,10 +391,12 @@ void Maxwell3D::ProcessCBData(u32 value) { // Don't allow writing past the end of the buffer. ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size); - const auto address = memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos); - ASSERT_MSG(address, "Invalid GPU address"); + const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos}; + + u8* ptr{memory_manager.GetPointer(address)}; + rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32)); + memory_manager.Write<u32>(address, value); - Memory::Write32(*address, value); dirty_flags.OnMemoryWrite(); // Increment the current buffer position. @@ -404,14 +404,10 @@ void Maxwell3D::ProcessCBData(u32 value) { } Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { - const GPUVAddr tic_base_address = regs.tic.TICAddress(); - - const GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry); - const auto tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu); - ASSERT_MSG(tic_address_cpu, "Invalid GPU address"); + const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)}; Texture::TICEntry tic_entry; - Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + memory_manager.ReadBlock(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear || tic_entry.header_version == Texture::TICHeaderVersion::Pitch, @@ -429,14 +425,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { } Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { - const GPUVAddr tsc_base_address = regs.tsc.TSCAddress(); - - const GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry); - const auto tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu); - ASSERT_MSG(tsc_address_cpu, "Invalid GPU address"); + const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)}; Texture::TSCEntry tsc_entry; - Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry)); + memory_manager.ReadBlock(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); return tsc_entry; } @@ -455,10 +447,7 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) { - const auto address = memory_manager.GpuToCpuAddress(current_texture); - ASSERT_MSG(address, "Invalid GPU address"); - - const Texture::TextureHandle tex_handle{Memory::Read32(*address)}; + const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(current_texture)}; Texture::FullTextureInfo tex_info{}; // TODO(Subv): Use the shader to determine which textures are actually accessed. @@ -493,10 +482,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage, ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size); - const auto tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address); - ASSERT_MSG(tex_address_cpu, "Invalid GPU address"); - - const Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)}; + const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)}; Texture::FullTextureInfo tex_info{}; tex_info.index = static_cast<u32>(offset); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 9dfea5999..5cca5c29a 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -9,6 +9,7 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" #include "video_core/textures/decoders.h" namespace Tegra::Engines { @@ -42,11 +43,6 @@ void MaxwellDMA::HandleCopy() { const GPUVAddr source = regs.src_address.Address(); const GPUVAddr dest = regs.dst_address.Address(); - const auto source_cpu = memory_manager.GpuToCpuAddress(source); - const auto dest_cpu = memory_manager.GpuToCpuAddress(dest); - ASSERT_MSG(source_cpu, "Invalid source GPU address"); - ASSERT_MSG(dest_cpu, "Invalid destination GPU address"); - // TODO(Subv): Perform more research and implement all features of this engine. ASSERT(regs.exec.enable_swizzle == 0); ASSERT(regs.exec.query_mode == Regs::QueryMode::None); @@ -69,7 +65,7 @@ void MaxwellDMA::HandleCopy() { // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count, // y_count). if (!regs.exec.enable_2d) { - Memory::CopyBlock(*dest_cpu, *source_cpu, regs.x_count); + memory_manager.CopyBlock(dest, source, regs.x_count); return; } @@ -78,9 +74,9 @@ void MaxwellDMA::HandleCopy() { // rectangle. There is no need to manually flush/invalidate the regions because // CopyBlock does that for us. for (u32 line = 0; line < regs.y_count; ++line) { - const VAddr source_line = *source_cpu + line * regs.src_pitch; - const VAddr dest_line = *dest_cpu + line * regs.dst_pitch; - Memory::CopyBlock(dest_line, source_line, regs.x_count); + const GPUVAddr source_line = source + line * regs.src_pitch; + const GPUVAddr dest_line = dest + line * regs.dst_pitch; + memory_manager.CopyBlock(dest_line, source_line, regs.x_count); } return; } @@ -89,15 +85,28 @@ void MaxwellDMA::HandleCopy() { const std::size_t copy_size = regs.x_count * regs.y_count; + auto source_ptr{memory_manager.GetPointer(source)}; + auto dst_ptr{memory_manager.GetPointer(dest)}; + + if (!source_ptr) { + LOG_ERROR(HW_GPU, "source_ptr is invalid"); + return; + } + + if (!dst_ptr) { + LOG_ERROR(HW_GPU, "dst_ptr is invalid"); + return; + } + const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) { // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated // copying. - Core::System::GetInstance().GPU().FlushRegion(*source_cpu, src_size); + rasterizer.FlushRegion(ToCacheAddr(source_ptr), src_size); // We have to invalidate the destination region to evict any outdated surfaces from the // cache. We do this before actually writing the new data because the destination address // might contain a dirty surface that will have to be written back to memory. - Core::System::GetInstance().GPU().InvalidateRegion(*dest_cpu, dst_size); + rasterizer.InvalidateRegion(ToCacheAddr(dst_ptr), dst_size); }; if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { @@ -110,8 +119,8 @@ void MaxwellDMA::HandleCopy() { copy_size * src_bytes_per_pixel); Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch, - regs.src_params.size_x, src_bytes_per_pixel, *source_cpu, - *dest_cpu, regs.src_params.BlockHeight(), regs.src_params.pos_x, + regs.src_params.size_x, src_bytes_per_pixel, source_ptr, dst_ptr, + regs.src_params.BlockHeight(), regs.src_params.pos_x, regs.src_params.pos_y); } else { ASSERT(regs.dst_params.size_z == 1); @@ -124,7 +133,7 @@ void MaxwellDMA::HandleCopy() { // If the input is linear and the output is tiled, swizzle the input and copy it over. Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, - src_bpp, *dest_cpu, *source_cpu, regs.dst_params.BlockHeight()); + src_bpp, dst_ptr, source_ptr, regs.dst_params.BlockHeight()); } } diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index c7eb15b6a..7f613370b 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -324,11 +324,11 @@ enum class TextureQueryType : u64 { enum class TextureProcessMode : u64 { None = 0, - LZ = 1, // Unknown, appears to be the same as none. + LZ = 1, // Load LOD of zero. LB = 2, // Load Bias. - LL = 3, // Load LOD (LevelOfDetail) - LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB - LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL + LL = 3, // Load LOD. + LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB. + LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL. }; enum class TextureMiscMode : u64 { @@ -1445,6 +1445,7 @@ public: Flow, Synch, Memory, + Texture, FloatSet, FloatSetPredicate, IntegerSet, @@ -1575,14 +1576,14 @@ private: INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"), INST("1110111011010---", Id::LDG, Type::Memory, "LDG"), INST("1110111011011---", Id::STG, Type::Memory, "STG"), - INST("110000----111---", Id::TEX, Type::Memory, "TEX"), - INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"), - INST("1101-00---------", Id::TEXS, Type::Memory, "TEXS"), - INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"), - INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"), - INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"), - INST("110111110110----", Id::TMML_B, Type::Memory, "TMML_B"), - INST("1101111101011---", Id::TMML, Type::Memory, "TMML"), + INST("110000----111---", Id::TEX, Type::Texture, "TEX"), + INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"), + INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"), + INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"), + INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"), + INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"), + INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), + INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 08abf8ac9..267a03f2d 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -12,6 +12,7 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/gpu.h" +#include "video_core/memory_manager.h" #include "video_core/renderer_base.h" namespace Tegra { @@ -274,7 +275,6 @@ void GPU::ProcessSemaphoreTriggerMethod() { const auto op = static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask); if (op == GpuSemaphoreOperation::WriteLong) { - auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress()); struct Block { u32 sequence; u32 zeros = 0; @@ -286,11 +286,9 @@ void GPU::ProcessSemaphoreTriggerMethod() { // TODO(Kmather73): Generate a real GPU timestamp and write it here instead of // CoreTiming block.timestamp = Core::System::GetInstance().CoreTiming().GetTicks(); - Memory::WriteBlock(*address, &block, sizeof(block)); + memory_manager->WriteBlock(regs.smaphore_address.SmaphoreAddress(), &block, sizeof(block)); } else { - const auto address = - memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress()); - const u32 word = Memory::Read32(*address); + const u32 word{memory_manager->Read<u32>(regs.smaphore_address.SmaphoreAddress())}; if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) || (op == GpuSemaphoreOperation::AcquireGequal && static_cast<s32>(word - regs.semaphore_sequence) > 0) || @@ -317,13 +315,11 @@ void GPU::ProcessSemaphoreTriggerMethod() { } void GPU::ProcessSemaphoreRelease() { - const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress()); - Memory::Write32(*address, regs.semaphore_release); + memory_manager->Write<u32>(regs.smaphore_address.SmaphoreAddress(), regs.semaphore_release); } void GPU::ProcessSemaphoreAcquire() { - const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress()); - const u32 word = Memory::Read32(*address); + const u32 word = memory_manager->Read<u32>(regs.smaphore_address.SmaphoreAddress()); const auto value = regs.semaphore_acquire; if (word != value) { regs.acquire_active = true; diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 56a203275..c1830ac8d 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -9,7 +9,11 @@ #include "common/common_types.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "video_core/dma_pusher.h" -#include "video_core/memory_manager.h" + +using CacheAddr = std::uintptr_t; +inline CacheAddr ToCacheAddr(const void* host_ptr) { + return reinterpret_cast<CacheAddr>(host_ptr); +} namespace Core { class System; @@ -119,6 +123,8 @@ enum class EngineID { MAXWELL_DMA_COPY_A = 0xB0B5, }; +class MemoryManager; + class GPU { public: explicit GPU(Core::System& system, VideoCore::RendererBase& renderer); @@ -209,13 +215,13 @@ public: std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0; /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory - virtual void FlushRegion(VAddr addr, u64 size) = 0; + virtual void FlushRegion(CacheAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be invalidated - virtual void InvalidateRegion(VAddr addr, u64 size) = 0; + virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be flushed and invalidated - virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; + virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0; private: void ProcessBindMethod(const MethodCall& method_call); @@ -239,9 +245,8 @@ protected: private: std::unique_ptr<Tegra::MemoryManager> memory_manager; - /// Mapping of command subchannels to their bound engine ids. + /// Mapping of command subchannels to their bound engine ids std::array<EngineID, 8> bound_engines = {}; - /// 3D engine std::unique_ptr<Engines::Maxwell3D> maxwell_3d; /// 2D engine diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp index ad0a747e3..8b355cf7b 100644 --- a/src/video_core/gpu_asynch.cpp +++ b/src/video_core/gpu_asynch.cpp @@ -22,15 +22,15 @@ void GPUAsynch::SwapBuffers( gpu_thread.SwapBuffers(std::move(framebuffer)); } -void GPUAsynch::FlushRegion(VAddr addr, u64 size) { +void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) { gpu_thread.FlushRegion(addr, size); } -void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) { +void GPUAsynch::InvalidateRegion(CacheAddr addr, u64 size) { gpu_thread.InvalidateRegion(addr, size); } -void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) { +void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { gpu_thread.FlushAndInvalidateRegion(addr, size); } diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h index e6a807aba..1dcc61a6c 100644 --- a/src/video_core/gpu_asynch.h +++ b/src/video_core/gpu_asynch.h @@ -26,9 +26,9 @@ public: void PushGPUEntries(Tegra::CommandList&& entries) override; void SwapBuffers( std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; - void FlushRegion(VAddr addr, u64 size) override; - void InvalidateRegion(VAddr addr, u64 size) override; - void FlushAndInvalidateRegion(VAddr addr, u64 size) override; + void FlushRegion(CacheAddr addr, u64 size) override; + void InvalidateRegion(CacheAddr addr, u64 size) override; + void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; private: GPUThread::ThreadManager gpu_thread; diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp index 4c00b96c7..2cfc900ed 100644 --- a/src/video_core/gpu_synch.cpp +++ b/src/video_core/gpu_synch.cpp @@ -22,15 +22,15 @@ void GPUSynch::SwapBuffers( renderer.SwapBuffers(std::move(framebuffer)); } -void GPUSynch::FlushRegion(VAddr addr, u64 size) { +void GPUSynch::FlushRegion(CacheAddr addr, u64 size) { renderer.Rasterizer().FlushRegion(addr, size); } -void GPUSynch::InvalidateRegion(VAddr addr, u64 size) { +void GPUSynch::InvalidateRegion(CacheAddr addr, u64 size) { renderer.Rasterizer().InvalidateRegion(addr, size); } -void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) { +void GPUSynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { renderer.Rasterizer().FlushAndInvalidateRegion(addr, size); } diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h index 7d5a241ff..766b5631c 100644 --- a/src/video_core/gpu_synch.h +++ b/src/video_core/gpu_synch.h @@ -21,9 +21,9 @@ public: void PushGPUEntries(Tegra::CommandList&& entries) override; void SwapBuffers( std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; - void FlushRegion(VAddr addr, u64 size) override; - void InvalidateRegion(VAddr addr, u64 size) override; - void FlushAndInvalidateRegion(VAddr addr, u64 size) override; + void FlushRegion(CacheAddr addr, u64 size) override; + void InvalidateRegion(CacheAddr addr, u64 size) override; + void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; }; } // namespace VideoCommon diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index c5bdd2a17..086b2f625 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -5,7 +5,6 @@ #include "common/assert.h" #include "common/microprofile.h" #include "core/frontend/scope_acquire_window_context.h" -#include "core/settings.h" #include "video_core/dma_pusher.h" #include "video_core/gpu.h" #include "video_core/gpu_thread.h" @@ -13,38 +12,13 @@ namespace VideoCommon::GPUThread { -/// Executes a single GPU thread command -static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer, - Tegra::DmaPusher& dma_pusher) { - if (const auto submit_list = std::get_if<SubmitListCommand>(command)) { - dma_pusher.Push(std::move(submit_list->entries)); - dma_pusher.DispatchCalls(); - } else if (const auto data = std::get_if<SwapBuffersCommand>(command)) { - renderer.SwapBuffers(data->framebuffer); - } else if (const auto data = std::get_if<FlushRegionCommand>(command)) { - renderer.Rasterizer().FlushRegion(data->addr, data->size); - } else if (const auto data = std::get_if<InvalidateRegionCommand>(command)) { - renderer.Rasterizer().InvalidateRegion(data->addr, data->size); - } else if (const auto data = std::get_if<FlushAndInvalidateRegionCommand>(command)) { - renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size); - } else { - UNREACHABLE(); - } -} - /// Runs the GPU thread static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher, SynchState& state) { - MicroProfileOnThreadCreate("GpuThread"); - auto WaitForWakeup = [&]() { - std::unique_lock<std::mutex> lock{state.signal_mutex}; - state.signal_condition.wait(lock, [&] { return !state.is_idle || !state.is_running; }); - }; - // Wait for first GPU command before acquiring the window context - WaitForWakeup(); + state.WaitForCommands(); // If emulation was stopped during disk shader loading, abort before trying to acquire context if (!state.is_running) { @@ -53,100 +27,72 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; + CommandDataContainer next; while (state.is_running) { - if (!state.is_running) { - return; - } - - { - // Thread has been woken up, so make the previous write queue the next read queue - std::lock_guard<std::mutex> lock{state.signal_mutex}; - std::swap(state.push_queue, state.pop_queue); - } - - // Execute all of the GPU commands - while (!state.pop_queue->empty()) { - ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher); - state.pop_queue->pop(); + state.WaitForCommands(); + while (!state.queue.Empty()) { + state.queue.Pop(next); + if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) { + dma_pusher.Push(std::move(submit_list->entries)); + dma_pusher.DispatchCalls(); + } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { + state.DecrementFramesCounter(); + renderer.SwapBuffers(std::move(data->framebuffer)); + } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { + renderer.Rasterizer().FlushRegion(data->addr, data->size); + } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { + renderer.Rasterizer().InvalidateRegion(data->addr, data->size); + } else if (const auto data = std::get_if<EndProcessingCommand>(&next.data)) { + return; + } else { + UNREACHABLE(); + } } - - state.UpdateIdleState(); - - // Signal that the GPU thread has finished processing commands - if (state.is_idle) { - state.idle_condition.notify_one(); - } - - // Wait for CPU thread to send more GPU commands - WaitForWakeup(); } } ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher) : renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer), - std::ref(dma_pusher), std::ref(state)}, - thread_id{thread.get_id()} {} + std::ref(dma_pusher), std::ref(state)} {} ThreadManager::~ThreadManager() { - { - // Notify GPU thread that a shutdown is pending - std::lock_guard<std::mutex> lock{state.signal_mutex}; - state.is_running = false; - } - - state.signal_condition.notify_one(); + // Notify GPU thread that a shutdown is pending + PushCommand(EndProcessingCommand()); thread.join(); } void ThreadManager::SubmitList(Tegra::CommandList&& entries) { - if (entries.empty()) { - return; - } - - PushCommand(SubmitListCommand(std::move(entries)), false, false); + PushCommand(SubmitListCommand(std::move(entries))); } void ThreadManager::SwapBuffers( std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { - PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false); + state.IncrementFramesCounter(); + PushCommand(SwapBuffersCommand(std::move(framebuffer))); + state.WaitForFrames(); } -void ThreadManager::FlushRegion(VAddr addr, u64 size) { - // Block the CPU when using accurate emulation - PushCommand(FlushRegionCommand(addr, size), Settings::values.use_accurate_gpu_emulation, false); +void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { + PushCommand(FlushRegionCommand(addr, size)); } -void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { - PushCommand(InvalidateRegionCommand(addr, size), true, true); +void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) { + if (state.queue.Empty()) { + // It's quicker to invalidate a single region on the CPU if the queue is already empty + renderer.Rasterizer().InvalidateRegion(addr, size); + } else { + PushCommand(InvalidateRegionCommand(addr, size)); + } } -void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { +void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { + // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important InvalidateRegion(addr, size); } -void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) { - { - std::lock_guard<std::mutex> lock{state.signal_mutex}; - - if ((allow_on_cpu && state.is_idle) || IsGpuThread()) { - // Execute the command synchronously on the current thread - ExecuteCommand(&command_data, renderer, dma_pusher); - return; - } - - // Push the command to the GPU thread - state.UpdateIdleState(); - state.push_queue->emplace(command_data); - } - - // Signal the GPU thread that commands are pending - state.signal_condition.notify_one(); - - if (wait_for_idle) { - // Wait for the GPU to be idle (all commands to be executed) - std::unique_lock<std::mutex> lock{state.idle_mutex}; - state.idle_condition.wait(lock, [this] { return static_cast<bool>(state.is_idle); }); - } +void ThreadManager::PushCommand(CommandData&& command_data) { + state.queue.Push(CommandDataContainer(std::move(command_data))); + state.SignalCommands(); } } // namespace VideoCommon::GPUThread diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index edb148b14..8cd7db1c6 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -13,6 +13,9 @@ #include <thread> #include <variant> +#include "common/threadsafe_queue.h" +#include "video_core/gpu.h" + namespace Tegra { struct FramebufferConfig; class DmaPusher; @@ -24,6 +27,9 @@ class RendererBase; namespace VideoCommon::GPUThread { +/// Command to signal to the GPU thread that processing has ended +struct EndProcessingCommand final {}; + /// Command to signal to the GPU thread that a command list is ready for processing struct SubmitListCommand final { explicit SubmitListCommand(Tegra::CommandList&& entries) : entries{std::move(entries)} {} @@ -36,59 +42,110 @@ struct SwapBuffersCommand final { explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer) : framebuffer{std::move(framebuffer)} {} - std::optional<const Tegra::FramebufferConfig> framebuffer; + std::optional<Tegra::FramebufferConfig> framebuffer; }; /// Command to signal to the GPU thread to flush a region struct FlushRegionCommand final { - explicit constexpr FlushRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {} + explicit constexpr FlushRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {} - const VAddr addr; - const u64 size; + CacheAddr addr; + u64 size; }; /// Command to signal to the GPU thread to invalidate a region struct InvalidateRegionCommand final { - explicit constexpr InvalidateRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {} + explicit constexpr InvalidateRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {} - const VAddr addr; - const u64 size; + CacheAddr addr; + u64 size; }; /// Command to signal to the GPU thread to flush and invalidate a region struct FlushAndInvalidateRegionCommand final { - explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size) + explicit constexpr FlushAndInvalidateRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {} - const VAddr addr; - const u64 size; + CacheAddr addr; + u64 size; }; -using CommandData = std::variant<SubmitListCommand, SwapBuffersCommand, FlushRegionCommand, - InvalidateRegionCommand, FlushAndInvalidateRegionCommand>; +using CommandData = + std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand, + InvalidateRegionCommand, FlushAndInvalidateRegionCommand>; + +struct CommandDataContainer { + CommandDataContainer() = default; + + CommandDataContainer(CommandData&& data) : data{std::move(data)} {} + + CommandDataContainer& operator=(const CommandDataContainer& t) { + data = std::move(t.data); + return *this; + } + + CommandData data; +}; /// Struct used to synchronize the GPU thread struct SynchState final { - std::atomic<bool> is_running{true}; - std::atomic<bool> is_idle{true}; - std::condition_variable signal_condition; - std::mutex signal_mutex; - std::condition_variable idle_condition; - std::mutex idle_mutex; - - // We use two queues for sending commands to the GPU thread, one for writing (push_queue) to and - // one for reading from (pop_queue). These are swapped whenever the current pop_queue becomes - // empty. This allows for efficient thread-safe access, as it does not require any copies. - - using CommandQueue = std::queue<CommandData>; - std::array<CommandQueue, 2> command_queues; - CommandQueue* push_queue{&command_queues[0]}; - CommandQueue* pop_queue{&command_queues[1]}; - - void UpdateIdleState() { - std::lock_guard<std::mutex> lock{idle_mutex}; - is_idle = command_queues[0].empty() && command_queues[1].empty(); + std::atomic_bool is_running{true}; + std::atomic_int queued_frame_count{}; + std::mutex frames_mutex; + std::mutex commands_mutex; + std::condition_variable commands_condition; + std::condition_variable frames_condition; + + void IncrementFramesCounter() { + std::lock_guard<std::mutex> lock{frames_mutex}; + ++queued_frame_count; + } + + void DecrementFramesCounter() { + { + std::lock_guard<std::mutex> lock{frames_mutex}; + --queued_frame_count; + + if (queued_frame_count) { + return; + } + } + frames_condition.notify_one(); } + + void WaitForFrames() { + { + std::lock_guard<std::mutex> lock{frames_mutex}; + if (!queued_frame_count) { + return; + } + } + + // Wait for the GPU to be idle (all commands to be executed) + { + std::unique_lock<std::mutex> lock{frames_mutex}; + frames_condition.wait(lock, [this] { return !queued_frame_count; }); + } + } + + void SignalCommands() { + { + std::unique_lock<std::mutex> lock{commands_mutex}; + if (queue.Empty()) { + return; + } + } + + commands_condition.notify_one(); + } + + void WaitForCommands() { + std::unique_lock<std::mutex> lock{commands_mutex}; + commands_condition.wait(lock, [this] { return !queue.Empty(); }); + } + + using CommandQueue = Common::SPSCQueue<CommandDataContainer>; + CommandQueue queue; }; /// Class used to manage the GPU thread @@ -105,22 +162,17 @@ public: std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer); /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory - void FlushRegion(VAddr addr, u64 size); + void FlushRegion(CacheAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be invalidated - void InvalidateRegion(VAddr addr, u64 size); + void InvalidateRegion(CacheAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be flushed and invalidated - void FlushAndInvalidateRegion(VAddr addr, u64 size); + void FlushAndInvalidateRegion(CacheAddr addr, u64 size); private: /// Pushes a command to be executed by the GPU thread - void PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu); - - /// Returns true if this is called by the GPU thread - bool IsGpuThread() const { - return std::this_thread::get_id() == thread_id; - } + void PushCommand(CommandData&& command_data); private: SynchState state; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 54abe5298..e76b59842 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -5,181 +5,446 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/gpu.h" #include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" namespace Tegra { MemoryManager::MemoryManager() { - // Mark the first page as reserved, so that 0 is not a valid GPUVAddr. Otherwise, games might - // try to use 0 as a valid address, which is also used to mean nullptr. This fixes a bug with - // Undertale using 0 for a render target. - PageSlot(0) = static_cast<u64>(PageStatus::Reserved); + std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr); + std::fill(page_table.attributes.begin(), page_table.attributes.end(), + Common::PageType::Unmapped); + page_table.Resize(address_space_width); + + // Initialize the map with a single free region covering the entire managed space. + VirtualMemoryArea initial_vma; + initial_vma.size = address_space_end; + vma_map.emplace(initial_vma.base, initial_vma); + + UpdatePageTableForVMA(initial_vma); } GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) { - const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, align, PageStatus::Unmapped)}; + const u64 aligned_size{Common::AlignUp(size, page_size)}; + const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; - ASSERT_MSG(gpu_addr, "unable to find available GPU memory"); + AllocateMemory(gpu_addr, 0, aligned_size); - for (u64 offset{}; offset < size; offset += PAGE_SIZE) { - VAddr& slot{PageSlot(*gpu_addr + offset)}; + return gpu_addr; +} - ASSERT(slot == static_cast<u64>(PageStatus::Unmapped)); +GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) { + const u64 aligned_size{Common::AlignUp(size, page_size)}; - slot = static_cast<u64>(PageStatus::Allocated); - } + AllocateMemory(gpu_addr, 0, aligned_size); - return *gpu_addr; + return gpu_addr; } -GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) { - for (u64 offset{}; offset < size; offset += PAGE_SIZE) { - VAddr& slot{PageSlot(gpu_addr + offset)}; +GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) { + const u64 aligned_size{Common::AlignUp(size, page_size)}; + const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; - ASSERT(slot == static_cast<u64>(PageStatus::Unmapped)); + MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr); - slot = static_cast<u64>(PageStatus::Allocated); - } + return gpu_addr; +} + +GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) { + ASSERT((gpu_addr & page_mask) == 0); + + const u64 aligned_size{Common::AlignUp(size, page_size)}; + + MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr); return gpu_addr; } -GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) { - const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, PAGE_SIZE, PageStatus::Unmapped)}; +GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { + ASSERT((gpu_addr & page_mask) == 0); - ASSERT_MSG(gpu_addr, "unable to find available GPU memory"); + const u64 aligned_size{Common::AlignUp(size, page_size)}; + const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))}; + + Core::System::GetInstance().Renderer().Rasterizer().FlushAndInvalidateRegion(cache_addr, + aligned_size); + UnmapRange(gpu_addr, aligned_size); + + return gpu_addr; +} - for (u64 offset{}; offset < size; offset += PAGE_SIZE) { - VAddr& slot{PageSlot(*gpu_addr + offset)}; +GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) { + // Find the first Free VMA. + const VMAHandle vma_handle{std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) { + if (vma.second.type != VirtualMemoryArea::Type::Unmapped) { + return false; + } - ASSERT(slot == static_cast<u64>(PageStatus::Unmapped)); + const VAddr vma_end{vma.second.base + vma.second.size}; + return vma_end > region_start && vma_end >= region_start + size; + })}; - slot = cpu_addr + offset; + if (vma_handle == vma_map.end()) { + return {}; } - const MappedRegion region{cpu_addr, *gpu_addr, size}; - mapped_regions.push_back(region); + return std::max(region_start, vma_handle->second.base); +} - return *gpu_addr; +bool MemoryManager::IsAddressValid(GPUVAddr addr) const { + return (addr >> page_bits) < page_table.pointers.size(); } -GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) { - ASSERT((gpu_addr & PAGE_MASK) == 0); +std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) { + if (!IsAddressValid(addr)) { + return {}; + } - if (PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Allocated)) { - // Page has been already mapped. In this case, we must find a new area of memory to use that - // is different than the specified one. Super Mario Odyssey hits this scenario when changing - // areas, but we do not want to overwrite the old pages. - // TODO(bunnei): We need to write a hardware test to confirm this behavior. + VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]}; + if (cpu_addr) { + return cpu_addr + (addr & page_mask); + } - LOG_ERROR(HW_GPU, "attempting to map addr 0x{:016X}, which is not available!", gpu_addr); + return {}; +} - const std::optional<GPUVAddr> new_gpu_addr{ - FindFreeBlock(gpu_addr, size, PAGE_SIZE, PageStatus::Allocated)}; +template <typename T> +T MemoryManager::Read(GPUVAddr addr) { + if (!IsAddressValid(addr)) { + return {}; + } - ASSERT_MSG(new_gpu_addr, "unable to find available GPU memory"); + const u8* page_pointer{page_table.pointers[addr >> page_bits]}; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + T value; + std::memcpy(&value, &page_pointer[addr & page_mask], sizeof(T)); + return value; + } - gpu_addr = *new_gpu_addr; + switch (page_table.attributes[addr >> page_bits]) { + case Common::PageType::Unmapped: + LOG_ERROR(HW_GPU, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, addr); + return 0; + case Common::PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr); + break; + default: + UNREACHABLE(); } + return {}; +} - for (u64 offset{}; offset < size; offset += PAGE_SIZE) { - VAddr& slot{PageSlot(gpu_addr + offset)}; +template <typename T> +void MemoryManager::Write(GPUVAddr addr, T data) { + if (!IsAddressValid(addr)) { + return; + } - ASSERT(slot == static_cast<u64>(PageStatus::Allocated)); + u8* page_pointer{page_table.pointers[addr >> page_bits]}; + if (page_pointer) { + // NOTE: Avoid adding any extra logic to this fast-path block + std::memcpy(&page_pointer[addr & page_mask], &data, sizeof(T)); + return; + } - slot = cpu_addr + offset; + switch (page_table.attributes[addr >> page_bits]) { + case Common::PageType::Unmapped: + LOG_ERROR(HW_GPU, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8, + static_cast<u32>(data), addr); + return; + case Common::PageType::Memory: + ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr); + break; + default: + UNREACHABLE(); } +} - const MappedRegion region{cpu_addr, gpu_addr, size}; - mapped_regions.push_back(region); +template u8 MemoryManager::Read<u8>(GPUVAddr addr); +template u16 MemoryManager::Read<u16>(GPUVAddr addr); +template u32 MemoryManager::Read<u32>(GPUVAddr addr); +template u64 MemoryManager::Read<u64>(GPUVAddr addr); +template void MemoryManager::Write<u8>(GPUVAddr addr, u8 data); +template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data); +template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data); +template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data); + +u8* MemoryManager::GetPointer(GPUVAddr addr) { + if (!IsAddressValid(addr)) { + return {}; + } - return gpu_addr; + u8* page_pointer{page_table.pointers[addr >> page_bits]}; + if (page_pointer) { + return page_pointer + (addr & page_mask); + } + + LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr); + return {}; } -GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { - ASSERT((gpu_addr & PAGE_MASK) == 0); +void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) { + std::memcpy(dest_buffer, GetPointer(src_addr), size); +} +void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) { + std::memcpy(GetPointer(dest_addr), src_buffer, size); +} - for (u64 offset{}; offset < size; offset += PAGE_SIZE) { - VAddr& slot{PageSlot(gpu_addr + offset)}; +void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) { + std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size); +} - ASSERT(slot != static_cast<u64>(PageStatus::Allocated) && - slot != static_cast<u64>(PageStatus::Unmapped)); +void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type, + VAddr backing_addr) { + LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size, + (base + size) * page_size); + + const VAddr end{base + size}; + ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}", + base + page_table.pointers.size()); + + std::fill(page_table.attributes.begin() + base, page_table.attributes.begin() + end, type); + + if (memory == nullptr) { + std::fill(page_table.pointers.begin() + base, page_table.pointers.begin() + end, memory); + std::fill(page_table.backing_addr.begin() + base, page_table.backing_addr.begin() + end, + backing_addr); + } else { + while (base != end) { + page_table.pointers[base] = memory; + page_table.backing_addr[base] = backing_addr; + + base += 1; + memory += page_size; + backing_addr += page_size; + } + } +} - slot = static_cast<u64>(PageStatus::Unmapped); +void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) { + ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base); + MapPages(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr); +} + +void MemoryManager::UnmapRegion(GPUVAddr base, u64 size) { + ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base); + MapPages(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped); +} + +bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { + ASSERT(base + size == next.base); + if (type != next.type) { + return {}; + } + if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) { + return {}; + } + if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) { + return {}; + } + return true; +} + +MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const { + if (target >= address_space_end) { + return vma_map.end(); + } else { + return std::prev(vma_map.upper_bound(target)); } +} - // Delete the region mappings that are contained within the unmapped region - mapped_regions.erase(std::remove_if(mapped_regions.begin(), mapped_regions.end(), - [&](const MappedRegion& region) { - return region.gpu_addr <= gpu_addr && - region.gpu_addr + region.size < gpu_addr + size; - }), - mapped_regions.end()); - return gpu_addr; +MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) { + VirtualMemoryArea& vma{vma_handle->second}; + + vma.type = VirtualMemoryArea::Type::Allocated; + vma.backing_addr = 0; + vma.backing_memory = {}; + UpdatePageTableForVMA(vma); + + return MergeAdjacent(vma_handle); } -GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const { - for (const auto& region : mapped_regions) { - const GPUVAddr region_end{region.gpu_addr + region.size}; - if (region_start >= region.gpu_addr && region_start < region_end) { - return region_end; - } +MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset, + u64 size) { + + // This is the appropriately sized VMA that will turn into our allocation. + VMAIter vma_handle{CarveVMA(target, size)}; + VirtualMemoryArea& vma{vma_handle->second}; + + ASSERT(vma.size == size); + + vma.offset = offset; + + return Allocate(vma_handle); +} + +MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size, + VAddr backing_addr) { + // This is the appropriately sized VMA that will turn into our allocation. + VMAIter vma_handle{CarveVMA(target, size)}; + VirtualMemoryArea& vma{vma_handle->second}; + + ASSERT(vma.size == size); + + vma.type = VirtualMemoryArea::Type::Mapped; + vma.backing_memory = memory; + vma.backing_addr = backing_addr; + UpdatePageTableForVMA(vma); + + return MergeAdjacent(vma_handle); +} + +void MemoryManager::UnmapRange(GPUVAddr target, u64 size) { + VMAIter vma{CarveVMARange(target, size)}; + const VAddr target_end{target + size}; + const VMAIter end{vma_map.end()}; + + // The comparison against the end of the range must be done using addresses since VMAs can be + // merged during this process, causing invalidation of the iterators. + while (vma != end && vma->second.base < target_end) { + // Unmapped ranges return to allocated state and can be reused + // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games + vma = std::next(Allocate(vma)); } - return {}; + + ASSERT(FindVMA(target)->second.size >= size); } -std::optional<GPUVAddr> MemoryManager::FindFreeBlock(GPUVAddr region_start, u64 size, u64 align, - PageStatus status) { - GPUVAddr gpu_addr{region_start}; - u64 free_space{}; - align = (align + PAGE_MASK) & ~PAGE_MASK; - - while (gpu_addr + free_space < MAX_ADDRESS) { - if (PageSlot(gpu_addr + free_space) == static_cast<u64>(status)) { - free_space += PAGE_SIZE; - if (free_space >= size) { - return gpu_addr; - } - } else { - gpu_addr += free_space + PAGE_SIZE; - free_space = 0; - gpu_addr = Common::AlignUp(gpu_addr, align); - } +MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) { + // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given + // non-const access to its container. + return vma_map.erase(iter, iter); // Erases an empty range of elements +} + +MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) { + ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size); + ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base); + + VMAIter vma_handle{StripIterConstness(FindVMA(base))}; + if (vma_handle == vma_map.end()) { + // Target address is outside the managed range + return {}; } - return {}; + const VirtualMemoryArea& vma{vma_handle->second}; + if (vma.type == VirtualMemoryArea::Type::Mapped) { + // Region is already allocated + return {}; + } + + const VAddr start_in_vma{base - vma.base}; + const VAddr end_in_vma{start_in_vma + size}; + + ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}", + vma.size, end_in_vma); + + if (end_in_vma < vma.size) { + // Split VMA at the end of the allocated region + SplitVMA(vma_handle, end_in_vma); + } + if (start_in_vma != 0) { + // Split VMA at the start of the allocated region + vma_handle = SplitVMA(vma_handle, start_in_vma); + } + + return vma_handle; } -std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) { - const VAddr base_addr{PageSlot(gpu_addr)}; +MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) { + ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size); + ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target); - if (base_addr == static_cast<u64>(PageStatus::Allocated) || - base_addr == static_cast<u64>(PageStatus::Unmapped) || - base_addr == static_cast<u64>(PageStatus::Reserved)) { + const VAddr target_end{target + size}; + ASSERT(target_end >= target); + ASSERT(size > 0); + + VMAIter begin_vma{StripIterConstness(FindVMA(target))}; + const VMAIter i_end{vma_map.lower_bound(target_end)}; + if (std::any_of(begin_vma, i_end, [](const auto& entry) { + return entry.second.type == VirtualMemoryArea::Type::Unmapped; + })) { return {}; } - return base_addr + (gpu_addr & PAGE_MASK); + if (target != begin_vma->second.base) { + begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base); + } + + VMAIter end_vma{StripIterConstness(FindVMA(target_end))}; + if (end_vma != vma_map.end() && target_end != end_vma->second.base) { + end_vma = SplitVMA(end_vma, target_end - end_vma->second.base); + } + + return begin_vma; } -std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const { - std::vector<GPUVAddr> results; - for (const auto& region : mapped_regions) { - if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) { - const u64 offset{cpu_addr - region.cpu_addr}; - results.push_back(region.gpu_addr + offset); +MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) { + VirtualMemoryArea& old_vma{vma_handle->second}; + VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA + + // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably + // a bug. This restriction might be removed later. + ASSERT(offset_in_vma < old_vma.size); + ASSERT(offset_in_vma > 0); + + old_vma.size = offset_in_vma; + new_vma.base += offset_in_vma; + new_vma.size -= offset_in_vma; + + switch (new_vma.type) { + case VirtualMemoryArea::Type::Unmapped: + break; + case VirtualMemoryArea::Type::Allocated: + new_vma.offset += offset_in_vma; + break; + case VirtualMemoryArea::Type::Mapped: + new_vma.backing_memory += offset_in_vma; + break; + } + + ASSERT(old_vma.CanBeMergedWith(new_vma)); + + return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); +} + +MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) { + const VMAIter next_vma{std::next(iter)}; + if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) { + iter->second.size += next_vma->second.size; + vma_map.erase(next_vma); + } + + if (iter != vma_map.begin()) { + VMAIter prev_vma{std::prev(iter)}; + if (prev_vma->second.CanBeMergedWith(iter->second)) { + prev_vma->second.size += iter->second.size; + vma_map.erase(iter); + iter = prev_vma; } } - return results; + + return iter; } -VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) { - auto& block{page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]}; - if (!block) { - block = std::make_unique<PageBlock>(); - block->fill(static_cast<VAddr>(PageStatus::Unmapped)); +void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { + switch (vma.type) { + case VirtualMemoryArea::Type::Unmapped: + UnmapRegion(vma.base, vma.size); + break; + case VirtualMemoryArea::Type::Allocated: + MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr); + break; + case VirtualMemoryArea::Type::Mapped: + MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr); + break; } - return (*block)[(gpu_addr >> PAGE_BITS) & PAGE_BLOCK_MASK]; } } // namespace Tegra diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index fb03497ca..34744bb27 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -1,67 +1,148 @@ -// Copyright 2018 yuzu emulator team +// 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 <map> #include <optional> -#include <vector> #include "common/common_types.h" +#include "common/page_table.h" namespace Tegra { -/// Virtual addresses in the GPU's memory map are 64 bit. -using GPUVAddr = u64; +/** + * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space + * with homogeneous attributes across its extents. In this particular implementation each VMA is + * also backed by a single host memory allocation. + */ +struct VirtualMemoryArea { + enum class Type : u8 { + Unmapped, + Allocated, + Mapped, + }; + + /// Virtual base address of the region. + GPUVAddr base{}; + /// Size of the region. + u64 size{}; + /// Memory area mapping type. + Type type{Type::Unmapped}; + /// CPU memory mapped address corresponding to this memory area. + VAddr backing_addr{}; + /// Offset into the backing_memory the mapping starts from. + std::size_t offset{}; + /// Pointer backing this VMA. + u8* backing_memory{}; + + /// Tests if this area can be merged to the right with `next`. + bool CanBeMergedWith(const VirtualMemoryArea& next) const; +}; class MemoryManager final { public: MemoryManager(); GPUVAddr AllocateSpace(u64 size, u64 align); - GPUVAddr AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align); + GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align); GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size); - GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size); - GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size); - GPUVAddr GetRegionEnd(GPUVAddr region_start) const; - std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr); - std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const; + GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size); + GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size); + std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr); + + template <typename T> + T Read(GPUVAddr addr); + + template <typename T> + void Write(GPUVAddr addr, T data); - static constexpr u64 PAGE_BITS = 16; - static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS; - static constexpr u64 PAGE_MASK = PAGE_SIZE - 1; + u8* GetPointer(GPUVAddr addr); + + void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size); + void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size); + void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size); private: - enum class PageStatus : u64 { - Unmapped = 0xFFFFFFFFFFFFFFFFULL, - Allocated = 0xFFFFFFFFFFFFFFFEULL, - Reserved = 0xFFFFFFFFFFFFFFFDULL, - }; + using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>; + using VMAHandle = VMAMap::const_iterator; + using VMAIter = VMAMap::iterator; - std::optional<GPUVAddr> FindFreeBlock(GPUVAddr region_start, u64 size, u64 align, - PageStatus status); - VAddr& PageSlot(GPUVAddr gpu_addr); - - static constexpr u64 MAX_ADDRESS{0x10000000000ULL}; - static constexpr u64 PAGE_TABLE_BITS{10}; - 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{}; - - struct MappedRegion { - VAddr cpu_addr; - GPUVAddr gpu_addr; - u64 size; - }; + bool IsAddressValid(GPUVAddr addr) const; + void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type, + VAddr backing_addr = 0); + void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr); + void UnmapRegion(GPUVAddr base, u64 size); + + /// Finds the VMA in which the given address is included in, or `vma_map.end()`. + VMAHandle FindVMA(GPUVAddr target) const; + + VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size); + + /** + * Maps an unmanaged host memory pointer at a given address. + * + * @param target The guest address to start the mapping at. + * @param memory The memory to be mapped. + * @param size Size of the mapping. + * @param state MemoryState tag to attach to the VMA. + */ + VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr); + + /// Unmaps a range of addresses, splitting VMAs as necessary. + void UnmapRange(GPUVAddr target, u64 size); + + /// Converts a VMAHandle to a mutable VMAIter. + VMAIter StripIterConstness(const VMAHandle& iter); + + /// Marks as the specfied VMA as allocated. + VMAIter Allocate(VMAIter vma); + + /** + * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing + * the appropriate error checking. + */ + VMAIter CarveVMA(GPUVAddr base, u64 size); + + /** + * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each + * end of the range. + */ + VMAIter CarveVMARange(GPUVAddr base, u64 size); + + /** + * Splits a VMA in two, at the specified offset. + * @returns the right side of the split, with the original iterator becoming the left side. + */ + VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma); + + /** + * Checks for and merges the specified VMA with adjacent ones if possible. + * @returns the merged VMA or the original if no merging was possible. + */ + VMAIter MergeAdjacent(VMAIter vma); + + /// Updates the pages corresponding to this VMA so they match the VMA's attributes. + void UpdatePageTableForVMA(const VirtualMemoryArea& vma); + + /// Finds a free (unmapped region) of the specified size starting at the specified address. + GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size); + +private: + static constexpr u64 page_bits{16}; + static constexpr u64 page_size{1 << page_bits}; + static constexpr u64 page_mask{page_size - 1}; + + /// Address space in bits, this is fairly arbitrary but sufficiently large. + static constexpr u32 address_space_width{39}; + /// Start address for mapping, this is fairly arbitrary but must be non-zero. + static constexpr GPUVAddr address_space_base{0x100000}; + /// End of address space, based on address space in bits. + static constexpr GPUVAddr address_space_end{1ULL << address_space_width}; - std::vector<MappedRegion> mapped_regions; + Common::PageTable page_table{page_bits}; + VMAMap vma_map; }; } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index b68f4fb13..3e91cbc83 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -6,7 +6,6 @@ #include <cstring> #include "common/assert.h" #include "common/common_types.h" -#include "core/memory.h" #include "video_core/morton.h" #include "video_core/surface.h" #include "video_core/textures/decoders.h" @@ -16,12 +15,12 @@ namespace VideoCore { using Surface::GetBytesPerPixel; using Surface::PixelFormat; -using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, std::size_t, VAddr); +using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*); using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>; template <bool morton_to_linear, PixelFormat format> static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, - u32 tile_width_spacing, u8* buffer, std::size_t buffer_size, VAddr addr) { + u32 tile_width_spacing, u8* buffer, u8* addr) { constexpr u32 bytes_per_pixel = GetBytesPerPixel(format); // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual @@ -34,150 +33,146 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth stride, height, depth, block_height, block_depth, tile_width_spacing); } else { - Tegra::Texture::CopySwizzledData( - (stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y, - depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), buffer, false, - block_height, block_depth, tile_width_spacing); + Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x, + (height + tile_size_y - 1) / tile_size_y, depth, + bytes_per_pixel, bytes_per_pixel, addr, buffer, false, + block_height, block_depth, tile_width_spacing); } } static constexpr ConversionArray morton_to_linear_fns = { - // clang-format off - MortonCopy<true, PixelFormat::ABGR8U>, - MortonCopy<true, PixelFormat::ABGR8S>, - MortonCopy<true, PixelFormat::ABGR8UI>, - MortonCopy<true, PixelFormat::B5G6R5U>, - MortonCopy<true, PixelFormat::A2B10G10R10U>, - MortonCopy<true, PixelFormat::A1B5G5R5U>, - MortonCopy<true, PixelFormat::R8U>, - MortonCopy<true, PixelFormat::R8UI>, - MortonCopy<true, PixelFormat::RGBA16F>, - MortonCopy<true, PixelFormat::RGBA16U>, - MortonCopy<true, PixelFormat::RGBA16UI>, - MortonCopy<true, PixelFormat::R11FG11FB10F>, - MortonCopy<true, PixelFormat::RGBA32UI>, - MortonCopy<true, PixelFormat::DXT1>, - MortonCopy<true, PixelFormat::DXT23>, - MortonCopy<true, PixelFormat::DXT45>, - MortonCopy<true, PixelFormat::DXN1>, - MortonCopy<true, PixelFormat::DXN2UNORM>, - MortonCopy<true, PixelFormat::DXN2SNORM>, - MortonCopy<true, PixelFormat::BC7U>, - MortonCopy<true, PixelFormat::BC6H_UF16>, - MortonCopy<true, PixelFormat::BC6H_SF16>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4>, - MortonCopy<true, PixelFormat::BGRA8>, - MortonCopy<true, PixelFormat::RGBA32F>, - MortonCopy<true, PixelFormat::RG32F>, - MortonCopy<true, PixelFormat::R32F>, - MortonCopy<true, PixelFormat::R16F>, - MortonCopy<true, PixelFormat::R16U>, - MortonCopy<true, PixelFormat::R16S>, - MortonCopy<true, PixelFormat::R16UI>, - MortonCopy<true, PixelFormat::R16I>, - MortonCopy<true, PixelFormat::RG16>, - MortonCopy<true, PixelFormat::RG16F>, - MortonCopy<true, PixelFormat::RG16UI>, - MortonCopy<true, PixelFormat::RG16I>, - MortonCopy<true, PixelFormat::RG16S>, - MortonCopy<true, PixelFormat::RGB32F>, - MortonCopy<true, PixelFormat::RGBA8_SRGB>, - MortonCopy<true, PixelFormat::RG8U>, - MortonCopy<true, PixelFormat::RG8S>, - MortonCopy<true, PixelFormat::RG32UI>, - MortonCopy<true, PixelFormat::R32UI>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4>, - MortonCopy<true, PixelFormat::BGRA8_SRGB>, - MortonCopy<true, PixelFormat::DXT1_SRGB>, - MortonCopy<true, PixelFormat::DXT23_SRGB>, - MortonCopy<true, PixelFormat::DXT45_SRGB>, - MortonCopy<true, PixelFormat::BC7U_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, - MortonCopy<true, PixelFormat::Z32F>, - MortonCopy<true, PixelFormat::Z16>, - MortonCopy<true, PixelFormat::Z24S8>, - MortonCopy<true, PixelFormat::S8Z24>, - MortonCopy<true, PixelFormat::Z32FS8>, - // clang-format on + MortonCopy<true, PixelFormat::ABGR8U>, + MortonCopy<true, PixelFormat::ABGR8S>, + MortonCopy<true, PixelFormat::ABGR8UI>, + MortonCopy<true, PixelFormat::B5G6R5U>, + MortonCopy<true, PixelFormat::A2B10G10R10U>, + MortonCopy<true, PixelFormat::A1B5G5R5U>, + MortonCopy<true, PixelFormat::R8U>, + MortonCopy<true, PixelFormat::R8UI>, + MortonCopy<true, PixelFormat::RGBA16F>, + MortonCopy<true, PixelFormat::RGBA16U>, + MortonCopy<true, PixelFormat::RGBA16UI>, + MortonCopy<true, PixelFormat::R11FG11FB10F>, + MortonCopy<true, PixelFormat::RGBA32UI>, + MortonCopy<true, PixelFormat::DXT1>, + MortonCopy<true, PixelFormat::DXT23>, + MortonCopy<true, PixelFormat::DXT45>, + MortonCopy<true, PixelFormat::DXN1>, + MortonCopy<true, PixelFormat::DXN2UNORM>, + MortonCopy<true, PixelFormat::DXN2SNORM>, + MortonCopy<true, PixelFormat::BC7U>, + MortonCopy<true, PixelFormat::BC6H_UF16>, + MortonCopy<true, PixelFormat::BC6H_SF16>, + MortonCopy<true, PixelFormat::ASTC_2D_4X4>, + MortonCopy<true, PixelFormat::BGRA8>, + MortonCopy<true, PixelFormat::RGBA32F>, + MortonCopy<true, PixelFormat::RG32F>, + MortonCopy<true, PixelFormat::R32F>, + MortonCopy<true, PixelFormat::R16F>, + MortonCopy<true, PixelFormat::R16U>, + MortonCopy<true, PixelFormat::R16S>, + MortonCopy<true, PixelFormat::R16UI>, + MortonCopy<true, PixelFormat::R16I>, + MortonCopy<true, PixelFormat::RG16>, + MortonCopy<true, PixelFormat::RG16F>, + MortonCopy<true, PixelFormat::RG16UI>, + MortonCopy<true, PixelFormat::RG16I>, + MortonCopy<true, PixelFormat::RG16S>, + MortonCopy<true, PixelFormat::RGB32F>, + MortonCopy<true, PixelFormat::RGBA8_SRGB>, + MortonCopy<true, PixelFormat::RG8U>, + MortonCopy<true, PixelFormat::RG8S>, + MortonCopy<true, PixelFormat::RG32UI>, + MortonCopy<true, PixelFormat::R32UI>, + MortonCopy<true, PixelFormat::ASTC_2D_8X8>, + MortonCopy<true, PixelFormat::ASTC_2D_8X5>, + MortonCopy<true, PixelFormat::ASTC_2D_5X4>, + MortonCopy<true, PixelFormat::BGRA8_SRGB>, + MortonCopy<true, PixelFormat::DXT1_SRGB>, + MortonCopy<true, PixelFormat::DXT23_SRGB>, + MortonCopy<true, PixelFormat::DXT45_SRGB>, + MortonCopy<true, PixelFormat::BC7U_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_5X5>, + MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_10X8>, + MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, + MortonCopy<true, PixelFormat::Z32F>, + MortonCopy<true, PixelFormat::Z16>, + MortonCopy<true, PixelFormat::Z24S8>, + MortonCopy<true, PixelFormat::S8Z24>, + MortonCopy<true, PixelFormat::Z32FS8>, }; static constexpr ConversionArray linear_to_morton_fns = { - // clang-format off - MortonCopy<false, PixelFormat::ABGR8U>, - MortonCopy<false, PixelFormat::ABGR8S>, - MortonCopy<false, PixelFormat::ABGR8UI>, - MortonCopy<false, PixelFormat::B5G6R5U>, - MortonCopy<false, PixelFormat::A2B10G10R10U>, - MortonCopy<false, PixelFormat::A1B5G5R5U>, - MortonCopy<false, PixelFormat::R8U>, - MortonCopy<false, PixelFormat::R8UI>, - MortonCopy<false, PixelFormat::RGBA16F>, - MortonCopy<false, PixelFormat::RGBA16U>, - MortonCopy<false, PixelFormat::RGBA16UI>, - MortonCopy<false, PixelFormat::R11FG11FB10F>, - MortonCopy<false, PixelFormat::RGBA32UI>, - MortonCopy<false, PixelFormat::DXT1>, - MortonCopy<false, PixelFormat::DXT23>, - MortonCopy<false, PixelFormat::DXT45>, - MortonCopy<false, PixelFormat::DXN1>, - MortonCopy<false, PixelFormat::DXN2UNORM>, - MortonCopy<false, PixelFormat::DXN2SNORM>, - MortonCopy<false, PixelFormat::BC7U>, - MortonCopy<false, PixelFormat::BC6H_UF16>, - MortonCopy<false, PixelFormat::BC6H_SF16>, - // TODO(Subv): Swizzling ASTC formats are not supported - nullptr, - MortonCopy<false, PixelFormat::BGRA8>, - MortonCopy<false, PixelFormat::RGBA32F>, - MortonCopy<false, PixelFormat::RG32F>, - MortonCopy<false, PixelFormat::R32F>, - MortonCopy<false, PixelFormat::R16F>, - MortonCopy<false, PixelFormat::R16U>, - MortonCopy<false, PixelFormat::R16S>, - MortonCopy<false, PixelFormat::R16UI>, - MortonCopy<false, PixelFormat::R16I>, - MortonCopy<false, PixelFormat::RG16>, - MortonCopy<false, PixelFormat::RG16F>, - MortonCopy<false, PixelFormat::RG16UI>, - MortonCopy<false, PixelFormat::RG16I>, - MortonCopy<false, PixelFormat::RG16S>, - MortonCopy<false, PixelFormat::RGB32F>, - MortonCopy<false, PixelFormat::RGBA8_SRGB>, - MortonCopy<false, PixelFormat::RG8U>, - MortonCopy<false, PixelFormat::RG8S>, - MortonCopy<false, PixelFormat::RG32UI>, - MortonCopy<false, PixelFormat::R32UI>, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::BGRA8_SRGB>, - MortonCopy<false, PixelFormat::DXT1_SRGB>, - MortonCopy<false, PixelFormat::DXT23_SRGB>, - MortonCopy<false, PixelFormat::DXT45_SRGB>, - MortonCopy<false, PixelFormat::BC7U_SRGB>, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::Z32F>, - MortonCopy<false, PixelFormat::Z16>, - MortonCopy<false, PixelFormat::Z24S8>, - MortonCopy<false, PixelFormat::S8Z24>, - MortonCopy<false, PixelFormat::Z32FS8>, - // clang-format on + MortonCopy<false, PixelFormat::ABGR8U>, + MortonCopy<false, PixelFormat::ABGR8S>, + MortonCopy<false, PixelFormat::ABGR8UI>, + MortonCopy<false, PixelFormat::B5G6R5U>, + MortonCopy<false, PixelFormat::A2B10G10R10U>, + MortonCopy<false, PixelFormat::A1B5G5R5U>, + MortonCopy<false, PixelFormat::R8U>, + MortonCopy<false, PixelFormat::R8UI>, + MortonCopy<false, PixelFormat::RGBA16F>, + MortonCopy<false, PixelFormat::RGBA16U>, + MortonCopy<false, PixelFormat::RGBA16UI>, + MortonCopy<false, PixelFormat::R11FG11FB10F>, + MortonCopy<false, PixelFormat::RGBA32UI>, + MortonCopy<false, PixelFormat::DXT1>, + MortonCopy<false, PixelFormat::DXT23>, + MortonCopy<false, PixelFormat::DXT45>, + MortonCopy<false, PixelFormat::DXN1>, + MortonCopy<false, PixelFormat::DXN2UNORM>, + MortonCopy<false, PixelFormat::DXN2SNORM>, + MortonCopy<false, PixelFormat::BC7U>, + MortonCopy<false, PixelFormat::BC6H_UF16>, + MortonCopy<false, PixelFormat::BC6H_SF16>, + // TODO(Subv): Swizzling ASTC formats are not supported + nullptr, + MortonCopy<false, PixelFormat::BGRA8>, + MortonCopy<false, PixelFormat::RGBA32F>, + MortonCopy<false, PixelFormat::RG32F>, + MortonCopy<false, PixelFormat::R32F>, + MortonCopy<false, PixelFormat::R16F>, + MortonCopy<false, PixelFormat::R16U>, + MortonCopy<false, PixelFormat::R16S>, + MortonCopy<false, PixelFormat::R16UI>, + MortonCopy<false, PixelFormat::R16I>, + MortonCopy<false, PixelFormat::RG16>, + MortonCopy<false, PixelFormat::RG16F>, + MortonCopy<false, PixelFormat::RG16UI>, + MortonCopy<false, PixelFormat::RG16I>, + MortonCopy<false, PixelFormat::RG16S>, + MortonCopy<false, PixelFormat::RGB32F>, + MortonCopy<false, PixelFormat::RGBA8_SRGB>, + MortonCopy<false, PixelFormat::RG8U>, + MortonCopy<false, PixelFormat::RG8S>, + MortonCopy<false, PixelFormat::RG32UI>, + MortonCopy<false, PixelFormat::R32UI>, + nullptr, + nullptr, + nullptr, + MortonCopy<false, PixelFormat::BGRA8_SRGB>, + MortonCopy<false, PixelFormat::DXT1_SRGB>, + MortonCopy<false, PixelFormat::DXT23_SRGB>, + MortonCopy<false, PixelFormat::DXT45_SRGB>, + MortonCopy<false, PixelFormat::BC7U_SRGB>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + MortonCopy<false, PixelFormat::Z32F>, + MortonCopy<false, PixelFormat::Z16>, + MortonCopy<false, PixelFormat::Z24S8>, + MortonCopy<false, PixelFormat::S8Z24>, + MortonCopy<false, PixelFormat::Z32FS8>, }; static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) { @@ -191,45 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor return morton_to_linear_fns[static_cast<std::size_t>(format)]; } -/// 8x8 Z-Order coordinate from 2D coordinates -static u32 MortonInterleave(u32 x, u32 y) { - static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; - static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; - return xlut[x % 8] + ylut[y % 8]; -} - -/// Calculates the offset of the position of the pixel in Morton order -static u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { - // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each - // of which is composed of four 2x2 subtiles each of which is composed of four texels. - // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. - // texels are laid out in a 2x2 subtile like this: - // 2 3 - // 0 1 - // - // The full 8x8 tile has the texels arranged like this: - // - // 42 43 46 47 58 59 62 63 - // 40 41 44 45 56 57 60 61 - // 34 35 38 39 50 51 54 55 - // 32 33 36 37 48 49 52 53 - // 10 11 14 15 26 27 30 31 - // 08 09 12 13 24 25 28 29 - // 02 03 06 07 18 19 22 23 - // 00 01 04 05 16 17 20 21 - // - // This pattern is what's called Z-order curve, or Morton order. - - const unsigned int block_height = 8; - const unsigned int coarse_x = x & ~7; - - u32 i = MortonInterleave(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - static u32 MortonInterleave128(u32 x, u32 y) { // 128x128 Z-Order coordinate from 2D coordinates static constexpr u32 xlut[] = { @@ -325,14 +281,14 @@ static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, - u8* buffer, std::size_t buffer_size, VAddr addr) { - + u8* buffer, u8* addr) { GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth, - tile_width_spacing, buffer, buffer_size, addr); + tile_width_spacing, buffer, addr); } -void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel, - u8* morton_data, u8* linear_data, bool morton_to_linear) { +void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, + u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) { + const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear; u8* data_ptrs[2]; for (u32 y = 0; y < height; ++y) { for (u32 x = 0; x < width; ++x) { diff --git a/src/video_core/morton.h b/src/video_core/morton.h index 065f59ce3..ee5b45555 100644 --- a/src/video_core/morton.h +++ b/src/video_core/morton.h @@ -13,9 +13,9 @@ enum class MortonSwizzleMode { MortonToLinear, LinearToMorton }; void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, - u8* buffer, std::size_t buffer_size, VAddr addr); + u8* buffer, u8* addr); -void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel, - u8* morton_data, u8* linear_data, bool morton_to_linear); +void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, + u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data); } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h index a7bcf26fb..9fc9f3056 100644 --- a/src/video_core/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache.h @@ -4,6 +4,7 @@ #pragma once +#include <mutex> #include <set> #include <unordered_map> @@ -12,14 +13,26 @@ #include "common/common_types.h" #include "core/settings.h" +#include "video_core/gpu.h" #include "video_core/rasterizer_interface.h" class RasterizerCacheObject { public: + explicit RasterizerCacheObject(const u8* host_ptr) + : host_ptr{host_ptr}, cache_addr{ToCacheAddr(host_ptr)} {} + virtual ~RasterizerCacheObject(); + CacheAddr GetCacheAddr() const { + return cache_addr; + } + + const u8* GetHostPtr() const { + return host_ptr; + } + /// Gets the address of the shader in guest memory, required for cache management - virtual VAddr GetAddr() const = 0; + virtual VAddr GetCpuAddr() const = 0; /// Gets the size of the shader in guest memory, required for cache management virtual std::size_t GetSizeInBytes() const = 0; @@ -58,6 +71,8 @@ private: bool is_registered{}; ///< Whether the object is currently registered with the cache bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory) u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing + CacheAddr cache_addr{}; ///< Cache address memory, unique from emulated virtual address space + const u8* host_ptr{}; ///< Pointer to the memory backing this cached region }; template <class T> @@ -68,7 +83,9 @@ public: explicit RasterizerCache(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {} /// Write any cached resources overlapping the specified region back to memory - void FlushRegion(Tegra::GPUVAddr addr, size_t size) { + void FlushRegion(CacheAddr addr, std::size_t size) { + std::lock_guard<std::recursive_mutex> lock{mutex}; + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; for (auto& object : objects) { FlushObject(object); @@ -76,7 +93,9 @@ public: } /// Mark the specified region as being invalidated - void InvalidateRegion(VAddr addr, u64 size) { + void InvalidateRegion(CacheAddr addr, u64 size) { + std::lock_guard<std::recursive_mutex> lock{mutex}; + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; for (auto& object : objects) { if (!object->IsRegistered()) { @@ -89,48 +108,60 @@ public: /// Invalidates everything in the cache void InvalidateAll() { + std::lock_guard<std::recursive_mutex> lock{mutex}; + while (interval_cache.begin() != interval_cache.end()) { Unregister(*interval_cache.begin()->second.begin()); } } protected: - /// Tries to get an object from the cache with the specified address - T TryGet(VAddr addr) const { + /// Tries to get an object from the cache with the specified cache address + T TryGet(CacheAddr addr) const { const auto iter = map_cache.find(addr); if (iter != map_cache.end()) return iter->second; return nullptr; } + T TryGet(const void* addr) const { + const auto iter = map_cache.find(ToCacheAddr(addr)); + if (iter != map_cache.end()) + return iter->second; + return nullptr; + } + /// Register an object into the cache - void Register(const T& object) { + virtual void Register(const T& object) { + std::lock_guard<std::recursive_mutex> lock{mutex}; + object->SetIsRegistered(true); interval_cache.add({GetInterval(object), ObjectSet{object}}); - map_cache.insert({object->GetAddr(), object}); - rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1); + map_cache.insert({object->GetCacheAddr(), object}); + rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), 1); } /// Unregisters an object from the cache - void Unregister(const T& object) { - object->SetIsRegistered(false); - rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1); - // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit - if (Settings::values.use_accurate_gpu_emulation) { - FlushObject(object); - } + virtual void Unregister(const T& object) { + std::lock_guard<std::recursive_mutex> lock{mutex}; + object->SetIsRegistered(false); + rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1); interval_cache.subtract({GetInterval(object), ObjectSet{object}}); - map_cache.erase(object->GetAddr()); + map_cache.erase(object->GetCacheAddr()); } /// Returns a ticks counter used for tracking when cached objects were last modified u64 GetModifiedTicks() { + std::lock_guard<std::recursive_mutex> lock{mutex}; + return ++modified_ticks; } /// Flushes the specified object, updating appropriate cache state as needed void FlushObject(const T& object) { + std::lock_guard<std::recursive_mutex> lock{mutex}; + if (!object->IsDirty()) { return; } @@ -140,7 +171,7 @@ protected: private: /// Returns a list of cached objects from the specified memory region, ordered by access time - std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) { + std::vector<T> GetSortedObjectsFromRegion(CacheAddr addr, u64 size) { if (size == 0) { return {}; } @@ -164,17 +195,18 @@ private: } using ObjectSet = std::set<T>; - using ObjectCache = std::unordered_map<VAddr, T>; - using IntervalCache = boost::icl::interval_map<VAddr, ObjectSet>; + using ObjectCache = std::unordered_map<CacheAddr, T>; + using IntervalCache = boost::icl::interval_map<CacheAddr, ObjectSet>; using ObjectInterval = typename IntervalCache::interval_type; static auto GetInterval(const T& object) { - return ObjectInterval::right_open(object->GetAddr(), - object->GetAddr() + object->GetSizeInBytes()); + return ObjectInterval::right_open(object->GetCacheAddr(), + object->GetCacheAddr() + object->GetSizeInBytes()); } ObjectCache map_cache; IntervalCache interval_cache; ///< Cache of objects u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing VideoCore::RasterizerInterface& rasterizer; + std::recursive_mutex mutex; }; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 6a1dc9cf6..d7b86df38 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -9,7 +9,6 @@ #include "common/common_types.h" #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" -#include "video_core/memory_manager.h" namespace VideoCore { @@ -35,14 +34,14 @@ public: virtual void FlushAll() = 0; /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory - virtual void FlushRegion(VAddr addr, u64 size) = 0; + virtual void FlushRegion(CacheAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be invalidated - virtual void InvalidateRegion(VAddr addr, u64 size) = 0; + virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0; /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory /// and invalidated - virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; + virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0; /// Attempt to use a faster method to perform a surface copy virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, @@ -63,7 +62,7 @@ public: } /// Increase/decrease the number of object in pages touching the specified region - virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {} + virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} /// Initialize disk cached resources for the game being emulated virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false, diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index b3062e5ba..f75c65825 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -13,24 +13,28 @@ namespace OpenGL { +CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset, + std::size_t alignment, u8* host_ptr) + : cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{ + host_ptr} {} + OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size) : RasterizerCache{rasterizer}, stream_buffer(size, true) {} -GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, - std::size_t alignment, bool cache) { +GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment, + bool cache) { auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager(); - const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)}; - ASSERT_MSG(cpu_addr, "Invalid GPU address"); // Cache management is a big overhead, so only cache entries with a given size. // TODO: Figure out which size is the best for given games. cache &= size >= 2048; + const auto& host_ptr{memory_manager.GetPointer(gpu_addr)}; if (cache) { - auto entry = TryGet(*cpu_addr); + auto entry = TryGet(host_ptr); if (entry) { - if (entry->size >= size && entry->alignment == alignment) { - return entry->offset; + if (entry->GetSize() >= size && entry->GetAlignment() == alignment) { + return entry->GetOffset(); } Unregister(entry); } @@ -39,17 +43,17 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size AlignBuffer(alignment); const GLintptr uploaded_offset = buffer_offset; - Memory::ReadBlock(*cpu_addr, buffer_ptr, size); + if (!host_ptr) { + return uploaded_offset; + } + std::memcpy(buffer_ptr, host_ptr, size); buffer_ptr += size; buffer_offset += size; if (cache) { - auto entry = std::make_shared<CachedBufferEntry>(); - entry->offset = uploaded_offset; - entry->size = size; - entry->alignment = alignment; - entry->addr = *cpu_addr; + auto entry = std::make_shared<CachedBufferEntry>( + *memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr); Register(entry); } diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index c11acfb79..fc33aa433 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -17,22 +17,39 @@ namespace OpenGL { class RasterizerOpenGL; -struct CachedBufferEntry final : public RasterizerCacheObject { - VAddr GetAddr() const override { - return addr; +class CachedBufferEntry final : public RasterizerCacheObject { +public: + explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset, + std::size_t alignment, u8* host_ptr); + + VAddr GetCpuAddr() const override { + return cpu_addr; } std::size_t GetSizeInBytes() const override { return size; } + std::size_t GetSize() const { + return size; + } + + GLintptr GetOffset() const { + return offset; + } + + std::size_t GetAlignment() const { + return alignment; + } + // We do not have to flush this cache as things in it are never modified by us. void Flush() override {} - VAddr addr; - std::size_t size; - GLintptr offset; - std::size_t alignment; +private: + VAddr cpu_addr{}; + std::size_t size{}; + GLintptr offset{}; + std::size_t alignment{}; }; class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> { @@ -41,7 +58,7 @@ public: /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been /// allocated. - GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, + GLintptr UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, bool cache = true); /// Uploads from a host memory. Returns host's buffer offset where it's been allocated. diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp index c7f32feaa..0fbfbad55 100644 --- a/src/video_core/renderer_opengl/gl_global_cache.cpp +++ b/src/video_core/renderer_opengl/gl_global_cache.cpp @@ -7,7 +7,6 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" -#include "core/memory.h" #include "video_core/renderer_opengl/gl_global_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" @@ -15,12 +14,13 @@ namespace OpenGL { -CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} { +CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr) + : cpu_addr{cpu_addr}, size{size}, RasterizerCacheObject{host_ptr} { buffer.Create(); // Bind and unbind the buffer so it gets allocated by the driver glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); - LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory"); + LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory"); } void CachedGlobalRegion::Reload(u32 size_) { @@ -35,10 +35,10 @@ void CachedGlobalRegion::Reload(u32 size_) { // TODO(Rodrigo): Get rid of Memory::GetPointer with a staging buffer glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle); - glBufferData(GL_SHADER_STORAGE_BUFFER, size, Memory::GetPointer(addr), GL_DYNAMIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, GetHostPtr(), GL_DYNAMIC_DRAW); } -GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 size) const { +GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const { const auto search{reserve.find(addr)}; if (search == reserve.end()) { return {}; @@ -46,19 +46,22 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 return search->second; } -GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 size) { - GlobalRegion region{TryGetReservedGlobalRegion(addr, size)}; +GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u32 size, + u8* host_ptr) { + GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)}; if (!region) { // No reserved surface available, create a new one and reserve it - region = std::make_shared<CachedGlobalRegion>(addr, size); + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const auto cpu_addr = *memory_manager.GpuToCpuAddress(addr); + region = std::make_shared<CachedGlobalRegion>(cpu_addr, size, host_ptr); ReserveGlobalRegion(region); } region->Reload(size); return region; } -void GlobalRegionCacheOpenGL::ReserveGlobalRegion(const GlobalRegion& region) { - reserve[region->GetAddr()] = region; +void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) { + reserve.insert_or_assign(region->GetCacheAddr(), std::move(region)); } GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer) @@ -69,22 +72,20 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion( Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) { auto& gpu{Core::System::GetInstance().GPU()}; - const auto cbufs = gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]; - const auto cbuf_addr = gpu.MemoryManager().GpuToCpuAddress( - cbufs.const_buffers[global_region.GetCbufIndex()].address + global_region.GetCbufOffset()); - ASSERT(cbuf_addr); - - const auto actual_addr_gpu = Memory::Read64(*cbuf_addr); - const auto size = Memory::Read32(*cbuf_addr + 8); - const auto actual_addr = gpu.MemoryManager().GpuToCpuAddress(actual_addr_gpu); - ASSERT(actual_addr); + auto& memory_manager{gpu.MemoryManager()}; + const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]}; + const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address + + global_region.GetCbufOffset()}; + const auto actual_addr{memory_manager.Read<u64>(addr)}; + const auto size{memory_manager.Read<u32>(addr + 8)}; // Look up global region in the cache based on address - GlobalRegion region = TryGet(*actual_addr); + const auto& host_ptr{memory_manager.GetPointer(actual_addr)}; + GlobalRegion region{TryGet(host_ptr)}; if (!region) { // No global region found - create a new one - region = GetUncachedGlobalRegion(*actual_addr, size); + region = GetUncachedGlobalRegion(actual_addr, size, host_ptr); Register(region); } diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h index 37830bb7c..5a21ab66f 100644 --- a/src/video_core/renderer_opengl/gl_global_cache.h +++ b/src/video_core/renderer_opengl/gl_global_cache.h @@ -27,15 +27,13 @@ using GlobalRegion = std::shared_ptr<CachedGlobalRegion>; class CachedGlobalRegion final : public RasterizerCacheObject { public: - explicit CachedGlobalRegion(VAddr addr, u32 size); + explicit CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr); - /// Gets the address of the shader in guest memory, required for cache management - VAddr GetAddr() const { - return addr; + VAddr GetCpuAddr() const override { + return cpu_addr; } - /// Gets the size of the shader in guest memory, required for cache management - std::size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const override { return size; } @@ -53,9 +51,8 @@ public: } private: - VAddr addr{}; + VAddr cpu_addr{}; u32 size{}; - OGLBuffer buffer; }; @@ -68,11 +65,11 @@ public: Tegra::Engines::Maxwell3D::Regs::ShaderStage stage); private: - GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const; - GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size); - void ReserveGlobalRegion(const GlobalRegion& region); + GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const; + GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u32 size, u8* host_ptr); + void ReserveGlobalRegion(GlobalRegion region); - std::unordered_map<VAddr, GlobalRegion> reserve; + std::unordered_map<CacheAddr, GlobalRegion> reserve; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp index 77d5cedd2..2bcbd3da2 100644 --- a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp +++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp @@ -40,16 +40,12 @@ GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) { return index_offset; } -GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, - u32 count) { +GLintptr PrimitiveAssembler::MakeQuadIndexed(GPUVAddr gpu_addr, std::size_t index_size, u32 count) { const std::size_t map_size{CalculateQuadSize(count)}; auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size); auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager(); - const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)}; - ASSERT_MSG(cpu_addr, "Invalid GPU address"); - - const u8* source{Memory::GetPointer(*cpu_addr)}; + const u8* source{memory_manager.GetPointer(gpu_addr)}; for (u32 primitive = 0; primitive < count / 4; ++primitive) { for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) { @@ -64,4 +60,4 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size return index_offset; } -} // namespace OpenGL
\ No newline at end of file +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.h b/src/video_core/renderer_opengl/gl_primitive_assembler.h index a8cb88eb5..0e2e7dc36 100644 --- a/src/video_core/renderer_opengl/gl_primitive_assembler.h +++ b/src/video_core/renderer_opengl/gl_primitive_assembler.h @@ -24,7 +24,7 @@ public: GLintptr MakeQuadArray(u32 first, u32 count); - GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count); + GLintptr MakeQuadIndexed(GPUVAddr gpu_addr, std::size_t index_size, u32 count); private: OGLBufferCache& buffer_cache; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index e038686ad..e06dfe43f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -102,8 +102,9 @@ struct FramebufferCacheKey { RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system, ScreenInfo& info) - : res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, emu_window{window}, - screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) { + : res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, + emu_window{window}, system{system}, screen_info{info}, + buffer_cache(*this, STREAM_BUFFER_SIZE) { // Create sampler objects for (std::size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); @@ -138,7 +139,7 @@ void RasterizerOpenGL::CheckExtensions() { } GLuint RasterizerOpenGL::SetupVertexFormat() { - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; if (!gpu.dirty_flags.vertex_attrib_format) { @@ -207,7 +208,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() { } void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; if (gpu.dirty_flags.vertex_array.none()) @@ -224,8 +225,8 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { if (!vertex_array.IsEnabled()) continue; - const Tegra::GPUVAddr start = vertex_array.StartAddress(); - const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + const GPUVAddr start = vertex_array.StartAddress(); + const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); ASSERT(end > start); const u64 size = end - start + 1; @@ -248,7 +249,7 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { } DrawParameters RasterizerOpenGL::SetupDraw() { - const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; const bool is_indexed = accelerate_draw == AccelDraw::Indexed; @@ -297,7 +298,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() { void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { MICROPROFILE_SCOPE(OpenGL_Shader); - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = system.GPU().Maxwell3D(); BaseBindings base_bindings; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; @@ -413,15 +414,15 @@ void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; std::size_t size = 0; for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { if (!regs.vertex_array[index].IsEnabled()) continue; - const Tegra::GPUVAddr start = regs.vertex_array[index].StartAddress(); - const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + const GPUVAddr start = regs.vertex_array[index].StartAddress(); + const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); ASSERT(end > start); size += end - start + 1; @@ -431,7 +432,7 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { } std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; return static_cast<std::size_t>(regs.index_array.count) * static_cast<std::size_t>(regs.index_array.FormatSizeInBytes()); @@ -448,7 +449,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); } -void RasterizerOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) { +void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { const u64 page_start{addr >> Memory::PAGE_BITS}; const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS}; @@ -487,7 +488,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers( OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents, std::optional<std::size_t> single_color_target) { MICROPROFILE_SCOPE(OpenGL_Framebuffer); - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents, @@ -581,7 +582,7 @@ void RasterizerOpenGL::Clear() { const auto prev_state{state}; SCOPE_EXIT({ prev_state.Apply(); }); - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; bool use_color{}; bool use_depth{}; bool use_stencil{}; @@ -672,7 +673,7 @@ void RasterizerOpenGL::DrawArrays() { return; MICROPROFILE_SCOPE(OpenGL_Drawing); - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; ConfigureFramebuffers(state); @@ -746,20 +747,26 @@ void RasterizerOpenGL::DrawArrays() { void RasterizerOpenGL::FlushAll() {} -void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { +void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); + if (!addr || !size) { + return; + } res_cache.FlushRegion(addr, size); } -void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { +void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); + if (!addr || !size) { + return; + } res_cache.InvalidateRegion(addr, size); shader_cache.InvalidateRegion(addr, size); global_cache.InvalidateRegion(addr, size); buffer_cache.InvalidateRegion(addr, size); } -void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { +void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { FlushRegion(addr, size); InvalidateRegion(addr, size); } @@ -781,7 +788,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, MICROPROFILE_SCOPE(OpenGL_CacheManagement); - const auto& surface{res_cache.TryFindFramebufferSurface(framebuffer_addr)}; + const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))}; if (!surface) { return {}; } @@ -804,104 +811,87 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, void RasterizerOpenGL::SamplerInfo::Create() { sampler.Create(); - mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear; - wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap; - uses_depth_compare = false; + mag_filter = Tegra::Texture::TextureFilter::Linear; + min_filter = Tegra::Texture::TextureFilter::Linear; + wrap_u = Tegra::Texture::WrapMode::Wrap; + wrap_v = Tegra::Texture::WrapMode::Wrap; + wrap_p = Tegra::Texture::WrapMode::Wrap; + use_depth_compare = false; depth_compare_func = Tegra::Texture::DepthCompareFunc::Never; - // default is GL_LINEAR_MIPMAP_LINEAR + // OpenGL's default is GL_LINEAR_MIPMAP_LINEAR glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - // Other attributes have correct defaults glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER); + + // Other attributes have correct defaults } void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { - const GLuint s = sampler.handle; + const GLuint sampler_id = sampler.handle; if (mag_filter != config.mag_filter) { mag_filter = config.mag_filter; glSamplerParameteri( - s, GL_TEXTURE_MAG_FILTER, + sampler_id, GL_TEXTURE_MAG_FILTER, MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None)); } - if (min_filter != config.min_filter || mip_filter != config.mip_filter) { + if (min_filter != config.min_filter || mipmap_filter != config.mipmap_filter) { min_filter = config.min_filter; - mip_filter = config.mip_filter; - glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, - MaxwellToGL::TextureFilterMode(min_filter, mip_filter)); + mipmap_filter = config.mipmap_filter; + glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER, + MaxwellToGL::TextureFilterMode(min_filter, mipmap_filter)); } if (wrap_u != config.wrap_u) { wrap_u = config.wrap_u; - glSamplerParameteri(s, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u)); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u)); } if (wrap_v != config.wrap_v) { wrap_v = config.wrap_v; - glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v)); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v)); } if (wrap_p != config.wrap_p) { wrap_p = config.wrap_p; - glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p)); + glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p)); } - if (uses_depth_compare != (config.depth_compare_enabled == 1)) { - uses_depth_compare = (config.depth_compare_enabled == 1); - if (uses_depth_compare) { - glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - } else { - glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } + if (const bool enabled = config.depth_compare_enabled == 1; use_depth_compare != enabled) { + use_depth_compare = enabled; + glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE, + use_depth_compare ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE); } if (depth_compare_func != config.depth_compare_func) { depth_compare_func = config.depth_compare_func; - glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, + glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC, MaxwellToGL::DepthCompareFunc(depth_compare_func)); } - GLvec4 new_border_color; - if (config.srgb_conversion) { - new_border_color[0] = config.srgb_border_color_r / 255.0f; - new_border_color[1] = config.srgb_border_color_g / 255.0f; - new_border_color[2] = config.srgb_border_color_g / 255.0f; - } else { - new_border_color[0] = config.border_color_r; - new_border_color[1] = config.border_color_g; - new_border_color[2] = config.border_color_b; - } - new_border_color[3] = config.border_color_a; - - if (border_color != new_border_color) { + if (const auto new_border_color = config.GetBorderColor(); border_color != new_border_color) { border_color = new_border_color; - glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, border_color.data()); + glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, border_color.data()); } - const float anisotropic_max = static_cast<float>(1 << config.max_anisotropy.Value()); - if (anisotropic_max != max_anisotropic) { - max_anisotropic = anisotropic_max; + if (const float anisotropic = config.GetMaxAnisotropy(); max_anisotropic != anisotropic) { + max_anisotropic = anisotropic; if (GLAD_GL_ARB_texture_filter_anisotropic) { - glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic); + glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic); } else if (GLAD_GL_EXT_texture_filter_anisotropic) { - glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic); + glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic); } } - const float lod_min = static_cast<float>(config.min_lod_clamp.Value()) / 256.0f; - if (lod_min != min_lod) { - min_lod = lod_min; - glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, min_lod); - } - const float lod_max = static_cast<float>(config.max_lod_clamp.Value()) / 256.0f; - if (lod_max != max_lod) { - max_lod = lod_max; - glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, max_lod); + if (const float min = config.GetMinLod(); min_lod != min) { + min_lod = min; + glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, min_lod); + } + if (const float max = config.GetMaxLod(); max_lod != max) { + max_lod = max; + glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, max_lod); } - const u32 bias = config.mip_lod_bias.Value(); - // Sign extend the 13-bit value. - constexpr u32 mask = 1U << (13 - 1); - const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f; - if (lod_bias != bias_lod) { - lod_bias = bias_lod; - glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias); + + if (const float bias = config.GetLodBias(); lod_bias != bias) { + lod_bias = bias; + glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, lod_bias); } } @@ -909,7 +899,7 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader const Shader& shader, GLuint program_handle, BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& gpu = Core::System::GetInstance().GPU(); + const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; const auto& entries = shader->GetShaderEntries().const_buffers; @@ -988,7 +978,7 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, GLuint program_handle, BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); - const auto& gpu = Core::System::GetInstance().GPU(); + const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); const auto& entries = shader->GetShaderEntries().samplers; @@ -1002,10 +992,9 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc); - Surface surface = res_cache.GetTextureSurface(texture, entry); - if (surface != nullptr) { + if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) { state.texture_units[current_bindpoint].texture = - entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle; + surface->Texture(entry.IsArray()).handle; surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, texture.tic.w_source); } else { @@ -1016,7 +1005,7 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s } void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry)); const std::size_t viewport_count = @@ -1039,7 +1028,7 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { void RasterizerOpenGL::SyncClipEnabled( const std::array<bool, Maxwell::Regs::NumClipDistances>& clip_mask) { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; const std::array<bool, Maxwell::Regs::NumClipDistances> reg_state{ regs.clip_distance_enabled.c0 != 0, regs.clip_distance_enabled.c1 != 0, regs.clip_distance_enabled.c2 != 0, regs.clip_distance_enabled.c3 != 0, @@ -1056,7 +1045,7 @@ void RasterizerOpenGL::SyncClipCoef() { } void RasterizerOpenGL::SyncCullMode() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.cull.enabled = regs.cull.enabled != 0; @@ -1080,14 +1069,14 @@ void RasterizerOpenGL::SyncCullMode() { } void RasterizerOpenGL::SyncPrimitiveRestart() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.primitive_restart.enabled = regs.primitive_restart.enabled; state.primitive_restart.index = regs.primitive_restart.index; } void RasterizerOpenGL::SyncDepthTestState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.depth.test_enabled = regs.depth_test_enable != 0; state.depth.write_mask = regs.depth_write_enabled ? GL_TRUE : GL_FALSE; @@ -1099,7 +1088,7 @@ void RasterizerOpenGL::SyncDepthTestState() { } void RasterizerOpenGL::SyncStencilTestState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.stencil.test_enabled = regs.stencil_enable != 0; if (!regs.stencil_enable) { @@ -1133,7 +1122,7 @@ void RasterizerOpenGL::SyncStencilTestState() { } void RasterizerOpenGL::SyncColorMask() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; const std::size_t count = regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1; for (std::size_t i = 0; i < count; i++) { @@ -1147,18 +1136,18 @@ void RasterizerOpenGL::SyncColorMask() { } void RasterizerOpenGL::SyncMultiSampleState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.multisample_control.alpha_to_coverage = regs.multisample_control.alpha_to_coverage != 0; state.multisample_control.alpha_to_one = regs.multisample_control.alpha_to_one != 0; } void RasterizerOpenGL::SyncFragmentColorClampState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.fragment_color_clamp.enabled = regs.frag_color_clamp != 0; } void RasterizerOpenGL::SyncBlendState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.blend_color.red = regs.blend_color.r; state.blend_color.green = regs.blend_color.g; @@ -1200,7 +1189,7 @@ void RasterizerOpenGL::SyncBlendState() { } void RasterizerOpenGL::SyncLogicOpState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.logic_op.enabled = regs.logic_op.enable != 0; @@ -1214,7 +1203,7 @@ void RasterizerOpenGL::SyncLogicOpState() { } void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry)); const std::size_t viewport_count = @@ -1236,17 +1225,17 @@ void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) { } void RasterizerOpenGL::SyncTransformFeedback() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; UNIMPLEMENTED_IF_MSG(regs.tfb_enabled != 0, "Transform feedbacks are not implemented"); } void RasterizerOpenGL::SyncPointState() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.point.size = regs.point_size; } void RasterizerOpenGL::SyncPolygonOffset() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0; state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0; state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0; @@ -1256,7 +1245,7 @@ void RasterizerOpenGL::SyncPolygonOffset() { } void RasterizerOpenGL::CheckAlphaTests() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& regs = system.GPU().Maxwell3D().regs; UNIMPLEMENTED_IF_MSG(regs.alpha_test_enabled != 0 && regs.rt_control.count > 1, "Alpha Testing is enabled with more than one rendertarget"); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 2f0524f85..30f3e8acb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -57,9 +57,9 @@ public: void DrawArrays() override; void Clear() 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; + void FlushRegion(CacheAddr addr, u64 size) override; + void InvalidateRegion(CacheAddr addr, u64 size) override; + void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst, const Common::Rectangle<u32>& src_rect, @@ -67,7 +67,7 @@ public: bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; bool AccelerateDrawBatch(bool is_indexed) override; - void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override; + void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override; void LoadDiskResources(const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) override; @@ -94,11 +94,12 @@ private: private: Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest; Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest; - Tegra::Texture::TextureMipmapFilter mip_filter = Tegra::Texture::TextureMipmapFilter::None; + Tegra::Texture::TextureMipmapFilter mipmap_filter = + Tegra::Texture::TextureMipmapFilter::None; Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge; Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge; Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge; - bool uses_depth_compare = false; + bool use_depth_compare = false; Tegra::Texture::DepthCompareFunc depth_compare_func = Tegra::Texture::DepthCompareFunc::Always; GLvec4 border_color = {}; @@ -214,6 +215,7 @@ private: GlobalRegionCacheOpenGL global_cache; Core::Frontend::EmuWindow& emu_window; + Core::System& system; ScreenInfo& screen_info; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 876698b37..0235317c0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -55,12 +55,11 @@ static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) { } } -void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { +void SurfaceParams::InitCacheParameters(GPUVAddr gpu_addr_) { auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)}; - addr = cpu_addr ? *cpu_addr : 0; gpu_addr = gpu_addr_; + host_ptr = memory_manager.GetPointer(gpu_addr_); size_in_bytes = SizeInBytesRaw(); if (IsPixelFormatASTC(pixel_format)) { @@ -223,7 +222,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, } /*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer( - u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format, + u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format, u32 block_width, u32 block_height, u32 block_depth, Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { SurfaceParams params{}; @@ -400,6 +399,27 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType return format; } +/// Returns the discrepant array target +constexpr GLenum GetArrayDiscrepantTarget(SurfaceTarget target) { + switch (target) { + case SurfaceTarget::Texture1D: + return GL_TEXTURE_1D_ARRAY; + case SurfaceTarget::Texture2D: + return GL_TEXTURE_2D_ARRAY; + case SurfaceTarget::Texture3D: + return GL_NONE; + case SurfaceTarget::Texture1DArray: + return GL_TEXTURE_1D; + case SurfaceTarget::Texture2DArray: + return GL_TEXTURE_2D; + case SurfaceTarget::TextureCubemap: + return GL_TEXTURE_CUBE_MAP_ARRAY; + case SurfaceTarget::TextureCubeArray: + return GL_TEXTURE_CUBE_MAP; + } + return GL_NONE; +} + Common::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const { u32 actual_height{std::max(1U, unaligned_height >> mip_level)}; if (IsPixelFormatASTC(pixel_format)) { @@ -425,7 +445,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params, MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), params.MipBlockHeight(mip_level), params.MipHeight(mip_level), params.MipBlockDepth(mip_level), 1, params.tile_width_spacing, - gl_buffer.data() + offset_gl, gl_size, params.addr + offset); + gl_buffer.data() + offset_gl, params.host_ptr + offset); offset += layer_size; offset_gl += gl_size; } @@ -434,7 +454,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params, MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), params.MipBlockHeight(mip_level), params.MipHeight(mip_level), params.MipBlockDepth(mip_level), depth, params.tile_width_spacing, - gl_buffer.data(), gl_buffer.size(), params.addr + offset); + gl_buffer.data(), params.host_ptr + offset); } } @@ -492,9 +512,9 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac "reinterpretation but the texture is tiled."); } const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes; - + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size, - Memory::GetPointer(dst_params.addr + src_params.size_in_bytes)); + memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes)); } glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); @@ -542,8 +562,14 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac } CachedSurface::CachedSurface(const SurfaceParams& params) - : params(params), gl_target(SurfaceTargetToGL(params.target)), - cached_size_in_bytes(params.size_in_bytes) { + : params{params}, gl_target{SurfaceTargetToGL(params.target)}, + cached_size_in_bytes{params.size_in_bytes}, RasterizerCacheObject{params.host_ptr} { + + const auto optional_cpu_addr{ + Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(params.gpu_addr)}; + ASSERT_MSG(optional_cpu_addr, "optional_cpu_addr is invalid"); + cpu_addr = *optional_cpu_addr; + texture.Create(gl_target); // TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0) @@ -582,19 +608,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params) ApplyTextureDefaults(texture.handle, params.max_mip_level); - OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, params.IdentityString()); - - // Clamp size to mapped GPU memory region - // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000 - // R32F render buffer. We do not yet know if this is a game bug or something else, but this - // check is necessary to prevent flushing from overwriting unmapped memory. - - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr}; - if (cached_size_in_bytes > max_size) { - LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size); - cached_size_in_bytes = max_size; - } + OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString()); } MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); @@ -612,10 +626,9 @@ void CachedSurface::LoadGLBuffer() { const u32 bpp = params.GetFormatBpp() / 8; const u32 copy_size = params.width * bpp; if (params.pitch == copy_size) { - std::memcpy(gl_buffer[0].data(), Memory::GetPointer(params.addr), - params.size_in_bytes_gl); + std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl); } else { - const u8* start = Memory::GetPointer(params.addr); + const u8* start{params.host_ptr}; u8* write_to = gl_buffer[0].data(); for (u32 h = params.height; h > 0; h--) { std::memcpy(write_to, start, copy_size); @@ -659,8 +672,6 @@ void CachedSurface::FlushGLBuffer() { glPixelStorei(GL_PACK_ROW_LENGTH, 0); Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width, params.height, params.depth, true, true); - const u8* const texture_src_data = Memory::GetPointer(params.addr); - ASSERT(texture_src_data); if (params.is_tiled) { ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", params.block_width, static_cast<u32>(params.target)); @@ -670,9 +681,9 @@ void CachedSurface::FlushGLBuffer() { const u32 bpp = params.GetFormatBpp() / 8; const u32 copy_size = params.width * bpp; if (params.pitch == copy_size) { - std::memcpy(Memory::GetPointer(params.addr), gl_buffer[0].data(), GetSizeInBytes()); + std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes()); } else { - u8* start = Memory::GetPointer(params.addr); + u8* start{params.host_ptr}; const u8* read_to = gl_buffer[0].data(); for (u32 h = params.height; h > 0; h--) { std::memcpy(start, read_to, copy_size); @@ -795,20 +806,22 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } -void CachedSurface::EnsureTextureView() { - if (texture_view.handle != 0) +void CachedSurface::EnsureTextureDiscrepantView() { + if (discrepant_view.handle != 0) return; - const GLenum target{TargetLayer()}; + const GLenum target{GetArrayDiscrepantTarget(params.target)}; + ASSERT(target != GL_NONE); + const GLuint num_layers{target == GL_TEXTURE_CUBE_MAP_ARRAY ? 6u : 1u}; constexpr GLuint min_layer = 0; constexpr GLuint min_level = 0; - glGenTextures(1, &texture_view.handle); - glTextureView(texture_view.handle, target, texture.handle, gl_internal_format, min_level, + glGenTextures(1, &discrepant_view.handle); + glTextureView(discrepant_view.handle, target, texture.handle, gl_internal_format, min_level, params.max_mip_level, min_layer, num_layers); - ApplyTextureDefaults(texture_view.handle, params.max_mip_level); - glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA, + ApplyTextureDefaults(discrepant_view.handle, params.max_mip_level); + glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, reinterpret_cast<const GLint*>(swizzle.data())); } @@ -834,8 +847,8 @@ void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, swizzle = {new_x, new_y, new_z, new_w}; const auto swizzle_data = reinterpret_cast<const GLint*>(swizzle.data()); glTextureParameteriv(texture.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); - if (texture_view.handle != 0) { - glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); + if (discrepant_view.handle != 0) { + glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); } } @@ -904,12 +917,12 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { } Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) { - if (params.addr == 0 || params.height * params.width == 0) { + if (!params.IsValid()) { return {}; } // Look up surface in the cache based on address - Surface surface{TryGet(params.addr)}; + Surface surface{TryGet(params.host_ptr)}; if (surface) { if (surface->GetSurfaceParams().IsCompatibleSurface(params)) { // Use the cached surface as-is unless it's not synced with memory @@ -920,7 +933,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres // If surface parameters changed and we care about keeping the previous data, recreate // the surface from the old one Surface new_surface{RecreateSurface(surface, params)}; - UnregisterSurface(surface); + Unregister(surface); Register(new_surface); if (new_surface->IsUploaded()) { RegisterReinterpretSurface(new_surface); @@ -928,7 +941,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres return new_surface; } else { // Delete the old surface before creating a new one to prevent collisions. - UnregisterSurface(surface); + Unregister(surface); } } @@ -958,14 +971,16 @@ void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface) { const auto& init_params{src_surface->GetSurfaceParams()}; const auto& dst_params{dst_surface->GetSurfaceParams()}; - VAddr address = init_params.addr; - const std::size_t layer_size = dst_params.LayerMemorySize(); + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + GPUVAddr address{init_params.gpu_addr}; + const std::size_t layer_size{dst_params.LayerMemorySize()}; for (u32 layer = 0; layer < dst_params.depth; layer++) { for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) { - const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap); - const Surface& copy = TryGet(sub_address); - if (!copy) + const GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)}; + const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))}; + if (!copy) { continue; + } const auto& src_params{copy->GetSurfaceParams()}; const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))}; const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))}; @@ -1140,7 +1155,8 @@ void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface, const auto& dst_params{dst_surface->GetSurfaceParams()}; // Flush enough memory for both the source and destination surface - FlushRegion(src_params.addr, std::max(src_params.MemorySize(), dst_params.MemorySize())); + FlushRegion(ToCacheAddr(src_params.host_ptr), + std::max(src_params.MemorySize(), dst_params.MemorySize())); LoadSurface(dst_surface); } @@ -1192,8 +1208,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, return new_surface; } -Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const { - return TryGet(addr); +Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const { + return TryGet(host_ptr); } void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) { @@ -1220,9 +1236,9 @@ static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfacePar return {}; } -static std::optional<u32> TryFindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap) { - const std::size_t size = params.LayerMemorySize(); - VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap); +static std::optional<u32> TryFindBestLayer(GPUVAddr addr, const SurfaceParams params, u32 mipmap) { + const std::size_t size{params.LayerMemorySize()}; + GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)}; for (u32 i = 0; i < params.depth; i++) { if (start == addr) { return {i}; @@ -1244,7 +1260,7 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa src_params.height == dst_params.MipHeight(*level) && src_params.block_height >= dst_params.MipBlockHeight(*level)) { const std::optional<u32> slot = - TryFindBestLayer(render_surface->GetAddr(), dst_params, *level); + TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level); if (slot.has_value()) { glCopyImageSubData(render_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0, 0, @@ -1260,8 +1276,8 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa } static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) { - const VAddr bound1 = blitted_surface->GetAddr() + blitted_surface->GetMemorySize(); - const VAddr bound2 = render_surface->GetAddr() + render_surface->GetMemorySize(); + const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize(); + const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize(); if (bound2 > bound1) return true; const auto& dst_params = blitted_surface->GetSurfaceParams(); @@ -1279,12 +1295,12 @@ static bool IsReinterpretInvalidSecond(const Surface render_surface, bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface, Surface intersect) { if (IsReinterpretInvalid(triggering_surface, intersect)) { - UnregisterSurface(intersect); + Unregister(intersect); return false; } if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) { if (IsReinterpretInvalidSecond(triggering_surface, intersect)) { - UnregisterSurface(intersect); + Unregister(intersect); return false; } FlushObject(intersect); @@ -1304,7 +1320,8 @@ void RasterizerCacheOpenGL::SignalPreDrawCall() { void RasterizerCacheOpenGL::SignalPostDrawCall() { for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) { if (current_color_buffers[i] != nullptr) { - Surface intersect = CollideOnReinterpretedSurface(current_color_buffers[i]->GetAddr()); + Surface intersect = + CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr()); if (intersect != nullptr) { PartialReinterpretSurface(current_color_buffers[i], intersect); texception = true; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 797bbdc9c..c644271d0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -109,6 +109,11 @@ struct SurfaceParams { return size; } + /// Returns true if the parameters constitute a valid rasterizer surface. + bool IsValid() const { + return gpu_addr && host_ptr && height && width; + } + /// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including /// mipmaps. std::size_t LayerMemorySize() const { @@ -210,7 +215,7 @@ struct SurfaceParams { /// Creates SurfaceParams for a depth buffer configuration static SurfaceParams CreateForDepthBuffer( - u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format, + u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format, u32 block_width, u32 block_height, u32 block_depth, Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); @@ -232,7 +237,7 @@ struct SurfaceParams { } /// Initializes parameters for caching, should be called after everything has been initialized - void InitCacheParameters(Tegra::GPUVAddr gpu_addr); + void InitCacheParameters(GPUVAddr gpu_addr); std::string TargetName() const { switch (target) { @@ -296,8 +301,8 @@ struct SurfaceParams { bool is_array; bool srgb_conversion; // Parameters used for caching - VAddr addr; - Tegra::GPUVAddr gpu_addr; + u8* host_ptr; + GPUVAddr gpu_addr; std::size_t size_in_bytes; std::size_t size_in_bytes_gl; @@ -345,10 +350,10 @@ class RasterizerOpenGL; class CachedSurface final : public RasterizerCacheObject { public: - CachedSurface(const SurfaceParams& params); + explicit CachedSurface(const SurfaceParams& params); - VAddr GetAddr() const override { - return params.addr; + VAddr GetCpuAddr() const override { + return cpu_addr; } std::size_t GetSizeInBytes() const override { @@ -367,31 +372,19 @@ public: return texture; } - const OGLTexture& TextureLayer() { - if (params.is_array) { - return Texture(); + const OGLTexture& Texture(bool as_array) { + if (params.is_array == as_array) { + return texture; + } else { + EnsureTextureDiscrepantView(); + return discrepant_view; } - EnsureTextureView(); - return texture_view; } GLenum Target() const { return gl_target; } - GLenum TargetLayer() const { - using VideoCore::Surface::SurfaceTarget; - switch (params.target) { - case SurfaceTarget::Texture1D: - return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP_ARRAY; - } - return Target(); - } - const SurfaceParams& GetSurfaceParams() const { return params; } @@ -431,10 +424,10 @@ public: private: void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle); - void EnsureTextureView(); + void EnsureTextureDiscrepantView(); OGLTexture texture; - OGLTexture texture_view; + OGLTexture discrepant_view; std::vector<std::vector<u8>> gl_buffer; SurfaceParams params{}; GLenum gl_target{}; @@ -444,6 +437,7 @@ private: std::size_t memory_size; bool reinterpreted = false; bool must_reload = false; + VAddr cpu_addr{}; }; class RasterizerCacheOpenGL final : public RasterizerCache<Surface> { @@ -461,7 +455,7 @@ public: Surface GetColorBufferSurface(std::size_t index, bool preserve_contents); /// Tries to find a framebuffer using on the provided CPU address - Surface TryFindFramebufferSurface(VAddr addr) const; + Surface TryFindFramebufferSurface(const u8* host_ptr) const; /// Copies the contents of one surface to another void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, @@ -518,12 +512,12 @@ private: std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers; Surface last_depth_buffer; - using SurfaceIntervalCache = boost::icl::interval_map<VAddr, Surface>; + using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>; using SurfaceInterval = typename SurfaceIntervalCache::interval_type; static auto GetReinterpretInterval(const Surface& object) { - return SurfaceInterval::right_open(object->GetAddr() + 1, - object->GetAddr() + object->GetMemorySize() - 1); + return SurfaceInterval::right_open(object->GetCacheAddr() + 1, + object->GetCacheAddr() + object->GetMemorySize() - 1); } // Reinterpreted surfaces are very fragil as the game may keep rendering into them. @@ -535,7 +529,7 @@ private: reinterpret_surface->MarkReinterpreted(); } - Surface CollideOnReinterpretedSurface(VAddr addr) const { + Surface CollideOnReinterpretedSurface(CacheAddr addr) const { const SurfaceInterval interval{addr}; for (auto& pair : boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) { @@ -544,13 +538,17 @@ private: return nullptr; } + void Register(const Surface& object) { + RasterizerCache<Surface>::Register(object); + } + /// Unregisters an object from the cache - void UnregisterSurface(const Surface& object) { + void Unregister(const Surface& object) { if (object->IsReinterpreted()) { auto interval = GetReinterpretInterval(object); reinterpreted_surfaces.erase(interval); } - Unregister(object); + RasterizerCache<Surface>::Unregister(object); } }; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 4883e4f62..1f8eca6f0 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -32,19 +32,16 @@ struct UnspecializedShader { namespace { /// Gets the address for the specified shader stage program -VAddr GetShaderAddress(Maxwell::ShaderProgram program) { - const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); - const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)]; - const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() + - shader_config.offset); - ASSERT_MSG(address, "Invalid GPU address"); - return *address; +GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) { + const auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; + const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]}; + return gpu.regs.code_address.CodeAddress() + shader_config.offset; } /// Gets the shader program code from memory for the specified address -ProgramCode GetShaderCode(VAddr addr) { +ProgramCode GetShaderCode(const u8* host_ptr) { ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH); - Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64)); + std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64)); return program_code; } @@ -214,12 +211,13 @@ std::set<GLenum> GetSupportedFormats() { } // namespace -CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, - ShaderDiskCacheOpenGL& disk_cache, +CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier, + Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache, const PrecompiledPrograms& precompiled_programs, - ProgramCode&& program_code, ProgramCode&& program_code_b) - : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type}, - disk_cache{disk_cache}, precompiled_programs{precompiled_programs} { + ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr) + : host_ptr{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier}, + program_type{program_type}, disk_cache{disk_cache}, + precompiled_programs{precompiled_programs}, RasterizerCacheObject{host_ptr} { const std::size_t code_size = CalculateProgramSize(program_code); const std::size_t code_size_b = @@ -243,12 +241,13 @@ CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderPro disk_cache.SaveRaw(raw); } -CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, - ShaderDiskCacheOpenGL& disk_cache, +CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier, + Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache, const PrecompiledPrograms& precompiled_programs, - GLShader::ProgramResult result) - : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type}, - disk_cache{disk_cache}, precompiled_programs{precompiled_programs} { + GLShader::ProgramResult result, u8* host_ptr) + : cpu_addr{cpu_addr}, unique_identifier{unique_identifier}, program_type{program_type}, + disk_cache{disk_cache}, precompiled_programs{precompiled_programs}, RasterizerCacheObject{ + host_ptr} { code = std::move(result.first); entries = result.second; @@ -271,7 +270,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); } - LabelGLObject(GL_PROGRAM, program->handle, addr); + LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); } handle = program->handle; @@ -323,7 +322,7 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); } - LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name); + LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name); return target_program->handle; }; @@ -486,29 +485,32 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { return last_shaders[static_cast<u32>(program)]; } - const VAddr program_addr{GetShaderAddress(program)}; + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const GPUVAddr program_addr{GetShaderAddress(program)}; // Look up shader in the cache based on address - Shader shader{TryGet(program_addr)}; + const auto& host_ptr{memory_manager.GetPointer(program_addr)}; + Shader shader{TryGet(host_ptr)}; if (!shader) { // No shader found - create a new one - ProgramCode program_code = GetShaderCode(program_addr); + ProgramCode program_code{GetShaderCode(host_ptr)}; ProgramCode program_code_b; if (program == Maxwell::ShaderProgram::VertexA) { - program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)); + program_code_b = GetShaderCode( + memory_manager.GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB))); } const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b); - + const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)}; const auto found = precompiled_shaders.find(unique_identifier); if (found != precompiled_shaders.end()) { shader = - std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache, - precompiled_programs, found->second); + std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache, + precompiled_programs, found->second, host_ptr); } else { shader = std::make_shared<CachedShader>( - program_addr, unique_identifier, program, disk_cache, precompiled_programs, - std::move(program_code), std::move(program_code_b)); + cpu_addr, unique_identifier, program, disk_cache, precompiled_programs, + std::move(program_code), std::move(program_code_b), host_ptr); } Register(shader); } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 97eed192f..fd1c85115 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -39,18 +39,18 @@ using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>; class CachedShader final : public RasterizerCacheObject { public: - explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, - ShaderDiskCacheOpenGL& disk_cache, + explicit CachedShader(VAddr cpu_addr, u64 unique_identifier, + Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache, const PrecompiledPrograms& precompiled_programs, - ProgramCode&& program_code, ProgramCode&& program_code_b); + ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr); - explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type, - ShaderDiskCacheOpenGL& disk_cache, + explicit CachedShader(VAddr cpu_addr, u64 unique_identifier, + Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache, const PrecompiledPrograms& precompiled_programs, - GLShader::ProgramResult result); + GLShader::ProgramResult result, u8* host_ptr); - VAddr GetAddr() const override { - return addr; + VAddr GetCpuAddr() const override { + return cpu_addr; } std::size_t GetSizeInBytes() const override { @@ -91,7 +91,8 @@ private: ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const; - VAddr addr{}; + u8* host_ptr{}; + VAddr cpu_addr{}; u64 unique_identifier{}; Maxwell::ShaderProgram program_type{}; ShaderDiskCacheOpenGL& disk_cache; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 72ff6ac6a..11d1169f0 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -5,7 +5,9 @@ #include <array> #include <string> #include <string_view> +#include <utility> #include <variant> +#include <vector> #include <fmt/format.h> @@ -717,7 +719,7 @@ private: } std::string GenerateTexture(Operation operation, const std::string& func, - bool is_extra_int = false) { + const std::vector<std::pair<Type, Node>>& extras) { constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); @@ -738,36 +740,47 @@ private: expr += Visit(operation[i]); const std::size_t next = i + 1; - if (next < count || has_array || has_shadow) + if (next < count) expr += ", "; } if (has_array) { - expr += "float(ftoi(" + Visit(meta->array) + "))"; + expr += ", float(ftoi(" + Visit(meta->array) + "))"; } if (has_shadow) { - if (has_array) - expr += ", "; - expr += Visit(meta->depth_compare); + expr += ", " + Visit(meta->depth_compare); } expr += ')'; - for (const Node extra : meta->extras) { + for (const auto& extra_pair : extras) { + const auto [type, operand] = extra_pair; + if (operand == nullptr) { + continue; + } expr += ", "; - if (is_extra_int) { - if (const auto immediate = std::get_if<ImmediateNode>(extra)) { + + switch (type) { + case Type::Int: + if (const auto immediate = std::get_if<ImmediateNode>(operand)) { // Inline the string as an immediate integer in GLSL (some extra arguments are // required to be constant) expr += std::to_string(static_cast<s32>(immediate->GetValue())); } else { - expr += "ftoi(" + Visit(extra) + ')'; + expr += "ftoi(" + Visit(operand) + ')'; } - } else { - expr += Visit(extra); + break; + case Type::Float: + expr += Visit(operand); + break; + default: { + const auto type_int = static_cast<u32>(type); + UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int); + expr += '0'; + break; + } } } - expr += ')'; - return expr; + return expr + ')'; } std::string Assign(Operation operation) { @@ -1146,7 +1159,7 @@ private: const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); - std::string expr = GenerateTexture(operation, "texture"); + std::string expr = GenerateTexture(operation, "texture", {{Type::Float, meta->bias}}); if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } @@ -1157,7 +1170,7 @@ private: const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); - std::string expr = GenerateTexture(operation, "textureLod"); + std::string expr = GenerateTexture(operation, "textureLod", {{Type::Float, meta->lod}}); if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } @@ -1168,7 +1181,8 @@ private: const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); - return GenerateTexture(operation, "textureGather", !meta->sampler.IsShadow()) + + const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; + return GenerateTexture(operation, "textureGather", {{type, meta->component}}) + GetSwizzle(meta->element); } @@ -1197,8 +1211,8 @@ private: ASSERT(meta); if (meta->element < 2) { - return "itof(int((" + GenerateTexture(operation, "textureQueryLod") + " * vec2(256))" + - GetSwizzle(meta->element) + "))"; + return "itof(int((" + GenerateTexture(operation, "textureQueryLod", {}) + + " * vec2(256))" + GetSwizzle(meta->element) + "))"; } return "0"; } @@ -1224,9 +1238,9 @@ private: else if (next < count) expr += ", "; } - for (std::size_t i = 0; i < meta->extras.size(); ++i) { + if (meta->lod) { expr += ", "; - expr += CastOperand(Visit(meta->extras.at(i)), Type::Int); + expr += CastOperand(Visit(meta->lod), Type::Int); } expr += ')'; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 8b510b6ae..5e3d862c6 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -164,12 +164,13 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf // Reset the screen info's display texture to its own permanent texture screen_info.display_texture = screen_info.texture.resource.handle; - Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes, - Memory::FlushMode::Flush); + rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes); - VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4, - Memory::GetPointer(framebuffer_addr), - gl_framebuffer_data.data(), true); + constexpr u32 linear_bpp = 4; + VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear, + framebuffer.width, framebuffer.height, bytes_per_pixel, + linear_bpp, Memory::GetPointer(framebuffer_addr), + gl_framebuffer_data.data()); glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 4a33a6c84..eac51ecb3 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -17,6 +17,11 @@ namespace Vulkan { +CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, + std::size_t alignment, u8* host_ptr) + : cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{ + host_ptr} {} + VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager, VideoCore::RasterizerInterface& rasterizer, const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size) @@ -34,19 +39,20 @@ VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager, VKBufferCache::~VKBufferCache() = default; -u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment, - bool cache) { +u64 VKBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment, bool cache) { const auto cpu_addr{tegra_memory_manager.GpuToCpuAddress(gpu_addr)}; - ASSERT(cpu_addr); + ASSERT_MSG(cpu_addr, "Invalid GPU address"); // Cache management is a big overhead, so only cache entries with a given size. // TODO: Figure out which size is the best for given games. cache &= size >= 2048; + const auto& host_ptr{Memory::GetPointer(*cpu_addr)}; if (cache) { - if (auto entry = TryGet(*cpu_addr); entry) { - if (entry->size >= size && entry->alignment == alignment) { - return entry->offset; + auto entry = TryGet(host_ptr); + if (entry) { + if (entry->GetSize() >= size && entry->GetAlignment() == alignment) { + return entry->GetOffset(); } Unregister(entry); } @@ -55,17 +61,17 @@ u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 AlignBuffer(alignment); const u64 uploaded_offset = buffer_offset; - Memory::ReadBlock(*cpu_addr, buffer_ptr, size); + if (!host_ptr) { + return uploaded_offset; + } + std::memcpy(buffer_ptr, host_ptr, size); buffer_ptr += size; buffer_offset += size; if (cache) { - auto entry = std::make_shared<CachedBufferEntry>(); - entry->offset = uploaded_offset; - entry->size = size; - entry->alignment = alignment; - entry->addr = *cpu_addr; + auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset, + alignment, host_ptr); Register(entry); } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index d8e916f31..08b786aad 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -24,22 +24,39 @@ class VKFence; class VKMemoryManager; class VKStreamBuffer; -struct CachedBufferEntry final : public RasterizerCacheObject { - VAddr GetAddr() const override { - return addr; +class CachedBufferEntry final : public RasterizerCacheObject { +public: + explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, std::size_t alignment, + u8* host_ptr); + + VAddr GetCpuAddr() const override { + return cpu_addr; } std::size_t GetSizeInBytes() const override { return size; } + std::size_t GetSize() const { + return size; + } + + u64 GetOffset() const { + return offset; + } + + std::size_t GetAlignment() const { + return alignment; + } + // We do not have to flush this cache as things in it are never modified by us. void Flush() override {} - VAddr addr; - std::size_t size; - u64 offset; - std::size_t alignment; +private: + VAddr cpu_addr{}; + std::size_t size{}; + u64 offset{}; + std::size_t alignment{}; }; class VKBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> { @@ -51,8 +68,7 @@ public: /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been /// allocated. - u64 UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4, - bool cache = true); + u64 UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4, bool cache = true); /// Uploads from a host memory. Returns host's buffer offset where it's been allocated. u64 UploadHostMemory(const u8* raw_pointer, std::size_t size, u64 alignment = 4); diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp new file mode 100644 index 000000000..ed3178f09 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp @@ -0,0 +1,81 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <optional> +#include <unordered_map> + +#include "common/assert.h" +#include "common/cityhash.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_sampler_cache.h" +#include "video_core/textures/texture.h" + +namespace Vulkan { + +static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4> color) { + // TODO(Rodrigo): Manage integer border colors + if (color == std::array<float, 4>{0, 0, 0, 0}) { + return vk::BorderColor::eFloatTransparentBlack; + } else if (color == std::array<float, 4>{0, 0, 0, 1}) { + return vk::BorderColor::eFloatOpaqueBlack; + } else if (color == std::array<float, 4>{1, 1, 1, 1}) { + return vk::BorderColor::eFloatOpaqueWhite; + } else { + return {}; + } +} + +std::size_t SamplerCacheKey::Hash() const { + static_assert(sizeof(raw) % sizeof(u64) == 0); + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64))); +} + +bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const { + return raw == rhs.raw; +} + +VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {} + +VKSamplerCache::~VKSamplerCache() = default; + +vk::Sampler VKSamplerCache::GetSampler(const Tegra::Texture::TSCEntry& tsc) { + const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc}); + auto& sampler = entry->second; + if (is_cache_miss) { + sampler = CreateSampler(tsc); + } + return *sampler; +} + +UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) { + const float max_anisotropy = tsc.GetMaxAnisotropy(); + const bool has_anisotropy = max_anisotropy > 1.0f; + + const auto border_color = tsc.GetBorderColor(); + const auto vk_border_color = TryConvertBorderColor(border_color); + UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}", + border_color[0], border_color[1], border_color[2], border_color[3]); + + constexpr bool unnormalized_coords = false; + + const vk::SamplerCreateInfo sampler_ci( + {}, MaxwellToVK::Sampler::Filter(tsc.mag_filter), + MaxwellToVK::Sampler::Filter(tsc.min_filter), + MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + MaxwellToVK::Sampler::WrapMode(tsc.wrap_u), MaxwellToVK::Sampler::WrapMode(tsc.wrap_v), + MaxwellToVK::Sampler::WrapMode(tsc.wrap_p), tsc.GetLodBias(), has_anisotropy, + max_anisotropy, tsc.depth_compare_enabled, + MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), tsc.GetMinLod(), + tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack), + unnormalized_coords); + + const auto& dld = device.GetDispatchLoader(); + const auto dev = device.GetLogical(); + return dev.createSamplerUnique(sampler_ci, nullptr, dld); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h new file mode 100644 index 000000000..c6394dc87 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h @@ -0,0 +1,56 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <unordered_map> + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/textures/texture.h" + +namespace Vulkan { + +class VKDevice; + +struct SamplerCacheKey final : public Tegra::Texture::TSCEntry { + std::size_t Hash() const; + + bool operator==(const SamplerCacheKey& rhs) const; + + bool operator!=(const SamplerCacheKey& rhs) const { + return !operator==(rhs); + } +}; + +} // namespace Vulkan + +namespace std { + +template <> +struct hash<Vulkan::SamplerCacheKey> { + std::size_t operator()(const Vulkan::SamplerCacheKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace Vulkan { + +class VKSamplerCache { +public: + explicit VKSamplerCache(const VKDevice& device); + ~VKSamplerCache(); + + vk::Sampler GetSampler(const Tegra::Texture::TSCEntry& tsc); + +private: + UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc); + + const VKDevice& device; + std::unordered_map<SamplerCacheKey, UniqueSampler> cache; +}; + +} // namespace Vulkan diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index 740ac3118..e4c438792 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -165,6 +165,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { {OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2}, {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, + {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp index 38f01ca50..ea3c71eed 100644 --- a/src/video_core/shader/decode/memory.cpp +++ b/src/video_core/shader/decode/memory.cpp @@ -17,24 +17,6 @@ using Tegra::Shader::Attribute; using Tegra::Shader::Instruction; using Tegra::Shader::OpCode; using Tegra::Shader::Register; -using Tegra::Shader::TextureMiscMode; -using Tegra::Shader::TextureProcessMode; -using Tegra::Shader::TextureType; - -static std::size_t GetCoordCount(TextureType texture_type) { - switch (texture_type) { - case TextureType::Texture1D: - return 1; - case TextureType::Texture2D: - return 2; - case TextureType::Texture3D: - case TextureType::TextureCube: - return 3; - default: - UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type)); - return 0; - } -} u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; @@ -247,194 +229,6 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } break; } - case OpCode::Id::TEX: { - UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - - if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete"); - } - - const TextureType texture_type{instr.tex.texture_type}; - const bool is_array = instr.tex.array != 0; - const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC); - const auto process_mode = instr.tex.GetTextureProcessMode(); - WriteTexInstructionFloat( - bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array)); - break; - } - case OpCode::Id::TEXS: { - const TextureType texture_type{instr.texs.GetTextureType()}; - const bool is_array{instr.texs.IsArrayTexture()}; - const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC); - const auto process_mode = instr.texs.GetTextureProcessMode(); - - if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete"); - } - - const Node4 components = - GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array); - - if (instr.texs.fp32_flag) { - WriteTexsInstructionFloat(bb, instr, components); - } else { - WriteTexsInstructionHalfFloat(bb, instr, components); - } - break; - } - case OpCode::Id::TLD4: { - ASSERT(instr.tld4.array == 0); - UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV), - "NDV is not implemented"); - UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP), - "PTP is not implemented"); - - if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete"); - } - - const auto texture_type = instr.tld4.texture_type.Value(); - const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC); - const bool is_array = instr.tld4.array != 0; - WriteTexInstructionFloat(bb, instr, - GetTld4Code(instr, texture_type, depth_compare, is_array)); - break; - } - case OpCode::Id::TLD4S: { - UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete"); - } - - const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC); - const Node op_a = GetRegister(instr.gpr8); - const Node op_b = GetRegister(instr.gpr20); - - // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. - std::vector<Node> coords; - if (depth_compare) { - // Note: TLD4S coordinate encoding works just like TEXS's - const Node op_y = GetRegister(instr.gpr8.Value() + 1); - coords.push_back(op_a); - coords.push_back(op_y); - coords.push_back(op_b); - } else { - coords.push_back(op_a); - coords.push_back(op_b); - } - std::vector<Node> extras; - extras.push_back(Immediate(static_cast<u32>(instr.tld4s.component))); - - const auto& sampler = - GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare); - - Node4 values; - for (u32 element = 0; element < values.size(); ++element) { - auto coords_copy = coords; - MetaTexture meta{sampler, {}, {}, extras, element}; - values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); - } - - WriteTexsInstructionFloat(bb, instr, values); - break; - } - case OpCode::Id::TXQ: { - if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete"); - } - - // TODO: The new commits on the texture refactor, change the way samplers work. - // Sadly, not all texture instructions specify the type of texture their sampler - // uses. This must be fixed at a later instance. - const auto& sampler = - GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false); - - u32 indexer = 0; - switch (instr.txq.query_type) { - case Tegra::Shader::TextureQueryType::Dimension: { - for (u32 element = 0; element < 4; ++element) { - if (!instr.txq.IsComponentEnabled(element)) { - continue; - } - MetaTexture meta{sampler, {}, {}, {}, element}; - const Node value = - Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8)); - SetTemporal(bb, indexer++, value); - } - for (u32 i = 0; i < indexer; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); - } - break; - } - default: - UNIMPLEMENTED_MSG("Unhandled texture query type: {}", - static_cast<u32>(instr.txq.query_type.Value())); - } - break; - } - case OpCode::Id::TMML: { - UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), - "NDV is not implemented"); - - if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete"); - } - - auto texture_type = instr.tmml.texture_type.Value(); - const bool is_array = instr.tmml.array != 0; - const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); - - std::vector<Node> coords; - - // TODO: Add coordinates for different samplers once other texture types are implemented. - switch (texture_type) { - case TextureType::Texture1D: - coords.push_back(GetRegister(instr.gpr8)); - break; - case TextureType::Texture2D: - coords.push_back(GetRegister(instr.gpr8.Value() + 0)); - coords.push_back(GetRegister(instr.gpr8.Value() + 1)); - break; - default: - UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); - - // Fallback to interpreting as a 2D texture for now - coords.push_back(GetRegister(instr.gpr8.Value() + 0)); - coords.push_back(GetRegister(instr.gpr8.Value() + 1)); - texture_type = TextureType::Texture2D; - } - - for (u32 element = 0; element < 2; ++element) { - auto params = coords; - MetaTexture meta{sampler, {}, {}, {}, element}; - const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); - SetTemporal(bb, element, value); - } - for (u32 element = 0; element < 2; ++element) { - SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element)); - } - - break; - } - case OpCode::Id::TLDS: { - const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; - const bool is_array{instr.tlds.IsArrayTexture()}; - - UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented"); - - if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) { - LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete"); - } - - WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array)); - break; - } default: UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName()); } @@ -442,291 +236,4 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { return pc; } -const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type, - bool is_array, bool is_shadow) { - const auto offset = static_cast<std::size_t>(sampler.index.Value()); - - // If this sampler has already been used, return the existing mapping. - const auto itr = - std::find_if(used_samplers.begin(), used_samplers.end(), - [&](const Sampler& entry) { return entry.GetOffset() == offset; }); - if (itr != used_samplers.end()) { - ASSERT(itr->GetType() == type && itr->IsArray() == is_array && - itr->IsShadow() == is_shadow); - return *itr; - } - - // Otherwise create a new mapping for this sampler - const std::size_t next_index = used_samplers.size(); - const Sampler entry{offset, next_index, type, is_array, is_shadow}; - return *used_samplers.emplace(entry).first; -} - -void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) { - u32 dest_elem = 0; - for (u32 elem = 0; elem < 4; ++elem) { - if (!instr.tex.IsComponentEnabled(elem)) { - // Skip disabled components - continue; - } - SetTemporal(bb, dest_elem++, components[elem]); - } - // After writing values in temporals, move them to the real registers - for (u32 i = 0; i < dest_elem; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); - } -} - -void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr, - const Node4& components) { - // TEXS has two destination registers and a swizzle. The first two elements in the swizzle - // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 - - u32 dest_elem = 0; - for (u32 component = 0; component < 4; ++component) { - if (!instr.texs.IsComponentEnabled(component)) - continue; - SetTemporal(bb, dest_elem++, components[component]); - } - - for (u32 i = 0; i < dest_elem; ++i) { - if (i < 2) { - // Write the first two swizzle components to gpr0 and gpr0+1 - SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i)); - } else { - ASSERT(instr.texs.HasTwoDestinations()); - // Write the rest of the swizzle components to gpr28 and gpr28+1 - SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i)); - } - } -} - -void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr, - const Node4& components) { - // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half - // float instruction). - - Node4 values; - u32 dest_elem = 0; - for (u32 component = 0; component < 4; ++component) { - if (!instr.texs.IsComponentEnabled(component)) - continue; - values[dest_elem++] = components[component]; - } - if (dest_elem == 0) - return; - - std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); }); - - const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]); - if (dest_elem <= 2) { - SetRegister(bb, instr.gpr0, first_value); - return; - } - - SetTemporal(bb, 0, first_value); - SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3])); - - SetRegister(bb, instr.gpr0, GetTemporal(0)); - SetRegister(bb, instr.gpr28, GetTemporal(1)); -} - -Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, - TextureProcessMode process_mode, std::vector<Node> coords, - Node array, Node depth_compare, u32 bias_offset) { - const bool is_array = array; - const bool is_shadow = depth_compare; - - UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) || - (texture_type == TextureType::TextureCube && is_array && is_shadow), - "This method is not supported."); - - const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow); - - const bool lod_needed = process_mode == TextureProcessMode::LZ || - process_mode == TextureProcessMode::LL || - process_mode == TextureProcessMode::LLA; - - // LOD selection (either via bias or explicit textureLod) not supported in GL for - // sampler2DArrayShadow and samplerCubeArrayShadow. - const bool gl_lod_supported = - !((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) || - (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow)); - - const OperationCode read_method = - lod_needed && gl_lod_supported ? OperationCode::TextureLod : OperationCode::Texture; - - UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported); - - std::vector<Node> extras; - if (process_mode != TextureProcessMode::None && gl_lod_supported) { - if (process_mode == TextureProcessMode::LZ) { - extras.push_back(Immediate(0.0f)); - } else { - // If present, lod or bias are always stored in the register indexed by the gpr20 - // field with an offset depending on the usage of the other registers - extras.push_back(GetRegister(instr.gpr20.Value() + bias_offset)); - } - } - - Node4 values; - for (u32 element = 0; element < values.size(); ++element) { - auto copy_coords = coords; - MetaTexture meta{sampler, array, depth_compare, extras, element}; - values[element] = Operation(read_method, meta, std::move(copy_coords)); - } - - return values; -} - -Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type, - TextureProcessMode process_mode, bool depth_compare, bool is_array) { - const bool lod_bias_enabled = - (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ); - - const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement( - texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5); - // If enabled arrays index is always stored in the gpr8 field - const u64 array_register = instr.gpr8.Value(); - // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used - const u64 coord_register = array_register + (is_array ? 1 : 0); - - std::vector<Node> coords; - for (std::size_t i = 0; i < coord_count; ++i) { - coords.push_back(GetRegister(coord_register + i)); - } - // 1D.DC in OpenGL the 2nd component is ignored. - if (depth_compare && !is_array && texture_type == TextureType::Texture1D) { - coords.push_back(Immediate(0.0f)); - } - - const Node array = is_array ? GetRegister(array_register) : nullptr; - - Node dc{}; - if (depth_compare) { - // Depth is always stored in the register signaled by gpr20 or in the next register if lod - // or bias are used - const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); - dc = GetRegister(depth_register); - } - - return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0); -} - -Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type, - TextureProcessMode process_mode, bool depth_compare, bool is_array) { - const bool lod_bias_enabled = - (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ); - - const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement( - texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4); - // If enabled arrays index is always stored in the gpr8 field - const u64 array_register = instr.gpr8.Value(); - // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used - const u64 coord_register = array_register + (is_array ? 1 : 0); - const u64 last_coord_register = - (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2)) - ? static_cast<u64>(instr.gpr20.Value()) - : coord_register + 1; - const u32 bias_offset = coord_count > 2 ? 1 : 0; - - std::vector<Node> coords; - for (std::size_t i = 0; i < coord_count; ++i) { - const bool last = (i == (coord_count - 1)) && (coord_count > 1); - coords.push_back(GetRegister(last ? last_coord_register : coord_register + i)); - } - - const Node array = is_array ? GetRegister(array_register) : nullptr; - - Node dc{}; - if (depth_compare) { - // Depth is always stored in the register signaled by gpr20 or in the next register if lod - // or bias are used - const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); - dc = GetRegister(depth_register); - } - - return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset); -} - -Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare, - bool is_array) { - const std::size_t coord_count = GetCoordCount(texture_type); - const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0); - const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); - - // If enabled arrays index is always stored in the gpr8 field - const u64 array_register = instr.gpr8.Value(); - // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used - const u64 coord_register = array_register + (is_array ? 1 : 0); - - std::vector<Node> coords; - for (size_t i = 0; i < coord_count; ++i) - coords.push_back(GetRegister(coord_register + i)); - - const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare); - - Node4 values; - for (u32 element = 0; element < values.size(); ++element) { - auto coords_copy = coords; - MetaTexture meta{sampler, GetRegister(array_register), {}, {}, element}; - values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); - } - - return values; -} - -Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { - const std::size_t type_coord_count = GetCoordCount(texture_type); - const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; - - // If enabled arrays index is always stored in the gpr8 field - const u64 array_register = instr.gpr8.Value(); - // if is array gpr20 is used - const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value(); - - const u64 last_coord_register = - ((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array - ? static_cast<u64>(instr.gpr20.Value()) - : coord_register + 1; - - std::vector<Node> coords; - for (std::size_t i = 0; i < type_coord_count; ++i) { - const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1); - coords.push_back(GetRegister(last ? last_coord_register : coord_register + i)); - } - - const Node array = is_array ? GetRegister(array_register) : nullptr; - // When lod is used always is in gpr20 - const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0); - - const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); - - Node4 values; - for (u32 element = 0; element < values.size(); ++element) { - auto coords_copy = coords; - MetaTexture meta{sampler, array, {}, {lod}, element}; - values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); - } - return values; -} - -std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement( - TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled, - std::size_t max_coords, std::size_t max_inputs) { - const std::size_t coord_count = GetCoordCount(texture_type); - - std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0); - const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0); - if (total_coord_count > max_coords || total_reg_count > max_inputs) { - UNIMPLEMENTED_MSG("Unsupported Texture operation"); - total_coord_count = std::min(total_coord_count, max_coords); - } - // 1D.DC OpenGL is using a vec3 but 2nd component is ignored later. - total_coord_count += - (depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0; - - return {coord_count, total_coord_count}; -} - } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp new file mode 100644 index 000000000..a99ae19bf --- /dev/null +++ b/src/video_core/shader/decode/texture.cpp @@ -0,0 +1,534 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <vector> +#include <fmt/format.h> + +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { + +using Tegra::Shader::Instruction; +using Tegra::Shader::OpCode; +using Tegra::Shader::Register; +using Tegra::Shader::TextureMiscMode; +using Tegra::Shader::TextureProcessMode; +using Tegra::Shader::TextureType; + +static std::size_t GetCoordCount(TextureType texture_type) { + switch (texture_type) { + case TextureType::Texture1D: + return 1; + case TextureType::Texture2D: + return 2; + case TextureType::Texture3D: + case TextureType::TextureCube: + return 3; + default: + UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type)); + return 0; + } +} + +u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { + const Instruction instr = {program_code[pc]}; + const auto opcode = OpCode::Decode(instr); + + switch (opcode->get().GetId()) { + case OpCode::Id::TEX: { + UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + + if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete"); + } + + const TextureType texture_type{instr.tex.texture_type}; + const bool is_array = instr.tex.array != 0; + const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC); + const auto process_mode = instr.tex.GetTextureProcessMode(); + WriteTexInstructionFloat( + bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array)); + break; + } + case OpCode::Id::TEXS: { + const TextureType texture_type{instr.texs.GetTextureType()}; + const bool is_array{instr.texs.IsArrayTexture()}; + const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC); + const auto process_mode = instr.texs.GetTextureProcessMode(); + + if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete"); + } + + const Node4 components = + GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array); + + if (instr.texs.fp32_flag) { + WriteTexsInstructionFloat(bb, instr, components); + } else { + WriteTexsInstructionHalfFloat(bb, instr, components); + } + break; + } + case OpCode::Id::TLD4: { + ASSERT(instr.tld4.array == 0); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV), + "NDV is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP), + "PTP is not implemented"); + + if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete"); + } + + const auto texture_type = instr.tld4.texture_type.Value(); + const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC); + const bool is_array = instr.tld4.array != 0; + WriteTexInstructionFloat(bb, instr, + GetTld4Code(instr, texture_type, depth_compare, is_array)); + break; + } + case OpCode::Id::TLD4S: { + UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete"); + } + + const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC); + const Node op_a = GetRegister(instr.gpr8); + const Node op_b = GetRegister(instr.gpr20); + + // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. + std::vector<Node> coords; + if (depth_compare) { + // Note: TLD4S coordinate encoding works just like TEXS's + const Node op_y = GetRegister(instr.gpr8.Value() + 1); + coords.push_back(op_a); + coords.push_back(op_y); + coords.push_back(op_b); + } else { + coords.push_back(op_a); + coords.push_back(op_b); + } + const Node component = Immediate(static_cast<u32>(instr.tld4s.component)); + + const auto& sampler = + GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare); + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto coords_copy = coords; + MetaTexture meta{sampler, {}, {}, {}, {}, component, element}; + values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); + } + + WriteTexsInstructionFloat(bb, instr, values); + break; + } + case OpCode::Id::TXQ: { + if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete"); + } + + // TODO: The new commits on the texture refactor, change the way samplers work. + // Sadly, not all texture instructions specify the type of texture their sampler + // uses. This must be fixed at a later instance. + const auto& sampler = + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false); + + u32 indexer = 0; + switch (instr.txq.query_type) { + case Tegra::Shader::TextureQueryType::Dimension: { + for (u32 element = 0; element < 4; ++element) { + if (!instr.txq.IsComponentEnabled(element)) { + continue; + } + MetaTexture meta{sampler, {}, {}, {}, {}, {}, element}; + const Node value = + Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8)); + SetTemporal(bb, indexer++, value); + } + for (u32 i = 0; i < indexer; ++i) { + SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + } + break; + } + default: + UNIMPLEMENTED_MSG("Unhandled texture query type: {}", + static_cast<u32>(instr.txq.query_type.Value())); + } + break; + } + case OpCode::Id::TMML: { + UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); + + if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete"); + } + + auto texture_type = instr.tmml.texture_type.Value(); + const bool is_array = instr.tmml.array != 0; + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + std::vector<Node> coords; + + // TODO: Add coordinates for different samplers once other texture types are implemented. + switch (texture_type) { + case TextureType::Texture1D: + coords.push_back(GetRegister(instr.gpr8)); + break; + case TextureType::Texture2D: + coords.push_back(GetRegister(instr.gpr8.Value() + 0)); + coords.push_back(GetRegister(instr.gpr8.Value() + 1)); + break; + default: + UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); + + // Fallback to interpreting as a 2D texture for now + coords.push_back(GetRegister(instr.gpr8.Value() + 0)); + coords.push_back(GetRegister(instr.gpr8.Value() + 1)); + texture_type = TextureType::Texture2D; + } + + for (u32 element = 0; element < 2; ++element) { + auto params = coords; + MetaTexture meta{sampler, {}, {}, {}, {}, {}, element}; + const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); + SetTemporal(bb, element, value); + } + for (u32 element = 0; element < 2; ++element) { + SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element)); + } + + break; + } + case OpCode::Id::TLDS: { + const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; + const bool is_array{instr.tlds.IsArrayTexture()}; + + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented"); + + if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) { + LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete"); + } + + WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array)); + break; + } + default: + UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName()); + } + + return pc; +} + +const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type, + bool is_array, bool is_shadow) { + const auto offset = static_cast<std::size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + const auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const Sampler& entry) { return entry.GetOffset() == offset; }); + if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array && + itr->IsShadow() == is_shadow); + return *itr; + } + + // Otherwise create a new mapping for this sampler + const std::size_t next_index = used_samplers.size(); + const Sampler entry{offset, next_index, type, is_array, is_shadow}; + return *used_samplers.emplace(entry).first; +} + +void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) { + u32 dest_elem = 0; + for (u32 elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + SetTemporal(bb, dest_elem++, components[elem]); + } + // After writing values in temporals, move them to the real registers + for (u32 i = 0; i < dest_elem; ++i) { + SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + } +} + +void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr, + const Node4& components) { + // TEXS has two destination registers and a swizzle. The first two elements in the swizzle + // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 + + u32 dest_elem = 0; + for (u32 component = 0; component < 4; ++component) { + if (!instr.texs.IsComponentEnabled(component)) + continue; + SetTemporal(bb, dest_elem++, components[component]); + } + + for (u32 i = 0; i < dest_elem; ++i) { + if (i < 2) { + // Write the first two swizzle components to gpr0 and gpr0+1 + SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i)); + } else { + ASSERT(instr.texs.HasTwoDestinations()); + // Write the rest of the swizzle components to gpr28 and gpr28+1 + SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i)); + } + } +} + +void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr, + const Node4& components) { + // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half + // float instruction). + + Node4 values; + u32 dest_elem = 0; + for (u32 component = 0; component < 4; ++component) { + if (!instr.texs.IsComponentEnabled(component)) + continue; + values[dest_elem++] = components[component]; + } + if (dest_elem == 0) + return; + + std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); }); + + const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]); + if (dest_elem <= 2) { + SetRegister(bb, instr.gpr0, first_value); + return; + } + + SetTemporal(bb, 0, first_value); + SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3])); + + SetRegister(bb, instr.gpr0, GetTemporal(0)); + SetRegister(bb, instr.gpr28, GetTemporal(1)); +} + +Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, + TextureProcessMode process_mode, std::vector<Node> coords, + Node array, Node depth_compare, u32 bias_offset) { + const bool is_array = array; + const bool is_shadow = depth_compare; + + UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) || + (texture_type == TextureType::TextureCube && is_array && is_shadow), + "This method is not supported."); + + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow); + + const bool lod_needed = process_mode == TextureProcessMode::LZ || + process_mode == TextureProcessMode::LL || + process_mode == TextureProcessMode::LLA; + + // LOD selection (either via bias or explicit textureLod) not supported in GL for + // sampler2DArrayShadow and samplerCubeArrayShadow. + const bool gl_lod_supported = + !((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) || + (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow)); + + const OperationCode read_method = + (lod_needed && gl_lod_supported) ? OperationCode::TextureLod : OperationCode::Texture; + + UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported); + + Node bias = {}; + Node lod = {}; + if (process_mode != TextureProcessMode::None && gl_lod_supported) { + switch (process_mode) { + case TextureProcessMode::LZ: + lod = Immediate(0.0f); + break; + case TextureProcessMode::LB: + // If present, lod or bias are always stored in the register indexed by the gpr20 + // field with an offset depending on the usage of the other registers + bias = GetRegister(instr.gpr20.Value() + bias_offset); + break; + case TextureProcessMode::LL: + lod = GetRegister(instr.gpr20.Value() + bias_offset); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode)); + break; + } + } + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto copy_coords = coords; + MetaTexture meta{sampler, array, depth_compare, bias, lod, {}, element}; + values[element] = Operation(read_method, meta, std::move(copy_coords)); + } + + return values; +} + +Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type, + TextureProcessMode process_mode, bool depth_compare, bool is_array) { + const bool lod_bias_enabled = + (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ); + + const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement( + texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5); + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + + std::vector<Node> coords; + for (std::size_t i = 0; i < coord_count; ++i) { + coords.push_back(GetRegister(coord_register + i)); + } + // 1D.DC in OpenGL the 2nd component is ignored. + if (depth_compare && !is_array && texture_type == TextureType::Texture1D) { + coords.push_back(Immediate(0.0f)); + } + + const Node array = is_array ? GetRegister(array_register) : nullptr; + + Node dc{}; + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 or in the next register if lod + // or bias are used + const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); + dc = GetRegister(depth_register); + } + + return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0); +} + +Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type, + TextureProcessMode process_mode, bool depth_compare, bool is_array) { + const bool lod_bias_enabled = + (process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ); + + const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement( + texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4); + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + const u64 last_coord_register = + (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2)) + ? static_cast<u64>(instr.gpr20.Value()) + : coord_register + 1; + const u32 bias_offset = coord_count > 2 ? 1 : 0; + + std::vector<Node> coords; + for (std::size_t i = 0; i < coord_count; ++i) { + const bool last = (i == (coord_count - 1)) && (coord_count > 1); + coords.push_back(GetRegister(last ? last_coord_register : coord_register + i)); + } + + const Node array = is_array ? GetRegister(array_register) : nullptr; + + Node dc{}; + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 or in the next register if lod + // or bias are used + const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); + dc = GetRegister(depth_register); + } + + return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset); +} + +Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare, + bool is_array) { + const std::size_t coord_count = GetCoordCount(texture_type); + const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0); + const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + + std::vector<Node> coords; + for (size_t i = 0; i < coord_count; ++i) + coords.push_back(GetRegister(coord_register + i)); + + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare); + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto coords_copy = coords; + MetaTexture meta{sampler, GetRegister(array_register), {}, {}, {}, {}, element}; + values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); + } + + return values; +} + +Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { + const std::size_t type_coord_count = GetCoordCount(texture_type); + const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // if is array gpr20 is used + const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value(); + + const u64 last_coord_register = + ((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array + ? static_cast<u64>(instr.gpr20.Value()) + : coord_register + 1; + + std::vector<Node> coords; + for (std::size_t i = 0; i < type_coord_count; ++i) { + const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1); + coords.push_back(GetRegister(last ? last_coord_register : coord_register + i)); + } + + const Node array = is_array ? GetRegister(array_register) : nullptr; + // When lod is used always is in gpr20 + const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0); + + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto coords_copy = coords; + MetaTexture meta{sampler, array, {}, {}, lod, {}, element}; + values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); + } + return values; +} + +std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement( + TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled, + std::size_t max_coords, std::size_t max_inputs) { + const std::size_t coord_count = GetCoordCount(texture_type); + + std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0); + const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0); + if (total_coord_count > max_coords || total_reg_count > max_inputs) { + UNIMPLEMENTED_MSG("Unsupported Texture operation"); + total_coord_count = std::min(total_coord_count, max_coords); + } + // 1D.DC OpenGL is using a vec3 but 2nd component is ignored later. + total_coord_count += + (depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0; + + return {coord_count, total_coord_count}; +} + +} // namespace VideoCommon::Shader
\ No newline at end of file diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 52c7f2c4e..5bc3a3900 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -290,7 +290,9 @@ struct MetaTexture { const Sampler& sampler; Node array{}; Node depth_compare{}; - std::vector<Node> extras; + Node bias{}; + Node lod{}; + Node component{}; u32 element{}; }; @@ -614,6 +616,7 @@ private: u32 DecodeHfma2(NodeBlock& bb, u32 pc); u32 DecodeConversion(NodeBlock& bb, u32 pc); u32 DecodeMemory(NodeBlock& bb, u32 pc); + u32 DecodeTexture(NodeBlock& bb, u32 pc); u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc); diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index cad7340f5..995d0e068 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -6,7 +6,6 @@ #include <cstring> #include "common/alignment.h" #include "common/assert.h" -#include "core/memory.h" #include "video_core/gpu.h" #include "video_core/textures/decoders.h" #include "video_core/textures/texture.h" @@ -230,18 +229,18 @@ u32 BytesPerPixel(TextureFormat format) { } } -void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, +void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 width_spacing) { CopySwizzledData((width + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, - bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true, - block_height, block_depth, width_spacing); + bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth, + width_spacing); } -std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, - u32 bytes_per_pixel, u32 width, u32 height, u32 depth, - u32 block_height, u32 block_depth, u32 width_spacing) { +std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height, + u32 block_depth, u32 width_spacing) { std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel, width, height, depth, block_height, block_depth, width_spacing); @@ -249,8 +248,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y } void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, - u32 block_height) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) { const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) / gob_size_x}; for (u32 line = 0; line < subrect_height; ++line) { @@ -262,17 +260,17 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 const u32 gob_address = gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height; const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x]; - const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel; - const VAddr dest_addr = swizzled_data + swizzled_offset; + u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel; + u8* dest_addr = swizzled_data + swizzled_offset; - Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel); + std::memcpy(dest_addr, source_line, bytes_per_pixel); } } } void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, - u32 block_height, u32 offset_x, u32 offset_y) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, + u32 offset_x, u32 offset_y) { for (u32 line = 0; line < subrect_height; ++line) { const u32 y2 = line + offset_y; const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height + @@ -282,10 +280,10 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 const u32 x2 = (x + offset_x) * bytes_per_pixel; const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height; const u32 swizzled_offset = gob_address + table[x2 % gob_size_x]; - const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel; - const VAddr source_addr = swizzled_data + swizzled_offset; + u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel; + u8* source_addr = swizzled_data + swizzled_offset; - Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel); + std::memcpy(dest_line, source_addr, bytes_per_pixel); } } } diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 65df86890..e078fa274 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -17,14 +17,14 @@ inline std::size_t GetGOBSize() { } /// Unswizzles a swizzled texture without changing its format. -void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, +void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height = TICEntry::DefaultBlockHeight, u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0); /// Unswizzles a swizzled texture without changing its format. -std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, - u32 bytes_per_pixel, u32 width, u32 height, u32 depth, +std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, + u32 width, u32 height, u32 depth, u32 block_height = TICEntry::DefaultBlockHeight, u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0); @@ -44,12 +44,11 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height /// Copies an untiled subrectangle into a tiled surface. void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, - u32 block_height); + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height); /// Copies a tiled subrectangle into a linear surface. void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, - u32 block_height, u32 offset_x, u32 offset_y); + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, + u32 offset_x, u32 offset_y); } // namespace Tegra::Texture diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 0fc5530f2..93ecc6e31 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" @@ -282,34 +283,62 @@ enum class TextureMipmapFilter : u32 { 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, DepthCompareFunc> depth_compare_func; - BitField<13, 1, u32> srgb_conversion; - BitField<20, 3, u32> max_anisotropy; + struct { + 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, DepthCompareFunc> depth_compare_func; + BitField<13, 1, u32> srgb_conversion; + BitField<20, 3, u32> max_anisotropy; + }; + union { + BitField<0, 2, TextureFilter> mag_filter; + BitField<4, 2, TextureFilter> min_filter; + BitField<6, 2, TextureMipmapFilter> mipmap_filter; + BitField<9, 1, u32> cubemap_interface_filtering; + BitField<12, 13, u32> mip_lod_bias; + }; + union { + BitField<0, 12, u32> min_lod_clamp; + BitField<12, 12, u32> max_lod_clamp; + BitField<24, 8, u32> srgb_border_color_r; + }; + union { + BitField<12, 8, u32> srgb_border_color_g; + BitField<20, 8, u32> srgb_border_color_b; + }; + std::array<f32, 4> border_color; + }; + std::array<u8, 0x20> raw; }; - union { - BitField<0, 2, TextureFilter> mag_filter; - BitField<4, 2, TextureFilter> min_filter; - BitField<6, 2, TextureMipmapFilter> mip_filter; - BitField<9, 1, u32> cubemap_interface_filtering; - BitField<12, 13, u32> mip_lod_bias; - }; - union { - BitField<0, 12, u32> min_lod_clamp; - BitField<12, 12, u32> max_lod_clamp; - BitField<24, 8, u32> srgb_border_color_r; - }; - union { - BitField<12, 8, u32> srgb_border_color_g; - BitField<20, 8, u32> srgb_border_color_b; - }; - float border_color_r; - float border_color_g; - float border_color_b; - float border_color_a; + + float GetMaxAnisotropy() const { + return static_cast<float>(1U << max_anisotropy); + } + + float GetMinLod() const { + return static_cast<float>(min_lod_clamp) / 256.0f; + } + + float GetMaxLod() const { + return static_cast<float>(max_lod_clamp) / 256.0f; + } + + float GetLodBias() const { + // Sign extend the 13-bit value. + constexpr u32 mask = 1U << (13 - 1); + return static_cast<s32>((mip_lod_bias ^ mask) - mask) / 256.0f; + } + + std::array<float, 4> GetBorderColor() const { + if (srgb_conversion) { + return {srgb_border_color_r / 255.0f, srgb_border_color_g / 255.0f, + srgb_border_color_b / 255.0f, border_color[3]}; + } + return border_color; + } }; static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size"); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 3b070bfbb..05ad19e1d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -24,8 +24,6 @@ void EmuThread::run() { MicroProfileOnThreadCreate("EmuThread"); - stop_run = false; - emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( @@ -40,7 +38,7 @@ void EmuThread::run() { render_window->DoneCurrent(); } - // holds whether the cpu was running during the last iteration, + // Holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step bool was_active = false; @@ -123,7 +121,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setAttribute(Qt::WA_AcceptTouchEvents); InputCommon::Init(); - InputCommon::StartJoystickEventHandler(); connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent), &GMainWindow::OnLoadComplete); } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 74dc6bb28..4650f96a3 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -209,7 +209,7 @@ void Config::ReadPlayerValues() { for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { auto& player = Settings::values.players[p]; - player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); + player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool(); player.type = static_cast<Settings::ControllerType>( qt_config @@ -269,7 +269,7 @@ void Config::ReadPlayerValues() { } void Config::ReadDebugValues() { - Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool(); + Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool(); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); Settings::values.debug_pad_buttons[i] = @@ -298,7 +298,7 @@ void Config::ReadDebugValues() { } void Config::ReadKeyboardValues() { - Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool(); + Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool(); std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); @@ -311,7 +311,7 @@ void Config::ReadKeyboardValues() { } void Config::ReadMouseValues() { - Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool(); + Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool(); for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); @@ -327,16 +327,14 @@ void Config::ReadMouseValues() { } void Config::ReadTouchscreenValues() { - Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool(); + Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool(); Settings::values.touchscreen.device = - qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString(); - - Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt(); - Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt(); - Settings::values.touchscreen.diameter_x = - qt_config->value("touchscreen_diameter_x", 15).toUInt(); - Settings::values.touchscreen.diameter_y = - qt_config->value("touchscreen_diameter_y", 15).toUInt(); + ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString(); + + Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt(); + Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt(); + Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt(); + Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt(); qt_config->endGroup(); } @@ -357,42 +355,41 @@ void Config::ReadValues() { ReadTouchscreenValues(); Settings::values.motion_device = - qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") + ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") .toString() .toStdString(); qt_config->beginGroup("Core"); - Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); - Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); + Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool(); + Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Renderer"); - Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat(); - Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool(); - Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt(); - Settings::values.use_disk_shader_cache = - qt_config->value("use_disk_shader_cache", false).toBool(); + Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat(); + Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool(); + Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt(); + Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool(); Settings::values.use_accurate_gpu_emulation = - qt_config->value("use_accurate_gpu_emulation", false).toBool(); + ReadSetting("use_accurate_gpu_emulation", false).toBool(); Settings::values.use_asynchronous_gpu_emulation = - qt_config->value("use_asynchronous_gpu_emulation", false).toBool(); + ReadSetting("use_asynchronous_gpu_emulation", false).toBool(); - Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat(); - Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat(); - Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat(); + Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat(); + Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat(); + Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat(); qt_config->endGroup(); qt_config->beginGroup("Audio"); - Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString(); Settings::values.enable_audio_stretching = - qt_config->value("enable_audio_stretching", true).toBool(); + ReadSetting("enable_audio_stretching", true).toBool(); Settings::values.audio_device_id = - qt_config->value("output_device", "auto").toString().toStdString(); - Settings::values.volume = qt_config->value("volume", 1).toFloat(); + ReadSetting("output_device", "auto").toString().toStdString(); + Settings::values.volume = ReadSetting("volume", 1).toFloat(); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); - Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); + Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool(); FileUtil::GetUserPath( FileUtil::UserPath::NANDDir, qt_config @@ -410,30 +407,30 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Core"); - Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); - Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); + Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool(); + Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("System"); - Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); - Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); + Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool(); + Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool(); - Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0, - Service::Account::MAX_USERS - 1); + Settings::values.current_user = + std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1); - Settings::values.language_index = qt_config->value("language_index", 1).toInt(); + Settings::values.language_index = ReadSetting("language_index", 1).toInt(); - const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool(); + const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool(); if (rng_seed_enabled) { - Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong(); + Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong(); } else { Settings::values.rng_seed = std::nullopt; } - const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool(); + const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool(); if (custom_rtc_enabled) { Settings::values.custom_rtc = - std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong()); + std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong()); } else { Settings::values.custom_rtc = std::nullopt; } @@ -441,35 +438,35 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); - Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); - Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool(); + Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString(); + Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Debugging"); - Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); - Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); - Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString(); - Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool(); - Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool(); + Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool(); + Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt(); + Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString(); + Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool(); + Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("WebService"); - Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool(); + Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool(); Settings::values.web_api_url = - qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString(); - Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString(); - Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); + ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString(); + Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString(); + Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString(); qt_config->endGroup(); const auto size = qt_config->beginReadArray("DisabledAddOns"); for (int i = 0; i < size; ++i) { qt_config->setArrayIndex(i); - const auto title_id = qt_config->value("title_id", 0).toULongLong(); + const auto title_id = ReadSetting("title_id", 0).toULongLong(); std::vector<std::string> out; const auto d_size = qt_config->beginReadArray("disabled"); for (int j = 0; j < d_size; ++j) { qt_config->setArrayIndex(j); - out.push_back(qt_config->value("d", "").toString().toStdString()); + out.push_back(ReadSetting("d", "").toString().toStdString()); } qt_config->endArray(); Settings::values.disabled_addons.insert_or_assign(title_id, out); @@ -477,41 +474,38 @@ void Config::ReadValues() { qt_config->endArray(); qt_config->beginGroup("UI"); - UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString(); UISettings::values.enable_discord_presence = - qt_config->value("enable_discord_presence", true).toBool(); + ReadSetting("enable_discord_presence", true).toBool(); UISettings::values.screenshot_resolution_factor = - static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt()); - UISettings::values.select_user_on_boot = - qt_config->value("select_user_on_boot", false).toBool(); + static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt()); + UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool(); qt_config->beginGroup("UIGameList"); - UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); - UISettings::values.show_add_ons = qt_config->value("show_add_ons", true).toBool(); - UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt(); - UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt(); - UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt(); + UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool(); + UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool(); + UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt(); + UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt(); + UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt(); qt_config->endGroup(); qt_config->beginGroup("UILayout"); - UISettings::values.geometry = qt_config->value("geometry").toByteArray(); - UISettings::values.state = qt_config->value("state").toByteArray(); - UISettings::values.renderwindow_geometry = - qt_config->value("geometryRenderWindow").toByteArray(); - UISettings::values.gamelist_header_state = - qt_config->value("gameListHeaderState").toByteArray(); + UISettings::values.geometry = ReadSetting("geometry").toByteArray(); + UISettings::values.state = ReadSetting("state").toByteArray(); + UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray(); + UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray(); UISettings::values.microprofile_geometry = - qt_config->value("microProfileDialogGeometry").toByteArray(); + ReadSetting("microProfileDialogGeometry").toByteArray(); UISettings::values.microprofile_visible = - qt_config->value("microProfileDialogVisible", false).toBool(); + ReadSetting("microProfileDialogVisible", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Paths"); - UISettings::values.roms_path = qt_config->value("romsPath").toString(); - UISettings::values.symbols_path = qt_config->value("symbolsPath").toString(); - UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString(); - UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool(); - UISettings::values.recent_files = qt_config->value("recentFiles").toStringList(); + UISettings::values.roms_path = ReadSetting("romsPath").toString(); + UISettings::values.symbols_path = ReadSetting("symbolsPath").toString(); + UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString(); + UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool(); + UISettings::values.recent_files = ReadSetting("recentFiles").toStringList(); qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); @@ -524,8 +518,8 @@ void Config::ReadValues() { qt_config->beginGroup(hotkey); UISettings::values.shortcuts.emplace_back(UISettings::Shortcut( group + "/" + hotkey, - UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(), - qt_config->value("Context").toInt()))); + UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(), + ReadSetting("Context").toInt()))); qt_config->endGroup(); } @@ -533,16 +527,16 @@ void Config::ReadValues() { } qt_config->endGroup(); - UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); - UISettings::values.fullscreen = qt_config->value("fullscreen", false).toBool(); - UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); - UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool(); - UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); - UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); - UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); - UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); - UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); - UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt(); + UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool(); + UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool(); + UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool(); + UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool(); + UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool(); + UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool(); + UISettings::values.first_start = ReadSetting("firstStart", true).toBool(); + UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt(); + UISettings::values.show_console = ReadSetting("showConsole", false).toBool(); + UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt(); ApplyDefaultProfileIfInputInvalid(); @@ -553,62 +547,79 @@ void Config::SavePlayerValues() { for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { const auto& player = Settings::values.players[p]; - qt_config->setValue(QString("player_%1_connected").arg(p), player.connected); - qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type)); + WriteSetting(QString("player_%1_connected").arg(p), player.connected, false); + WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type), + static_cast<u8>(Settings::ControllerType::DualJoycon)); - qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left); - qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right); - qt_config->setValue(QString("player_%1_button_color_left").arg(p), - player.button_color_left); - qt_config->setValue(QString("player_%1_button_color_right").arg(p), - player.button_color_right); + WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left, + Settings::JOYCON_BODY_NEON_BLUE); + WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right, + Settings::JOYCON_BODY_NEON_RED); + WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left, + Settings::JOYCON_BUTTONS_NEON_BLUE); + WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right, + Settings::JOYCON_BUTTONS_NEON_RED); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - qt_config->setValue(QString("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(player.buttons[i])); + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteSetting(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(player.buttons[i]), + QString::fromStdString(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - qt_config->setValue(QString("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(player.analogs[i])); + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + WriteSetting(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(player.analogs[i]), + QString::fromStdString(default_param)); } } } void Config::SaveDebugValues() { - qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled); + WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - qt_config->setValue(QString("debug_pad_") + - QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(Settings::values.debug_pad_buttons[i])); + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteSetting(QString("debug_pad_") + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_buttons[i]), + QString::fromStdString(default_param)); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - qt_config->setValue(QString("debug_pad_") + - QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(Settings::values.debug_pad_analogs[i])); + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + WriteSetting(QString("debug_pad_") + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_analogs[i]), + QString::fromStdString(default_param)); } } void Config::SaveMouseValues() { - qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled); + WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false); for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { - qt_config->setValue(QString("mouse_") + - QString::fromStdString(Settings::NativeMouseButton::mapping[i]), - QString::fromStdString(Settings::values.mouse_buttons[i])); + std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); + WriteSetting(QString("mouse_") + + QString::fromStdString(Settings::NativeMouseButton::mapping[i]), + QString::fromStdString(Settings::values.mouse_buttons[i]), + QString::fromStdString(default_param)); } } void Config::SaveTouchscreenValues() { - qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled); - qt_config->setValue("touchscreen_device", - QString::fromStdString(Settings::values.touchscreen.device)); - - qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger); - qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle); - qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x); - qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y); + WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true); + WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device), + "engine:emu_window"); + + WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0); + WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0); + WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15); + WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15); } void Config::SaveValues() { @@ -619,91 +630,96 @@ void Config::SaveValues() { SaveMouseValues(); SaveTouchscreenValues(); - qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); - qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled); + WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device), + "engine:motion_emu,update_period:100,sensitivity:0.01"); + WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false); qt_config->endGroup(); qt_config->beginGroup("Core"); - qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); - qt_config->setValue("use_multi_core", Settings::values.use_multi_core); + WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true); + WriteSetting("use_multi_core", Settings::values.use_multi_core, false); qt_config->endGroup(); qt_config->beginGroup("Renderer"); - qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor); - qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit); - qt_config->setValue("frame_limit", Settings::values.frame_limit); - qt_config->setValue("use_disk_shader_cache", Settings::values.use_disk_shader_cache); - qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation); - qt_config->setValue("use_asynchronous_gpu_emulation", - Settings::values.use_asynchronous_gpu_emulation); + WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0); + WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true); + WriteSetting("frame_limit", Settings::values.frame_limit, 100); + WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true); + WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false); + WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation, + false); // Cast to double because Qt's written float values are not human-readable - qt_config->setValue("bg_red", (double)Settings::values.bg_red); - qt_config->setValue("bg_green", (double)Settings::values.bg_green); - qt_config->setValue("bg_blue", (double)Settings::values.bg_blue); + WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0); + WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0); + WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0); qt_config->endGroup(); qt_config->beginGroup("Audio"); - qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); - qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); - qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); - qt_config->setValue("volume", Settings::values.volume); + WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto"); + WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); + WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); + WriteSetting("volume", Settings::values.volume, 1.0f); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); - qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); - qt_config->setValue("nand_directory", - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); - qt_config->setValue("sdmc_directory", - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true); + WriteSetting("nand_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + WriteSetting("sdmc_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); qt_config->endGroup(); qt_config->beginGroup("System"); - qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); - qt_config->setValue("enable_nfc", Settings::values.enable_nfc); - qt_config->setValue("current_user", Settings::values.current_user); - qt_config->setValue("language_index", Settings::values.language_index); + WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false); + WriteSetting("enable_nfc", Settings::values.enable_nfc, true); + WriteSetting("current_user", Settings::values.current_user, 0); + WriteSetting("language_index", Settings::values.language_index, 1); - qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); - qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0)); + WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false); + WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0); - qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value()); - qt_config->setValue("custom_rtc", - QVariant::fromValue<long long>( - Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count())); + WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false); + WriteSetting("custom_rtc", + QVariant::fromValue<long long>( + Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), + 0); qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); - qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); - qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys); + WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info"); + WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false); qt_config->endGroup(); qt_config->beginGroup("Debugging"); - qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); - qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); - qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args)); - qt_config->setValue("dump_exefs", Settings::values.dump_exefs); - qt_config->setValue("dump_nso", Settings::values.dump_nso); + WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false); + WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689); + WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), ""); + WriteSetting("dump_exefs", Settings::values.dump_exefs, false); + WriteSetting("dump_nso", Settings::values.dump_nso, false); qt_config->endGroup(); qt_config->beginGroup("WebService"); - qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry); - qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url)); - qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username)); - qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); + WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true); + WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url), + "https://api.yuzu-emu.org"); + WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username)); + WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); qt_config->endGroup(); qt_config->beginWriteArray("DisabledAddOns"); int i = 0; for (const auto& elem : Settings::values.disabled_addons) { qt_config->setArrayIndex(i); - qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); + WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0); qt_config->beginWriteArray("disabled"); for (std::size_t j = 0; j < elem.second.size(); ++j) { qt_config->setArrayIndex(static_cast<int>(j)); - qt_config->setValue("d", QString::fromStdString(elem.second[j])); + WriteSetting("d", QString::fromStdString(elem.second[j]), ""); } qt_config->endArray(); ++i; @@ -711,60 +727,86 @@ void Config::SaveValues() { qt_config->endArray(); qt_config->beginGroup("UI"); - qt_config->setValue("theme", UISettings::values.theme); - qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); - qt_config->setValue("screenshot_resolution_factor", - UISettings::values.screenshot_resolution_factor); - qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot); + WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second); + WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true); + WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor, + 0); + WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false); qt_config->beginGroup("UIGameList"); - qt_config->setValue("show_unknown", UISettings::values.show_unknown); - qt_config->setValue("show_add_ons", UISettings::values.show_add_ons); - qt_config->setValue("icon_size", UISettings::values.icon_size); - qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id); - qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id); + WriteSetting("show_unknown", UISettings::values.show_unknown, true); + WriteSetting("show_add_ons", UISettings::values.show_add_ons, true); + WriteSetting("icon_size", UISettings::values.icon_size, 64); + WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3); + WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2); qt_config->endGroup(); qt_config->beginGroup("UILayout"); - qt_config->setValue("geometry", UISettings::values.geometry); - qt_config->setValue("state", UISettings::values.state); - qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry); - qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state); - qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry); - qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible); + WriteSetting("geometry", UISettings::values.geometry); + WriteSetting("state", UISettings::values.state); + WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry); + WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state); + WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry); + WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false); qt_config->endGroup(); qt_config->beginGroup("Paths"); - qt_config->setValue("romsPath", UISettings::values.roms_path); - qt_config->setValue("symbolsPath", UISettings::values.symbols_path); - qt_config->setValue("screenshotPath", UISettings::values.screenshot_path); - qt_config->setValue("gameListRootDir", UISettings::values.gamedir); - qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); - qt_config->setValue("recentFiles", UISettings::values.recent_files); + WriteSetting("romsPath", UISettings::values.roms_path); + WriteSetting("symbolsPath", UISettings::values.symbols_path); + WriteSetting("screenshotPath", UISettings::values.screenshot_path); + WriteSetting("gameListRootDir", UISettings::values.gamedir, "."); + WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false); + WriteSetting("recentFiles", UISettings::values.recent_files); qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); for (auto shortcut : UISettings::values.shortcuts) { - qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first); - qt_config->setValue(shortcut.first + "/Context", shortcut.second.second); + WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first); + WriteSetting(shortcut.first + "/Context", shortcut.second.second); } qt_config->endGroup(); - qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); - qt_config->setValue("fullscreen", UISettings::values.fullscreen); - qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); - qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar); - qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); - qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); - qt_config->setValue("firstStart", UISettings::values.first_start); - qt_config->setValue("calloutFlags", UISettings::values.callout_flags); - qt_config->setValue("showConsole", UISettings::values.show_console); - qt_config->setValue("profileIndex", UISettings::values.profile_index); + WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true); + WriteSetting("fullscreen", UISettings::values.fullscreen, false); + WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true); + WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true); + WriteSetting("showStatusBar", UISettings::values.show_status_bar, true); + WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true); + WriteSetting("firstStart", UISettings::values.first_start, true); + WriteSetting("calloutFlags", UISettings::values.callout_flags, 0); + WriteSetting("showConsole", UISettings::values.show_console, false); + WriteSetting("profileIndex", UISettings::values.profile_index, 0); qt_config->endGroup(); } +QVariant Config::ReadSetting(const QString& name) const { + return qt_config->value(name); +} + +QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const { + QVariant result; + if (qt_config->value(name + "/default", false).toBool()) { + result = default_value; + } else { + result = qt_config->value(name, default_value); + } + return result; +} + +void Config::WriteSetting(const QString& name, const QVariant& value) { + qt_config->setValue(name, value); +} + +void Config::WriteSetting(const QString& name, const QVariant& value, + const QVariant& default_value) { + qt_config->setValue(name + "/default", value == default_value); + qt_config->setValue(name, value); +} + void Config::Reload() { ReadValues(); + // To apply default value changes + SaveValues(); Settings::Apply(); } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index e73ad19bb..f4185db18 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,6 +42,11 @@ private: void SaveMouseValues(); void SaveTouchscreenValues(); + QVariant ReadSetting(const QString& name) const; + QVariant ReadSetting(const QString& name, const QVariant& default_value) const; + void WriteSetting(const QString& name, const QVariant& value); + void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); + std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; }; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 71683da8e..11023ed63 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -261,7 +261,7 @@ void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { if (surface_address != new_value) { - surface_address = static_cast<Tegra::GPUVAddr>(new_value); + surface_address = static_cast<GPUVAddr>(new_value); surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); emit Update(); @@ -383,13 +383,12 @@ void GraphicsSurfaceWidget::OnUpdate() { // TODO: Implement a good way to visualize alpha components! QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); - std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. auto unswizzled_data = Tegra::Texture::UnswizzleTexture( - *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, - surface_height, 1U); + gpu.MemoryManager().GetPointer(surface_address), 1, 1, + Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U); auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, surface_width, surface_height); diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h index 323e39d94..89445b18f 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.h +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -87,7 +87,7 @@ private: QPushButton* save_surface; Source surface_source; - Tegra::GPUVAddr surface_address; + GPUVAddr surface_address; unsigned surface_width; unsigned surface_height; Tegra::Texture::TextureFormat surface_format; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 7df8eff53..de7a26e14 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -135,16 +135,16 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { } EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { - InputCommon::Init(); - - SDL_SetMainReady(); - // Initialize the window if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); } + InputCommon::Init(); + + SDL_SetMainReady(); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); @@ -201,11 +201,9 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { } EmuWindow_SDL2::~EmuWindow_SDL2() { - InputCommon::SDL::CloseSDLJoysticks(); + InputCommon::Shutdown(); SDL_GL_DeleteContext(gl_context); SDL_Quit(); - - InputCommon::Shutdown(); } void EmuWindow_SDL2::SwapBuffers() { @@ -262,7 +260,6 @@ void EmuWindow_SDL2::PollEvents() { is_open = false; break; default: - InputCommon::SDL::HandleGameControllerEvent(event); break; } } diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index c6c66a787..245f25847 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -114,9 +114,9 @@ int main(int argc, char** argv) { }; while (optind < argc) { - char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index); + int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index); if (arg != -1) { - switch (arg) { + switch (static_cast<char>(arg)) { case 'g': errno = 0; gdb_port = strtoul(optarg, &endarg, 0); |