diff options
Diffstat (limited to '')
72 files changed, 2862 insertions, 741 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bd6ac6716..3adf13a3f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -38,6 +38,7 @@ add_library(common STATIC common_precompiled_headers.h common_types.h concepts.h + container_hash.h demangle.cpp demangle.h div_ceil.h @@ -91,6 +92,7 @@ add_library(common STATIC multi_level_page_table.h nvidia_flags.cpp nvidia_flags.h + overflow.h page_table.cpp page_table.h param_package.cpp @@ -113,6 +115,8 @@ add_library(common STATIC socket_types.h spin_lock.cpp spin_lock.h + steady_clock.cpp + steady_clock.h stream.cpp stream.h string_util.cpp @@ -129,6 +133,7 @@ add_library(common STATIC time_zone.h tiny_mt.h tree.h + typed_address.h uint128.h unique_function.h uuid.cpp @@ -142,13 +147,33 @@ add_library(common STATIC zstd_compression.h ) +if (WIN32) + target_sources(common PRIVATE + windows/timer_resolution.cpp + windows/timer_resolution.h + ) + target_link_libraries(common PRIVATE ntdll) +endif() + +if(ANDROID) + target_sources(common + PRIVATE + fs/fs_android.cpp + fs/fs_android.h + ) +endif() + if(ARCHITECTURE_x86_64) target_sources(common PRIVATE x64/cpu_detect.cpp x64/cpu_detect.h + x64/cpu_wait.cpp + x64/cpu_wait.h x64/native_clock.cpp x64/native_clock.h + x64/rdtsc.cpp + x64/rdtsc.h x64/xbyak_abi.h x64/xbyak_util.h ) @@ -176,8 +201,13 @@ endif() create_target_directory_groups(common) -target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) -target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd demangle) +target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile Threads::Threads) +target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle) + +if (ANDROID) + # For ASharedMemory_create + target_link_libraries(common PRIVATE android) +endif() if (YUZU_USE_PRECOMPILED_HEADERS) target_precompile_headers(common PRIVATE precompiled_headers.h) diff --git a/src/common/address_space.h b/src/common/address_space.h index 9222b2fdc..8683c23c3 100644 --- a/src/common/address_space.h +++ b/src/common/address_space.h @@ -12,7 +12,8 @@ namespace Common { template <typename VaType, size_t AddressSpaceBits> -concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits; +concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= +AddressSpaceBits; struct EmptyStruct {}; @@ -21,7 +22,7 @@ struct EmptyStruct {}; */ template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct> -requires AddressSpaceValid<VaType, AddressSpaceBits> + requires AddressSpaceValid<VaType, AddressSpaceBits> class FlatAddressSpaceMap { public: /// The maximum VA that this AS can technically reach @@ -109,7 +110,7 @@ private: * initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block */ template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> -requires AddressSpaceValid<VaType, AddressSpaceBits> + requires AddressSpaceValid<VaType, AddressSpaceBits> class FlatAllocator : public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> { private: diff --git a/src/common/address_space.inc b/src/common/address_space.inc index 2195dabd5..1ee82df53 100644 --- a/src/common/address_space.inc +++ b/src/common/address_space.inc @@ -72,7 +72,7 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf } }()}; - if (block_end_predecessor->virt >= virt) { + if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) { // If this block's start would be overlapped by the map then reuse it as a tail // block block_end_predecessor->virt = virt_end; @@ -336,7 +336,7 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) { ASSERT_MSG(false, "Unexpected allocator state!"); } - auto search_predecessor{this->blocks.begin()}; + auto search_predecessor{std::next(this->blocks.begin())}; auto search_successor{std::next(search_predecessor)}; while (search_successor != this->blocks.end() && diff --git a/src/common/alignment.h b/src/common/alignment.h index 7e897334b..fa715d497 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -10,7 +10,7 @@ namespace Common { template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr T AlignUp(T value, size_t size) { auto mod{static_cast<T>(value % size)}; value -= mod; @@ -18,31 +18,31 @@ requires std::is_unsigned_v<T> } template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr T AlignUpLog2(T value, size_t align_log2) { return static_cast<T>((value + ((1ULL << align_log2) - 1)) >> align_log2 << align_log2); } template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr T AlignDown(T value, size_t size) { return static_cast<T>(value - value % size); } template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr bool Is4KBAligned(T value) { return (value & 0xFFF) == 0; } template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr bool IsWordAligned(T value) { return (value & 0b11) == 0; } template <typename T> -requires std::is_integral_v<T> + requires std::is_integral_v<T> [[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) { using U = typename std::make_unsigned_t<T>; const U mask = static_cast<U>(alignment - 1); @@ -50,7 +50,7 @@ requires std::is_integral_v<T> } template <typename T, typename U> -requires std::is_integral_v<T> + requires std::is_integral_v<T> [[nodiscard]] constexpr T DivideUp(T x, U y) { return (x + (y - 1)) / y; } @@ -73,11 +73,11 @@ public: constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {} [[nodiscard]] T* allocate(size_type n) { - return static_cast<T*>(::operator new (n * sizeof(T), std::align_val_t{Align})); + return static_cast<T*>(::operator new(n * sizeof(T), std::align_val_t{Align})); } void deallocate(T* p, size_type n) { - ::operator delete (p, n * sizeof(T), std::align_val_t{Align}); + ::operator delete(p, n * sizeof(T), std::align_val_t{Align}); } template <typename T2> diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h index 4a3100fa4..f32060196 100644 --- a/src/common/announce_multiplayer_room.h +++ b/src/common/announce_multiplayer_room.h @@ -66,7 +66,7 @@ public: * @param description The room description * @param port The port of the room * @param net_version The version of the libNetwork that gets used - * @param has_password True if the room is passowrd protected + * @param has_password True if the room is password protected * @param preferred_game The preferred game of the room * @param preferred_game_id The title id of the preferred game */ diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h index aef3b66a4..d997f10ba 100644 --- a/src/common/atomic_helpers.h +++ b/src/common/atomic_helpers.h @@ -75,7 +75,7 @@ extern "C" void AnnotateHappensAfter(const char*, int, void*); #if defined(AE_VCPP) || defined(AE_ICC) #define AE_FORCEINLINE __forceinline #elif defined(AE_GCC) -//#define AE_FORCEINLINE __attribute__((always_inline)) +// #define AE_FORCEINLINE __attribute__((always_inline)) #define AE_FORCEINLINE inline #else #define AE_FORCEINLINE inline diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h index 535148b4d..c6110c542 100644 --- a/src/common/bit_cast.h +++ b/src/common/bit_cast.h @@ -3,19 +3,21 @@ #pragma once -#include <cstring> -#include <type_traits> +#include <version> + +#ifdef __cpp_lib_bit_cast +#include <bit> +#endif namespace Common { template <typename To, typename From> -[[nodiscard]] std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> && - std::is_trivially_copyable_v<To>, - To> -BitCast(const From& src) noexcept { - To dst; - std::memcpy(&dst, &src, sizeof(To)); - return dst; +constexpr inline To BitCast(const From& from) { +#ifdef __cpp_lib_bit_cast + return std::bit_cast<To>(from); +#else + return __builtin_bit_cast(To, from); +#endif } } // namespace Common diff --git a/src/common/bit_field.h b/src/common/bit_field.h index e4e58ea45..0168ff9cb 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -188,3 +188,8 @@ private: template <std::size_t Position, std::size_t Bits, typename T> using BitFieldBE = BitField<Position, Bits, T, BETag>; + +template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag> +inline auto format_as(BitField<Position, Bits, T, EndianTag> bitfield) { + return bitfield.Value(); +} diff --git a/src/common/bit_util.h b/src/common/bit_util.h index e4e6287f3..13368b439 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -45,19 +45,19 @@ template <typename T> } template <typename T> -requires std::is_unsigned_v<T> + requires std::is_unsigned_v<T> [[nodiscard]] constexpr bool IsPow2(T value) { return std::has_single_bit(value); } template <typename T> -requires std::is_integral_v<T> + requires std::is_integral_v<T> [[nodiscard]] T NextPow2(T value) { return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U))); } template <size_t bit_index, typename T> -requires std::is_integral_v<T> + requires std::is_integral_v<T> [[nodiscard]] constexpr bool Bit(const T value) { static_assert(bit_index < BitSize<T>(), "bit_index must be smaller than size of T"); return ((value >> bit_index) & T(1)) == T(1); diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index 21217801e..bd87aa09b 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -1,158 +1,249 @@ -// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se> -// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include <atomic> -#include <bit> #include <condition_variable> -#include <memory> +#include <cstddef> #include <mutex> #include <new> -#include <stop_token> -#include <type_traits> -#include <utility> + +#include "common/polyfill_thread.h" namespace Common { -#if defined(__cpp_lib_hardware_interference_size) -constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size; -#else -constexpr size_t hardware_interference_size = 64; -#endif +namespace detail { +constexpr size_t DefaultCapacity = 0x1000; +} // namespace detail + +template <typename T, size_t Capacity = detail::DefaultCapacity> +class SPSCQueue { + static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two."); -template <typename T, size_t capacity = 0x400> -class MPSCQueue { public: - explicit MPSCQueue() : allocator{std::allocator<Slot<T>>()} { - // Allocate one extra slot to prevent false sharing on the last slot - slots = allocator.allocate(capacity + 1); - // Allocators are not required to honor alignment for over-aligned types - // (see http://eel.is/c++draft/allocator.requirements#10) so we verify - // alignment here - if (reinterpret_cast<uintptr_t>(slots) % alignof(Slot<T>) != 0) { - allocator.deallocate(slots, capacity + 1); - throw std::bad_alloc(); - } - for (size_t i = 0; i < capacity; ++i) { - std::construct_at(&slots[i]); - } - static_assert(std::has_single_bit(capacity), "capacity must be an integer power of 2"); - static_assert(alignof(Slot<T>) == hardware_interference_size, - "Slot must be aligned to cache line boundary to prevent false sharing"); - static_assert(sizeof(Slot<T>) % hardware_interference_size == 0, - "Slot size must be a multiple of cache line size to prevent " - "false sharing between adjacent slots"); - static_assert(sizeof(MPSCQueue) % hardware_interference_size == 0, - "Queue size must be a multiple of cache line size to " - "prevent false sharing between adjacent queues"); - } - - ~MPSCQueue() noexcept { - for (size_t i = 0; i < capacity; ++i) { - std::destroy_at(&slots[i]); - } - allocator.deallocate(slots, capacity + 1); + template <typename... Args> + bool TryEmplace(Args&&... args) { + return Emplace<PushMode::Try>(std::forward<Args>(args)...); } - // The queue must be both non-copyable and non-movable - MPSCQueue(const MPSCQueue&) = delete; - MPSCQueue& operator=(const MPSCQueue&) = delete; + template <typename... Args> + void EmplaceWait(Args&&... args) { + Emplace<PushMode::Wait>(std::forward<Args>(args)...); + } - MPSCQueue(MPSCQueue&&) = delete; - MPSCQueue& operator=(MPSCQueue&&) = delete; + bool TryPop(T& t) { + return Pop<PopMode::Try>(t); + } - void Push(const T& v) noexcept { - static_assert(std::is_nothrow_copy_constructible_v<T>, - "T must be nothrow copy constructible"); - emplace(v); + void PopWait(T& t) { + Pop<PopMode::Wait>(t); } - template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>> - void Push(P&& v) noexcept { - emplace(std::forward<P>(v)); + void PopWait(T& t, std::stop_token stop_token) { + Pop<PopMode::WaitWithStopToken>(t, stop_token); } - void Pop(T& v, std::stop_token stop) noexcept { - auto const tail = tail_.fetch_add(1); - auto& slot = slots[idx(tail)]; - if (!slot.turn.test()) { - std::unique_lock lock{cv_mutex}; - cv.wait(lock, stop, [&slot] { return slot.turn.test(); }); - } - v = slot.move(); - slot.destroy(); - slot.turn.clear(); - slot.turn.notify_one(); + T PopWait() { + T t; + Pop<PopMode::Wait>(t); + return t; + } + + T PopWait(std::stop_token stop_token) { + T t; + Pop<PopMode::WaitWithStopToken>(t, stop_token); + return t; } private: - template <typename U = T> - struct Slot { - ~Slot() noexcept { - if (turn.test()) { - destroy(); + enum class PushMode { + Try, + Wait, + Count, + }; + + enum class PopMode { + Try, + Wait, + WaitWithStopToken, + Count, + }; + + template <PushMode Mode, typename... Args> + bool Emplace(Args&&... args) { + const size_t write_index = m_write_index.load(std::memory_order::relaxed); + + if constexpr (Mode == PushMode::Try) { + // Check if we have free slots to write to. + if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) { + return false; } + } else if constexpr (Mode == PushMode::Wait) { + // Wait until we have free slots to write to. + std::unique_lock lock{producer_cv_mutex}; + producer_cv.wait(lock, [this, write_index] { + return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity; + }); + } else { + static_assert(Mode < PushMode::Count, "Invalid PushMode."); } - template <typename... Args> - void construct(Args&&... args) noexcept { - static_assert(std::is_nothrow_constructible_v<U, Args&&...>, - "T must be nothrow constructible with Args&&..."); - std::construct_at(reinterpret_cast<U*>(&storage), std::forward<Args>(args)...); - } + // Determine the position to write to. + const size_t pos = write_index % Capacity; - void destroy() noexcept { - static_assert(std::is_nothrow_destructible_v<U>, "T must be nothrow destructible"); - std::destroy_at(reinterpret_cast<U*>(&storage)); - } + // Emplace into the queue. + std::construct_at(std::addressof(m_data[pos]), std::forward<Args>(args)...); + + // Increment the write index. + ++m_write_index; + + // Notify the consumer that we have pushed into the queue. + std::scoped_lock lock{consumer_cv_mutex}; + consumer_cv.notify_one(); + + return true; + } + + template <PopMode Mode> + bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) { + const size_t read_index = m_read_index.load(std::memory_order::relaxed); - U&& move() noexcept { - return reinterpret_cast<U&&>(storage); + if constexpr (Mode == PopMode::Try) { + // Check if the queue is empty. + if (read_index == m_write_index.load(std::memory_order::acquire)) { + return false; + } + } else if constexpr (Mode == PopMode::Wait) { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + consumer_cv.wait(lock, [this, read_index] { + return read_index != m_write_index.load(std::memory_order::acquire); + }); + } else if constexpr (Mode == PopMode::WaitWithStopToken) { + // Wait until the queue is not empty. + std::unique_lock lock{consumer_cv_mutex}; + Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] { + return read_index != m_write_index.load(std::memory_order::acquire); + }); + if (stop_token.stop_requested()) { + return false; + } + } else { + static_assert(Mode < PopMode::Count, "Invalid PopMode."); } - // Align to avoid false sharing between adjacent slots - alignas(hardware_interference_size) std::atomic_flag turn{}; - struct aligned_store { - struct type { - alignas(U) unsigned char data[sizeof(U)]; - }; - }; - typename aligned_store::type storage; - }; + // Determine the position to read from. + const size_t pos = read_index % Capacity; + + // Pop the data off the queue, moving it. + t = std::move(m_data[pos]); + + // Increment the read index. + ++m_read_index; + + // Notify the producer that we have popped off the queue. + std::scoped_lock lock{producer_cv_mutex}; + producer_cv.notify_one(); + + return true; + } + alignas(128) std::atomic_size_t m_read_index{0}; + alignas(128) std::atomic_size_t m_write_index{0}; + + std::array<T, Capacity> m_data; + + std::condition_variable_any producer_cv; + std::mutex producer_cv_mutex; + std::condition_variable_any consumer_cv; + std::mutex consumer_cv_mutex; +}; + +template <typename T, size_t Capacity = detail::DefaultCapacity> +class MPSCQueue { +public: template <typename... Args> - void emplace(Args&&... args) noexcept { - static_assert(std::is_nothrow_constructible_v<T, Args&&...>, - "T must be nothrow constructible with Args&&..."); - auto const head = head_.fetch_add(1); - auto& slot = slots[idx(head)]; - slot.turn.wait(true); - slot.construct(std::forward<Args>(args)...); - slot.turn.test_and_set(); - cv.notify_one(); + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{write_mutex}; + return spsc_queue.TryEmplace(std::forward<Args>(args)...); } - constexpr size_t idx(size_t i) const noexcept { - return i & mask; + template <typename... Args> + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{write_mutex}; + spsc_queue.EmplaceWait(std::forward<Args>(args)...); } - static constexpr size_t mask = capacity - 1; + bool TryPop(T& t) { + return spsc_queue.TryPop(t); + } - // Align to avoid false sharing between head_ and tail_ - alignas(hardware_interference_size) std::atomic<size_t> head_{0}; - alignas(hardware_interference_size) std::atomic<size_t> tail_{0}; + void PopWait(T& t) { + spsc_queue.PopWait(t); + } - std::mutex cv_mutex; - std::condition_variable_any cv; + void PopWait(T& t, std::stop_token stop_token) { + spsc_queue.PopWait(t, stop_token); + } + + T PopWait() { + return spsc_queue.PopWait(); + } - Slot<T>* slots; - [[no_unique_address]] std::allocator<Slot<T>> allocator; + T PopWait(std::stop_token stop_token) { + return spsc_queue.PopWait(stop_token); + } - static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>, - "T must be nothrow copy or move assignable"); +private: + SPSCQueue<T, Capacity> spsc_queue; + std::mutex write_mutex; +}; - static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible"); +template <typename T, size_t Capacity = detail::DefaultCapacity> +class MPMCQueue { +public: + template <typename... Args> + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{write_mutex}; + return spsc_queue.TryEmplace(std::forward<Args>(args)...); + } + + template <typename... Args> + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{write_mutex}; + spsc_queue.EmplaceWait(std::forward<Args>(args)...); + } + + bool TryPop(T& t) { + std::scoped_lock lock{read_mutex}; + return spsc_queue.TryPop(t); + } + + void PopWait(T& t) { + std::scoped_lock lock{read_mutex}; + spsc_queue.PopWait(t); + } + + void PopWait(T& t, std::stop_token stop_token) { + std::scoped_lock lock{read_mutex}; + spsc_queue.PopWait(t, stop_token); + } + + T PopWait() { + std::scoped_lock lock{read_mutex}; + return spsc_queue.PopWait(); + } + + T PopWait(std::stop_token stop_token) { + std::scoped_lock lock{read_mutex}; + return spsc_queue.PopWait(stop_token); + } + +private: + SPSCQueue<T, Capacity> spsc_queue; + std::mutex write_mutex; + std::mutex read_mutex; }; } // namespace Common diff --git a/src/common/concepts.h b/src/common/concepts.h index a9acff3e7..61df1d32a 100644 --- a/src/common/concepts.h +++ b/src/common/concepts.h @@ -16,9 +16,9 @@ concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>; // is available on all supported platforms. template <typename Derived, typename Base> concept DerivedFrom = requires { - std::is_base_of_v<Base, Derived>; - std::is_convertible_v<const volatile Derived*, const volatile Base*>; -}; + std::is_base_of_v<Base, Derived>; + std::is_convertible_v<const volatile Derived*, const volatile Base*>; + }; // TODO: Replace with std::convertible_to when libc++ implements it. template <typename From, typename To> diff --git a/src/common/container_hash.h b/src/common/container_hash.h new file mode 100644 index 000000000..a5e357745 --- /dev/null +++ b/src/common/container_hash.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2005-2014 Daniel James +// SPDX-FileCopyrightText: 2016 Austin Appleby +// SPDX-License-Identifier: BSL-1.0 + +#include <array> +#include <climits> +#include <cstdint> +#include <limits> +#include <type_traits> +#include <vector> + +namespace Common { + +namespace detail { + +template <typename T> + requires std::is_unsigned_v<T> +inline std::size_t HashValue(T val) { + const unsigned int size_t_bits = std::numeric_limits<std::size_t>::digits; + const unsigned int length = + (std::numeric_limits<T>::digits - 1) / static_cast<unsigned int>(size_t_bits); + + std::size_t seed = 0; + + for (unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) { + seed ^= static_cast<size_t>(val >> i) + (seed << 6) + (seed >> 2); + } + + seed ^= static_cast<size_t>(val) + (seed << 6) + (seed >> 2); + + return seed; +} + +template <size_t Bits> +struct HashCombineImpl { + template <typename T> + static inline T fn(T seed, T value) { + seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +template <> +struct HashCombineImpl<64> { + static inline std::uint64_t fn(std::uint64_t h, std::uint64_t k) { + const std::uint64_t m = (std::uint64_t(0xc6a4a793) << 32) + 0x5bd1e995; + const int r = 47; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + // Completely arbitrary number, to prevent 0's + // from hashing to 0. + h += 0xe6546b64; + + return h; + } +}; + +} // namespace detail + +template <typename T> +inline void HashCombine(std::size_t& seed, const T& v) { + seed = detail::HashCombineImpl<sizeof(std::size_t) * CHAR_BIT>::fn(seed, detail::HashValue(v)); +} + +template <typename It> +inline std::size_t HashRange(It first, It last) { + std::size_t seed = 0; + + for (; first != last; ++first) { + HashCombine<typename std::iterator_traits<It>::value_type>(seed, *first); + } + + return seed; +} + +template <typename T, size_t Size> +std::size_t HashValue(const std::array<T, Size>& v) { + return HashRange(v.cbegin(), v.cend()); +} + +template <typename T, typename Allocator> +std::size_t HashValue(const std::vector<T, Allocator>& v) { + return HashRange(v.cbegin(), v.cend()); +} + +} // namespace Common diff --git a/src/common/demangle.cpp b/src/common/demangle.cpp index f4246f666..3310faf86 100644 --- a/src/common/demangle.cpp +++ b/src/common/demangle.cpp @@ -1,13 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <llvm/Demangle/Demangle.h> + #include "common/demangle.h" #include "common/scope_exit.h" -namespace llvm { -char* itaniumDemangle(const char* mangled_name, char* buf, size_t* n, int* status); -} - namespace Common { std::string DemangleSymbol(const std::string& mangled) { diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h index eebc279c2..c12477d42 100644 --- a/src/common/div_ceil.h +++ b/src/common/div_ceil.h @@ -10,14 +10,14 @@ namespace Common { /// Ceiled integer division. template <typename N, typename D> -requires std::is_integral_v<N> && std::is_unsigned_v<D> + requires std::is_integral_v<N> && std::is_unsigned_v<D> [[nodiscard]] constexpr N DivCeil(N number, D divisor) { return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor); } /// Ceiled integer division with logarithmic divisor in base 2 template <typename N, typename D> -requires std::is_integral_v<N> && std::is_unsigned_v<D> + requires std::is_integral_v<N> && std::is_unsigned_v<D> [[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) { return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); } diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp index 054277a2b..4fabe7e52 100644 --- a/src/common/dynamic_library.cpp +++ b/src/common/dynamic_library.cpp @@ -22,6 +22,8 @@ DynamicLibrary::DynamicLibrary(const char* filename) { void(Open(filename)); } +DynamicLibrary::DynamicLibrary(void* handle_) : handle{handle_} {} + DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)} {} diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h index f42bdf441..662d454d4 100644 --- a/src/common/dynamic_library.h +++ b/src/common/dynamic_library.h @@ -20,6 +20,9 @@ public: /// Automatically loads the specified library. Call IsOpen() to check validity before use. explicit DynamicLibrary(const char* filename); + /// Initializes the dynamic library with an already opened handle. + explicit DynamicLibrary(void* handle_); + /// Moves the library. DynamicLibrary(DynamicLibrary&&) noexcept; DynamicLibrary& operator=(DynamicLibrary&&) noexcept; diff --git a/src/common/error.cpp b/src/common/error.cpp index ddb03bd45..1b2009db7 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -30,7 +30,8 @@ std::string NativeErrorToString(int e) { return ret; #else char err_str[255]; -#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) +#if defined(ANDROID) || \ + (defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))) // Thread safe (GNU-specific) const char* str = strerror_r(e, err_str, sizeof(err_str)); return std::string(str); diff --git a/src/common/expected.h b/src/common/expected.h index 6e6c86ee7..5fccfbcbd 100644 --- a/src/common/expected.h +++ b/src/common/expected.h @@ -64,7 +64,7 @@ struct no_init_t { * Additionally, this requires E to be trivially destructible */ template <typename T, typename E, bool = std::is_trivially_destructible_v<T>> -requires std::is_trivially_destructible_v<E> + requires std::is_trivially_destructible_v<E> struct expected_storage_base { constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {} @@ -111,7 +111,7 @@ struct expected_storage_base { * Additionally, this requires E to be trivially destructible */ template <typename T, typename E> -requires std::is_trivially_destructible_v<E> + requires std::is_trivially_destructible_v<E> struct expected_storage_base<T, E, true> { constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {} @@ -251,7 +251,7 @@ struct expected_operations_base : expected_storage_base<T, E> { * Additionally, this requires E to be trivially copy constructible */ template <typename T, typename E, bool = std::is_trivially_copy_constructible_v<T>> -requires std::is_trivially_copy_constructible_v<E> + requires std::is_trivially_copy_constructible_v<E> struct expected_copy_base : expected_operations_base<T, E> { using expected_operations_base<T, E>::expected_operations_base; }; @@ -261,7 +261,7 @@ struct expected_copy_base : expected_operations_base<T, E> { * Additionally, this requires E to be trivially copy constructible */ template <typename T, typename E> -requires std::is_trivially_copy_constructible_v<E> + requires std::is_trivially_copy_constructible_v<E> struct expected_copy_base<T, E, false> : expected_operations_base<T, E> { using expected_operations_base<T, E>::expected_operations_base; @@ -289,7 +289,7 @@ struct expected_copy_base<T, E, false> : expected_operations_base<T, E> { * Additionally, this requires E to be trivially move constructible */ template <typename T, typename E, bool = std::is_trivially_move_constructible_v<T>> -requires std::is_trivially_move_constructible_v<E> + requires std::is_trivially_move_constructible_v<E> struct expected_move_base : expected_copy_base<T, E> { using expected_copy_base<T, E>::expected_copy_base; }; @@ -299,7 +299,7 @@ struct expected_move_base : expected_copy_base<T, E> { * Additionally, this requires E to be trivially move constructible */ template <typename T, typename E> -requires std::is_trivially_move_constructible_v<E> + requires std::is_trivially_move_constructible_v<E> struct expected_move_base<T, E, false> : expected_copy_base<T, E> { using expected_copy_base<T, E>::expected_copy_base; @@ -330,9 +330,9 @@ template <typename T, typename E, bool = std::conjunction_v<std::is_trivially_copy_assignable<T>, std::is_trivially_copy_constructible<T>, std::is_trivially_destructible<T>>> -requires std::conjunction_v<std::is_trivially_copy_assignable<E>, - std::is_trivially_copy_constructible<E>, - std::is_trivially_destructible<E>> + requires std::conjunction_v<std::is_trivially_copy_assignable<E>, + std::is_trivially_copy_constructible<E>, + std::is_trivially_destructible<E>> struct expected_copy_assign_base : expected_move_base<T, E> { using expected_move_base<T, E>::expected_move_base; }; @@ -342,9 +342,9 @@ struct expected_copy_assign_base : expected_move_base<T, E> { * Additionally, this requires E to be trivially copy assignable */ template <typename T, typename E> -requires std::conjunction_v<std::is_trivially_copy_assignable<E>, - std::is_trivially_copy_constructible<E>, - std::is_trivially_destructible<E>> + requires std::conjunction_v<std::is_trivially_copy_assignable<E>, + std::is_trivially_copy_constructible<E>, + std::is_trivially_destructible<E>> struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> { using expected_move_base<T, E>::expected_move_base; @@ -371,9 +371,9 @@ template <typename T, typename E, bool = std::conjunction_v<std::is_trivially_move_assignable<T>, std::is_trivially_move_constructible<T>, std::is_trivially_destructible<T>>> -requires std::conjunction_v<std::is_trivially_move_assignable<E>, - std::is_trivially_move_constructible<E>, - std::is_trivially_destructible<E>> + requires std::conjunction_v<std::is_trivially_move_assignable<E>, + std::is_trivially_move_constructible<E>, + std::is_trivially_destructible<E>> struct expected_move_assign_base : expected_copy_assign_base<T, E> { using expected_copy_assign_base<T, E>::expected_copy_assign_base; }; @@ -383,9 +383,9 @@ struct expected_move_assign_base : expected_copy_assign_base<T, E> { * Additionally, this requires E to be trivially move assignable */ template <typename T, typename E> -requires std::conjunction_v<std::is_trivially_move_assignable<E>, - std::is_trivially_move_constructible<E>, - std::is_trivially_destructible<E>> + requires std::conjunction_v<std::is_trivially_move_assignable<E>, + std::is_trivially_move_constructible<E>, + std::is_trivially_destructible<E>> struct expected_move_assign_base<T, E, false> : expected_copy_assign_base<T, E> { using expected_copy_assign_base<T, E>::expected_copy_assign_base; @@ -412,7 +412,7 @@ struct expected_move_assign_base<T, E, false> : expected_copy_assign_base<T, E> */ template <typename T, typename E, bool EnableCopy = std::is_copy_constructible_v<T>, bool EnableMove = std::is_move_constructible_v<T>> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> struct expected_delete_ctor_base { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base&) = default; @@ -422,7 +422,7 @@ struct expected_delete_ctor_base { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> struct expected_delete_ctor_base<T, E, true, false> { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base&) = default; @@ -432,7 +432,7 @@ struct expected_delete_ctor_base<T, E, true, false> { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> struct expected_delete_ctor_base<T, E, false, true> { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; @@ -442,7 +442,7 @@ struct expected_delete_ctor_base<T, E, false, true> { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>> struct expected_delete_ctor_base<T, E, false, false> { expected_delete_ctor_base() = default; expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; @@ -460,8 +460,8 @@ template < typename T, typename E, bool EnableCopy = std::conjunction_v<std::is_copy_constructible<T>, std::is_copy_assignable<T>>, bool EnableMove = std::conjunction_v<std::is_move_constructible<T>, std::is_move_assignable<T>>> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, - std::is_copy_assignable<E>, std::is_move_assignable<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, + std::is_copy_assignable<E>, std::is_move_assignable<E>> struct expected_delete_assign_base { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base&) = default; @@ -471,8 +471,8 @@ struct expected_delete_assign_base { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, - std::is_copy_assignable<E>, std::is_move_assignable<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, + std::is_copy_assignable<E>, std::is_move_assignable<E>> struct expected_delete_assign_base<T, E, true, false> { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base&) = default; @@ -482,8 +482,8 @@ struct expected_delete_assign_base<T, E, true, false> { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, - std::is_copy_assignable<E>, std::is_move_assignable<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, + std::is_copy_assignable<E>, std::is_move_assignable<E>> struct expected_delete_assign_base<T, E, false, true> { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base&) = default; @@ -493,8 +493,8 @@ struct expected_delete_assign_base<T, E, false, true> { }; template <typename T, typename E> -requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, - std::is_copy_assignable<E>, std::is_move_assignable<E>> + requires std::conjunction_v<std::is_copy_constructible<E>, std::is_move_constructible<E>, + std::is_copy_assignable<E>, std::is_move_assignable<E>> struct expected_delete_assign_base<T, E, false, false> { expected_delete_assign_base() = default; expected_delete_assign_base(const expected_delete_assign_base&) = default; diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index bc92b360b..c991b7cf1 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -90,7 +90,7 @@ Fiber::~Fiber() { } void Fiber::Exit() { - ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber"); + ASSERT_MSG(impl->is_thread_fiber, "Exiting non main thread fiber"); if (!impl->is_thread_fiber) { return; } diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index f899b0d54..b0f3ae2cc 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -22,7 +22,7 @@ class FixedPoint; namespace detail { // helper templates to make magic with types :) -// these allow us to determine resonable types from +// these allow us to determine reasonable types from // a desired size, they also let us infer the next largest type // from a type which is nice for the division op template <size_t T> diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 656b03cc5..b0b25eb43 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -5,6 +5,9 @@ #include "common/fs/file.h" #include "common/fs/fs.h" +#ifdef ANDROID +#include "common/fs/fs_android.h" +#endif #include "common/logging/log.h" #ifdef _WIN32 @@ -252,6 +255,23 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File } else { _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); } +#elif ANDROID + if (Android::IsContentUri(path)) { + ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!"); + const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read); + if (fd != -1) { + file = fdopen(fd, "r"); + const auto error_num = errno; + if (error_num != 0 && file == nullptr) { + LOG_ERROR(Common_Filesystem, "Error opening file: {}, error: {}", path.c_str(), + strerror(error_num)); + } + } else { + LOG_ERROR(Common_Filesystem, "Error opening file: {}", path.c_str()); + } + } else { + file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); + } #else file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); #endif @@ -372,6 +392,23 @@ u64 IOFile::GetSize() const { // Flush any unwritten buffered data into the file prior to retrieving the file size. std::fflush(file); +#if ANDROID + u64 file_size = 0; + if (Android::IsContentUri(file_path)) { + file_size = Android::GetSize(file_path); + } else { + std::error_code ec; + + file_size = fs::file_size(file_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + return 0; + } + } +#else std::error_code ec; const auto file_size = fs::file_size(file_path, ec); @@ -381,6 +418,7 @@ u64 IOFile::GetSize() const { PathToUTF8String(file_path), ec.message()); return 0; } +#endif return file_size; } diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index e1716c62d..36e67c145 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -3,6 +3,9 @@ #include "common/fs/file.h" #include "common/fs/fs.h" +#ifdef ANDROID +#include "common/fs/fs_android.h" +#endif #include "common/fs/path_util.h" #include "common/logging/log.h" @@ -433,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable if (True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -441,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable if (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -490,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, if (True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -498,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, if (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -525,15 +528,39 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, // Generic Filesystem Operations bool Exists(const fs::path& path) { +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::Exists(path); + } else { + return fs::exists(path); + } +#else return fs::exists(path); +#endif } bool IsFile(const fs::path& path) { +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return !Android::IsDirectory(path); + } else { + return fs::is_regular_file(path); + } +#else return fs::is_regular_file(path); +#endif } bool IsDir(const fs::path& path) { +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::IsDirectory(path); + } else { + return fs::is_directory(path); + } +#else return fs::is_directory(path); +#endif } fs::path GetCurrentDir() { @@ -578,6 +605,12 @@ fs::file_type GetEntryType(const fs::path& path) { } u64 GetSize(const fs::path& path) { +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::GetSize(path); + } +#endif + std::error_code ec; const auto file_size = fs::file_size(path, ec); diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp new file mode 100644 index 000000000..298a79bac --- /dev/null +++ b/src/common/fs/fs_android.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/fs/fs_android.h" + +namespace Common::FS::Android { + +JNIEnv* GetEnvForThread() { + thread_local static struct OwnedEnv { + OwnedEnv() { + status = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); + if (status == JNI_EDETACHED) + g_jvm->AttachCurrentThread(&env, nullptr); + } + + ~OwnedEnv() { + if (status == JNI_EDETACHED) + g_jvm->DetachCurrentThread(); + } + + int status; + JNIEnv* env = nullptr; + } owned; + return owned.env; +} + +void RegisterCallbacks(JNIEnv* env, jclass clazz) { + env->GetJavaVM(&g_jvm); + native_library = clazz; + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) +#define F(JMethodID, JMethodName, Signature) \ + JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); + ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) + ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR +} + +void UnRegisterCallbacks() { +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) +#define F(JMethodID) JMethodID = nullptr; + ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) + ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR +} + +bool IsContentUri(const std::string& path) { + constexpr std::string_view prefix = "content://"; + if (path.size() < prefix.size()) [[unlikely]] { + return false; + } + + return path.find(prefix) == 0; +} + +int OpenContentUri(const std::string& filepath, OpenMode openmode) { + if (open_content_uri == nullptr) + return -1; + + const char* mode = ""; + switch (openmode) { + case OpenMode::Read: + mode = "r"; + break; + default: + UNIMPLEMENTED(); + return -1; + } + auto env = GetEnvForThread(); + jstring j_filepath = env->NewStringUTF(filepath.c_str()); + jstring j_mode = env->NewStringUTF(mode); + return env->CallStaticIntMethod(native_library, open_content_uri, j_filepath, j_mode); +} + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, ReturnValue, JMethodID, Caller) +#define F(FunctionName, ReturnValue, JMethodID, Caller) \ + ReturnValue FunctionName(const std::string& filepath) { \ + if (JMethodID == nullptr) { \ + return 0; \ + } \ + auto env = GetEnvForThread(); \ + jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ + return env->Caller(native_library, JMethodID, j_filepath); \ + } +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +#undef F +#undef FR + +} // namespace Common::FS::Android diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h new file mode 100644 index 000000000..b441c2a12 --- /dev/null +++ b/src/common/fs/fs_android.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> +#include <vector> +#include <jni.h> + +#define ANDROID_STORAGE_FUNCTIONS(V) \ + V(OpenContentUri, int, (const std::string& filepath, OpenMode openmode), open_content_uri, \ + "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") + +#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ + V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \ + V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \ + "(Ljava/lang/String;)Z") \ + V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") + +namespace Common::FS::Android { + +static JavaVM* g_jvm = nullptr; +static jclass native_library = nullptr; + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) +#define F(JMethodID) static jmethodID JMethodID = nullptr; +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR + +enum class OpenMode { + Read, + Write, + ReadWrite, + WriteAppend, + WriteTruncate, + ReadWriteAppend, + ReadWriteTruncate, + Never +}; + +void RegisterCallbacks(JNIEnv* env, jclass clazz); + +void UnRegisterCallbacks(); + +bool IsContentUri(const std::string& path); + +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ + F(FunctionName, Parameters, ReturnValue) +#define F(FunctionName, Parameters, ReturnValue) ReturnValue FunctionName Parameters; +ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, ReturnValue) +#define F(FunctionName, ReturnValue) ReturnValue FunctionName(const std::string& filepath); +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +#undef F +#undef FR + +} // namespace Common::FS::Android diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index c77c112f1..61bac9eba 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -10,6 +10,7 @@ // Sub-directories contained within a yuzu data directory +#define AMIIBO_DIR "amiibo" #define CACHE_DIR "cache" #define CONFIG_DIR "config" #define DUMP_DIR "dump" diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h index 5a4090c19..900f85d24 100644 --- a/src/common/fs/fs_types.h +++ b/src/common/fs/fs_types.h @@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); * @returns A boolean value. * Return true to indicate whether the callback is successful, false otherwise. */ -using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; +using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>; } // namespace Common::FS diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index defa3e918..d71cfacc6 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -6,6 +6,9 @@ #include <unordered_map> #include "common/fs/fs.h" +#ifdef ANDROID +#include "common/fs/fs_android.h" +#endif #include "common/fs/fs_paths.h" #include "common/fs/path_util.h" #include "common/logging/log.h" @@ -80,9 +83,7 @@ public: yuzu_paths.insert_or_assign(yuzu_path, new_path); } -private: - PathManagerImpl() { - fs::path yuzu_path; + void Reinitialize(fs::path yuzu_path = {}) { fs::path yuzu_path_cache; fs::path yuzu_path_config; @@ -95,6 +96,10 @@ private: yuzu_path_cache = yuzu_path / CACHE_DIR; yuzu_path_config = yuzu_path / CONFIG_DIR; +#elif ANDROID + ASSERT(!yuzu_path.empty()); + yuzu_path_cache = yuzu_path / CACHE_DIR; + yuzu_path_config = yuzu_path / CONFIG_DIR; #else yuzu_path = GetCurrentDir() / PORTABLE_DIR; @@ -109,6 +114,7 @@ private: #endif GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); + GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR); GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache); GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config); GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); @@ -122,6 +128,11 @@ private: GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); } +private: + PathManagerImpl() { + Reinitialize(); + } + ~PathManagerImpl() = default; void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { @@ -210,6 +221,10 @@ fs::path RemoveTrailingSeparators(const fs::path& path) { return fs::path{string_path}; } +void SetAppDirectory(const std::string& app_directory) { + PathManagerImpl::GetInstance().Reinitialize(app_directory); +} + const fs::path& GetYuzuPath(YuzuPath yuzu_path) { return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); } @@ -350,6 +365,12 @@ std::vector<std::string> SplitPathComponents(std::string_view filename) { std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { std::string path(path_); +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return path; + } +#endif // ANDROID + char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 13d713f1e..ba28964d0 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -12,6 +12,7 @@ namespace Common::FS { enum class YuzuPath { YuzuDir, // Where yuzu stores its data. + AmiiboDir, // Where Amiibo backups are stored. CacheDir, // Where cached filesystem data is stored. ConfigDir, // Where config files are stored. DumpDir, // Where dumped data is stored. @@ -181,6 +182,14 @@ template <typename Path> #endif /** + * Sets the directory used for application storage. Used on Android where we do not know internal + * storage until informed by the frontend. + * + * @param app_directory Directory to use for application storage. + */ +void SetAppDirectory(const std::string& app_directory); + +/** * Gets the filesystem path associated with the YuzuPath enum. * * @param yuzu_path YuzuPath enum diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 611c7d1a3..ba22595e0 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -11,9 +11,14 @@ #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv +#ifdef ANDROID +#include <android/sharedmem.h> +#endif + #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include <boost/icl/interval_set.hpp> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> @@ -322,7 +327,7 @@ private: } /// Return true when a given memory region is a "nieche" and the placeholders don't have to be - /// splitted. + /// split. bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const { const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length}); if (it != placeholders.end() && it->lower() == virtual_offset + length) { @@ -366,17 +371,20 @@ public: } // Backing memory initialization -#if defined(__FreeBSD__) && __FreeBSD__ < 13 +#ifdef ANDROID + fd = ASharedMemory_create("HostMemory", backing_size); +#elif defined(__FreeBSD__) && __FreeBSD__ < 13 // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 fd = shm_open(SHM_ANON, O_RDWR, 0600); #else fd = memfd_create("HostMemory", 0); #endif - if (fd == -1) { + if (fd < 0) { LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno)); throw std::bad_alloc{}; } +#ifndef ANDROID // Defined to extend the file with zeros int ret = ftruncate(fd, backing_size); if (ret != 0) { @@ -384,6 +392,7 @@ public: strerror(errno)); throw std::bad_alloc{}; } +#endif backing_base = static_cast<u8*>( mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); @@ -415,6 +424,7 @@ public: madvise(virtual_base, virtual_size, MADV_HUGEPAGE); #endif + placeholders.add({0, virtual_size}); good = true; } @@ -423,6 +433,10 @@ public: } void Map(size_t virtual_offset, size_t host_offset, size_t length) { + { + std::scoped_lock lock{placeholder_mutex}; + placeholders.subtract({virtual_offset, virtual_offset + length}); + } void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, host_offset); @@ -433,6 +447,19 @@ public: // The method name is wrong. We're still talking about the virtual range. // We don't want to unmap, we want to reserve this memory. + { + std::scoped_lock lock{placeholder_mutex}; + auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1}); + + if (it != placeholders.end()) { + size_t prev_upper = virtual_offset + length; + virtual_offset = std::min(virtual_offset, it->lower()); + length = std::max(it->upper(), prev_upper) - virtual_offset; + } + + placeholders.add({virtual_offset, virtual_offset + length}); + } + void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); @@ -476,6 +503,9 @@ private: } int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create + + boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders + std::mutex placeholder_mutex; ///< Mutex for placeholders }; #else // ^^^ Linux ^^^ vvv Generic vvv @@ -484,7 +514,7 @@ class HostMemory::Impl { public: explicit Impl(size_t /*backing_size */, size_t /* virtual_size */) { // This is just a place holder. - // Please implement fastmem in a propper way on your platform. + // Please implement fastmem in a proper way on your platform. throw std::bad_alloc{}; } diff --git a/src/common/input.h b/src/common/input.h index d27b1d772..ea30770ae 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -15,7 +15,7 @@ namespace Common::Input { -// Type of data that is expected to recieve or send +// Type of data that is expected to receive or send enum class InputType { None, Battery, @@ -46,11 +46,13 @@ enum class PollingMode { // Constant polling of buttons, analogs and motion data Active, // Only update on button change, digital analogs - Pasive, + Passive, // Enable near field communication polling NFC, // Enable infrared camera polling IR, + // Enable ring controller polling + Ring, }; enum class CameraFormat { @@ -62,41 +64,35 @@ enum class CameraFormat { None, }; -// Vibration reply from the controller -enum class VibrationError { - None, +// Different results that can happen from a device request +enum class DriverResult { + Success, + WrongReply, + Timeout, + UnsupportedControllerType, + HandleInUse, + ErrorReadingData, + ErrorWritingData, + NoDeviceDetected, + InvalidHandle, NotSupported, Disabled, Unknown, }; -// Polling mode reply from the controller -enum class PollingError { - None, - NotSupported, - Unknown, -}; - // Nfc reply from the controller enum class NfcState { Success, NewAmiibo, WaitingForAmiibo, AmiiboRemoved, - NotAnAmiibo, + InvalidTagType, NotSupported, WrongDeviceState, WriteFailed, Unknown, }; -// Ir camera reply from the controller -enum class CameraError { - None, - NotSupported, - Unknown, -}; - // Hint for amplification curve to be used enum class VibrationAmplificationType { Linear, @@ -107,7 +103,7 @@ enum class VibrationAmplificationType { struct AnalogProperties { // Anything below this value will be detected as zero float deadzone{}; - // Anyting above this values will be detected as one + // Anything above this values will be detected as one float range{1.0f}; // Minimum value to be detected as active float threshold{0.5f}; @@ -115,6 +111,8 @@ struct AnalogProperties { float offset{}; // Invert direction of the sensor data bool inverted{}; + // Invert the state if it's converted to a button + bool inverted_button{}; // Press once to activate, press again to release bool toggle{}; }; @@ -134,6 +132,8 @@ struct ButtonStatus { bool inverted{}; // Press once to activate, press again to release bool toggle{}; + // Spams the button when active + bool turbo{}; // Internal lock for the toggle status bool locked{}; }; @@ -190,6 +190,8 @@ struct TouchStatus { struct BodyColorStatus { u32 body{}; u32 buttons{}; + u32 left_grip{}; + u32 right_grip{}; }; // HD rumble data @@ -209,15 +211,29 @@ struct LedStatus { bool led_4{}; }; -// Raw data fom camera +// Raw data from camera struct CameraStatus { CameraFormat format{CameraFormat::None}; std::vector<u8> data{}; }; struct NfcStatus { - NfcState state{}; - std::vector<u8> data{}; + NfcState state{NfcState::Unknown}; + u8 uuid_length; + u8 protocol; + u8 tag_type; + std::array<u8, 10> uuid; +}; + +struct MifareData { + u8 command; + u8 sector; + std::array<u8, 0x6> key; + std::array<u8, 0x10> data; +}; + +struct MifareRequest { + std::array<MifareData, 0x10> data; }; // List of buttons to be passed to Qt that can be translated @@ -228,17 +244,31 @@ enum class ButtonNames { Engine, // This will display the button by value instead of the button name Value, + + // Joycon button names ButtonLeft, ButtonRight, ButtonDown, ButtonUp, - TriggerZ, - TriggerR, - TriggerL, ButtonA, ButtonB, ButtonX, ButtonY, + ButtonPlus, + ButtonMinus, + ButtonHome, + ButtonCapture, + ButtonStickL, + ButtonStickR, + TriggerL, + TriggerZL, + TriggerSL, + TriggerR, + TriggerZR, + TriggerSR, + + // GC button names + TriggerZ, ButtonStart, // DS4 button names @@ -278,7 +308,7 @@ struct CallbackStatus { BatteryStatus battery_status{}; VibrationStatus vibration_status{}; CameraFormat camera_status{CameraFormat::None}; - NfcState nfc_status{NfcState::Unknown}; + NfcStatus nfc_status{}; std::vector<u8> raw_data{}; }; @@ -316,31 +346,54 @@ class OutputDevice { public: virtual ~OutputDevice() = default; - virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {} + virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) { + return DriverResult::NotSupported; + } - virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) { - return VibrationError::NotSupported; + virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) { + return DriverResult::NotSupported; } virtual bool IsVibrationEnabled() { return false; } - virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { - return PollingError::NotSupported; + virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) { + return DriverResult::NotSupported; } - virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { - return CameraError::NotSupported; + virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { + return DriverResult::NotSupported; } virtual NfcState SupportsNfc() const { return NfcState::NotSupported; } + virtual NfcState StartNfcPolling() { + return NfcState::NotSupported; + } + + virtual NfcState StopNfcPolling() { + return NfcState::NotSupported; + } + + virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) { + return NfcState::NotSupported; + } + virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) { return NfcState::NotSupported; } + + virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request, + [[maybe_unused]] MifareRequest& out_data) { + return NfcState::NotSupported; + } + + virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) { + return NfcState::NotSupported; + } }; /// An abstract class template for a factory that can create input devices. @@ -412,7 +465,7 @@ inline void UnregisterOutputFactory(const std::string& name) { } /** - * Create an input device from given paramters. + * Create an input device from given parameters. * @tparam InputDeviceType the type of input devices to create * @param params a serialized ParamPackage string that contains all parameters for creating the * device diff --git a/src/common/intrusive_list.h b/src/common/intrusive_list.h new file mode 100644 index 000000000..d330dc1c2 --- /dev/null +++ b/src/common/intrusive_list.h @@ -0,0 +1,631 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/parent_of_member.h" + +namespace Common { + +// Forward declare implementation class for Node. +namespace impl { + +class IntrusiveListImpl; + +} + +class IntrusiveListNode { + YUZU_NON_COPYABLE(IntrusiveListNode); + +private: + friend class impl::IntrusiveListImpl; + + IntrusiveListNode* m_prev; + IntrusiveListNode* m_next; + +public: + constexpr IntrusiveListNode() : m_prev(this), m_next(this) {} + + constexpr bool IsLinked() const { + return m_next != this; + } + +private: + constexpr void LinkPrev(IntrusiveListNode* node) { + // We can't link an already linked node. + ASSERT(!node->IsLinked()); + this->SplicePrev(node, node); + } + + constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) { + // Splice a range into the list. + auto last_prev = last->m_prev; + first->m_prev = m_prev; + last_prev->m_next = this; + m_prev->m_next = first; + m_prev = last_prev; + } + + constexpr void LinkNext(IntrusiveListNode* node) { + // We can't link an already linked node. + ASSERT(!node->IsLinked()); + return this->SpliceNext(node, node); + } + + constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) { + // Splice a range into the list. + auto last_prev = last->m_prev; + first->m_prev = this; + last_prev->m_next = m_next; + m_next->m_prev = last_prev; + m_next = first; + } + + constexpr void Unlink() { + this->Unlink(m_next); + } + + constexpr void Unlink(IntrusiveListNode* last) { + // Unlink a node from a next node. + auto last_prev = last->m_prev; + m_prev->m_next = last; + last->m_prev = m_prev; + last_prev->m_next = this; + m_prev = last_prev; + } + + constexpr IntrusiveListNode* GetPrev() { + return m_prev; + } + + constexpr const IntrusiveListNode* GetPrev() const { + return m_prev; + } + + constexpr IntrusiveListNode* GetNext() { + return m_next; + } + + constexpr const IntrusiveListNode* GetNext() const { + return m_next; + } +}; +// DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value); + +namespace impl { + +class IntrusiveListImpl { + YUZU_NON_COPYABLE(IntrusiveListImpl); + +private: + IntrusiveListNode m_root_node; + +public: + template <bool Const> + class Iterator; + + using value_type = IntrusiveListNode; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + template <bool Const> + class Iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveListImpl::value_type; + using difference_type = typename IntrusiveListImpl::difference_type; + using pointer = + std::conditional_t<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>; + using reference = std::conditional_t<Const, IntrusiveListImpl::const_reference, + IntrusiveListImpl::reference>; + + private: + pointer m_node; + + public: + constexpr explicit Iterator(pointer n) : m_node(n) {} + + constexpr bool operator==(const Iterator& rhs) const { + return m_node == rhs.m_node; + } + + constexpr pointer operator->() const { + return m_node; + } + + constexpr reference operator*() const { + return *m_node; + } + + constexpr Iterator& operator++() { + m_node = m_node->m_next; + return *this; + } + + constexpr Iterator& operator--() { + m_node = m_node->m_prev; + return *this; + } + + constexpr Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + constexpr Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + constexpr operator Iterator<true>() const { + return Iterator<true>(m_node); + } + + constexpr Iterator<false> GetNonConstIterator() const { + return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node)); + } + }; + +public: + constexpr IntrusiveListImpl() : m_root_node() {} + + // Iterator accessors. + constexpr iterator begin() { + return iterator(m_root_node.GetNext()); + } + + constexpr const_iterator begin() const { + return const_iterator(m_root_node.GetNext()); + } + + constexpr iterator end() { + return iterator(std::addressof(m_root_node)); + } + + constexpr const_iterator end() const { + return const_iterator(std::addressof(m_root_node)); + } + + constexpr iterator iterator_to(reference v) { + // Only allow iterator_to for values in lists. + ASSERT(v.IsLinked()); + return iterator(std::addressof(v)); + } + + constexpr const_iterator iterator_to(const_reference v) const { + // Only allow iterator_to for values in lists. + ASSERT(v.IsLinked()); + return const_iterator(std::addressof(v)); + } + + // Content management. + constexpr bool empty() const { + return !m_root_node.IsLinked(); + } + + constexpr size_type size() const { + return static_cast<size_type>(std::distance(this->begin(), this->end())); + } + + constexpr reference back() { + return *m_root_node.GetPrev(); + } + + constexpr const_reference back() const { + return *m_root_node.GetPrev(); + } + + constexpr reference front() { + return *m_root_node.GetNext(); + } + + constexpr const_reference front() const { + return *m_root_node.GetNext(); + } + + constexpr void push_back(reference node) { + m_root_node.LinkPrev(std::addressof(node)); + } + + constexpr void push_front(reference node) { + m_root_node.LinkNext(std::addressof(node)); + } + + constexpr void pop_back() { + m_root_node.GetPrev()->Unlink(); + } + + constexpr void pop_front() { + m_root_node.GetNext()->Unlink(); + } + + constexpr iterator insert(const_iterator pos, reference node) { + pos.GetNonConstIterator()->LinkPrev(std::addressof(node)); + return iterator(std::addressof(node)); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl& o) { + splice_impl(pos, o.begin(), o.end()); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first) { + const_iterator last(first); + std::advance(last, 1); + splice_impl(pos, first, last); + } + + constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first, + const_iterator last) { + splice_impl(pos, first, last); + } + + constexpr iterator erase(const_iterator pos) { + if (pos == this->end()) { + return this->end(); + } + iterator it(pos.GetNonConstIterator()); + (it++)->Unlink(); + return it; + } + + constexpr void clear() { + while (!this->empty()) { + this->pop_front(); + } + } + +private: + constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) { + if (_first == _last) { + return; + } + iterator pos(_pos.GetNonConstIterator()); + iterator first(_first.GetNonConstIterator()); + iterator last(_last.GetNonConstIterator()); + first->Unlink(std::addressof(*last)); + pos->SplicePrev(std::addressof(*first), std::addressof(*first)); + } +}; + +} // namespace impl + +template <class T, class Traits> +class IntrusiveList { + YUZU_NON_COPYABLE(IntrusiveList); + +private: + impl::IntrusiveListImpl m_impl; + +public: + template <bool Const> + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + template <bool Const> + class Iterator { + public: + friend class Common::IntrusiveList<T, Traits>; + + using ImplIterator = + std::conditional_t<Const, Common::impl::IntrusiveListImpl::const_iterator, + Common::impl::IntrusiveListImpl::iterator>; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveList::value_type; + using difference_type = typename IntrusiveList::difference_type; + using pointer = + std::conditional_t<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>; + using reference = + std::conditional_t<Const, IntrusiveList::const_reference, IntrusiveList::reference>; + + private: + ImplIterator m_iterator; + + private: + constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {} + + constexpr ImplIterator GetImplIterator() const { + return m_iterator; + } + + public: + constexpr bool operator==(const Iterator& rhs) const { + return m_iterator == rhs.m_iterator; + } + + constexpr pointer operator->() const { + return std::addressof(Traits::GetParent(*m_iterator)); + } + + constexpr reference operator*() const { + return Traits::GetParent(*m_iterator); + } + + constexpr Iterator& operator++() { + ++m_iterator; + return *this; + } + + constexpr Iterator& operator--() { + --m_iterator; + return *this; + } + + constexpr Iterator operator++(int) { + const Iterator it{*this}; + ++m_iterator; + return it; + } + + constexpr Iterator operator--(int) { + const Iterator it{*this}; + --m_iterator; + return it; + } + + constexpr operator Iterator<true>() const { + return Iterator<true>(m_iterator); + } + }; + +private: + static constexpr IntrusiveListNode& GetNode(reference ref) { + return Traits::GetNode(ref); + } + + static constexpr IntrusiveListNode const& GetNode(const_reference ref) { + return Traits::GetNode(ref); + } + + static constexpr reference GetParent(IntrusiveListNode& node) { + return Traits::GetParent(node); + } + + static constexpr const_reference GetParent(IntrusiveListNode const& node) { + return Traits::GetParent(node); + } + +public: + constexpr IntrusiveList() : m_impl() {} + + // Iterator accessors. + constexpr iterator begin() { + return iterator(m_impl.begin()); + } + + constexpr const_iterator begin() const { + return const_iterator(m_impl.begin()); + } + + constexpr iterator end() { + return iterator(m_impl.end()); + } + + constexpr const_iterator end() const { + return const_iterator(m_impl.end()); + } + + constexpr const_iterator cbegin() const { + return this->begin(); + } + + constexpr const_iterator cend() const { + return this->end(); + } + + constexpr reverse_iterator rbegin() { + return reverse_iterator(this->end()); + } + + constexpr const_reverse_iterator rbegin() const { + return const_reverse_iterator(this->end()); + } + + constexpr reverse_iterator rend() { + return reverse_iterator(this->begin()); + } + + constexpr const_reverse_iterator rend() const { + return const_reverse_iterator(this->begin()); + } + + constexpr const_reverse_iterator crbegin() const { + return this->rbegin(); + } + + constexpr const_reverse_iterator crend() const { + return this->rend(); + } + + constexpr iterator iterator_to(reference v) { + return iterator(m_impl.iterator_to(GetNode(v))); + } + + constexpr const_iterator iterator_to(const_reference v) const { + return const_iterator(m_impl.iterator_to(GetNode(v))); + } + + // Content management. + constexpr bool empty() const { + return m_impl.empty(); + } + + constexpr size_type size() const { + return m_impl.size(); + } + + constexpr reference back() { + return GetParent(m_impl.back()); + } + + constexpr const_reference back() const { + return GetParent(m_impl.back()); + } + + constexpr reference front() { + return GetParent(m_impl.front()); + } + + constexpr const_reference front() const { + return GetParent(m_impl.front()); + } + + constexpr void push_back(reference ref) { + m_impl.push_back(GetNode(ref)); + } + + constexpr void push_front(reference ref) { + m_impl.push_front(GetNode(ref)); + } + + constexpr void pop_back() { + m_impl.pop_back(); + } + + constexpr void pop_front() { + m_impl.pop_front(); + } + + constexpr iterator insert(const_iterator pos, reference ref) { + return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref))); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o) { + m_impl.splice(pos.GetImplIterator(), o.m_impl); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator()); + } + + constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first, + const_iterator last) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(), + last.GetImplIterator()); + } + + constexpr iterator erase(const_iterator pos) { + return iterator(m_impl.erase(pos.GetImplIterator())); + } + + constexpr void clear() { + m_impl.clear(); + } +}; + +template <auto T, class Derived = Common::impl::GetParentType<T>> +class IntrusiveListMemberTraits; + +template <class Parent, IntrusiveListNode Parent::*Member, class Derived> +class IntrusiveListMemberTraits<Member, Derived> { +public: + using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>; + +private: + friend class IntrusiveList<Derived, IntrusiveListMemberTraits>; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return parent.*Member; + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return parent.*Member; + } + + static Derived& GetParent(IntrusiveListNode& node) { + return Common::GetParentReference<Member, Derived>(std::addressof(node)); + } + + static Derived const& GetParent(IntrusiveListNode const& node) { + return Common::GetParentReference<Member, Derived>(std::addressof(node)); + } +}; + +template <auto T, class Derived = Common::impl::GetParentType<T>> +class IntrusiveListMemberTraitsByNonConstexprOffsetOf; + +template <class Parent, IntrusiveListNode Parent::*Member, class Derived> +class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> { +public: + using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>; + +private: + friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return parent.*Member; + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return parent.*Member; + } + + static Derived& GetParent(IntrusiveListNode& node) { + return *reinterpret_cast<Derived*>(reinterpret_cast<char*>(std::addressof(node)) - + GetOffset()); + } + + static Derived const& GetParent(IntrusiveListNode const& node) { + return *reinterpret_cast<const Derived*>( + reinterpret_cast<const char*>(std::addressof(node)) - GetOffset()); + } + + static uintptr_t GetOffset() { + return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived*>(0)->*Member)); + } +}; + +template <class Derived> +class IntrusiveListBaseNode : public IntrusiveListNode {}; + +template <class Derived> +class IntrusiveListBaseTraits { +public: + using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>; + +private: + friend class IntrusiveList<Derived, IntrusiveListBaseTraits>; + + static constexpr IntrusiveListNode& GetNode(Derived& parent) { + return static_cast<IntrusiveListNode&>( + static_cast<IntrusiveListBaseNode<Derived>&>(parent)); + } + + static constexpr IntrusiveListNode const& GetNode(Derived const& parent) { + return static_cast<const IntrusiveListNode&>( + static_cast<const IntrusiveListBaseNode<Derived>&>(parent)); + } + + static constexpr Derived& GetParent(IntrusiveListNode& node) { + return static_cast<Derived&>(static_cast<IntrusiveListBaseNode<Derived>&>(node)); + } + + static constexpr Derived const& GetParent(IntrusiveListNode const& node) { + return static_cast<const Derived&>( + static_cast<const IntrusiveListBaseNode<Derived>&>(node)); + } +}; + +} // namespace Common diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h index 93046615e..bc2940fa0 100644 --- a/src/common/intrusive_red_black_tree.h +++ b/src/common/intrusive_red_black_tree.h @@ -96,10 +96,6 @@ public: return m_node == rhs.m_node; } - constexpr bool operator!=(const Iterator& rhs) const { - return !(*this == rhs); - } - constexpr pointer operator->() const { return m_node; } @@ -242,19 +238,21 @@ public: template <typename T> concept HasRedBlackKeyType = requires { - { std::is_same<typename T::RedBlackKeyType, void>::value } -> std::convertible_to<bool>; -}; + { + std::is_same<typename T::RedBlackKeyType, void>::value + } -> std::convertible_to<bool>; + }; namespace impl { - template <typename T, typename Default> - consteval auto* GetRedBlackKeyType() { - if constexpr (HasRedBlackKeyType<T>) { - return static_cast<typename T::RedBlackKeyType*>(nullptr); - } else { - return static_cast<Default*>(nullptr); - } +template <typename T, typename Default> +consteval auto* GetRedBlackKeyType() { + if constexpr (HasRedBlackKeyType<T>) { + return static_cast<typename T::RedBlackKeyType*>(nullptr); + } else { + return static_cast<Default*>(nullptr); } +} } // namespace impl @@ -322,10 +320,6 @@ public: return m_impl == rhs.m_impl; } - constexpr bool operator!=(const Iterator& rhs) const { - return !(*this == rhs); - } - constexpr pointer operator->() const { return Traits::GetParent(std::addressof(*m_impl)); } diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 2a3bded40..6e8e8eb36 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -28,7 +28,7 @@ #ifdef _WIN32 #include "common/string_util.h" #endif -#include "common/threadsafe_queue.h" +#include "common/bounded_threadsafe_queue.h" namespace Common::Log { @@ -155,6 +155,26 @@ public: void EnableForStacktrace() override {} }; +#ifdef ANDROID +/** + * Backend that writes to the Android logcat + */ +class LogcatBackend : public Backend { +public: + explicit LogcatBackend() = default; + + ~LogcatBackend() override = default; + + void Write(const Entry& entry) override { + PrintMessageToLogcat(entry); + } + + void Flush() override {} + + void EnableForStacktrace() override {} +}; +#endif + bool initialization_in_progress_suppress_logging = true; /** @@ -204,11 +224,11 @@ public: void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, std::string&& message) { - if (!filter.CheckMessage(log_class, log_level)) + if (!filter.CheckMessage(log_class, log_level)) { return; - const Entry& entry = - CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)); - message_queue.Push(entry); + } + message_queue.EmplaceWait( + CreateEntry(log_class, log_level, filename, line_num, function, std::move(message))); } private: @@ -225,7 +245,7 @@ private: ForEachBackend([&entry](Backend& backend) { backend.Write(entry); }); }; while (!stop_token.stop_requested()) { - entry = message_queue.PopWait(stop_token); + message_queue.PopWait(entry, stop_token); if (entry.filename != nullptr) { write_logs(); } @@ -233,7 +253,7 @@ private: // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a // case where a system is repeatedly spamming logs even on close. int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100; - while (max_logs_to_write-- && message_queue.Pop(entry)) { + while (max_logs_to_write-- && message_queue.TryPop(entry)) { write_logs(); } }); @@ -260,6 +280,9 @@ private: lambda(static_cast<Backend&>(debugger_backend)); lambda(static_cast<Backend&>(color_console_backend)); lambda(static_cast<Backend&>(file_backend)); +#ifdef ANDROID + lambda(static_cast<Backend&>(lc_backend)); +#endif } static void Deleter(Impl* ptr) { @@ -272,8 +295,11 @@ private: DebuggerBackend debugger_backend{}; ColorConsoleBackend color_console_backend{}; FileBackend file_backend; +#ifdef ANDROID + LogcatBackend lc_backend{}; +#endif - MPSCQueue<Entry, true> message_queue{}; + MPSCQueue<Entry> message_queue{}; std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; std::jthread backend_thread; }; diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index a959acb74..c95909561 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -119,7 +119,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Service, NPNS) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ - SUB(Service, NVFlinger) \ + SUB(Service, Nvnflinger) \ SUB(Service, OLSC) \ SUB(Service, PCIE) \ SUB(Service, PCTL) \ diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 09398ea64..2c453177b 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -8,6 +8,10 @@ #include <windows.h> #endif +#ifdef ANDROID +#include <android/log.h> +#endif + #include "common/assert.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -106,4 +110,35 @@ void PrintColoredMessage(const Entry& entry) { #undef ESC #endif } + +void PrintMessageToLogcat(const Entry& entry) { +#ifdef ANDROID + const auto str = FormatLogMessage(entry); + + android_LogPriority android_log_priority; + switch (entry.log_level) { + case Level::Trace: + android_log_priority = ANDROID_LOG_VERBOSE; + break; + case Level::Debug: + android_log_priority = ANDROID_LOG_DEBUG; + break; + case Level::Info: + android_log_priority = ANDROID_LOG_INFO; + break; + case Level::Warning: + android_log_priority = ANDROID_LOG_WARN; + break; + case Level::Error: + android_log_priority = ANDROID_LOG_ERROR; + break; + case Level::Critical: + android_log_priority = ANDROID_LOG_FATAL; + break; + case Level::Count: + UNREACHABLE(); + } + __android_log_print(android_log_priority, "YuzuNative", "%s", str.c_str()); +#endif +} } // namespace Common::Log diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 0d0ec4370..68417420b 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -15,4 +15,6 @@ std::string FormatLogMessage(const Entry& entry); void PrintMessage(const Entry& entry); /// Prints the same message as `PrintMessage`, but colored according to the severity level. void PrintColoredMessage(const Entry& entry); +/// Formats and prints a log entry to the android logcat. +void PrintMessageToLogcat(const Entry& entry); } // namespace Common::Log diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 595c15ada..8356e3183 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -29,107 +29,107 @@ enum class Level : u8 { * filter.cpp. */ enum class Class : u8 { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - Core, ///< LLE emulation core - Core_ARM, ///< ARM CPU core - Core_Timing, ///< CoreTiming functions - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Debug_Emulated, ///< Debug messages from the emulated programs - Debug_GPU, ///< GPU debugging tools - Debug_Breakpoint, ///< Logging breakpoints and watchpoints - Debug_GDBStub, ///< GDB Stub - Kernel, ///< The HLE implementation of the CTR kernel - Kernel_SVC, ///< Kernel system calls - Service, ///< HLE implementation of system services. Each major service - ///< should have its own subclass. - Service_ACC, ///< The ACC (Accounts) service - Service_AM, ///< The AM (Applet manager) service - Service_AOC, ///< The AOC (AddOn Content) service - Service_APM, ///< The APM (Performance) service - Service_ARP, ///< The ARP service - Service_Audio, ///< The Audio (Audio control) service - Service_BCAT, ///< The BCAT service - Service_BGTC, ///< The BGTC (Background Task Controller) service - Service_BPC, ///< The BPC service - Service_BTDRV, ///< The Bluetooth driver service - Service_BTM, ///< The BTM service - Service_Capture, ///< The capture service - Service_ERPT, ///< The error reporting service - Service_ETicket, ///< The ETicket service - Service_EUPLD, ///< The error upload service - Service_Fatal, ///< The Fatal service - Service_FGM, ///< The FGM service - Service_Friend, ///< The friend service - Service_FS, ///< The FS (Filesystem) service - Service_GRC, ///< The game recording service - Service_HID, ///< The HID (Human interface device) service - Service_IRS, ///< The IRS service - Service_JIT, ///< The JIT service - Service_LBL, ///< The LBL (LCD backlight) service - Service_LDN, ///< The LDN (Local domain network) service - Service_LDR, ///< The loader service - Service_LM, ///< The LM (Logger) service - Service_Migration, ///< The migration service - Service_Mii, ///< The Mii service - Service_MM, ///< The MM (Multimedia) service - Service_MNPP, ///< The MNPP service - Service_NCM, ///< The NCM service - Service_NFC, ///< The NFC (Near-field communication) service - Service_NFP, ///< The NFP service - Service_NGCT, ///< The NGCT (No Good Content for Terra) service - Service_NIFM, ///< The NIFM (Network interface) service - Service_NIM, ///< The NIM service - Service_NOTIF, ///< The NOTIF (Notification) service - Service_NPNS, ///< The NPNS service - Service_NS, ///< The NS services - Service_NVDRV, ///< The NVDRV (Nvidia driver) service - Service_NVFlinger, ///< The NVFlinger service - Service_OLSC, ///< The OLSC service - Service_PCIE, ///< The PCIe service - Service_PCTL, ///< The PCTL (Parental control) service - Service_PCV, ///< The PCV service - Service_PM, ///< The PM service - Service_PREPO, ///< The PREPO (Play report) service - Service_PSC, ///< The PSC service - Service_PTM, ///< The PTM service - Service_SET, ///< The SET (Settings) service - Service_SM, ///< The SM (Service manager) service - Service_SPL, ///< The SPL service - Service_SSL, ///< The SSL service - Service_TCAP, ///< The TCAP service. - Service_Time, ///< The time service - Service_USB, ///< The USB (Universal Serial Bus) service - Service_VI, ///< The VI (Video interface) service - Service_WLAN, ///< The WLAN (Wireless local area network) service - HW, ///< Low-level hardware emulation - HW_Memory, ///< Memory-map and address translation - HW_LCD, ///< LCD register emulation - HW_GPU, ///< GPU control emulation - HW_AES, ///< AES engine emulation - IPC, ///< IPC interface - Frontend, ///< Emulator UI - Render, ///< Emulator video output and hardware acceleration - Render_Software, ///< Software renderer backend - Render_OpenGL, ///< OpenGL backend - Render_Vulkan, ///< Vulkan backend - Shader, ///< Shader recompiler - Shader_SPIRV, ///< Shader SPIR-V code generation - Shader_GLASM, ///< Shader GLASM code generation - Shader_GLSL, ///< Shader GLSL code generation - Audio, ///< Audio emulation - Audio_DSP, ///< The HLE implementation of the DSP - Audio_Sink, ///< Emulator audio output backend - Loader, ///< ROM loader - CheatEngine, ///< Memory manipulation and engine VM functions - Crypto, ///< Cryptographic engine/functions - Input, ///< Input emulation - Network, ///< Network emulation - WebService, ///< Interface to yuzu Web Services - Count ///< Total number of logging classes + Log, ///< Messages about the log system itself + Common, ///< Library routines + Common_Filesystem, ///< Filesystem interface library + Common_Memory, ///< Memory mapping and management functions + Core, ///< LLE emulation core + Core_ARM, ///< ARM CPU core + Core_Timing, ///< CoreTiming functions + Config, ///< Emulator configuration (including commandline) + Debug, ///< Debugging tools + Debug_Emulated, ///< Debug messages from the emulated programs + Debug_GPU, ///< GPU debugging tools + Debug_Breakpoint, ///< Logging breakpoints and watchpoints + Debug_GDBStub, ///< GDB Stub + Kernel, ///< The HLE implementation of the CTR kernel + Kernel_SVC, ///< Kernel system calls + Service, ///< HLE implementation of system services. Each major service + ///< should have its own subclass. + Service_ACC, ///< The ACC (Accounts) service + Service_AM, ///< The AM (Applet manager) service + Service_AOC, ///< The AOC (AddOn Content) service + Service_APM, ///< The APM (Performance) service + Service_ARP, ///< The ARP service + Service_Audio, ///< The Audio (Audio control) service + Service_BCAT, ///< The BCAT service + Service_BGTC, ///< The BGTC (Background Task Controller) service + Service_BPC, ///< The BPC service + Service_BTDRV, ///< The Bluetooth driver service + Service_BTM, ///< The BTM service + Service_Capture, ///< The capture service + Service_ERPT, ///< The error reporting service + Service_ETicket, ///< The ETicket service + Service_EUPLD, ///< The error upload service + Service_Fatal, ///< The Fatal service + Service_FGM, ///< The FGM service + Service_Friend, ///< The friend service + Service_FS, ///< The FS (Filesystem) service + Service_GRC, ///< The game recording service + Service_HID, ///< The HID (Human interface device) service + Service_IRS, ///< The IRS service + Service_JIT, ///< The JIT service + Service_LBL, ///< The LBL (LCD backlight) service + Service_LDN, ///< The LDN (Local domain network) service + Service_LDR, ///< The loader service + Service_LM, ///< The LM (Logger) service + Service_Migration, ///< The migration service + Service_Mii, ///< The Mii service + Service_MM, ///< The MM (Multimedia) service + Service_MNPP, ///< The MNPP service + Service_NCM, ///< The NCM service + Service_NFC, ///< The NFC (Near-field communication) service + Service_NFP, ///< The NFP service + Service_NGCT, ///< The NGCT (No Good Content for Terra) service + Service_NIFM, ///< The NIFM (Network interface) service + Service_NIM, ///< The NIM service + Service_NOTIF, ///< The NOTIF (Notification) service + Service_NPNS, ///< The NPNS service + Service_NS, ///< The NS services + Service_NVDRV, ///< The NVDRV (Nvidia driver) service + Service_Nvnflinger, ///< The Nvnflinger service + Service_OLSC, ///< The OLSC service + Service_PCIE, ///< The PCIe service + Service_PCTL, ///< The PCTL (Parental control) service + Service_PCV, ///< The PCV service + Service_PM, ///< The PM service + Service_PREPO, ///< The PREPO (Play report) service + Service_PSC, ///< The PSC service + Service_PTM, ///< The PTM service + Service_SET, ///< The SET (Settings) service + Service_SM, ///< The SM (Service manager) service + Service_SPL, ///< The SPL service + Service_SSL, ///< The SSL service + Service_TCAP, ///< The TCAP service. + Service_Time, ///< The time service + Service_USB, ///< The USB (Universal Serial Bus) service + Service_VI, ///< The VI (Video interface) service + Service_WLAN, ///< The WLAN (Wireless local area network) service + HW, ///< Low-level hardware emulation + HW_Memory, ///< Memory-map and address translation + HW_LCD, ///< LCD register emulation + HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation + IPC, ///< IPC interface + Frontend, ///< Emulator UI + Render, ///< Emulator video output and hardware acceleration + Render_Software, ///< Software renderer backend + Render_OpenGL, ///< OpenGL backend + Render_Vulkan, ///< Vulkan backend + Shader, ///< Shader recompiler + Shader_SPIRV, ///< Shader SPIR-V code generation + Shader_GLASM, ///< Shader GLASM code generation + Shader_GLSL, ///< Shader GLSL code generation + Audio, ///< Audio emulation + Audio_DSP, ///< The HLE implementation of the DSP + Audio_Sink, ///< Emulator audio output backend + Loader, ///< ROM loader + CheatEngine, ///< Memory manipulation and engine VM functions + Crypto, ///< Cryptographic engine/functions + Input, ///< Input emulation + Network, ///< Network emulation + WebService, ///< Interface to yuzu Web Services + Count ///< Total number of logging classes }; } // namespace Common::Log diff --git a/src/common/make_unique_for_overwrite.h b/src/common/make_unique_for_overwrite.h index c7413cf51..17f81bba4 100644 --- a/src/common/make_unique_for_overwrite.h +++ b/src/common/make_unique_for_overwrite.h @@ -9,17 +9,19 @@ namespace Common { template <class T> -requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() { + requires(!std::is_array_v<T>) +std::unique_ptr<T> make_unique_for_overwrite() { return std::unique_ptr<T>(new T); } template <class T> -requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) { + requires std::is_unbounded_array_v<T> +std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) { return std::unique_ptr<T>(new std::remove_extent_t<T>[n]); } template <class T, class... Args> -requires std::is_bounded_array_v<T> + requires std::is_bounded_array_v<T> void make_unique_for_overwrite(Args&&...) = delete; } // namespace Common diff --git a/src/common/overflow.h b/src/common/overflow.h new file mode 100644 index 000000000..44d8e7e73 --- /dev/null +++ b/src/common/overflow.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <type_traits> +#include "bit_cast.h" + +namespace Common { + +template <typename T> + requires(std::is_integral_v<T> && std::is_signed_v<T>) +inline T WrappingAdd(T lhs, T rhs) { + using U = std::make_unsigned_t<T>; + + U lhs_u = BitCast<U>(lhs); + U rhs_u = BitCast<U>(rhs); + + return BitCast<T>(lhs_u + rhs_u); +} + +} // namespace Common diff --git a/src/common/polyfill_ranges.h b/src/common/polyfill_ranges.h index ca44bfaef..512dbcbcb 100644 --- a/src/common/polyfill_ranges.h +++ b/src/common/polyfill_ranges.h @@ -18,9 +18,9 @@ namespace ranges { template <typename T> concept range = requires(T& t) { - begin(t); - end(t); -}; + begin(t); + end(t); + }; template <typename T> concept input_range = range<T>; @@ -421,7 +421,7 @@ struct generate_fn { } template <typename R, std::copy_constructible F> - requires std::invocable<F&> && ranges::output_range<R> + requires std::invocable<F&> && ranges::output_range<R> constexpr ranges::iterator_t<R> operator()(R&& r, F gen) const { return operator()(ranges::begin(r), ranges::end(r), std::move(gen)); } diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h index 5a8d1ce08..b5ef055db 100644 --- a/src/common/polyfill_thread.h +++ b/src/common/polyfill_thread.h @@ -11,6 +11,8 @@ #ifdef __cpp_lib_jthread +#include <chrono> +#include <condition_variable> #include <stop_token> #include <thread> @@ -21,23 +23,36 @@ void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { cv.wait(lock, token, std::move(pred)); } +template <typename Rep, typename Period> +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) { + std::condition_variable_any cv; + std::mutex m; + + // Perform the timed wait. + std::unique_lock lk{m}; + return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); }); +} + } // namespace Common #else #include <atomic> +#include <chrono> +#include <condition_variable> #include <functional> -#include <list> +#include <map> #include <memory> #include <mutex> #include <optional> #include <thread> #include <type_traits> +#include <utility> namespace std { namespace polyfill { -using stop_state_callbacks = list<function<void()>>; +using stop_state_callback = size_t; class stop_state { public: @@ -45,61 +60,69 @@ public: ~stop_state() = default; bool request_stop() { - stop_state_callbacks callbacks; + unique_lock lk{m_lock}; - { - scoped_lock lk{m_lock}; + if (m_stop_requested) { + // Already set, nothing to do. + return false; + } - if (m_stop_requested.load()) { - // Already set, nothing to do - return false; - } + // Mark stop requested. + m_stop_requested = true; - // Set as requested - m_stop_requested = true; + while (!m_callbacks.empty()) { + // Get an iterator to the first element. + const auto it = m_callbacks.begin(); - // Copy callback list - callbacks = m_callbacks; - } + // Move the callback function out of the map. + function<void()> f; + swap(it->second, f); + + // Erase the now-empty map element. + m_callbacks.erase(it); - for (auto callback : callbacks) { - callback(); + // Run the callback. + if (f) { + f(); + } } return true; } bool stop_requested() const { - return m_stop_requested.load(); + unique_lock lk{m_lock}; + return m_stop_requested; } - stop_state_callbacks::const_iterator insert_callback(function<void()> f) { - stop_state_callbacks::const_iterator ret{}; - bool should_run{}; - - { - scoped_lock lk{m_lock}; - should_run = m_stop_requested.load(); - m_callbacks.push_front(f); - ret = m_callbacks.begin(); - } + stop_state_callback insert_callback(function<void()> f) { + unique_lock lk{m_lock}; - if (should_run) { - f(); + if (m_stop_requested) { + // Stop already requested. Don't insert anything, + // just run the callback synchronously. + if (f) { + f(); + } + return 0; } + // Insert the callback. + stop_state_callback ret = ++m_next_callback; + m_callbacks.emplace(ret, move(f)); return ret; } - void remove_callback(stop_state_callbacks::const_iterator it) { - scoped_lock lk{m_lock}; - m_callbacks.erase(it); + void remove_callback(stop_state_callback cb) { + unique_lock lk{m_lock}; + m_callbacks.erase(cb); } private: - mutex m_lock; - atomic<bool> m_stop_requested; - stop_state_callbacks m_callbacks; + mutable recursive_mutex m_lock; + map<stop_state_callback, function<void()>> m_callbacks; + stop_state_callback m_next_callback{0}; + bool m_stop_requested{false}; }; } // namespace polyfill @@ -190,7 +213,7 @@ public: using callback_type = Callback; template <typename C> - requires constructible_from<Callback, C> + requires constructible_from<Callback, C> explicit stop_callback(const stop_token& st, C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) : m_stop_state(st.m_stop_state) { @@ -199,7 +222,7 @@ public: } } template <typename C> - requires constructible_from<Callback, C> + requires constructible_from<Callback, C> explicit stop_callback(stop_token&& st, C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) : m_stop_state(move(st.m_stop_state)) { @@ -209,7 +232,7 @@ public: } ~stop_callback() { if (m_stop_state && m_callback) { - m_stop_state->remove_callback(*m_callback); + m_stop_state->remove_callback(m_callback); } } @@ -220,7 +243,7 @@ public: private: shared_ptr<polyfill::stop_state> m_stop_state; - optional<polyfill::stop_state_callbacks::const_iterator> m_callback; + polyfill::stop_state_callback m_callback; }; template <typename Callback> @@ -318,6 +341,28 @@ void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { cv.wait(lock, [&] { return pred() || token.stop_requested(); }); } +template <typename Rep, typename Period> +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) { + if (token.stop_requested()) { + return false; + } + + bool stop_requested = false; + std::condition_variable cv; + std::mutex m; + + std::stop_callback cb(token, [&] { + // Wake up the waiting thread. + std::unique_lock lk{m}; + stop_requested = true; + cv.notify_one(); + }); + + // Perform the timed wait. + std::unique_lock lk{m}; + return !cv.wait_for(lk, rel_time, [&] { return stop_requested; }); +} + } // namespace Common #endif diff --git a/src/common/range_map.h b/src/common/range_map.h index 79c7ef547..ab73993e3 100644 --- a/src/common/range_map.h +++ b/src/common/range_map.h @@ -38,12 +38,12 @@ public: Map(address, address_end, null_value); } - [[nodiscard]] size_t GetContinousSizeFrom(KeyTBase address) const { + [[nodiscard]] size_t GetContinuousSizeFrom(KeyTBase address) const { const KeyT new_address = static_cast<KeyT>(address); if (new_address < 0) { return 0; } - return ContinousSizeInternal(new_address); + return ContinuousSizeInternal(new_address); } [[nodiscard]] ValueT GetValueAt(KeyT address) const { @@ -59,7 +59,7 @@ private: using IteratorType = typename MapType::iterator; using ConstIteratorType = typename MapType::const_iterator; - size_t ContinousSizeInternal(KeyT address) const { + size_t ContinuousSizeInternal(KeyT address) const { const auto it = GetFirstElementBeforeOrOn(address); if (it == container.end() || it->second == null_value) { return 0; diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h index 4c328ab44..416680d44 100644 --- a/src/common/ring_buffer.h +++ b/src/common/ring_buffer.h @@ -9,6 +9,7 @@ #include <cstddef> #include <cstring> #include <new> +#include <span> #include <type_traits> #include <vector> @@ -53,7 +54,7 @@ public: return push_count; } - std::size_t Push(const std::vector<T>& input) { + std::size_t Push(const std::span<T> input) { return Push(input.data(), input.size()); } diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h index 1245a5086..6fe907953 100644 --- a/src/common/scratch_buffer.h +++ b/src/common/scratch_buffer.h @@ -3,6 +3,9 @@ #pragma once +#include <iterator> + +#include "common/concepts.h" #include "common/make_unique_for_overwrite.h" namespace Common { @@ -16,6 +19,12 @@ namespace Common { template <typename T> class ScratchBuffer { public: + using iterator = T*; + using const_iterator = const T*; + using value_type = T; + using element_type = T; + using iterator_category = std::contiguous_iterator_tag; + ScratchBuffer() = default; explicit ScratchBuffer(size_t initial_capacity) @@ -23,6 +32,10 @@ public: buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {} ~ScratchBuffer() = default; + ScratchBuffer(const ScratchBuffer&) = delete; + ScratchBuffer& operator=(const ScratchBuffer&) = delete; + ScratchBuffer(ScratchBuffer&&) = default; + ScratchBuffer& operator=(ScratchBuffer&&) = default; /// This will only grow the buffer's capacity if size is greater than the current capacity. /// The previously held data will remain intact. @@ -86,6 +99,12 @@ public: return buffer_capacity; } + void swap(ScratchBuffer& other) noexcept { + std::swap(last_requested_size, other.last_requested_size); + std::swap(buffer_capacity, other.buffer_capacity); + std::swap(buffer, other.buffer); + } + private: size_t last_requested_size{}; size_t buffer_capacity{}; diff --git a/src/common/settings.cpp b/src/common/settings.cpp index b1a2aa8b2..66dffc9bf 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#if __cpp_lib_chrono >= 201907L +#include <chrono> +#endif #include <string_view> #include "common/assert.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" +#include "common/time_zone.h" namespace Settings { @@ -14,18 +18,23 @@ Values values; static bool configuring_global = true; std::string GetTimeZoneString() { - static constexpr std::array timezones{ - "auto", "default", "CET", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", - "EST", "EST5EDT", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", - "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan", - "Kwajalein", "Libya", "MET", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", - "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", - "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu", - }; - const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue()); - ASSERT(time_zone_index < timezones.size()); - return timezones[time_zone_index]; + ASSERT(time_zone_index < Common::TimeZone::GetTimeZoneStrings().size()); + + std::string location_name; + if (time_zone_index == 0) { // Auto +#if __cpp_lib_chrono >= 201907L + const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb(); + const std::chrono::time_zone* current_zone = time_zone_data.current_zone(); + std::string_view current_zone_name = current_zone->name(); + location_name = current_zone_name; +#else + location_name = Common::TimeZone::FindSystemTimeZone(); +#endif + } else { + location_name = Common::TimeZone::GetTimeZoneStrings()[time_zone_index]; + } + return location_name; } void LogSettings() { @@ -45,6 +54,7 @@ void LogSettings() { log_setting("System_LanguageIndex", values.language_index.GetValue()); log_setting("System_RegionIndex", values.region_index.GetValue()); log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); + log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue()); log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue()); log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue()); @@ -59,7 +69,10 @@ void LogSettings() { values.use_asynchronous_gpu_emulation.GetValue()); log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue()); log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); - log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); + log_setting("Renderer_AsyncASTC", values.async_astc.GetValue()); + log_setting("Renderer_AstcRecompression", values.astc_recompression.GetValue()); + log_setting("Renderer_UseVsync", values.vsync_mode.GetValue()); + log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue()); log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); @@ -76,6 +89,13 @@ void LogSettings() { log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue()); log_setting("Input_EnableMotion", values.motion_enabled.GetValue()); log_setting("Input_EnableVibration", values.vibration_enabled.GetValue()); + log_setting("Input_EnableTouch", values.touchscreen.enabled); + log_setting("Input_EnableMouse", values.mouse_enabled.GetValue()); + log_setting("Input_EnableKeyboard", values.keyboard_enabled.GetValue()); + log_setting("Input_EnableRingController", values.enable_ring_controller.GetValue()); + log_setting("Input_EnableIrSensor", values.enable_ir_sensor.GetValue()); + log_setting("Input_EnableCustomJoycon", values.enable_joycon_driver.GetValue()); + log_setting("Input_EnableCustomProController", values.enable_procon_driver.GetValue()); log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue()); } @@ -183,7 +203,7 @@ void RestoreGlobalState(bool is_powered_on) { // Core values.use_multi_core.SetGlobal(true); - values.use_extended_memory_layout.SetGlobal(true); + values.use_unsafe_extended_memory_layout.SetGlobal(true); // CPU values.cpu_accuracy.SetGlobal(true); @@ -197,9 +217,14 @@ void RestoreGlobalState(bool is_powered_on) { // Renderer values.fsr_sharpening_slider.SetGlobal(true); values.renderer_backend.SetGlobal(true); + values.async_presentation.SetGlobal(true); values.renderer_force_max_clock.SetGlobal(true); values.vulkan_device.SetGlobal(true); + values.fullscreen_mode.SetGlobal(true); values.aspect_ratio.SetGlobal(true); + values.resolution_setup.SetGlobal(true); + values.scaling_filter.SetGlobal(true); + values.anti_aliasing.SetGlobal(true); values.max_anisotropy.SetGlobal(true); values.use_speed_limit.SetGlobal(true); values.speed_limit.SetGlobal(true); @@ -208,15 +233,18 @@ void RestoreGlobalState(bool is_powered_on) { values.use_asynchronous_gpu_emulation.SetGlobal(true); values.nvdec_emulation.SetGlobal(true); values.accelerate_astc.SetGlobal(true); - values.use_vsync.SetGlobal(true); + values.async_astc.SetGlobal(true); + values.astc_recompression.SetGlobal(true); + values.use_reactive_flushing.SetGlobal(true); values.shader_backend.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true); - values.use_pessimistic_flushes.SetGlobal(true); values.use_vulkan_driver_pipeline_cache.SetGlobal(true); values.bg_red.SetGlobal(true); values.bg_green.SetGlobal(true); values.bg_blue.SetGlobal(true); + values.enable_compute_pipelines.SetGlobal(true); + values.use_video_framerate.SetGlobal(true); // System values.language_index.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 80b2eeabc..ae5ed93d8 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -16,6 +16,13 @@ namespace Settings { +enum class VSyncMode : u32 { + Immediate = 0, + Mailbox = 1, + FIFO = 2, + FIFORelaxed = 3, +}; + enum class RendererBackend : u32 { OpenGL = 0, Vulkan = 1, @@ -83,6 +90,12 @@ enum class AntiAliasing : u32 { LastAA = Smaa, }; +enum class AstcRecompression : u32 { + Uncompressed = 0, + Bc1 = 1, + Bc3 = 2, +}; + struct ResolutionScalingInfo { u32 up_scale{1}; u32 down_shift{0}; @@ -128,23 +141,25 @@ public: /** * Sets a default value, label, and setting value. * - * @param default_val Intial value of the setting, and default value of the setting + * @param default_val Initial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit Setting(const Type& default_val, const std::string& name) requires(!ranged) + explicit Setting(const Type& default_val, const std::string& name) + requires(!ranged) : value{default_val}, default_value{default_val}, label{name} {} virtual ~Setting() = default; /** * Sets a default value, minimum value, maximum value, and label. * - * @param default_val Intial value of the setting, and default value of the setting + * @param default_val Initial value of the setting, and default value of the setting * @param min_val Sets the minimum allowed value of the setting * @param max_val Sets the maximum allowed value of the setting * @param name Label for the setting */ explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) requires(ranged) + const std::string& name) + requires(ranged) : value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {} @@ -229,23 +244,25 @@ public: /** * Sets a default value, label, and setting value. * - * @param default_val Intial value of the setting, and default value of the setting + * @param default_val Initial value of the setting, and default value of the setting * @param name Label for the setting */ - explicit SwitchableSetting(const Type& default_val, const std::string& name) requires(!ranged) + explicit SwitchableSetting(const Type& default_val, const std::string& name) + requires(!ranged) : Setting<Type>{default_val, name} {} virtual ~SwitchableSetting() = default; /** * Sets a default value, minimum value, maximum value, and label. * - * @param default_val Intial value of the setting, and default value of the setting + * @param default_val Initial value of the setting, and default value of the setting * @param min_val Sets the minimum allowed value of the setting * @param max_val Sets the maximum allowed value of the setting * @param name Label for the setting */ explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val, - const std::string& name) requires(ranged) + const std::string& name) + requires(ranged) : Setting<Type, true>{default_val, min_val, max_val, name} {} /** @@ -384,7 +401,8 @@ struct Values { // Core SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; - SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"}; + SwitchableSetting<bool> use_unsafe_extended_memory_layout{false, + "use_unsafe_extended_memory_layout"}; // Cpu SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, @@ -418,6 +436,7 @@ struct Values { // Renderer SwitchableSetting<RendererBackend, true> renderer_backend{ RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"}; + SwitchableSetting<bool> async_presentation{false, "async_presentation"}; SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"}; Setting<bool> renderer_debug{false, "debug"}; Setting<bool> renderer_shader_feedback{false, "shader_feedback"}; @@ -449,14 +468,22 @@ struct Values { SwitchableSetting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"}; SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"}; SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"}; - SwitchableSetting<bool> use_vsync{true, "use_vsync"}; + SwitchableSetting<bool> async_astc{false, "async_astc"}; + Setting<VSyncMode, true> vsync_mode{VSyncMode::FIFO, VSyncMode::Immediate, + VSyncMode::FIFORelaxed, "use_vsync"}; + SwitchableSetting<bool> use_reactive_flushing{true, "use_reactive_flushing"}; SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLSL, ShaderBackend::GLSL, ShaderBackend::SPIRV, "shader_backend"}; SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; - SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"}; SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true, "use_vulkan_driver_pipeline_cache"}; + SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"}; + SwitchableSetting<AstcRecompression, true> astc_recompression{ + AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3, + "astc_recompression"}; + SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"}; + SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"}; SwitchableSetting<u8> bg_red{0, "bg_red"}; SwitchableSetting<u8> bg_green{0, "bg_green"}; @@ -483,6 +510,8 @@ struct Values { Setting<bool> enable_raw_input{false, "enable_raw_input"}; Setting<bool> controller_navigation{true, "controller_navigation"}; + Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"}; + Setting<bool> enable_procon_driver{false, "enable_procon_driver"}; SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"}; SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"}; @@ -496,9 +525,16 @@ struct Values { Setting<bool> tas_loop{false, "tas_loop"}; Setting<bool> mouse_panning{false, "mouse_panning"}; - Setting<u8, true> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; - Setting<bool> mouse_enabled{false, "mouse_enabled"}; + Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"}; + Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"}; + Setting<u8, true> mouse_panning_deadzone_x_counterweight{ + 0, 0, 100, "mouse_panning_deadzone_x_counterweight"}; + Setting<u8, true> mouse_panning_deadzone_y_counterweight{ + 0, 0, 100, "mouse_panning_deadzone_y_counterweight"}; + Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"}; + Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"}; + Setting<bool> mouse_enabled{false, "mouse_enabled"}; Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"}; Setting<bool> keyboard_enabled{false, "keyboard_enabled"}; @@ -518,6 +554,8 @@ struct Values { Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"}; Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"}; + Setting<bool> random_amiibo_id{false, "random_amiibo_id"}; + // Data Storage Setting<bool> use_virtual_sd{true, "use_virtual_sd"}; Setting<bool> gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/common/steady_clock.cpp b/src/common/steady_clock.cpp new file mode 100644 index 000000000..9415eed29 --- /dev/null +++ b/src/common/steady_clock.cpp @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#if defined(_WIN32) +#include <windows.h> +#else +#include <time.h> +#endif + +#include "common/steady_clock.h" + +namespace Common { + +#ifdef _WIN32 +static s64 WindowsQueryPerformanceFrequency() { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return frequency.QuadPart; +} + +static s64 WindowsQueryPerformanceCounter() { + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart; +} + +static s64 GetSystemTimeNS() { + // GetSystemTimePreciseAsFileTime returns the file time in 100ns units. + static constexpr s64 Multiplier = 100; + // Convert Windows epoch to Unix epoch. + static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL; + + FILETIME filetime; + GetSystemTimePreciseAsFileTime(&filetime); + return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) + + static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch); +} +#endif + +SteadyClock::time_point SteadyClock::Now() noexcept { +#if defined(_WIN32) + static const auto freq = WindowsQueryPerformanceFrequency(); + const auto counter = WindowsQueryPerformanceCounter(); + + // 10 MHz is a very common QPC frequency on modern PCs. + // Optimizing for this specific frequency can double the performance of + // this function by avoiding the expensive frequency conversion path. + static constexpr s64 TenMHz = 10'000'000; + + if (freq == TenMHz) [[likely]] { + static_assert(period::den % TenMHz == 0); + static constexpr s64 Multiplier = period::den / TenMHz; + return time_point{duration{counter * Multiplier}}; + } + + const auto whole = (counter / freq) * period::den; + const auto part = (counter % freq) * period::den / freq; + return time_point{duration{whole + part}}; +#elif defined(__APPLE__) + return time_point{duration{clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)}}; +#else + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}}; +#endif +} + +RealTimeClock::time_point RealTimeClock::Now() noexcept { +#if defined(_WIN32) + return time_point{duration{GetSystemTimeNS()}}; +#elif defined(__APPLE__) + return time_point{duration{clock_gettime_nsec_np(CLOCK_REALTIME)}}; +#else + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}}; +#endif +} + +}; // namespace Common diff --git a/src/common/steady_clock.h b/src/common/steady_clock.h new file mode 100644 index 000000000..dbd0e2513 --- /dev/null +++ b/src/common/steady_clock.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <chrono> + +#include "common/common_types.h" + +namespace Common { + +struct SteadyClock { + using rep = s64; + using period = std::nano; + using duration = std::chrono::nanoseconds; + using time_point = std::chrono::time_point<SteadyClock>; + + static constexpr bool is_steady = true; + + [[nodiscard]] static time_point Now() noexcept; +}; + +struct RealTimeClock { + using rep = s64; + using period = std::nano; + using duration = std::chrono::nanoseconds; + using time_point = std::chrono::time_point<RealTimeClock>; + + static constexpr bool is_steady = false; + + [[nodiscard]] static time_point Now() noexcept; +}; + +} // namespace Common diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index b26db4796..feab1653d 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -30,7 +30,7 @@ std::string ToUpper(std::string str) { return str; } -std::string StringFromBuffer(const std::vector<u8>& data) { +std::string StringFromBuffer(std::span<const u8> data) { return std::string(data.begin(), std::find(data.begin(), data.end(), '\0')); } @@ -125,18 +125,18 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st return result; } -std::string UTF16ToUTF8(const std::u16string& input) { +std::string UTF16ToUTF8(std::u16string_view input) { std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; - return convert.to_bytes(input); + return convert.to_bytes(input.data(), input.data() + input.size()); } -std::u16string UTF8ToUTF16(const std::string& input) { +std::u16string UTF8ToUTF16(std::string_view input) { std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; - return convert.from_bytes(input); + return convert.from_bytes(input.data(), input.data() + input.size()); } #ifdef _WIN32 -static std::wstring CPToUTF16(u32 code_page, const std::string& input) { +static std::wstring CPToUTF16(u32 code_page, std::string_view input) { const auto size = MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0); @@ -154,7 +154,7 @@ static std::wstring CPToUTF16(u32 code_page, const std::string& input) { return output; } -std::string UTF16ToUTF8(const std::wstring& input) { +std::string UTF16ToUTF8(std::wstring_view input) { const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr); if (size == 0) { @@ -172,7 +172,7 @@ std::string UTF16ToUTF8(const std::wstring& input) { return output; } -std::wstring UTF8ToUTF16W(const std::string& input) { +std::wstring UTF8ToUTF16W(std::string_view input) { return CPToUTF16(CP_UTF8, input); } diff --git a/src/common/string_util.h b/src/common/string_util.h index ce18a33cf..c351f1a0c 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -5,6 +5,7 @@ #pragma once #include <cstddef> +#include <span> #include <string> #include <vector> #include "common/common_types.h" @@ -17,7 +18,7 @@ namespace Common { /// Make a string uppercase [[nodiscard]] std::string ToUpper(std::string str); -[[nodiscard]] std::string StringFromBuffer(const std::vector<u8>& data); +[[nodiscard]] std::string StringFromBuffer(std::span<const u8> data); [[nodiscard]] std::string StripSpaces(const std::string& s); [[nodiscard]] std::string StripQuotes(const std::string& s); @@ -35,12 +36,12 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ [[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest); -[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input); -[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input); +[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); +[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); #ifdef _WIN32 -[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input); -[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str); +[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); +[[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str); #endif diff --git a/src/common/swap.h b/src/common/swap.h index 037b82781..085baaf9a 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -229,7 +229,7 @@ public: value = swap(swap() - 1); return old; } - // Comparaison + // Comparison // v == i bool operator==(const swapped_t& i) const { return swap() == i.swap(); @@ -368,7 +368,7 @@ public: // Member /** todo **/ - // Arithmetics + // Arithmetic template <typename S, typename T2, typename F2> friend S operator+(const S& p, const swapped_t v); @@ -384,7 +384,7 @@ public: template <typename S, typename T2, typename F2> friend S operator%(const S& p, const swapped_t v); - // Arithmetics + assignments + // Arithmetic + assignments template <typename S, typename T2, typename F2> friend S operator+=(const S& p, const swapped_t v); @@ -415,7 +415,7 @@ public: friend bool operator==(const S& p, const swapped_t v); }; -// Arithmetics +// Arithmetic template <typename S, typename T, typename F> S operator+(const S& i, const swap_struct_t<T, F> v) { return i + v.swap(); @@ -441,7 +441,7 @@ S operator%(const S& i, const swap_struct_t<T, F> v) { return i % v.swap(); } -// Arithmetics + assignments +// Arithmetic + assignments template <typename S, typename T, typename F> S& operator+=(S& i, const swap_struct_t<T, F> v) { i += v.swap(); @@ -465,7 +465,7 @@ S operator&(const swap_struct_t<T, F> v, const S& i) { return static_cast<S>(v.swap() & i); } -// Comparaison +// Comparison template <typename S, typename T, typename F> bool operator<(const S& p, const swap_struct_t<T, F> v) { return p < v.swap(); diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp index d26394359..91352912d 100644 --- a/src/common/telemetry.cpp +++ b/src/common/telemetry.cpp @@ -97,6 +97,7 @@ void AppendCPUInfo(FieldCollection& fc) { add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq); add_field("CPU_Extension_x64_POPCNT", caps.popcnt); add_field("CPU_Extension_x64_SHA", caps.sha); + add_field("CPU_Extension_x64_WAITPKG", caps.waitpkg); #else fc.AddField(FieldType::UserSystem, "CPU_Model", "Other"); #endif diff --git a/src/common/thread.h b/src/common/thread.h index 8ae169b4e..c6976fb6c 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -55,7 +55,7 @@ public: is_set = false; } - [[nodiscard]] bool IsSet() { + [[nodiscard]] bool IsSet() const { return is_set; } diff --git a/src/common/time_zone.cpp b/src/common/time_zone.cpp index 126836b01..d8d7896c6 100644 --- a/src/common/time_zone.cpp +++ b/src/common/time_zone.cpp @@ -2,14 +2,33 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <chrono> +#include <exception> #include <iomanip> #include <sstream> +#include <stdexcept> +#include <fmt/chrono.h> +#include <fmt/core.h> #include "common/logging/log.h" +#include "common/settings.h" #include "common/time_zone.h" namespace Common::TimeZone { +// Time zone strings +constexpr std::array timezones{ + "GMT", "GMT", "CET", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", + "EST", "EST5EDT", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", + "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan", + "Kwajalein", "Libya", "MET", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", + "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", + "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu", +}; + +const std::array<const char*, 46>& GetTimeZoneStrings() { + return timezones; +} + std::string GetDefaultTimeZone() { return "GMT"; } @@ -18,10 +37,7 @@ static std::string GetOsTimeZoneOffset() { const std::time_t t{std::time(nullptr)}; const std::tm tm{*std::localtime(&t)}; - std::stringstream ss; - ss << std::put_time(&tm, "%z"); // Get the current timezone offset, e.g. "-400", as a string - - return ss.str(); + return fmt::format("{:%z}", tm); } static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) { @@ -45,4 +61,43 @@ std::chrono::seconds GetCurrentOffsetSeconds() { return std::chrono::seconds{seconds}; } +// Key is [Hours * 100 + Minutes], multiplied by 100 if DST +const static std::map<s64, const char*> off_timezones = { + {530, "Asia/Calcutta"}, {930, "Australia/Darwin"}, {845, "Australia/Eucla"}, + {103000, "Australia/Adelaide"}, {1030, "Australia/Lord_Howe"}, {630, "Indian/Cocos"}, + {1245, "Pacific/Chatham"}, {134500, "Pacific/Chatham"}, {-330, "Canada/Newfoundland"}, + {-23000, "Canada/Newfoundland"}, {430, "Asia/Kabul"}, {330, "Asia/Tehran"}, + {43000, "Asia/Tehran"}, {545, "Asia/Kathmandu"}, {-930, "Asia/Marquesas"}, +}; + +std::string FindSystemTimeZone() { +#if defined(MINGW) + // MinGW has broken strftime -- https://sourceforge.net/p/mingw-w64/bugs/793/ + // e.g. fmt::format("{:%z}") -- returns "Eastern Daylight Time" when it should be "-0400" + return timezones[0]; +#else + const s64 seconds = static_cast<s64>(GetCurrentOffsetSeconds().count()); + + const s64 minutes = seconds / 60; + const s64 hours = minutes / 60; + + const s64 minutes_off = minutes - hours * 60; + + if (minutes_off != 0) { + const auto the_time = std::time(nullptr); + const struct std::tm& local = *std::localtime(&the_time); + const bool is_dst = local.tm_isdst != 0; + + const s64 tz_index = (hours * 100 + minutes_off) * (is_dst ? 100 : 1); + + try { + return off_timezones.at(tz_index); + } catch (std::out_of_range&) { + LOG_ERROR(Common, "Time zone {} not handled, defaulting to hour offset.", tz_index); + } + } + return fmt::format("Etc/GMT{:s}{:d}", hours > 0 ? "-" : "+", std::abs(hours)); +#endif +} + } // namespace Common::TimeZone diff --git a/src/common/time_zone.h b/src/common/time_zone.h index 99cae6ef2..f574d5c04 100644 --- a/src/common/time_zone.h +++ b/src/common/time_zone.h @@ -3,15 +3,21 @@ #pragma once +#include <array> #include <chrono> #include <string> namespace Common::TimeZone { +[[nodiscard]] const std::array<const char*, 46>& GetTimeZoneStrings(); + /// Gets the default timezone, i.e. "GMT" [[nodiscard]] std::string GetDefaultTimeZone(); /// Gets the offset of the current timezone (from the default), in seconds [[nodiscard]] std::chrono::seconds GetCurrentOffsetSeconds(); +/// Searches time zone offsets for the closest offset to the system time zone +[[nodiscard]] std::string FindSystemTimeZone(); + } // namespace Common::TimeZone diff --git a/src/common/tree.h b/src/common/tree.h index f77859209..f4fc43de3 100644 --- a/src/common/tree.h +++ b/src/common/tree.h @@ -103,12 +103,12 @@ concept IsRBEntry = CheckRBEntry<T>::value; template <typename T> concept HasRBEntry = requires(T& t, const T& ct) { - { t.GetRBEntry() } -> std::same_as<RBEntry<T>&>; - { ct.GetRBEntry() } -> std::same_as<const RBEntry<T>&>; -}; + { t.GetRBEntry() } -> std::same_as<RBEntry<T>&>; + { ct.GetRBEntry() } -> std::same_as<const RBEntry<T>&>; + }; template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> class RBHead { private: T* m_rbh_root = nullptr; @@ -130,90 +130,90 @@ public: }; template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr RBEntry<T>& RB_ENTRY(T* t) { return t->GetRBEntry(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr const RBEntry<T>& RB_ENTRY(const T* t) { return t->GetRBEntry(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr T* RB_LEFT(T* t) { return RB_ENTRY(t).Left(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr const T* RB_LEFT(const T* t) { return RB_ENTRY(t).Left(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr T* RB_RIGHT(T* t) { return RB_ENTRY(t).Right(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr const T* RB_RIGHT(const T* t) { return RB_ENTRY(t).Right(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr T* RB_PARENT(T* t) { return RB_ENTRY(t).Parent(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr const T* RB_PARENT(const T* t) { return RB_ENTRY(t).Parent(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET_LEFT(T* t, T* e) { RB_ENTRY(t).SetLeft(e); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET_RIGHT(T* t, T* e) { RB_ENTRY(t).SetRight(e); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET_PARENT(T* t, T* e) { RB_ENTRY(t).SetParent(e); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr bool RB_IS_BLACK(const T* t) { return RB_ENTRY(t).IsBlack(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr bool RB_IS_RED(const T* t) { return RB_ENTRY(t).IsRed(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> [[nodiscard]] constexpr RBColor RB_COLOR(const T* t) { return RB_ENTRY(t).Color(); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET_COLOR(T* t, RBColor c) { RB_ENTRY(t).SetColor(c); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET(T* elm, T* parent) { auto& rb_entry = RB_ENTRY(elm); rb_entry.SetParent(parent); @@ -223,14 +223,14 @@ constexpr void RB_SET(T* elm, T* parent) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_SET_BLACKRED(T* black, T* red) { RB_SET_COLOR(black, RBColor::RB_BLACK); RB_SET_COLOR(red, RBColor::RB_RED); } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_ROTATE_LEFT(RBHead<T>& head, T* elm, T*& tmp) { tmp = RB_RIGHT(elm); if (RB_SET_RIGHT(elm, RB_LEFT(tmp)); RB_RIGHT(elm) != nullptr) { @@ -252,7 +252,7 @@ constexpr void RB_ROTATE_LEFT(RBHead<T>& head, T* elm, T*& tmp) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_ROTATE_RIGHT(RBHead<T>& head, T* elm, T*& tmp) { tmp = RB_LEFT(elm); if (RB_SET_LEFT(elm, RB_RIGHT(tmp)); RB_LEFT(elm) != nullptr) { @@ -274,7 +274,7 @@ constexpr void RB_ROTATE_RIGHT(RBHead<T>& head, T* elm, T*& tmp) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_REMOVE_COLOR(RBHead<T>& head, T* parent, T* elm) { T* tmp; while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head.Root()) { @@ -358,7 +358,7 @@ constexpr void RB_REMOVE_COLOR(RBHead<T>& head, T* parent, T* elm) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_REMOVE(RBHead<T>& head, T* elm) { T* child = nullptr; T* parent = nullptr; @@ -451,7 +451,7 @@ constexpr T* RB_REMOVE(RBHead<T>& head, T* elm) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr void RB_INSERT_COLOR(RBHead<T>& head, T* elm) { T *parent = nullptr, *tmp = nullptr; while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) { @@ -499,7 +499,7 @@ constexpr void RB_INSERT_COLOR(RBHead<T>& head, T* elm) { } template <typename T, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_INSERT(RBHead<T>& head, T* elm, Compare cmp) { T* parent = nullptr; T* tmp = head.Root(); @@ -534,7 +534,7 @@ constexpr T* RB_INSERT(RBHead<T>& head, T* elm, Compare cmp) { } template <typename T, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_FIND(RBHead<T>& head, T* elm, Compare cmp) { T* tmp = head.Root(); @@ -553,7 +553,7 @@ constexpr T* RB_FIND(RBHead<T>& head, T* elm, Compare cmp) { } template <typename T, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_NFIND(RBHead<T>& head, T* elm, Compare cmp) { T* tmp = head.Root(); T* res = nullptr; @@ -574,7 +574,7 @@ constexpr T* RB_NFIND(RBHead<T>& head, T* elm, Compare cmp) { } template <typename T, typename U, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_FIND_KEY(RBHead<T>& head, const U& key, Compare cmp) { T* tmp = head.Root(); @@ -593,7 +593,7 @@ constexpr T* RB_FIND_KEY(RBHead<T>& head, const U& key, Compare cmp) { } template <typename T, typename U, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_NFIND_KEY(RBHead<T>& head, const U& key, Compare cmp) { T* tmp = head.Root(); T* res = nullptr; @@ -614,7 +614,7 @@ constexpr T* RB_NFIND_KEY(RBHead<T>& head, const U& key, Compare cmp) { } template <typename T, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_FIND_EXISTING(RBHead<T>& head, T* elm, Compare cmp) { T* tmp = head.Root(); @@ -631,7 +631,7 @@ constexpr T* RB_FIND_EXISTING(RBHead<T>& head, T* elm, Compare cmp) { } template <typename T, typename U, typename Compare> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_FIND_EXISTING_KEY(RBHead<T>& head, const U& key, Compare cmp) { T* tmp = head.Root(); @@ -648,7 +648,7 @@ constexpr T* RB_FIND_EXISTING_KEY(RBHead<T>& head, const U& key, Compare cmp) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_NEXT(T* elm) { if (RB_RIGHT(elm)) { elm = RB_RIGHT(elm); @@ -669,7 +669,7 @@ constexpr T* RB_NEXT(T* elm) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_PREV(T* elm) { if (RB_LEFT(elm)) { elm = RB_LEFT(elm); @@ -690,7 +690,7 @@ constexpr T* RB_PREV(T* elm) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_MIN(RBHead<T>& head) { T* tmp = head.Root(); T* parent = nullptr; @@ -704,7 +704,7 @@ constexpr T* RB_MIN(RBHead<T>& head) { } template <typename T> -requires HasRBEntry<T> + requires HasRBEntry<T> constexpr T* RB_MAX(RBHead<T>& head) { T* tmp = head.Root(); T* parent = nullptr; diff --git a/src/common/typed_address.h b/src/common/typed_address.h new file mode 100644 index 000000000..64f4a07c2 --- /dev/null +++ b/src/common/typed_address.h @@ -0,0 +1,315 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <compare> +#include <type_traits> +#include <fmt/format.h> + +#include "common/common_types.h" + +namespace Common { + +template <bool Virtual, typename T> +class TypedAddress { +public: + // Constructors. + constexpr inline TypedAddress() : m_address(0) {} + constexpr inline TypedAddress(uint64_t a) : m_address(a) {} + + template <typename U> + constexpr inline explicit TypedAddress(const U* ptr) + : m_address(reinterpret_cast<uint64_t>(ptr)) {} + + // Copy constructor. + constexpr inline TypedAddress(const TypedAddress& rhs) = default; + + // Assignment operator. + constexpr inline TypedAddress& operator=(const TypedAddress& rhs) = default; + + // Arithmetic operators. + template <typename I> + constexpr inline TypedAddress operator+(I rhs) const { + static_assert(std::is_integral_v<I>); + return m_address + rhs; + } + + constexpr inline TypedAddress operator+(TypedAddress rhs) const { + return m_address + rhs.m_address; + } + + constexpr inline TypedAddress operator++() { + return ++m_address; + } + + constexpr inline TypedAddress operator++(int) { + return m_address++; + } + + template <typename I> + constexpr inline TypedAddress operator-(I rhs) const { + static_assert(std::is_integral_v<I>); + return m_address - rhs; + } + + constexpr inline ptrdiff_t operator-(TypedAddress rhs) const { + return m_address - rhs.m_address; + } + + constexpr inline TypedAddress operator--() { + return --m_address; + } + + constexpr inline TypedAddress operator--(int) { + return m_address--; + } + + template <typename I> + constexpr inline TypedAddress operator+=(I rhs) { + static_assert(std::is_integral_v<I>); + m_address += rhs; + return *this; + } + + template <typename I> + constexpr inline TypedAddress operator-=(I rhs) { + static_assert(std::is_integral_v<I>); + m_address -= rhs; + return *this; + } + + // Logical operators. + constexpr inline uint64_t operator&(uint64_t mask) const { + return m_address & mask; + } + + constexpr inline uint64_t operator|(uint64_t mask) const { + return m_address | mask; + } + + template <typename I> + constexpr inline TypedAddress operator|=(I rhs) { + static_assert(std::is_integral_v<I>); + m_address |= rhs; + return *this; + } + + constexpr inline uint64_t operator<<(int shift) const { + return m_address << shift; + } + + constexpr inline uint64_t operator>>(int shift) const { + return m_address >> shift; + } + + template <typename U> + constexpr inline size_t operator/(U size) const { + return m_address / size; + } + + constexpr explicit operator bool() const { + return m_address != 0; + } + + // constexpr inline uint64_t operator%(U align) const { return m_address % align; } + + // Comparison operators. + constexpr bool operator==(const TypedAddress&) const = default; + constexpr auto operator<=>(const TypedAddress&) const = default; + + // For convenience, also define comparison operators versus uint64_t. + constexpr inline bool operator==(uint64_t rhs) const { + return m_address == rhs; + } + + // Allow getting the address explicitly, for use in accessors. + constexpr inline uint64_t GetValue() const { + return m_address; + } + +private: + uint64_t m_address{}; +}; + +struct PhysicalAddressTag {}; +struct VirtualAddressTag {}; +struct ProcessAddressTag {}; + +using PhysicalAddress = TypedAddress<false, PhysicalAddressTag>; +using VirtualAddress = TypedAddress<true, VirtualAddressTag>; +using ProcessAddress = TypedAddress<true, ProcessAddressTag>; + +// Define accessors. +template <typename T> +concept IsTypedAddress = std::same_as<T, PhysicalAddress> || std::same_as<T, VirtualAddress> || + std::same_as<T, ProcessAddress>; + +template <typename T> +constexpr inline T Null = [] { + if constexpr (std::is_same<T, uint64_t>::value) { + return 0; + } else { + static_assert(std::is_same<T, PhysicalAddress>::value || + std::is_same<T, VirtualAddress>::value || + std::is_same<T, ProcessAddress>::value); + return T(0); + } +}(); + +// Basic type validations. +static_assert(sizeof(PhysicalAddress) == sizeof(uint64_t)); +static_assert(sizeof(VirtualAddress) == sizeof(uint64_t)); +static_assert(sizeof(ProcessAddress) == sizeof(uint64_t)); + +static_assert(std::is_trivially_copyable_v<PhysicalAddress>); +static_assert(std::is_trivially_copyable_v<VirtualAddress>); +static_assert(std::is_trivially_copyable_v<ProcessAddress>); + +static_assert(std::is_trivially_copy_constructible_v<PhysicalAddress>); +static_assert(std::is_trivially_copy_constructible_v<VirtualAddress>); +static_assert(std::is_trivially_copy_constructible_v<ProcessAddress>); + +static_assert(std::is_trivially_move_constructible_v<PhysicalAddress>); +static_assert(std::is_trivially_move_constructible_v<VirtualAddress>); +static_assert(std::is_trivially_move_constructible_v<ProcessAddress>); + +static_assert(std::is_trivially_copy_assignable_v<PhysicalAddress>); +static_assert(std::is_trivially_copy_assignable_v<VirtualAddress>); +static_assert(std::is_trivially_copy_assignable_v<ProcessAddress>); + +static_assert(std::is_trivially_move_assignable_v<PhysicalAddress>); +static_assert(std::is_trivially_move_assignable_v<VirtualAddress>); +static_assert(std::is_trivially_move_assignable_v<ProcessAddress>); + +static_assert(std::is_trivially_destructible_v<PhysicalAddress>); +static_assert(std::is_trivially_destructible_v<VirtualAddress>); +static_assert(std::is_trivially_destructible_v<ProcessAddress>); + +static_assert(Null<uint64_t> == 0); +static_assert(Null<PhysicalAddress> == Null<uint64_t>); +static_assert(Null<VirtualAddress> == Null<uint64_t>); +static_assert(Null<ProcessAddress> == Null<uint64_t>); + +// Constructor/assignment validations. +static_assert([] { + const PhysicalAddress a(5); + PhysicalAddress b(a); + return b; +}() == PhysicalAddress(5)); +static_assert([] { + const PhysicalAddress a(5); + PhysicalAddress b(10); + b = a; + return b; +}() == PhysicalAddress(5)); + +// Arithmetic validations. +static_assert(PhysicalAddress(10) + 5 == PhysicalAddress(15)); +static_assert(PhysicalAddress(10) - 5 == PhysicalAddress(5)); +static_assert([] { + PhysicalAddress v(10); + v += 5; + return v; +}() == PhysicalAddress(15)); +static_assert([] { + PhysicalAddress v(10); + v -= 5; + return v; +}() == PhysicalAddress(5)); +static_assert(PhysicalAddress(10)++ == PhysicalAddress(10)); +static_assert(++PhysicalAddress(10) == PhysicalAddress(11)); +static_assert(PhysicalAddress(10)-- == PhysicalAddress(10)); +static_assert(--PhysicalAddress(10) == PhysicalAddress(9)); + +// Logical validations. +static_assert((PhysicalAddress(0b11111111) >> 1) == 0b01111111); +static_assert((PhysicalAddress(0b10101010) >> 1) == 0b01010101); +static_assert((PhysicalAddress(0b11111111) << 1) == 0b111111110); +static_assert((PhysicalAddress(0b01010101) << 1) == 0b10101010); +static_assert((PhysicalAddress(0b11111111) & 0b01010101) == 0b01010101); +static_assert((PhysicalAddress(0b11111111) & 0b10101010) == 0b10101010); +static_assert((PhysicalAddress(0b01010101) & 0b10101010) == 0b00000000); +static_assert((PhysicalAddress(0b00000000) | 0b01010101) == 0b01010101); +static_assert((PhysicalAddress(0b11111111) | 0b01010101) == 0b11111111); +static_assert((PhysicalAddress(0b10101010) | 0b01010101) == 0b11111111); + +// Comparisons. +static_assert(PhysicalAddress(0) == PhysicalAddress(0)); +static_assert(PhysicalAddress(0) != PhysicalAddress(1)); +static_assert(PhysicalAddress(0) < PhysicalAddress(1)); +static_assert(PhysicalAddress(0) <= PhysicalAddress(1)); +static_assert(PhysicalAddress(1) > PhysicalAddress(0)); +static_assert(PhysicalAddress(1) >= PhysicalAddress(0)); + +static_assert(!(PhysicalAddress(0) == PhysicalAddress(1))); +static_assert(!(PhysicalAddress(0) != PhysicalAddress(0))); +static_assert(!(PhysicalAddress(1) < PhysicalAddress(0))); +static_assert(!(PhysicalAddress(1) <= PhysicalAddress(0))); +static_assert(!(PhysicalAddress(0) > PhysicalAddress(1))); +static_assert(!(PhysicalAddress(0) >= PhysicalAddress(1))); + +} // namespace Common + +template <bool Virtual, typename T> +constexpr inline uint64_t GetInteger(Common::TypedAddress<Virtual, T> address) { + return address.GetValue(); +} + +template <> +struct fmt::formatter<Common::PhysicalAddress> { + constexpr auto parse(fmt::format_parse_context& ctx) { + return ctx.begin(); + } + template <typename FormatContext> + auto format(const Common::PhysicalAddress& addr, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue())); + } +}; + +template <> +struct fmt::formatter<Common::ProcessAddress> { + constexpr auto parse(fmt::format_parse_context& ctx) { + return ctx.begin(); + } + template <typename FormatContext> + auto format(const Common::ProcessAddress& addr, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue())); + } +}; + +template <> +struct fmt::formatter<Common::VirtualAddress> { + constexpr auto parse(fmt::format_parse_context& ctx) { + return ctx.begin(); + } + template <typename FormatContext> + auto format(const Common::VirtualAddress& addr, FormatContext& ctx) { + return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue())); + } +}; + +namespace std { + +template <> +struct hash<Common::PhysicalAddress> { + size_t operator()(const Common::PhysicalAddress& k) const noexcept { + return k.GetValue(); + } +}; + +template <> +struct hash<Common::ProcessAddress> { + size_t operator()(const Common::ProcessAddress& k) const noexcept { + return k.GetValue(); + } +}; + +template <> +struct hash<Common::VirtualAddress> { + size_t operator()(const Common::VirtualAddress& k) const noexcept { + return k.GetValue(); + } +}; + +} // namespace std diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp index 89e1ed225..035df7fe0 100644 --- a/src/common/uuid.cpp +++ b/src/common/uuid.cpp @@ -48,7 +48,7 @@ std::array<u8, 0x10> ConstructFromRawString(std::string_view raw_string) { } std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) { - std::array<u8, 0x10> uuid; + std::array<u8, 0x10> uuid{}; size_t i = 0; diff --git a/src/common/vector_math.h b/src/common/vector_math.h index e62eeea2e..b4885835d 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -259,6 +259,20 @@ public: return *this; } + void RotateFromOrigin(float roll, float pitch, float yaw) { + float temp = y; + y = std::cos(roll) * y - std::sin(roll) * z; + z = std::sin(roll) * temp + std::cos(roll) * z; + + temp = x; + x = std::cos(pitch) * x + std::sin(pitch) * z; + z = -std::sin(pitch) * temp + std::cos(pitch) * z; + + temp = x; + x = std::cos(yaw) * x - std::sin(yaw) * y; + y = std::sin(yaw) * temp + std::cos(yaw) * y; + } + [[nodiscard]] constexpr T Length2() const { return x * x + y * y + z * z; } @@ -348,9 +362,7 @@ public: // _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all // component names (x<->r) and permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - [[nodiscard]] constexpr Vec2<T> name() const { \ - return Vec2<T>(a, b); \ - } + [[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); } #define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \ _DEFINE_SWIZZLER2(a, b, a##b); \ _DEFINE_SWIZZLER2(a, b, a2##b2); \ @@ -543,9 +555,7 @@ public: // DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and // permutations (xy<->yx) #define _DEFINE_SWIZZLER2(a, b, name) \ - [[nodiscard]] constexpr Vec2<T> name() const { \ - return Vec2<T>(a, b); \ - } + [[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); } #define DEFINE_SWIZZLER2_COMP1(a, a2) \ _DEFINE_SWIZZLER2(a, a, a##a); \ _DEFINE_SWIZZLER2(a, a, a2##a2) @@ -570,9 +580,7 @@ public: #undef _DEFINE_SWIZZLER2 #define _DEFINE_SWIZZLER3(a, b, c, name) \ - [[nodiscard]] constexpr Vec3<T> name() const { \ - return Vec3<T>(a, b, c); \ - } + [[nodiscard]] constexpr Vec3<T> name() const { return Vec3<T>(a, b, c); } #define DEFINE_SWIZZLER3_COMP1(a, a2) \ _DEFINE_SWIZZLER3(a, a, a, a##a##a); \ _DEFINE_SWIZZLER3(a, a, a, a2##a2##a2) @@ -641,8 +649,8 @@ template <typename T> // linear interpolation via float: 0.0=begin, 1.0=end template <typename X> -[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) - Lerp(const X& begin, const X& end, const float t) { +[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, + const float t) { return begin * (1.f - t) + end * t; } diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index ae07f2811..dc0dcbd68 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -1,96 +1,76 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/uint128.h" +#include "common/steady_clock.h" #include "common/wall_clock.h" #ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" #include "common/x64/native_clock.h" +#include "common/x64/rdtsc.h" #endif namespace Common { -using base_timer = std::chrono::steady_clock; -using base_time_point = std::chrono::time_point<base_timer>; - class StandardWallClock final : public WallClock { public: - explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_) - : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) { - start_time = base_timer::now(); + explicit StandardWallClock() : start_time{SteadyClock::Now()} {} + + std::chrono::nanoseconds GetTimeNS() const override { + return SteadyClock::Now() - start_time; + } + + std::chrono::microseconds GetTimeUS() const override { + return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den); } - std::chrono::nanoseconds GetTimeNS() override { - base_time_point current = base_timer::now(); - auto elapsed = current - start_time; - return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed); + std::chrono::milliseconds GetTimeMS() const override { + return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den); } - std::chrono::microseconds GetTimeUS() override { - base_time_point current = base_timer::now(); - auto elapsed = current - start_time; - return std::chrono::duration_cast<std::chrono::microseconds>(elapsed); + u64 GetCNTPCT() const override { + return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; } - std::chrono::milliseconds GetTimeMS() override { - base_time_point current = base_timer::now(); - auto elapsed = current - start_time; - return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed); + u64 GetGPUTick() const override { + return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; } - u64 GetClockCycles() override { - std::chrono::nanoseconds time_now = GetTimeNS(); - const u128 temporary = - Common::Multiply64Into128(time_now.count(), emulated_clock_frequency); - return Common::Divide128On32(temporary, 1000000000).first; + u64 GetHostTicksNow() const override { + return static_cast<u64>(SteadyClock::Now().time_since_epoch().count()); } - u64 GetCPUCycles() override { - std::chrono::nanoseconds time_now = GetTimeNS(); - const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency); - return Common::Divide128On32(temporary, 1000000000).first; + u64 GetHostTicksElapsed() const override { + return static_cast<u64>(GetTimeNS().count()); } - void Pause([[maybe_unused]] bool is_paused) override { - // Do nothing in this clock type. + bool IsNative() const override { + return false; } private: - base_time_point start_time; + SteadyClock::time_point start_time; }; +std::unique_ptr<WallClock> CreateOptimalClock() { #ifdef ARCHITECTURE_x86_64 - -std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, - u64 emulated_clock_frequency) { const auto& caps = GetCPUCaps(); - u64 rtsc_frequency = 0; - if (caps.invariant_tsc) { - rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency(); - } - // Fallback to StandardWallClock if the hardware TSC does not have the precision greater than: - // - A nanosecond - // - The emulated CPU frequency - // - The emulated clock counter frequency (CNTFRQ) - if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency || - rtsc_frequency <= emulated_clock_frequency) { - return std::make_unique<StandardWallClock>(emulated_cpu_frequency, - emulated_clock_frequency); + if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) { + return std::make_unique<X64::NativeClock>(caps.tsc_frequency); } else { - return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency, - rtsc_frequency); + // Fallback to StandardWallClock if the hardware TSC + // - Is not invariant + // - Is not more precise than GPUTickFreq + return std::make_unique<StandardWallClock>(); } -} - #else - -std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, - u64 emulated_clock_frequency) { - return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency); + return std::make_unique<StandardWallClock>(); +#endif } -#endif +std::unique_ptr<WallClock> CreateStandardWallClock() { + return std::make_unique<StandardWallClock>(); +} } // namespace Common diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 828a523a8..f45d3d8c5 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.h @@ -5,6 +5,7 @@ #include <chrono> #include <memory> +#include <ratio> #include "common/common_types.h" @@ -12,47 +13,82 @@ namespace Common { class WallClock { public: - static constexpr u64 NS_RATIO = 1'000'000'000; - static constexpr u64 US_RATIO = 1'000'000; - static constexpr u64 MS_RATIO = 1'000; + static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz + static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz + static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz virtual ~WallClock() = default; - /// Returns current wall time in nanoseconds - [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; + /// @returns The time in nanoseconds since the construction of this clock. + virtual std::chrono::nanoseconds GetTimeNS() const = 0; - /// Returns current wall time in microseconds - [[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0; + /// @returns The time in microseconds since the construction of this clock. + virtual std::chrono::microseconds GetTimeUS() const = 0; - /// Returns current wall time in milliseconds - [[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0; + /// @returns The time in milliseconds since the construction of this clock. + virtual std::chrono::milliseconds GetTimeMS() const = 0; - /// Returns current wall time in emulated clock cycles - [[nodiscard]] virtual u64 GetClockCycles() = 0; + /// @returns The guest CNTPCT ticks since the construction of this clock. + virtual u64 GetCNTPCT() const = 0; - /// Returns current wall time in emulated cpu cycles - [[nodiscard]] virtual u64 GetCPUCycles() = 0; + /// @returns The guest GPU ticks since the construction of this clock. + virtual u64 GetGPUTick() const = 0; - virtual void Pause(bool is_paused) = 0; + /// @returns The raw host timer ticks since an indeterminate epoch. + virtual u64 GetHostTicksNow() const = 0; - /// Tells if the wall clock, uses the host CPU's hardware clock - [[nodiscard]] bool IsNative() const { - return is_native; + /// @returns The raw host timer ticks since the construction of this clock. + virtual u64 GetHostTicksElapsed() const = 0; + + /// @returns Whether the clock directly uses the host's hardware clock. + virtual bool IsNative() const = 0; + + static inline u64 NSToCNTPCT(u64 ns) { + return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + } + + static inline u64 NSToGPUTick(u64 ns) { + return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den; + } + + // Cycle Timing + + static inline u64 CPUTickToNS(u64 cpu_tick) { + return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den; + } + + static inline u64 CPUTickToUS(u64 cpu_tick) { + return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den; + } + + static inline u64 CPUTickToCNTPCT(u64 cpu_tick) { + return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den; + } + + static inline u64 CPUTickToGPUTick(u64 cpu_tick) { + return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den; } protected: - explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_) - : emulated_cpu_frequency{emulated_cpu_frequency_}, - emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {} + using NsRatio = std::nano; + using UsRatio = std::micro; + using MsRatio = std::milli; + + using NsToUsRatio = std::ratio_divide<std::nano, std::micro>; + using NsToMsRatio = std::ratio_divide<std::nano, std::milli>; + using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>; + using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>; - u64 emulated_cpu_frequency; - u64 emulated_clock_frequency; + // Cycle Timing -private: - bool is_native; + using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>; + using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>; + using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>; + using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>; }; -[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, - u64 emulated_clock_frequency); +std::unique_ptr<WallClock> CreateOptimalClock(); + +std::unique_ptr<WallClock> CreateStandardWallClock(); } // namespace Common diff --git a/src/common/windows/timer_resolution.cpp b/src/common/windows/timer_resolution.cpp new file mode 100644 index 000000000..29c6e5c7e --- /dev/null +++ b/src/common/windows/timer_resolution.cpp @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <windows.h> + +#include "common/windows/timer_resolution.h" + +extern "C" { +// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtQueryTimerResolution.html +NTSYSAPI LONG NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution, + PULONG CurrentResolution); + +// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html +NTSYSAPI LONG NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, + PULONG CurrentResolution); + +// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FThread%2FNtDelayExecution.html +NTSYSAPI LONG NTAPI NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval); +} + +// Defines for compatibility with older Windows 10 SDKs. + +#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED +#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x1 +#endif +#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION +#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4 +#endif + +namespace Common::Windows { + +namespace { + +using namespace std::chrono; + +constexpr nanoseconds ToNS(ULONG hundred_ns) { + return nanoseconds{hundred_ns * 100}; +} + +constexpr ULONG ToHundredNS(nanoseconds ns) { + return static_cast<ULONG>(ns.count()) / 100; +} + +struct TimerResolution { + std::chrono::nanoseconds minimum; + std::chrono::nanoseconds maximum; + std::chrono::nanoseconds current; +}; + +TimerResolution GetTimerResolution() { + ULONG MinimumTimerResolution; + ULONG MaximumTimerResolution; + ULONG CurrentTimerResolution; + NtQueryTimerResolution(&MinimumTimerResolution, &MaximumTimerResolution, + &CurrentTimerResolution); + return { + .minimum{ToNS(MinimumTimerResolution)}, + .maximum{ToNS(MaximumTimerResolution)}, + .current{ToNS(CurrentTimerResolution)}, + }; +} + +void SetHighQoS() { + // https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service + PROCESS_POWER_THROTTLING_STATE PowerThrottling{ + .Version{PROCESS_POWER_THROTTLING_CURRENT_VERSION}, + .ControlMask{PROCESS_POWER_THROTTLING_EXECUTION_SPEED | + PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION}, + .StateMask{}, + }; + SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling, + sizeof(PROCESS_POWER_THROTTLING_STATE)); +} + +} // Anonymous namespace + +nanoseconds GetMinimumTimerResolution() { + return GetTimerResolution().minimum; +} + +nanoseconds GetMaximumTimerResolution() { + return GetTimerResolution().maximum; +} + +nanoseconds GetCurrentTimerResolution() { + return GetTimerResolution().current; +} + +nanoseconds SetCurrentTimerResolution(nanoseconds timer_resolution) { + // Set the timer resolution, and return the current timer resolution. + const auto DesiredTimerResolution = ToHundredNS(timer_resolution); + ULONG CurrentTimerResolution; + NtSetTimerResolution(DesiredTimerResolution, TRUE, &CurrentTimerResolution); + return ToNS(CurrentTimerResolution); +} + +nanoseconds SetCurrentTimerResolutionToMaximum() { + SetHighQoS(); + return SetCurrentTimerResolution(GetMaximumTimerResolution()); +} + +void SleepForOneTick() { + LARGE_INTEGER DelayInterval{ + .QuadPart{-1}, + }; + NtDelayExecution(FALSE, &DelayInterval); +} + +} // namespace Common::Windows diff --git a/src/common/windows/timer_resolution.h b/src/common/windows/timer_resolution.h new file mode 100644 index 000000000..e1e50a62d --- /dev/null +++ b/src/common/windows/timer_resolution.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <chrono> + +namespace Common::Windows { + +/// Returns the minimum (least precise) supported timer resolution in nanoseconds. +std::chrono::nanoseconds GetMinimumTimerResolution(); + +/// Returns the maximum (most precise) supported timer resolution in nanoseconds. +std::chrono::nanoseconds GetMaximumTimerResolution(); + +/// Returns the current timer resolution in nanoseconds. +std::chrono::nanoseconds GetCurrentTimerResolution(); + +/** + * Sets the current timer resolution. + * + * @param timer_resolution Timer resolution in nanoseconds. + * + * @returns The current timer resolution. + */ +std::chrono::nanoseconds SetCurrentTimerResolution(std::chrono::nanoseconds timer_resolution); + +/** + * Sets the current timer resolution to the maximum supported timer resolution. + * + * @returns The current timer resolution. + */ +std::chrono::nanoseconds SetCurrentTimerResolutionToMaximum(); + +/// Sleep for one tick of the current timer resolution. +void SleepForOneTick(); + +} // namespace Common::Windows diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index e54383a4a..c998b1197 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp @@ -14,6 +14,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/x64/cpu_detect.h" +#include "common/x64/rdtsc.h" #ifdef _WIN32 #include <windows.h> @@ -144,6 +145,7 @@ static CPUCaps Detect() { caps.bmi2 = Common::Bit<8>(cpu_id[1]); caps.sha = Common::Bit<29>(cpu_id[1]); + caps.waitpkg = Common::Bit<5>(cpu_id[2]); caps.gfni = Common::Bit<8>(cpu_id[2]); __cpuidex(cpu_id, 0x00000007, 0x00000001); @@ -186,6 +188,8 @@ static CPUCaps Detect() { caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) * caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_denominator; + } else { + caps.tsc_frequency = X64::EstimateRDTSCFrequency(); } } diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index ca8db19d6..8253944d6 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h @@ -67,6 +67,7 @@ struct CPUCaps { bool pclmulqdq : 1; bool popcnt : 1; bool sha : 1; + bool waitpkg : 1; }; /** diff --git a/src/common/x64/cpu_wait.cpp b/src/common/x64/cpu_wait.cpp new file mode 100644 index 000000000..c53dd4945 --- /dev/null +++ b/src/common/x64/cpu_wait.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#include "common/x64/cpu_detect.h" +#include "common/x64/cpu_wait.h" +#include "common/x64/rdtsc.h" + +namespace Common::X64 { + +#ifdef _MSC_VER +__forceinline static void TPAUSE() { + // 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. + // For reference: + // At 1 GHz, 100K cycles is 100us + // At 2 GHz, 100K cycles is 50us + // At 4 GHz, 100K cycles is 25us + static constexpr auto PauseCycles = 100'000; + _tpause(0, FencedRDTSC() + PauseCycles); +} +#else +static void TPAUSE() { + // 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. + // For reference: + // At 1 GHz, 100K cycles is 100us + // At 2 GHz, 100K cycles is 50us + // At 4 GHz, 100K cycles is 25us + static constexpr auto PauseCycles = 100'000; + const auto tsc = FencedRDTSC() + PauseCycles; + const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF); + const auto edx = static_cast<u32>(tsc >> 32); + asm volatile("tpause %0" : : "r"(0), "d"(edx), "a"(eax)); +} +#endif + +void MicroSleep() { + static const bool has_waitpkg = GetCPUCaps().waitpkg; + + if (has_waitpkg) { + TPAUSE(); + } else { + std::this_thread::yield(); + } +} + +} // namespace Common::X64 diff --git a/src/common/x64/cpu_wait.h b/src/common/x64/cpu_wait.h new file mode 100644 index 000000000..99d3757a7 --- /dev/null +++ b/src/common/x64/cpu_wait.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common::X64 { + +void MicroSleep(); + +} // namespace Common::X64 diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp index 8b08332ab..7d2a26bd9 100644 --- a/src/common/x64/native_clock.cpp +++ b/src/common/x64/native_clock.cpp @@ -1,136 +1,50 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <array> -#include <chrono> -#include <thread> - -#include "common/atomic_ops.h" #include "common/uint128.h" #include "common/x64/native_clock.h" +#include "common/x64/rdtsc.h" -#ifdef _MSC_VER -#include <intrin.h> -#endif - -namespace Common { - -#ifdef _MSC_VER -__forceinline static u64 FencedRDTSC() { - _mm_lfence(); - _ReadWriteBarrier(); - const u64 result = __rdtsc(); - _mm_lfence(); - _ReadWriteBarrier(); - return result; -} -#else -static u64 FencedRDTSC() { - u64 result; - asm volatile("lfence\n\t" - "rdtsc\n\t" - "shl $32, %%rdx\n\t" - "or %%rdx, %0\n\t" - "lfence" - : "=a"(result) - : - : "rdx", "memory", "cc"); - return result; -} -#endif - -u64 EstimateRDTSCFrequency() { - // Discard the first result measuring the rdtsc. - FencedRDTSC(); - std::this_thread::sleep_for(std::chrono::milliseconds{1}); - FencedRDTSC(); +namespace Common::X64 { - // Get the current time. - const auto start_time = std::chrono::steady_clock::now(); - const u64 tsc_start = FencedRDTSC(); - // Wait for 200 milliseconds. - std::this_thread::sleep_for(std::chrono::milliseconds{200}); - const auto end_time = std::chrono::steady_clock::now(); - const u64 tsc_end = FencedRDTSC(); - // Calculate differences. - const u64 timer_diff = static_cast<u64>( - std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count()); - const u64 tsc_diff = tsc_end - tsc_start; - const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff); - return tsc_freq; -} +NativeClock::NativeClock(u64 rdtsc_frequency_) + : start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_}, + ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)}, + us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)}, + ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)}, + cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)}, + gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {} -namespace X64 { -NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, - u64 rtsc_frequency_) - : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{ - rtsc_frequency_} { - time_point.inner.last_measure = FencedRDTSC(); - time_point.inner.accumulated_ticks = 0U; - ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency); - us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency); - ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency); - clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency); - cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency); +std::chrono::nanoseconds NativeClock::GetTimeNS() const { + return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)}; } -u64 NativeClock::GetRTSC() { - TimePoint new_time_point{}; - TimePoint current_time_point{}; - - current_time_point.pack = Common::AtomicLoad128(time_point.pack.data()); - do { - const u64 current_measure = FencedRDTSC(); - u64 diff = current_measure - current_time_point.inner.last_measure; - diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0) - new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure - ? current_measure - : current_time_point.inner.last_measure; - new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff; - } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack, - current_time_point.pack, current_time_point.pack)); - return new_time_point.inner.accumulated_ticks; +std::chrono::microseconds NativeClock::GetTimeUS() const { + return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)}; } -void NativeClock::Pause(bool is_paused) { - if (!is_paused) { - TimePoint current_time_point{}; - TimePoint new_time_point{}; - - current_time_point.pack = Common::AtomicLoad128(time_point.pack.data()); - do { - new_time_point.pack = current_time_point.pack; - new_time_point.inner.last_measure = FencedRDTSC(); - } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack, - current_time_point.pack, current_time_point.pack)); - } +std::chrono::milliseconds NativeClock::GetTimeMS() const { + return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)}; } -std::chrono::nanoseconds NativeClock::GetTimeNS() { - const u64 rtsc_value = GetRTSC(); - return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)}; +u64 NativeClock::GetCNTPCT() const { + return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor); } -std::chrono::microseconds NativeClock::GetTimeUS() { - const u64 rtsc_value = GetRTSC(); - return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)}; +u64 NativeClock::GetGPUTick() const { + return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor); } -std::chrono::milliseconds NativeClock::GetTimeMS() { - const u64 rtsc_value = GetRTSC(); - return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)}; +u64 NativeClock::GetHostTicksNow() const { + return FencedRDTSC(); } -u64 NativeClock::GetClockCycles() { - const u64 rtsc_value = GetRTSC(); - return MultiplyHigh(rtsc_value, clock_rtsc_factor); +u64 NativeClock::GetHostTicksElapsed() const { + return FencedRDTSC() - start_ticks; } -u64 NativeClock::GetCPUCycles() { - const u64 rtsc_value = GetRTSC(); - return MultiplyHigh(rtsc_value, cpu_rtsc_factor); +bool NativeClock::IsNative() const { + return true; } -} // namespace X64 - -} // namespace Common +} // namespace Common::X64 diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h index 38ae7a462..334415eff 100644 --- a/src/common/x64/native_clock.h +++ b/src/common/x64/native_clock.h @@ -5,51 +5,37 @@ #include "common/wall_clock.h" -namespace Common { +namespace Common::X64 { -namespace X64 { class NativeClock final : public WallClock { public: - explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, - u64 rtsc_frequency_); + explicit NativeClock(u64 rdtsc_frequency_); - std::chrono::nanoseconds GetTimeNS() override; + std::chrono::nanoseconds GetTimeNS() const override; - std::chrono::microseconds GetTimeUS() override; + std::chrono::microseconds GetTimeUS() const override; - std::chrono::milliseconds GetTimeMS() override; + std::chrono::milliseconds GetTimeMS() const override; - u64 GetClockCycles() override; + u64 GetCNTPCT() const override; - u64 GetCPUCycles() override; + u64 GetGPUTick() const override; - void Pause(bool is_paused) override; + u64 GetHostTicksNow() const override; + + u64 GetHostTicksElapsed() const override; + + bool IsNative() const override; private: - u64 GetRTSC(); - - union alignas(16) TimePoint { - TimePoint() : pack{} {} - u128 pack{}; - struct Inner { - u64 last_measure{}; - u64 accumulated_ticks{}; - } inner; - }; - - TimePoint time_point; - - // factors - u64 clock_rtsc_factor{}; - u64 cpu_rtsc_factor{}; - u64 ns_rtsc_factor{}; - u64 us_rtsc_factor{}; - u64 ms_rtsc_factor{}; - - u64 rtsc_frequency; + u64 start_ticks; + u64 rdtsc_frequency; + + u64 ns_rdtsc_factor; + u64 us_rdtsc_factor; + u64 ms_rdtsc_factor; + u64 cntpct_rdtsc_factor; + u64 gputick_rdtsc_factor; }; -} // namespace X64 - -u64 EstimateRDTSCFrequency(); -} // namespace Common +} // namespace Common::X64 diff --git a/src/common/x64/rdtsc.cpp b/src/common/x64/rdtsc.cpp new file mode 100644 index 000000000..9273274a3 --- /dev/null +++ b/src/common/x64/rdtsc.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> + +#include "common/steady_clock.h" +#include "common/uint128.h" +#include "common/x64/rdtsc.h" + +namespace Common::X64 { + +template <u64 Nearest> +static u64 RoundToNearest(u64 value) { + const auto mod = value % Nearest; + return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod); +} + +u64 EstimateRDTSCFrequency() { + // Discard the first result measuring the rdtsc. + FencedRDTSC(); + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + FencedRDTSC(); + + // Get the current time. + const auto start_time = RealTimeClock::Now(); + const u64 tsc_start = FencedRDTSC(); + // Wait for 100 milliseconds. + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + const auto end_time = RealTimeClock::Now(); + const u64 tsc_end = FencedRDTSC(); + // Calculate differences. + const u64 timer_diff = static_cast<u64>( + std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count()); + const u64 tsc_diff = tsc_end - tsc_start; + const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff); + return RoundToNearest<100'000>(tsc_freq); +} + +} // namespace Common::X64 diff --git a/src/common/x64/rdtsc.h b/src/common/x64/rdtsc.h new file mode 100644 index 000000000..0ec4f52f9 --- /dev/null +++ b/src/common/x64/rdtsc.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#include "common/common_types.h" + +namespace Common::X64 { + +#ifdef _MSC_VER +__forceinline static u64 FencedRDTSC() { + _mm_lfence(); + _ReadWriteBarrier(); + const u64 result = __rdtsc(); + _mm_lfence(); + _ReadWriteBarrier(); + return result; +} +#else +static inline u64 FencedRDTSC() { + u64 eax; + u64 edx; + asm volatile("lfence\n\t" + "rdtsc\n\t" + "lfence\n\t" + : "=a"(eax), "=d"(edx)); + return (edx << 32) | eax; +} +#endif + +u64 EstimateRDTSCFrequency(); + +} // namespace Common::X64 diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp index b71a41b78..cb6ec171b 100644 --- a/src/common/zstd_compression.cpp +++ b/src/common/zstd_compression.cpp @@ -33,7 +33,7 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) { const std::size_t decompressed_size = - ZSTD_getDecompressedSize(compressed.data(), compressed.size()); + ZSTD_getFrameContentSize(compressed.data(), compressed.size()); std::vector<u8> decompressed(decompressed_size); const std::size_t uncompressed_result_size = ZSTD_decompress( |