diff options
Diffstat (limited to '')
322 files changed, 13402 insertions, 4698 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 45332cf95..9884a4a0b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -38,6 +38,8 @@ add_library(common STATIC common_precompiled_headers.h common_types.h concepts.h + demangle.cpp + demangle.h div_ceil.h dynamic_library.cpp dynamic_library.h @@ -175,7 +177,7 @@ 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) +target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle) 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/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/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_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/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/demangle.cpp b/src/common/demangle.cpp new file mode 100644 index 000000000..3310faf86 --- /dev/null +++ b/src/common/demangle.cpp @@ -0,0 +1,35 @@ +// 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 Common { + +std::string DemangleSymbol(const std::string& mangled) { + auto is_itanium = [](const std::string& name) -> bool { + // A valid Itanium encoding requires 1-4 leading underscores, followed by 'Z'. + auto pos = name.find_first_not_of('_'); + return pos > 0 && pos <= 4 && pos < name.size() && name[pos] == 'Z'; + }; + + if (mangled.empty()) { + return mangled; + } + + char* demangled = nullptr; + SCOPE_EXIT({ std::free(demangled); }); + + if (is_itanium(mangled)) { + demangled = llvm::itaniumDemangle(mangled.c_str(), nullptr, nullptr, nullptr); + } + + if (!demangled) { + return mangled; + } + return demangled; +} + +} // namespace Common diff --git a/src/common/demangle.h b/src/common/demangle.h new file mode 100644 index 000000000..f072d22f3 --- /dev/null +++ b/src/common/demangle.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +namespace Common { + +std::string DemangleSymbol(const std::string& mangled); + +} // namespace Common 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/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/input.h b/src/common/input.h index d27b1d772..b5748a6c8 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -51,6 +51,8 @@ enum class PollingMode { NFC, // Enable infrared camera polling IR, + // Enable ring controller polling + Ring, }; enum class CameraFormat { @@ -62,21 +64,22 @@ 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, @@ -90,13 +93,6 @@ enum class NfcState { Unknown, }; -// Ir camera reply from the controller -enum class CameraError { - None, - NotSupported, - Unknown, -}; - // Hint for amplification curve to be used enum class VibrationAmplificationType { Linear, @@ -134,6 +130,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 +188,8 @@ struct TouchStatus { struct BodyColorStatus { u32 body{}; u32 buttons{}; + u32 left_grip{}; + u32 right_grip{}; }; // HD rumble data @@ -228,17 +228,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 @@ -316,22 +330,24 @@ 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 { diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h index 93046615e..5f6b34e82 100644 --- a/src/common/intrusive_red_black_tree.h +++ b/src/common/intrusive_red_black_tree.h @@ -242,19 +242,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 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/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/settings.h b/src/common/settings.h index 80b2eeabc..64db66f37 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -131,7 +131,8 @@ public: * @param default_val Intial 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; @@ -144,7 +145,8 @@ public: * @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} {} @@ -232,7 +234,8 @@ public: * @param default_val Intial 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; @@ -245,7 +248,8 @@ public: * @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} {} /** @@ -483,6 +487,7 @@ 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"}; SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"}; SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"}; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index b26db4796..e0b6180c5 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')); } diff --git a/src/common/string_util.h b/src/common/string_util.h index ce18a33cf..f8aecc875 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); 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/vector_math.h b/src/common/vector_math.h index e62eeea2e..0e2095c45 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -348,9 +348,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 +541,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 +566,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 +635,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/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5afdeb5ff..f16072e6c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -182,6 +182,8 @@ add_library(core STATIC hle/kernel/k_auto_object_container.cpp hle/kernel/k_auto_object_container.h hle/kernel/k_affinity_mask.h + hle/kernel/k_capabilities.cpp + hle/kernel/k_capabilities.h hle/kernel/k_class_token.cpp hle/kernel/k_class_token.h hle/kernel/k_client_port.cpp @@ -193,6 +195,8 @@ add_library(core STATIC hle/kernel/k_condition_variable.cpp hle/kernel/k_condition_variable.h hle/kernel/k_debug.h + hle/kernel/k_device_address_space.cpp + hle/kernel/k_device_address_space.h hle/kernel/k_dynamic_page_manager.h hle/kernel/k_dynamic_resource_manager.h hle/kernel/k_dynamic_slab_heap.h @@ -294,7 +298,42 @@ add_library(core STATIC hle/kernel/svc.h hle/kernel/svc_common.h hle/kernel/svc_types.h - hle/kernel/svc_wrap.h + hle/kernel/svc/svc_activity.cpp + hle/kernel/svc/svc_address_arbiter.cpp + hle/kernel/svc/svc_address_translation.cpp + hle/kernel/svc/svc_cache.cpp + hle/kernel/svc/svc_code_memory.cpp + hle/kernel/svc/svc_condition_variable.cpp + hle/kernel/svc/svc_debug.cpp + hle/kernel/svc/svc_debug_string.cpp + hle/kernel/svc/svc_device_address_space.cpp + hle/kernel/svc/svc_event.cpp + hle/kernel/svc/svc_exception.cpp + hle/kernel/svc/svc_info.cpp + hle/kernel/svc/svc_interrupt_event.cpp + hle/kernel/svc/svc_io_pool.cpp + hle/kernel/svc/svc_ipc.cpp + hle/kernel/svc/svc_kernel_debug.cpp + hle/kernel/svc/svc_light_ipc.cpp + hle/kernel/svc/svc_lock.cpp + hle/kernel/svc/svc_memory.cpp + hle/kernel/svc/svc_physical_memory.cpp + hle/kernel/svc/svc_port.cpp + hle/kernel/svc/svc_power_management.cpp + hle/kernel/svc/svc_process.cpp + hle/kernel/svc/svc_process_memory.cpp + hle/kernel/svc/svc_processor.cpp + hle/kernel/svc/svc_query_memory.cpp + hle/kernel/svc/svc_register.cpp + hle/kernel/svc/svc_resource_limit.cpp + hle/kernel/svc/svc_secure_monitor_call.cpp + hle/kernel/svc/svc_session.cpp + hle/kernel/svc/svc_shared_memory.cpp + hle/kernel/svc/svc_synchronization.cpp + hle/kernel/svc/svc_thread.cpp + hle/kernel/svc/svc_thread_profiler.cpp + hle/kernel/svc/svc_tick.cpp + hle/kernel/svc/svc_transfer_memory.cpp hle/result.h hle/service/acc/acc.cpp hle/service/acc/acc.h diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 2df7b0ee8..8aa7b9641 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -1,14 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#ifndef _MSC_VER -#include <cxxabi.h> -#endif - #include <map> #include <optional> + #include "common/bit_field.h" #include "common/common_types.h" +#include "common/demangle.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/arm/symbols.h" @@ -71,20 +69,8 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt const auto symbol_set = symbols.find(entry.module); if (symbol_set != symbols.end()) { const auto symbol = Symbols::GetSymbolName(symbol_set->second, entry.offset); - if (symbol.has_value()) { -#ifdef _MSC_VER - // TODO(DarkLordZach): Add demangling of symbol names. - entry.name = *symbol; -#else - int status{-1}; - char* demangled{abi::__cxa_demangle(symbol->c_str(), nullptr, nullptr, &status)}; - if (status == 0 && demangled != nullptr) { - entry.name = demangled; - std::free(demangled); - } else { - entry.name = *symbol; - } -#endif + if (symbol) { + entry.name = Common::DemangleSymbol(*symbol); } } } diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index a64a9ac64..9c02b7b31 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -11,6 +11,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/scope_exit.h" +#include "common/settings.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/debugger/gdbstub.h" @@ -731,7 +732,25 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) { auto* process = system.CurrentProcess(); auto& page_table = process->PageTable(); - if (command_str == "get info") { + const char* commands = "Commands:\n" + " get fastmem\n" + " get info\n" + " get mappings\n"; + + if (command_str == "get fastmem") { + if (Settings::IsFastmemEnabled()) { + const auto& impl = page_table.PageTableImpl(); + const auto region = reinterpret_cast<uintptr_t>(impl.fastmem_arena); + const auto region_bits = impl.current_address_space_width_in_bits; + const auto region_size = 1ULL << region_bits; + + reply = fmt::format("Region bits: {}\n" + "Host address: {:#x} - {:#x}\n", + region_bits, region, region + region_size - 1); + } else { + reply = "Fastmem is not enabled.\n"; + } + } else if (command_str == "get info") { Loader::AppLoader::Modules modules; system.GetAppLoader().ReadNSOModules(modules); @@ -787,9 +806,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) { cur_addr = next_address; } } else if (command_str == "help") { - reply = "Commands:\n get info\n get mappings\n"; + reply = commands; } else { - reply = "Unknown command.\nCommands:\n get info\n get mappings\n"; + reply = "Unknown command.\n"; + reply += commands; } std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()}; diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h index 13cbdb734..45567b840 100644 --- a/src/core/hardware_properties.h +++ b/src/core/hardware_properties.h @@ -25,6 +25,26 @@ constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, }; +static constexpr inline size_t NumVirtualCores = Common::BitSize<u64>(); + +static constexpr inline u64 VirtualCoreMask = [] { + u64 mask = 0; + for (size_t i = 0; i < NumVirtualCores; ++i) { + mask |= (UINT64_C(1) << i); + } + return mask; +}(); + +static constexpr inline u64 ConvertVirtualCoreMaskToPhysical(u64 v_core_mask) { + u64 p_core_mask = 0; + while (v_core_mask != 0) { + const u64 next = std::countr_zero(v_core_mask); + v_core_mask &= ~(static_cast<u64>(1) << next); + p_core_mask |= (static_cast<u64>(1) << VirtualToPhysicalCoreMap[next]); + } + return p_core_mask; +} + // Cortex-A57 supports 4 memory watchpoints constexpr u64 NUM_WATCHPOINTS = 4; diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 7a01f3f4c..631aa6ad2 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> +#include <common/scope_exit.h> #include "common/polyfill_ranges.h" #include "common/thread.h" @@ -11,6 +12,7 @@ namespace Core::HID { constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_TRIGGER_MAX = 0x7fff; +constexpr u32 TURBO_BUTTON_DELAY = 4; // Use a common UUID for TAS and Virtual Gamepad constexpr Common::UUID TAS_UUID = Common::UUID{{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; @@ -93,6 +95,7 @@ void EmulatedController::ReloadFromSettings() { motion_params[index] = Common::ParamPackage(player.motions[index]); } + controller.color_values = {}; controller.colors_state.fullkey = { .body = GetNpadColor(player.body_color_left), .button = GetNpadColor(player.button_color_left), @@ -106,6 +109,8 @@ void EmulatedController::ReloadFromSettings() { .button = GetNpadColor(player.button_color_right), }; + ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs); + // Other or debug controller should always be a pro controller if (npad_id_type != NpadIdType::Other) { SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); @@ -132,18 +137,28 @@ void EmulatedController::LoadDevices() { trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL]; trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR]; + color_params[LeftIndex] = left_joycon; + color_params[RightIndex] = right_joycon; + color_params[LeftIndex].Set("color", true); + color_params[RightIndex].Set("color", true); + battery_params[LeftIndex] = left_joycon; battery_params[RightIndex] = right_joycon; battery_params[LeftIndex].Set("battery", true); battery_params[RightIndex].Set("battery", true); - camera_params = Common::ParamPackage{"engine:camera,camera:1"}; - nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; + camera_params[0] = right_joycon; + camera_params[0].Set("camera", true); + camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; + ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; + nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; + nfc_params[1] = right_joycon; + nfc_params[1].Set("nfc", true); output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; - output_params[2] = camera_params; - output_params[3] = nfc_params; + output_params[2] = camera_params[1]; + output_params[3] = nfc_params[0]; output_params[LeftIndex].Set("output", true); output_params[RightIndex].Set("output", true); output_params[2].Set("output", true); @@ -159,8 +174,11 @@ void EmulatedController::LoadDevices() { Common::Input::CreateInputDevice); std::ranges::transform(battery_params, battery_devices.begin(), Common::Input::CreateInputDevice); - camera_devices = Common::Input::CreateInputDevice(camera_params); - nfc_devices = Common::Input::CreateInputDevice(nfc_params); + std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice); + std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice); + std::ranges::transform(ring_params, ring_analog_devices.begin(), + Common::Input::CreateInputDevice); + std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice); std::ranges::transform(output_params, output_devices.begin(), Common::Input::CreateOutputDevice); @@ -322,6 +340,19 @@ void EmulatedController::ReloadInput() { battery_devices[index]->ForceUpdate(); } + for (std::size_t index = 0; index < color_devices.size(); ++index) { + if (!color_devices[index]) { + continue; + } + color_devices[index]->SetCallback({ + .on_change = + [this, index](const Common::Input::CallbackStatus& callback) { + SetColors(callback, index); + }, + }); + color_devices[index]->ForceUpdate(); + } + for (std::size_t index = 0; index < motion_devices.size(); ++index) { if (!motion_devices[index]) { continue; @@ -335,22 +366,37 @@ void EmulatedController::ReloadInput() { motion_devices[index]->ForceUpdate(); } - if (camera_devices) { - camera_devices->SetCallback({ + for (std::size_t index = 0; index < camera_devices.size(); ++index) { + if (!camera_devices[index]) { + continue; + } + camera_devices[index]->SetCallback({ .on_change = [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); }, }); - camera_devices->ForceUpdate(); + camera_devices[index]->ForceUpdate(); } - if (nfc_devices) { - if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) { - nfc_devices->SetCallback({ - .on_change = - [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); }, - }); - nfc_devices->ForceUpdate(); + for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) { + if (!ring_analog_devices[index]) { + continue; + } + ring_analog_devices[index]->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); }, + }); + ring_analog_devices[index]->ForceUpdate(); + } + + for (std::size_t index = 0; index < nfc_devices.size(); ++index) { + if (!nfc_devices[index]) { + continue; } + nfc_devices[index]->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); }, + }); + nfc_devices[index]->ForceUpdate(); } // Register TAS devices. No need to force update @@ -402,6 +448,7 @@ void EmulatedController::ReloadInput() { }, }); } + turbo_button_state = 0; } void EmulatedController::UnloadInput() { @@ -420,6 +467,9 @@ void EmulatedController::UnloadInput() { for (auto& battery : battery_devices) { battery.reset(); } + for (auto& color : color_devices) { + color.reset(); + } for (auto& output : output_devices) { output.reset(); } @@ -435,8 +485,15 @@ void EmulatedController::UnloadInput() { for (auto& stick : virtual_stick_devices) { stick.reset(); } - camera_devices.reset(); - nfc_devices.reset(); + for (auto& camera : camera_devices) { + camera.reset(); + } + for (auto& ring : ring_analog_devices) { + ring.reset(); + } + for (auto& nfc : nfc_devices) { + nfc.reset(); + } } void EmulatedController::EnableConfiguration() { @@ -448,6 +505,11 @@ void EmulatedController::EnableConfiguration() { void EmulatedController::DisableConfiguration() { is_configuring = false; + // Get Joycon colors before turning on the controller + for (const auto& color_device : color_devices) { + color_device->ForceUpdate(); + } + // Apply temporary npad type to the real controller if (tmp_npad_type != npad_type) { if (is_connected) { @@ -501,6 +563,9 @@ void EmulatedController::SaveCurrentConfig() { for (std::size_t index = 0; index < player.motions.size(); ++index) { player.motions[index] = motion_params[index].Serialize(); } + if (npad_id_type == NpadIdType::Player1) { + Settings::values.ringcon_analogs = ring_params[0].Serialize(); + } } void EmulatedController::RestoreConfig() { @@ -624,6 +689,7 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback } current_status.toggle = new_status.toggle; + current_status.turbo = new_status.turbo; current_status.uuid = uuid; // Update button status with current @@ -772,17 +838,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, if (index >= controller.stick_values.size()) { return; } - std::unique_lock lock{mutex}; + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); }); + std::scoped_lock lock{mutex}; const auto stick_value = TransformToStick(callback); // Only read stick values that have the same uuid or are over the threshold to avoid flapping if (controller.stick_values[index].uuid != uuid) { const bool is_tas = uuid == TAS_UUID; if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) { + trigger_guard.Cancel(); return; } if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) { + trigger_guard.Cancel(); return; } } @@ -793,8 +863,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, if (is_configuring) { controller.analog_stick_state.left = {}; controller.analog_stick_state.right = {}; - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Stick, false); return; } @@ -819,9 +887,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down); break; } - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Stick, true); } void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback, @@ -829,7 +894,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac if (index >= controller.trigger_values.size()) { return; } - std::unique_lock lock{mutex}; + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); }); + std::scoped_lock lock{mutex}; const auto trigger_value = TransformToTrigger(callback); // Only read trigger values that have the same uuid or are pressed once @@ -845,13 +912,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac if (is_configuring) { controller.gc_trigger_state.left = 0; controller.gc_trigger_state.right = 0; - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Trigger, false); return; } // Only GC controllers have analog triggers if (npad_type != NpadStyleIndex::GameCube) { + trigger_guard.Cancel(); return; } @@ -868,9 +934,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac controller.npad_button_state.zr.Assign(trigger.pressed.value); break; } - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Trigger, true); } void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback, @@ -878,7 +941,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback if (index >= controller.motion_values.size()) { return; } - std::unique_lock lock{mutex}; + SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); }); + std::scoped_lock lock{mutex}; auto& raw_status = controller.motion_values[index].raw_status; auto& emulated = controller.motion_values[index].emulated; @@ -899,8 +963,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback force_update_motion = raw_status.force_update; if (is_configuring) { - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Motion, false); return; } @@ -910,9 +972,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback motion.rotation = emulated.GetRotations(); motion.orientation = emulated.GetOrientation(); motion.is_at_rest = !emulated.IsMoving(motion_sensitivity); +} - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Motion, true); +void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback, + std::size_t index) { + if (index >= controller.color_values.size()) { + return; + } + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); }); + std::scoped_lock lock{mutex}; + controller.color_values[index] = TransformToColor(callback); + + if (is_configuring) { + return; + } + + if (controller.color_values[index].body == 0) { + trigger_guard.Cancel(); + return; + } + + controller.colors_state.fullkey = { + .body = GetNpadColor(controller.color_values[index].body), + .button = GetNpadColor(controller.color_values[index].buttons), + }; + if (npad_type == NpadStyleIndex::ProController) { + controller.colors_state.left = { + .body = GetNpadColor(controller.color_values[index].left_grip), + .button = GetNpadColor(controller.color_values[index].buttons), + }; + controller.colors_state.right = { + .body = GetNpadColor(controller.color_values[index].right_grip), + .button = GetNpadColor(controller.color_values[index].buttons), + }; + } else { + switch (index) { + case LeftIndex: + controller.colors_state.left = { + .body = GetNpadColor(controller.color_values[index].body), + .button = GetNpadColor(controller.color_values[index].buttons), + }; + break; + case RightIndex: + controller.colors_state.right = { + .body = GetNpadColor(controller.color_values[index].body), + .button = GetNpadColor(controller.color_values[index].buttons), + }; + break; + } + } } void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, @@ -920,12 +1029,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac if (index >= controller.battery_values.size()) { return; } - std::unique_lock lock{mutex}; + SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); }); + std::scoped_lock lock{mutex}; controller.battery_values[index] = TransformToBattery(callback); if (is_configuring) { - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Battery, false); return; } @@ -981,18 +1089,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac }; break; } - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Battery, true); } void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) { - std::unique_lock lock{mutex}; + SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); }); + std::scoped_lock lock{mutex}; controller.camera_values = TransformToCamera(callback); if (is_configuring) { - lock.unlock(); - TriggerOnChange(ControllerTriggerType::IrSensor, false); return; } @@ -1000,18 +1104,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback controller.camera_state.format = static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format); controller.camera_state.data = controller.camera_values.data; +} - lock.unlock(); - TriggerOnChange(ControllerTriggerType::IrSensor, true); +void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) { + SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); }); + std::scoped_lock lock{mutex}; + const auto force_value = TransformToStick(callback); + + controller.ring_analog_value = force_value.x; + + if (is_configuring) { + return; + } + + controller.ring_analog_state.force = force_value.x.value; } void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { - std::unique_lock lock{mutex}; + SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); }); + std::scoped_lock lock{mutex}; controller.nfc_values = TransformToNfc(callback); if (is_configuring) { - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Nfc, false); return; } @@ -1019,9 +1133,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { controller.nfc_values.state, controller.nfc_values.data, }; - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Nfc, true); } bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { @@ -1053,7 +1164,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v .type = type, }; return output_devices[device_index]->SetVibration(status) == - Common::Input::VibrationError::None; + Common::Input::DriverResult::Success; } bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { @@ -1075,16 +1186,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { return output_devices[device_index]->IsVibrationEnabled(); } -bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { - LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); - auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; +Common::Input::DriverResult EmulatedController::SetPollingMode( + EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { + LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); + + auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)]; + auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[3]; - const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); - const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode); + if (device_index == EmulatedDeviceIndex::LeftIndex) { + return left_output_device->SetPollingMode(polling_mode); + } + + if (device_index == EmulatedDeviceIndex::RightIndex) { + const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); + const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); - return virtual_nfc_result == Common::Input::PollingError::None || - mapped_nfc_result == Common::Input::PollingError::None; + if (virtual_nfc_result == Common::Input::DriverResult::Success) { + return virtual_nfc_result; + } + return mapped_nfc_result; + } + + left_output_device->SetPollingMode(polling_mode); + right_output_device->SetPollingMode(polling_mode); + nfc_output_device->SetPollingMode(polling_mode); + return Common::Input::DriverResult::Success; } bool EmulatedController::SetCameraFormat( @@ -1095,13 +1222,22 @@ bool EmulatedController::SetCameraFormat( auto& camera_output_device = output_devices[2]; if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( - camera_format)) == Common::Input::CameraError::None) { + camera_format)) == Common::Input::DriverResult::Success) { return true; } // Fallback to Qt camera if native device doesn't have support return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>( - camera_format)) == Common::Input::CameraError::None; + camera_format)) == Common::Input::DriverResult::Success; +} + +Common::ParamPackage EmulatedController::GetRingParam() const { + return ring_params[0]; +} + +void EmulatedController::SetRingParam(Common::ParamPackage param) { + ring_params[0] = std::move(param); + ReloadInput(); } bool EmulatedController::HasNfc() const { @@ -1255,39 +1391,35 @@ void EmulatedController::Connect(bool use_temporary_value) { return; } - std::unique_lock lock{mutex}; + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }); + std::scoped_lock lock{mutex}; if (is_configuring) { tmp_is_connected = true; - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Connected, false); return; } if (is_connected) { + trigger_guard.Cancel(); return; } is_connected = true; - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Connected, true); } void EmulatedController::Disconnect() { - std::unique_lock lock{mutex}; + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }); + std::scoped_lock lock{mutex}; if (is_configuring) { tmp_is_connected = false; - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Disconnected, false); return; } if (!is_connected) { + trigger_guard.Cancel(); return; } is_connected = false; - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Disconnected, true); } bool EmulatedController::IsConnected(bool get_temporary_value) const { @@ -1312,19 +1444,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c } void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { - std::unique_lock lock{mutex}; + auto trigger_guard = + SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }); + std::scoped_lock lock{mutex}; if (is_configuring) { if (tmp_npad_type == npad_type_) { + trigger_guard.Cancel(); return; } tmp_npad_type = npad_type_; - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Type, false); return; } if (npad_type == npad_type_) { + trigger_guard.Cancel(); return; } if (is_connected) { @@ -1332,9 +1466,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { NpadIdTypeToIndex(npad_id_type)); } npad_type = npad_type_; - - lock.unlock(); - TriggerOnChange(ControllerTriggerType::Type, true); } LedPattern EmulatedController::GetLedPattern() const { @@ -1395,6 +1526,10 @@ CameraValues EmulatedController::GetCameraValues() const { return controller.camera_values; } +RingAnalogValue EmulatedController::GetRingSensorValues() const { + return controller.ring_analog_value; +} + HomeButtonState EmulatedController::GetHomeButtons() const { std::scoped_lock lock{mutex}; if (is_configuring) { @@ -1416,7 +1551,7 @@ NpadButtonState EmulatedController::GetNpadButtons() const { if (is_configuring) { return {}; } - return controller.npad_button_state; + return {controller.npad_button_state.raw & GetTurboButtonMask()}; } DebugPadButton EmulatedController::GetDebugPadButtons() const { @@ -1428,7 +1563,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const { } AnalogSticks EmulatedController::GetSticks() const { - std::unique_lock lock{mutex}; + std::scoped_lock lock{mutex}; if (is_configuring) { return {}; @@ -1478,6 +1613,10 @@ const CameraState& EmulatedController::GetCamera() const { return controller.camera_state; } +RingSensorForce EmulatedController::GetRingSensorForce() const { + return controller.ring_analog_state; +} + const NfcState& EmulatedController::GetNfc() const { std::scoped_lock lock{mutex}; return controller.nfc_state; @@ -1520,4 +1659,74 @@ void EmulatedController::DeleteCallback(int key) { } callback_list.erase(iterator); } + +void EmulatedController::TurboButtonUpdate() { + turbo_button_state = (turbo_button_state + 1) % (TURBO_BUTTON_DELAY * 2); +} + +NpadButton EmulatedController::GetTurboButtonMask() const { + // Apply no mask when disabled + if (turbo_button_state < TURBO_BUTTON_DELAY) { + return {NpadButton::All}; + } + + NpadButtonState button_mask{}; + for (std::size_t index = 0; index < controller.button_values.size(); ++index) { + if (!controller.button_values[index].turbo) { + continue; + } + + switch (index) { + case Settings::NativeButton::A: + button_mask.a.Assign(1); + break; + case Settings::NativeButton::B: + button_mask.b.Assign(1); + break; + case Settings::NativeButton::X: + button_mask.x.Assign(1); + break; + case Settings::NativeButton::Y: + button_mask.y.Assign(1); + break; + case Settings::NativeButton::L: + button_mask.l.Assign(1); + break; + case Settings::NativeButton::R: + button_mask.r.Assign(1); + break; + case Settings::NativeButton::ZL: + button_mask.zl.Assign(1); + break; + case Settings::NativeButton::ZR: + button_mask.zr.Assign(1); + break; + case Settings::NativeButton::DLeft: + button_mask.left.Assign(1); + break; + case Settings::NativeButton::DUp: + button_mask.up.Assign(1); + break; + case Settings::NativeButton::DRight: + button_mask.right.Assign(1); + break; + case Settings::NativeButton::DDown: + button_mask.down.Assign(1); + break; + case Settings::NativeButton::SL: + button_mask.left_sl.Assign(1); + button_mask.right_sl.Assign(1); + break; + case Settings::NativeButton::SR: + button_mask.left_sr.Assign(1); + button_mask.right_sr.Assign(1); + break; + default: + break; + } + } + + return static_cast<NpadButton>(~button_mask.raw); +} + } // namespace Core::HID diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index a398543a6..b02bf35c4 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -35,19 +35,27 @@ using ControllerMotionDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>; using TriggerDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; +using ColorDevices = + std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; using BatteryDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; -using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; -using NfcDevices = std::unique_ptr<Common::Input::InputDevice>; +using CameraDevices = + std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; +using RingAnalogDevices = + std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; +using NfcDevices = + std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>; using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; +using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>; using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; -using CameraParams = Common::ParamPackage; -using NfcParams = Common::ParamPackage; +using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>; +using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>; using OutputParams = std::array<Common::ParamPackage, output_devices_size>; using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; @@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; using CameraValues = Common::Input::CameraStatus; +using RingAnalogValue = Common::Input::AnalogStatus; using NfcValues = Common::Input::NfcStatus; using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; @@ -84,6 +93,10 @@ struct CameraState { std::size_t sample{}; }; +struct RingSensorForce { + f32 force; +}; + struct NfcState { Common::Input::NfcState state{}; std::vector<u8> data{}; @@ -116,6 +129,7 @@ struct ControllerStatus { BatteryValues battery_values{}; VibrationValues vibration_values{}; CameraValues camera_values{}; + RingAnalogValue ring_analog_value{}; NfcValues nfc_values{}; // Data for HID serices @@ -129,6 +143,7 @@ struct ControllerStatus { ControllerColors colors_state{}; BatteryLevelState battery_state{}; CameraState camera_state{}; + RingSensorForce ring_analog_state{}; NfcState nfc_state{}; }; @@ -141,6 +156,7 @@ enum class ControllerTriggerType { Battery, Vibration, IrSensor, + RingController, Nfc, Connected, Disconnected, @@ -294,6 +310,9 @@ public: /// Returns the latest camera status from the controller with parameters CameraValues GetCameraValues() const; + /// Returns the latest status of analog input from the ring sensor with parameters + RingAnalogValue GetRingSensorValues() const; + /// Returns the latest status of button input for the hid::HomeButton service HomeButtonState GetHomeButtons() const; @@ -324,6 +343,9 @@ public: /// Returns the latest camera status from the controller const CameraState& GetCamera() const; + /// Returns the latest ringcon force sensor value + RingSensorForce GetRingSensorForce() const; + /// Returns the latest ntag status from the controller const NfcState& GetNfc() const; @@ -341,10 +363,12 @@ public: /** * Sets the desired data to be polled from a controller + * @param device_index index of the controller to set the polling mode * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc. - * @return true if SetPollingMode was successfull + * @return driver result from this command */ - bool SetPollingMode(Common::Input::PollingMode polling_mode); + Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index, + Common::Input::PollingMode polling_mode); /** * Sets the desired camera format to be polled from a controller @@ -353,6 +377,15 @@ public: */ bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); + // Returns the current mapped ring device + Common::ParamPackage GetRingParam() const; + + /** + * Updates the current mapped ring device + * @param param ParamPackage with ring sensor data to be mapped + */ + void SetRingParam(Common::ParamPackage param); + /// Returns true if the device has nfc support bool HasNfc() const; @@ -378,6 +411,9 @@ public: */ void DeleteCallback(int key); + /// Swaps the state of the turbo buttons + void TurboButtonUpdate(); + private: /// creates input devices from params void LoadDevices(); @@ -433,9 +469,16 @@ private: void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index); /** + * Updates the color status of the controller + * @param callback A CallbackStatus containing the color status + * @param index color ID of the to be updated + */ + void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index); + + /** * Updates the battery status of the controller * @param callback A CallbackStatus containing the battery status - * @param index Button ID of the to be updated + * @param index battery ID of the to be updated */ void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); @@ -446,6 +489,12 @@ private: void SetCamera(const Common::Input::CallbackStatus& callback); /** + * Updates the ring analog sensor status of the ring controller + * @param callback A CallbackStatus containing the force status + */ + void SetRingAnalog(const Common::Input::CallbackStatus& callback); + + /** * Updates the nfc status of the controller * @param callback A CallbackStatus containing the nfc status */ @@ -465,6 +514,8 @@ private: */ void TriggerOnChange(ControllerTriggerType type, bool is_service_update); + NpadButton GetTurboButtonMask() const; + const NpadIdType npad_id_type; NpadStyleIndex npad_type{NpadStyleIndex::None}; NpadStyleIndex original_npad_type{NpadStyleIndex::None}; @@ -474,6 +525,7 @@ private: bool system_buttons_enabled{true}; f32 motion_sensitivity{0.01f}; bool force_update_motion{false}; + u32 turbo_button_state{0}; // Temporary values to avoid doing changes while the controller is in configuring mode NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; @@ -484,7 +536,9 @@ private: ControllerMotionParams motion_params; TriggerParams trigger_params; BatteryParams battery_params; + ColorParams color_params; CameraParams camera_params; + RingAnalogParams ring_params; NfcParams nfc_params; OutputParams output_params; @@ -493,7 +547,9 @@ private: ControllerMotionDevices motion_devices; TriggerDevices trigger_devices; BatteryDevices battery_devices; + ColorDevices color_devices; CameraDevices camera_devices; + RingAnalogDevices ring_analog_devices; NfcDevices nfc_devices; OutputDevices output_devices; diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp index e421828d2..836f32c0f 100644 --- a/src/core/hid/emulated_devices.cpp +++ b/src/core/hid/emulated_devices.cpp @@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default; EmulatedDevices::~EmulatedDevices() = default; void EmulatedDevices::ReloadFromSettings() { - ring_params = Common::ParamPackage(Settings::values.ringcon_analogs); ReloadInput(); } @@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() { key_index++; } - ring_analog_device = Common::Input::CreateInputDevice(ring_params); - for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { if (!mouse_button_devices[index]) { continue; @@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() { }, }); } - - if (ring_analog_device) { - ring_analog_device->SetCallback({ - .on_change = - [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); }, - }); - } } void EmulatedDevices::UnloadInput() { @@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() { for (auto& button : keyboard_modifier_devices) { button.reset(); } - ring_analog_device.reset(); } void EmulatedDevices::EnableConfiguration() { @@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() { if (!is_configuring) { return; } - Settings::values.ringcon_analogs = ring_params.Serialize(); } void EmulatedDevices::RestoreConfig() { @@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() { ReloadFromSettings(); } -Common::ParamPackage EmulatedDevices::GetRingParam() const { - return ring_params; -} - -void EmulatedDevices::SetRingParam(Common::ParamPackage param) { - ring_params = std::move(param); - ReloadInput(); -} - void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index) { if (index >= device_status.keyboard_values.size()) { @@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac TriggerOnChange(DeviceTriggerType::Mouse); } -void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) { - std::lock_guard lock{mutex}; - const auto force_value = TransformToStick(callback); - - device_status.ring_analog_value = force_value.x; - - if (is_configuring) { - device_status.ring_analog_value = {}; - TriggerOnChange(DeviceTriggerType::RingController); - return; - } - - device_status.ring_analog_state.force = force_value.x.value; - - TriggerOnChange(DeviceTriggerType::RingController); -} - KeyboardValues EmulatedDevices::GetKeyboardValues() const { std::scoped_lock lock{mutex}; return device_status.keyboard_values; @@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const { return device_status.mouse_button_values; } -RingAnalogValue EmulatedDevices::GetRingSensorValues() const { - return device_status.ring_analog_value; -} - KeyboardKey EmulatedDevices::GetKeyboard() const { std::scoped_lock lock{mutex}; return device_status.keyboard_state; @@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const { return device_status.mouse_wheel_state; } -RingSensorForce EmulatedDevices::GetRingSensorForce() const { - return device_status.ring_analog_state; -} - void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h index 4cdbf9dc6..76f9150df 100644 --- a/src/core/hid/emulated_devices.h +++ b/src/core/hid/emulated_devices.h @@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMouseWheel::NumMouseWheels>; using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>; -using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>; using MouseButtonParams = std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; -using RingAnalogParams = Common::ParamPackage; using KeyboardValues = std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; @@ -41,17 +39,12 @@ using MouseButtonValues = using MouseAnalogValues = std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>; using MouseStickValue = Common::Input::TouchStatus; -using RingAnalogValue = Common::Input::AnalogStatus; struct MousePosition { f32 x; f32 y; }; -struct RingSensorForce { - f32 force; -}; - struct DeviceStatus { // Data from input_common KeyboardValues keyboard_values{}; @@ -59,7 +52,6 @@ struct DeviceStatus { MouseButtonValues mouse_button_values{}; MouseAnalogValues mouse_analog_values{}; MouseStickValue mouse_stick_value{}; - RingAnalogValue ring_analog_value{}; // Data for HID serices KeyboardKey keyboard_state{}; @@ -67,7 +59,6 @@ struct DeviceStatus { MouseButton mouse_button_state{}; MousePosition mouse_position_state{}; AnalogStickState mouse_wheel_state{}; - RingSensorForce ring_analog_state{}; }; enum class DeviceTriggerType { @@ -138,9 +129,6 @@ public: /// Returns the latest status of button input from the mouse with parameters MouseButtonValues GetMouseButtonsValues() const; - /// Returns the latest status of analog input from the ring sensor with parameters - RingAnalogValue GetRingSensorValues() const; - /// Returns the latest status of button input from the keyboard KeyboardKey GetKeyboard() const; @@ -156,9 +144,6 @@ public: /// Returns the latest mouse wheel change AnalogStickState GetMouseWheel() const; - /// Returns the latest ringcon force sensor value - RingSensorForce GetRingSensorForce() const; - /** * Adds a callback to the list of events * @param update_callback InterfaceUpdateCallback that will be triggered @@ -224,14 +209,11 @@ private: bool is_configuring{false}; - RingAnalogParams ring_params; - KeyboardDevices keyboard_devices; KeyboardModifierDevices keyboard_modifier_devices; MouseButtonDevices mouse_button_devices; MouseAnalogDevices mouse_analog_devices; MouseStickDevice mouse_stick_device; - RingAnalogDevice ring_analog_device; mutable std::mutex mutex; mutable std::mutex callback_mutex; diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 502692875..3f7b8c090 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal return nfc; } +Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) { + switch (callback.type) { + case Common::Input::InputType::Color: + return callback.color_status; + break; + default: + LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type); + return {}; + break; + } +} + void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { const auto& properties = analog.properties; float& raw_value = analog.raw_value; diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index b7eb6e660..c51c03e57 100644 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h @@ -88,11 +88,19 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu * Converts raw input data into a valid nfc status. * * @param callback Supported callbacks: Nfc. - * @return A valid CameraObject object. + * @return A valid data tag vector. */ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback); /** + * Converts raw input data into a valid color status. + * + * @param callback Supported callbacks: Color. + * @return A valid Color object. + */ +Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback); + +/** * Converts raw analog data into a valid analog value * @param analog An analog object containing raw data and properties * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 738b6d0f1..494151eef 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -11,6 +11,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/scratch_buffer.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/k_auto_object.h" @@ -325,7 +326,7 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_threa return ResultSuccess; } -std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { +std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) const { const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && BufferDescriptorA()[buffer_index].Size()}; if (is_buffer_a) { @@ -345,6 +346,33 @@ std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { } } +std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { + static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_a; + static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_x; + + const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && + BufferDescriptorA()[buffer_index].Size()}; + if (is_buffer_a) { + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorA().size() > buffer_index, { return {}; }, + "BufferDescriptorA invalid buffer_index {}", buffer_index); + auto& read_buffer = read_buffer_a[buffer_index]; + read_buffer.resize_destructive(BufferDescriptorA()[buffer_index].Size()); + memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), read_buffer.data(), + read_buffer.size()); + return read_buffer; + } else { + ASSERT_OR_EXECUTE_MSG( + BufferDescriptorX().size() > buffer_index, { return {}; }, + "BufferDescriptorX invalid buffer_index {}", buffer_index); + auto& read_buffer = read_buffer_x[buffer_index]; + read_buffer.resize_destructive(BufferDescriptorX()[buffer_index].Size()); + memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), read_buffer.data(), + read_buffer.size()); + return read_buffer; + } +} + std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, std::size_t buffer_index) const { if (size == 0) { diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index e252b5f4b..5bf4f171b 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -7,6 +7,7 @@ #include <functional> #include <memory> #include <optional> +#include <span> #include <string> #include <type_traits> #include <vector> @@ -270,8 +271,11 @@ public: return domain_message_header.has_value(); } - /// Helper function to read a buffer using the appropriate buffer descriptor - [[nodiscard]] std::vector<u8> ReadBuffer(std::size_t buffer_index = 0) const; + /// Helper function to get a span of a buffer using the appropriate buffer descriptor + [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const; + + /// Helper function to read a copy of a buffer using the appropriate buffer descriptor + [[nodiscard]] std::vector<u8> ReadBufferCopy(std::size_t buffer_index = 0) const; /// Helper function to write a buffer using the appropriate buffer descriptor std::size_t WriteBuffer(const void* buffer, std::size_t size, diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index 7b363eb1e..571acf4b2 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/init/init_slab_setup.h" #include "core/hle/kernel/k_code_memory.h" #include "core/hle/kernel/k_debug.h" +#include "core/hle/kernel/k_device_address_space.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_event_info.h" #include "core/hle/kernel/k_memory_layout.h" @@ -43,6 +44,7 @@ namespace Kernel::Init { HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ##__VA_ARGS__) \ HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__) \ HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ##__VA_ARGS__) \ + HANDLER(KDeviceAddressSpace, (SLAB_COUNT(KDeviceAddressSpace)), ##__VA_ARGS__) \ HANDLER(KSession, (SLAB_COUNT(KSession)), ##__VA_ARGS__) \ HANDLER(KThreadLocalPage, \ (SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \ diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index 2827763d5..e8118c2b8 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -24,9 +24,7 @@ private: friend class ::Kernel::KClassTokenGenerator; \ static constexpr inline auto ObjectType = ::Kernel::KClassTokenGenerator::ObjectType::CLASS; \ static constexpr inline const char* const TypeName = #CLASS; \ - static constexpr inline ClassTokenType ClassToken() { \ - return ::Kernel::ClassToken<CLASS>; \ - } \ + static constexpr inline ClassTokenType ClassToken() { return ::Kernel::ClassToken<CLASS>; } \ \ public: \ YUZU_NON_COPYABLE(CLASS); \ @@ -37,15 +35,9 @@ public: constexpr ClassTokenType Token = ClassToken(); \ return TypeObj(TypeName, Token); \ } \ - static constexpr const char* GetStaticTypeName() { \ - return TypeName; \ - } \ - virtual TypeObj GetTypeObj() ATTRIBUTE { \ - return GetStaticTypeObj(); \ - } \ - virtual const char* GetTypeName() ATTRIBUTE { \ - return GetStaticTypeName(); \ - } \ + static constexpr const char* GetStaticTypeName() { return TypeName; } \ + virtual TypeObj GetTypeObj() ATTRIBUTE { return GetStaticTypeObj(); } \ + virtual const char* GetTypeName() ATTRIBUTE { return GetStaticTypeName(); } \ \ private: \ constexpr bool operator!=(const TypeObj& rhs) @@ -245,8 +237,8 @@ public: } template <typename U> - requires(std::derived_from<T, U> || - std::derived_from<U, T>) constexpr KScopedAutoObject(KScopedAutoObject<U>&& rhs) { + requires(std::derived_from<T, U> || std::derived_from<U, T>) + constexpr KScopedAutoObject(KScopedAutoObject<U>&& rhs) { if constexpr (std::derived_from<U, T>) { // Upcast. m_obj = rhs.m_obj; diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp new file mode 100644 index 000000000..64f1d7371 --- /dev/null +++ b/src/core/hle/kernel/k_capabilities.cpp @@ -0,0 +1,358 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hardware_properties.h" +#include "core/hle/kernel/k_capabilities.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_page_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_version.h" + +namespace Kernel { + +Result KCapabilities::InitializeForKIP(std::span<const u32> kern_caps, KPageTable* page_table) { + // We're initializing an initial process. + m_svc_access_flags.reset(); + m_irq_access_flags.reset(); + m_debug_capabilities = 0; + m_handle_table_size = 0; + m_intended_kernel_version = 0; + m_program_type = 0; + + // Initial processes may run on all cores. + constexpr u64 VirtMask = Core::Hardware::VirtualCoreMask; + constexpr u64 PhysMask = Core::Hardware::ConvertVirtualCoreMaskToPhysical(VirtMask); + + m_core_mask = VirtMask; + m_phys_core_mask = PhysMask; + + // Initial processes may use any user priority they like. + m_priority_mask = ~0xFULL; + + // Here, Nintendo sets the kernel version to the current kernel version. + // We will follow suit and set the version to the highest supported kernel version. + KernelVersion intended_kernel_version{}; + intended_kernel_version.major_version.Assign(Svc::SupportedKernelMajorVersion); + intended_kernel_version.minor_version.Assign(Svc::SupportedKernelMinorVersion); + m_intended_kernel_version = intended_kernel_version.raw; + + // Parse the capabilities array. + R_RETURN(this->SetCapabilities(kern_caps, page_table)); +} + +Result KCapabilities::InitializeForUser(std::span<const u32> user_caps, KPageTable* page_table) { + // We're initializing a user process. + m_svc_access_flags.reset(); + m_irq_access_flags.reset(); + m_debug_capabilities = 0; + m_handle_table_size = 0; + m_intended_kernel_version = 0; + m_program_type = 0; + + // User processes must specify what cores/priorities they can use. + m_core_mask = 0; + m_priority_mask = 0; + + // Parse the user capabilities array. + R_RETURN(this->SetCapabilities(user_caps, page_table)); +} + +Result KCapabilities::SetCorePriorityCapability(const u32 cap) { + // We can't set core/priority if we've already set them. + R_UNLESS(m_core_mask == 0, ResultInvalidArgument); + R_UNLESS(m_priority_mask == 0, ResultInvalidArgument); + + // Validate the core/priority. + CorePriority pack{cap}; + const u32 min_core = pack.minimum_core_id; + const u32 max_core = pack.maximum_core_id; + const u32 max_prio = pack.lowest_thread_priority; + const u32 min_prio = pack.highest_thread_priority; + + R_UNLESS(min_core <= max_core, ResultInvalidCombination); + R_UNLESS(min_prio <= max_prio, ResultInvalidCombination); + R_UNLESS(max_core < Core::Hardware::NumVirtualCores, ResultInvalidCoreId); + + ASSERT(max_prio < Common::BitSize<u64>()); + + // Set core mask. + for (auto core_id = min_core; core_id <= max_core; core_id++) { + m_core_mask |= (1ULL << core_id); + } + ASSERT((m_core_mask & Core::Hardware::VirtualCoreMask) == m_core_mask); + + // Set physical core mask. + m_phys_core_mask = Core::Hardware::ConvertVirtualCoreMaskToPhysical(m_core_mask); + + // Set priority mask. + for (auto prio = min_prio; prio <= max_prio; prio++) { + m_priority_mask |= (1ULL << prio); + } + + // We must have some core/priority we can use. + R_UNLESS(m_core_mask != 0, ResultInvalidArgument); + R_UNLESS(m_priority_mask != 0, ResultInvalidArgument); + + // Processes must not have access to kernel thread priorities. + R_UNLESS((m_priority_mask & 0xF) == 0, ResultInvalidArgument); + + R_SUCCEED(); +} + +Result KCapabilities::SetSyscallMaskCapability(const u32 cap, u32& set_svc) { + // Validate the index. + SyscallMask pack{cap}; + const u32 mask = pack.mask; + const u32 index = pack.index; + + const u32 index_flag = (1U << index); + R_UNLESS((set_svc & index_flag) == 0, ResultInvalidCombination); + set_svc |= index_flag; + + // Set SVCs. + for (size_t i = 0; i < decltype(SyscallMask::mask)::bits; i++) { + const u32 svc_id = static_cast<u32>(decltype(SyscallMask::mask)::bits * index + i); + if (mask & (1U << i)) { + R_UNLESS(this->SetSvcAllowed(svc_id), ResultOutOfRange); + } + } + + R_SUCCEED(); +} + +Result KCapabilities::MapRange_(const u32 cap, const u32 size_cap, KPageTable* page_table) { + const auto range_pack = MapRange{cap}; + const auto size_pack = MapRangeSize{size_cap}; + + // Get/validate address/size + const u64 phys_addr = range_pack.address.Value() * PageSize; + + // Validate reserved bits are unused. + R_UNLESS(size_pack.reserved.Value() == 0, ResultOutOfRange); + + const size_t num_pages = size_pack.pages; + const size_t size = num_pages * PageSize; + R_UNLESS(num_pages != 0, ResultInvalidSize); + R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); + R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); + + // Do the mapping. + [[maybe_unused]] const KMemoryPermission perm = range_pack.read_only.Value() + ? KMemoryPermission::UserRead + : KMemoryPermission::UserReadWrite; + if (MapRangeSize{size_cap}.normal) { + // R_RETURN(page_table->MapStatic(phys_addr, size, perm)); + } else { + // R_RETURN(page_table->MapIo(phys_addr, size, perm)); + } + + UNIMPLEMENTED(); + R_SUCCEED(); +} + +Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) { + // Get/validate address/size + const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; + const size_t num_pages = 1; + const size_t size = num_pages * PageSize; + R_UNLESS(num_pages != 0, ResultInvalidSize); + R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); + R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); + + // Do the mapping. + // R_RETURN(page_table->MapIo(phys_addr, size, KMemoryPermission_UserReadWrite)); + + UNIMPLEMENTED(); + R_SUCCEED(); +} + +template <typename F> +Result KCapabilities::ProcessMapRegionCapability(const u32 cap, F f) { + // Define the allowed memory regions. + constexpr std::array<KMemoryRegionType, 4> MemoryRegions{ + KMemoryRegionType_None, + KMemoryRegionType_KernelTraceBuffer, + KMemoryRegionType_OnMemoryBootImage, + KMemoryRegionType_DTB, + }; + + // Extract regions/read only. + const MapRegion pack{cap}; + const std::array<RegionType, 3> types{pack.region0, pack.region1, pack.region2}; + const std::array<u32, 3> ro{pack.read_only0, pack.read_only1, pack.read_only2}; + + for (size_t i = 0; i < types.size(); i++) { + const auto type = types[i]; + const auto perm = ro[i] ? KMemoryPermission::UserRead : KMemoryPermission::UserReadWrite; + switch (type) { + case RegionType::NoMapping: + break; + case RegionType::KernelTraceBuffer: + case RegionType::OnMemoryBootImage: + case RegionType::DTB: + R_TRY(f(MemoryRegions[static_cast<u32>(type)], perm)); + break; + default: + R_THROW(ResultNotFound); + } + } + + R_SUCCEED(); +} + +Result KCapabilities::MapRegion_(const u32 cap, KPageTable* page_table) { + // Map each region into the process's page table. + R_RETURN(ProcessMapRegionCapability( + cap, [](KMemoryRegionType region_type, KMemoryPermission perm) -> Result { + // R_RETURN(page_table->MapRegion(region_type, perm)); + UNIMPLEMENTED(); + R_SUCCEED(); + })); +} + +Result KCapabilities::CheckMapRegion(KernelCore& kernel, const u32 cap) { + // Check that each region has a physical backing store. + R_RETURN(ProcessMapRegionCapability( + cap, [&](KMemoryRegionType region_type, KMemoryPermission perm) -> Result { + R_UNLESS(kernel.MemoryLayout().GetPhysicalMemoryRegionTree().FindFirstDerived( + region_type) != nullptr, + ResultOutOfRange); + R_SUCCEED(); + })); +} + +Result KCapabilities::SetInterruptPairCapability(const u32 cap) { + // Extract interrupts. + const InterruptPair pack{cap}; + const std::array<u32, 2> ids{pack.interrupt_id0, pack.interrupt_id1}; + + for (size_t i = 0; i < ids.size(); i++) { + if (ids[i] != PaddingInterruptId) { + UNIMPLEMENTED(); + // R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(ids[i]), ResultOutOfRange); + // R_UNLESS(this->SetInterruptPermitted(ids[i]), ResultOutOfRange); + } + } + + R_SUCCEED(); +} + +Result KCapabilities::SetProgramTypeCapability(const u32 cap) { + // Validate. + const ProgramType pack{cap}; + R_UNLESS(pack.reserved == 0, ResultReservedUsed); + + m_program_type = pack.type; + R_SUCCEED(); +} + +Result KCapabilities::SetKernelVersionCapability(const u32 cap) { + // Ensure we haven't set our version before. + R_UNLESS(KernelVersion{m_intended_kernel_version}.major_version == 0, ResultInvalidArgument); + + // Set, ensure that we set a valid version. + m_intended_kernel_version = cap; + R_UNLESS(KernelVersion{m_intended_kernel_version}.major_version != 0, ResultInvalidArgument); + + R_SUCCEED(); +} + +Result KCapabilities::SetHandleTableCapability(const u32 cap) { + // Validate. + const HandleTable pack{cap}; + R_UNLESS(pack.reserved == 0, ResultReservedUsed); + + m_handle_table_size = pack.size; + R_SUCCEED(); +} + +Result KCapabilities::SetDebugFlagsCapability(const u32 cap) { + // Validate. + const DebugFlags pack{cap}; + R_UNLESS(pack.reserved == 0, ResultReservedUsed); + + DebugFlags debug_capabilities{m_debug_capabilities}; + debug_capabilities.allow_debug.Assign(pack.allow_debug); + debug_capabilities.force_debug.Assign(pack.force_debug); + m_debug_capabilities = debug_capabilities.raw; + + R_SUCCEED(); +} + +Result KCapabilities::SetCapability(const u32 cap, u32& set_flags, u32& set_svc, + KPageTable* page_table) { + // Validate this is a capability we can act on. + const auto type = GetCapabilityType(cap); + R_UNLESS(type != CapabilityType::Invalid, ResultInvalidArgument); + + // If the type is padding, we have no work to do. + R_SUCCEED_IF(type == CapabilityType::Padding); + + // Check that we haven't already processed this capability. + const auto flag = GetCapabilityFlag(type); + R_UNLESS(((set_flags & InitializeOnceFlags) & flag) == 0, ResultInvalidCombination); + set_flags |= flag; + + // Process the capability. + switch (type) { + case CapabilityType::CorePriority: + R_RETURN(this->SetCorePriorityCapability(cap)); + case CapabilityType::SyscallMask: + R_RETURN(this->SetSyscallMaskCapability(cap, set_svc)); + case CapabilityType::MapIoPage: + R_RETURN(this->MapIoPage_(cap, page_table)); + case CapabilityType::MapRegion: + R_RETURN(this->MapRegion_(cap, page_table)); + case CapabilityType::InterruptPair: + R_RETURN(this->SetInterruptPairCapability(cap)); + case CapabilityType::ProgramType: + R_RETURN(this->SetProgramTypeCapability(cap)); + case CapabilityType::KernelVersion: + R_RETURN(this->SetKernelVersionCapability(cap)); + case CapabilityType::HandleTable: + R_RETURN(this->SetHandleTableCapability(cap)); + case CapabilityType::DebugFlags: + R_RETURN(this->SetDebugFlagsCapability(cap)); + default: + R_THROW(ResultInvalidArgument); + } +} + +Result KCapabilities::SetCapabilities(std::span<const u32> caps, KPageTable* page_table) { + u32 set_flags = 0, set_svc = 0; + + for (size_t i = 0; i < caps.size(); i++) { + const u32 cap{caps[i]}; + + if (GetCapabilityType(cap) == CapabilityType::MapRange) { + // Check that the pair cap exists. + R_UNLESS((++i) < caps.size(), ResultInvalidCombination); + + // Check the pair cap is a map range cap. + const u32 size_cap{caps[i]}; + R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, + ResultInvalidCombination); + + // Map the range. + R_TRY(this->MapRange_(cap, size_cap, page_table)); + } else { + R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table)); + } + } + + R_SUCCEED(); +} + +Result KCapabilities::CheckCapabilities(KernelCore& kernel, std::span<const u32> caps) { + for (auto cap : caps) { + // Check the capability refers to a valid region. + if (GetCapabilityType(cap) == CapabilityType::MapRegion) { + R_TRY(CheckMapRegion(kernel, cap)); + } + } + + R_SUCCEED(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_capabilities.h b/src/core/hle/kernel/k_capabilities.h new file mode 100644 index 000000000..cd96f8d23 --- /dev/null +++ b/src/core/hle/kernel/k_capabilities.h @@ -0,0 +1,295 @@ + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <bitset> +#include <span> + +#include "common/bit_field.h" +#include "common/common_types.h" + +#include "core/hle/kernel/svc_types.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KPageTable; +class KernelCore; + +class KCapabilities { +public: + constexpr explicit KCapabilities() = default; + + Result InitializeForKIP(std::span<const u32> kern_caps, KPageTable* page_table); + Result InitializeForUser(std::span<const u32> user_caps, KPageTable* page_table); + + static Result CheckCapabilities(KernelCore& kernel, std::span<const u32> user_caps); + + constexpr u64 GetCoreMask() const { + return m_core_mask; + } + + constexpr u64 GetPhysicalCoreMask() const { + return m_phys_core_mask; + } + + constexpr u64 GetPriorityMask() const { + return m_priority_mask; + } + + constexpr s32 GetHandleTableSize() const { + return m_handle_table_size; + } + + constexpr const Svc::SvcAccessFlagSet& GetSvcPermissions() const { + return m_svc_access_flags; + } + + constexpr bool IsPermittedSvc(u32 id) const { + return (id < m_svc_access_flags.size()) && m_svc_access_flags[id]; + } + + constexpr bool IsPermittedInterrupt(u32 id) const { + return (id < m_irq_access_flags.size()) && m_irq_access_flags[id]; + } + + constexpr bool IsPermittedDebug() const { + return DebugFlags{m_debug_capabilities}.allow_debug.Value() != 0; + } + + constexpr bool CanForceDebug() const { + return DebugFlags{m_debug_capabilities}.force_debug.Value() != 0; + } + + constexpr u32 GetIntendedKernelMajorVersion() const { + return KernelVersion{m_intended_kernel_version}.major_version; + } + + constexpr u32 GetIntendedKernelMinorVersion() const { + return KernelVersion{m_intended_kernel_version}.minor_version; + } + +private: + static constexpr size_t InterruptIdCount = 0x400; + using InterruptFlagSet = std::bitset<InterruptIdCount>; + + enum class CapabilityType : u32 { + CorePriority = (1U << 3) - 1, + SyscallMask = (1U << 4) - 1, + MapRange = (1U << 6) - 1, + MapIoPage = (1U << 7) - 1, + MapRegion = (1U << 10) - 1, + InterruptPair = (1U << 11) - 1, + ProgramType = (1U << 13) - 1, + KernelVersion = (1U << 14) - 1, + HandleTable = (1U << 15) - 1, + DebugFlags = (1U << 16) - 1, + + Invalid = 0U, + Padding = ~0U, + }; + + using RawCapabilityValue = u32; + + static constexpr CapabilityType GetCapabilityType(const RawCapabilityValue value) { + return static_cast<CapabilityType>((~value & (value + 1)) - 1); + } + + static constexpr u32 GetCapabilityFlag(CapabilityType type) { + return static_cast<u32>(type) + 1; + } + + template <CapabilityType Type> + static constexpr inline u32 CapabilityFlag = static_cast<u32>(Type) + 1; + + template <CapabilityType Type> + static constexpr inline u32 CapabilityId = std::countr_zero(CapabilityFlag<Type>); + + union CorePriority { + static_assert(CapabilityId<CapabilityType::CorePriority> + 1 == 4); + + RawCapabilityValue raw; + BitField<0, 4, CapabilityType> id; + BitField<4, 6, u32> lowest_thread_priority; + BitField<10, 6, u32> highest_thread_priority; + BitField<16, 8, u32> minimum_core_id; + BitField<24, 8, u32> maximum_core_id; + }; + + union SyscallMask { + static_assert(CapabilityId<CapabilityType::SyscallMask> + 1 == 5); + + RawCapabilityValue raw; + BitField<0, 5, CapabilityType> id; + BitField<5, 24, u32> mask; + BitField<29, 3, u32> index; + }; + + // #undef MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES + static constexpr u64 PhysicalMapAllowedMask = (1ULL << 36) - 1; + + union MapRange { + static_assert(CapabilityId<CapabilityType::MapRange> + 1 == 7); + + RawCapabilityValue raw; + BitField<0, 7, CapabilityType> id; + BitField<7, 24, u32> address; + BitField<31, 1, u32> read_only; + }; + + union MapRangeSize { + static_assert(CapabilityId<CapabilityType::MapRange> + 1 == 7); + + RawCapabilityValue raw; + BitField<0, 7, CapabilityType> id; + BitField<7, 20, u32> pages; + BitField<27, 4, u32> reserved; + BitField<31, 1, u32> normal; + }; + + union MapIoPage { + static_assert(CapabilityId<CapabilityType::MapIoPage> + 1 == 8); + + RawCapabilityValue raw; + BitField<0, 8, CapabilityType> id; + BitField<8, 24, u32> address; + }; + + enum class RegionType : u32 { + NoMapping = 0, + KernelTraceBuffer = 1, + OnMemoryBootImage = 2, + DTB = 3, + }; + + union MapRegion { + static_assert(CapabilityId<CapabilityType::MapRegion> + 1 == 11); + + RawCapabilityValue raw; + BitField<0, 11, CapabilityType> id; + BitField<11, 6, RegionType> region0; + BitField<17, 1, u32> read_only0; + BitField<18, 6, RegionType> region1; + BitField<24, 1, u32> read_only1; + BitField<25, 6, RegionType> region2; + BitField<31, 1, u32> read_only2; + }; + + union InterruptPair { + static_assert(CapabilityId<CapabilityType::InterruptPair> + 1 == 12); + + RawCapabilityValue raw; + BitField<0, 12, CapabilityType> id; + BitField<12, 10, u32> interrupt_id0; + BitField<22, 10, u32> interrupt_id1; + }; + + union ProgramType { + static_assert(CapabilityId<CapabilityType::ProgramType> + 1 == 14); + + RawCapabilityValue raw; + BitField<0, 14, CapabilityType> id; + BitField<14, 3, u32> type; + BitField<17, 15, u32> reserved; + }; + + union KernelVersion { + static_assert(CapabilityId<CapabilityType::KernelVersion> + 1 == 15); + + RawCapabilityValue raw; + BitField<0, 15, CapabilityType> id; + BitField<15, 4, u32> major_version; + BitField<19, 13, u32> minor_version; + }; + + union HandleTable { + static_assert(CapabilityId<CapabilityType::HandleTable> + 1 == 16); + + RawCapabilityValue raw; + BitField<0, 16, CapabilityType> id; + BitField<16, 10, u32> size; + BitField<26, 6, u32> reserved; + }; + + union DebugFlags { + static_assert(CapabilityId<CapabilityType::DebugFlags> + 1 == 17); + + RawCapabilityValue raw; + BitField<0, 17, CapabilityType> id; + BitField<17, 1, u32> allow_debug; + BitField<18, 1, u32> force_debug; + BitField<19, 13, u32> reserved; + }; + + static_assert(sizeof(CorePriority) == 4); + static_assert(sizeof(SyscallMask) == 4); + static_assert(sizeof(MapRange) == 4); + static_assert(sizeof(MapRangeSize) == 4); + static_assert(sizeof(MapIoPage) == 4); + static_assert(sizeof(MapRegion) == 4); + static_assert(sizeof(InterruptPair) == 4); + static_assert(sizeof(ProgramType) == 4); + static_assert(sizeof(KernelVersion) == 4); + static_assert(sizeof(HandleTable) == 4); + static_assert(sizeof(DebugFlags) == 4); + + static constexpr u32 InitializeOnceFlags = + CapabilityFlag<CapabilityType::CorePriority> | CapabilityFlag<CapabilityType::ProgramType> | + CapabilityFlag<CapabilityType::KernelVersion> | + CapabilityFlag<CapabilityType::HandleTable> | CapabilityFlag<CapabilityType::DebugFlags>; + + static const u32 PaddingInterruptId = 0x3FF; + static_assert(PaddingInterruptId < InterruptIdCount); + +private: + constexpr bool SetSvcAllowed(u32 id) { + if (id < m_svc_access_flags.size()) [[likely]] { + m_svc_access_flags[id] = true; + return true; + } else { + return false; + } + } + + constexpr bool SetInterruptPermitted(u32 id) { + if (id < m_irq_access_flags.size()) [[likely]] { + m_irq_access_flags[id] = true; + return true; + } else { + return false; + } + } + + Result SetCorePriorityCapability(const u32 cap); + Result SetSyscallMaskCapability(const u32 cap, u32& set_svc); + Result MapRange_(const u32 cap, const u32 size_cap, KPageTable* page_table); + Result MapIoPage_(const u32 cap, KPageTable* page_table); + Result MapRegion_(const u32 cap, KPageTable* page_table); + Result SetInterruptPairCapability(const u32 cap); + Result SetProgramTypeCapability(const u32 cap); + Result SetKernelVersionCapability(const u32 cap); + Result SetHandleTableCapability(const u32 cap); + Result SetDebugFlagsCapability(const u32 cap); + + template <typename F> + static Result ProcessMapRegionCapability(const u32 cap, F f); + static Result CheckMapRegion(KernelCore& kernel, const u32 cap); + + Result SetCapability(const u32 cap, u32& set_flags, u32& set_svc, KPageTable* page_table); + Result SetCapabilities(std::span<const u32> caps, KPageTable* page_table); + +private: + Svc::SvcAccessFlagSet m_svc_access_flags{}; + InterruptFlagSet m_irq_access_flags{}; + u64 m_core_mask{}; + u64 m_phys_core_mask{}; + u64 m_priority_mask{}; + u32 m_debug_capabilities{}; + s32 m_handle_table_size{}; + u32 m_intended_kernel_version{}; + u32 m_program_type{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index d9da1e600..884eba001 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp @@ -74,7 +74,7 @@ Result KCodeMemory::Map(VAddr address, size_t size) { R_UNLESS(!m_is_mapped, ResultInvalidState); // Map the memory. - R_TRY(kernel.CurrentProcess()->PageTable().MapPages( + R_TRY(kernel.CurrentProcess()->PageTable().MapPageGroup( address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite)); // Mark ourselves as mapped. @@ -91,8 +91,8 @@ Result KCodeMemory::Unmap(VAddr address, size_t size) { KScopedLightLock lk(m_lock); // Unmap the memory. - R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, *m_page_group, - KMemoryState::CodeOut)); + R_TRY(kernel.CurrentProcess()->PageTable().UnmapPageGroup(address, *m_page_group, + KMemoryState::CodeOut)); // Mark ourselves as unmapped. m_is_mapped = false; @@ -125,8 +125,8 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission } // Map the memory. - R_TRY( - m_owner->PageTable().MapPages(address, *m_page_group, KMemoryState::GeneratedCode, k_perm)); + R_TRY(m_owner->PageTable().MapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode, + k_perm)); // Mark ourselves as mapped. m_is_owner_mapped = true; @@ -142,7 +142,7 @@ Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) { KScopedLightLock lk(m_lock); // Unmap the memory. - R_TRY(m_owner->PageTable().UnmapPages(address, *m_page_group, KMemoryState::GeneratedCode)); + R_TRY(m_owner->PageTable().UnmapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode)); // Mark ourselves as unmapped. m_is_owner_mapped = false; diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index 124149697..0c6b20db3 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -171,7 +171,7 @@ Result KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) R_UNLESS(owner_thread != nullptr, ResultInvalidHandle); // Update the lock. - cur_thread->SetAddressKey(addr, value); + cur_thread->SetUserAddressKey(addr, value); owner_thread->AddWaiter(cur_thread); // Begin waiting. diff --git a/src/core/hle/kernel/k_device_address_space.cpp b/src/core/hle/kernel/k_device_address_space.cpp new file mode 100644 index 000000000..27659ea3b --- /dev/null +++ b/src/core/hle/kernel/k_device_address_space.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/k_device_address_space.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { + +KDeviceAddressSpace::KDeviceAddressSpace(KernelCore& kernel_) + : KAutoObjectWithSlabHeapAndContainer(kernel_), m_lock(kernel_), m_is_initialized(false) {} +KDeviceAddressSpace::~KDeviceAddressSpace() = default; + +void KDeviceAddressSpace::Initialize() { + // This just forwards to the device page table manager. + // KDevicePageTable::Initialize(); +} + +// Member functions. +Result KDeviceAddressSpace::Initialize(u64 address, u64 size) { + // Initialize the device page table. + // R_TRY(m_table.Initialize(address, size)); + + // Set member variables. + m_space_address = address; + m_space_size = size; + m_is_initialized = true; + + R_SUCCEED(); +} + +void KDeviceAddressSpace::Finalize() { + // Finalize the table. + // m_table.Finalize(); +} + +Result KDeviceAddressSpace::Attach(Svc::DeviceName device_name) { + // Lock the address space. + KScopedLightLock lk(m_lock); + + // Attach. + // R_RETURN(m_table.Attach(device_name, m_space_address, m_space_size)); + R_SUCCEED(); +} + +Result KDeviceAddressSpace::Detach(Svc::DeviceName device_name) { + // Lock the address space. + KScopedLightLock lk(m_lock); + + // Detach. + // R_RETURN(m_table.Detach(device_name)); + R_SUCCEED(); +} + +Result KDeviceAddressSpace::Map(KPageTable* page_table, VAddr process_address, size_t size, + u64 device_address, u32 option, bool is_aligned) { + // Check that the address falls within the space. + R_UNLESS((m_space_address <= device_address && + device_address + size - 1 <= m_space_address + m_space_size - 1), + ResultInvalidCurrentMemory); + + // Decode the option. + const Svc::MapDeviceAddressSpaceOption option_pack{option}; + const auto device_perm = option_pack.permission.Value(); + const auto flags = option_pack.flags.Value(); + const auto reserved = option_pack.reserved.Value(); + + // Validate the option. + // TODO: It is likely that this check for flags == none is only on NX board. + R_UNLESS(flags == Svc::MapDeviceAddressSpaceFlag::None, ResultInvalidEnumValue); + R_UNLESS(reserved == 0, ResultInvalidEnumValue); + + // Lock the address space. + KScopedLightLock lk(m_lock); + + // Lock the page table to prevent concurrent device mapping operations. + // KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock(); + + // Lock the pages. + bool is_io{}; + R_TRY(page_table->LockForMapDeviceAddressSpace(std::addressof(is_io), process_address, size, + ConvertToKMemoryPermission(device_perm), + is_aligned, true)); + + // Ensure that if we fail, we don't keep unmapped pages locked. + ON_RESULT_FAILURE { + ASSERT(page_table->UnlockForDeviceAddressSpace(process_address, size) == ResultSuccess); + }; + + // Check that the io status is allowable. + if (is_io) { + R_UNLESS(static_cast<u32>(flags & Svc::MapDeviceAddressSpaceFlag::NotIoRegister) == 0, + ResultInvalidCombination); + } + + // Map the pages. + { + // Perform the mapping. + // R_TRY(m_table.Map(page_table, process_address, size, device_address, device_perm, + // is_aligned, is_io)); + + // Ensure that we unmap the pages if we fail to update the protections. + // NOTE: Nintendo does not check the result of this unmap call. + // ON_RESULT_FAILURE { m_table.Unmap(device_address, size); }; + + // Update the protections in accordance with how much we mapped. + // R_TRY(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size)); + } + + // We succeeded. + R_SUCCEED(); +} + +Result KDeviceAddressSpace::Unmap(KPageTable* page_table, VAddr process_address, size_t size, + u64 device_address) { + // Check that the address falls within the space. + R_UNLESS((m_space_address <= device_address && + device_address + size - 1 <= m_space_address + m_space_size - 1), + ResultInvalidCurrentMemory); + + // Lock the address space. + KScopedLightLock lk(m_lock); + + // Lock the page table to prevent concurrent device mapping operations. + // KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock(); + + // Lock the pages. + R_TRY(page_table->LockForUnmapDeviceAddressSpace(process_address, size, true)); + + // Unmap the pages. + { + // If we fail to unmap, we want to do a partial unlock. + // ON_RESULT_FAILURE { + // ASSERT(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size) == + // ResultSuccess); + // }; + + // Perform the unmap. + // R_TRY(m_table.Unmap(page_table, process_address, size, device_address)); + } + + // Unlock the pages. + ASSERT(page_table->UnlockForDeviceAddressSpace(process_address, size) == ResultSuccess); + + R_SUCCEED(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_device_address_space.h b/src/core/hle/kernel/k_device_address_space.h new file mode 100644 index 000000000..4709df995 --- /dev/null +++ b/src/core/hle/kernel/k_device_address_space.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string> + +#include "common/common_types.h" +#include "core/hle/kernel/k_page_table.h" +#include "core/hle/kernel/slab_helpers.h" +#include "core/hle/result.h" + +namespace Kernel { + +class KDeviceAddressSpace final + : public KAutoObjectWithSlabHeapAndContainer<KDeviceAddressSpace, KAutoObjectWithList> { + KERNEL_AUTOOBJECT_TRAITS(KDeviceAddressSpace, KAutoObject); + +public: + explicit KDeviceAddressSpace(KernelCore& kernel); + ~KDeviceAddressSpace(); + + Result Initialize(u64 address, u64 size); + void Finalize(); + + bool IsInitialized() const { + return m_is_initialized; + } + static void PostDestroy(uintptr_t arg) {} + + Result Attach(Svc::DeviceName device_name); + Result Detach(Svc::DeviceName device_name); + + Result MapByForce(KPageTable* page_table, VAddr process_address, size_t size, + u64 device_address, u32 option) { + R_RETURN(this->Map(page_table, process_address, size, device_address, option, false)); + } + + Result MapAligned(KPageTable* page_table, VAddr process_address, size_t size, + u64 device_address, u32 option) { + R_RETURN(this->Map(page_table, process_address, size, device_address, option, true)); + } + + Result Unmap(KPageTable* page_table, VAddr process_address, size_t size, u64 device_address); + + static void Initialize(); + +private: + Result Map(KPageTable* page_table, VAddr process_address, size_t size, u64 device_address, + u32 option, bool is_aligned); + +private: + KLightLock m_lock; + // KDevicePageTable m_table; + u64 m_space_address{}; + u64 m_space_size{}; + bool m_is_initialized{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp index 43185320d..d791acbe3 100644 --- a/src/core/hle/kernel/k_light_lock.cpp +++ b/src/core/hle/kernel/k_light_lock.cpp @@ -68,7 +68,7 @@ bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { // Add the current thread as a waiter on the owner. KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL); - cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); + cur_thread->SetKernelAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); owner_thread->AddWaiter(cur_thread); // Begin waiting to hold the lock. diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h index fd6e1d3e6..17fa1a6ed 100644 --- a/src/core/hle/kernel/k_memory_layout.h +++ b/src/core/hle/kernel/k_memory_layout.h @@ -67,9 +67,9 @@ constexpr size_t KernelPageBufferAdditionalSize = 0x33C000; constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize + KernelPageBufferHeapSize; -constexpr bool IsKernelAddressKey(VAddr key) { - return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast; -} +//! NB: Use KThread::GetAddressKeyIsKernel(). +//! See explanation for deviation of GetAddressKey. +bool IsKernelAddressKey(VAddr key) = delete; constexpr bool IsKernelAddress(VAddr address) { return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 9c7ac22dc..2e13d5d0d 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -435,6 +435,9 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si KPageGroup pg{m_kernel, m_block_info_manager}; AddRegionToPages(src_address, num_pages, pg); + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + // Reprotect the source as kernel-read/not mapped. const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); @@ -447,7 +450,10 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si }); // Map the alias pages. - R_TRY(MapPages(dst_address, pg, new_perm)); + const KPageProperties dst_properties = {new_perm, false, false, + DisableMergeAttribute::DisableHead}; + R_TRY( + this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false)); // We successfully mapped the alias pages, so we don't need to unprotect the src pages on // failure. @@ -1881,7 +1887,8 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) { R_SUCCEED(); } -Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) { +Result KPageTable::MapMemory(KProcessAddress dst_address, KProcessAddress src_address, + size_t size) { // Lock the table. KScopedLightLock lk(m_general_lock); @@ -1902,53 +1909,73 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) KMemoryAttribute::None)); // Create an update allocator for the source. - Result src_allocator_result{ResultSuccess}; + Result src_allocator_result; KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. - Result dst_allocator_result{ResultSuccess}; + Result dst_allocator_result; KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); R_TRY(dst_allocator_result); // Map the memory. - KPageGroup page_linked_list{m_kernel, m_block_info_manager}; - const size_t num_pages{size / PageSize}; - const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>( - KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); - const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; - - AddRegionToPages(src_address, num_pages, page_linked_list); { + // Determine the number of pages being operated on. + const size_t num_pages = size / PageSize; + + // Create page groups for the memory being unmapped. + KPageGroup pg{m_kernel, m_block_info_manager}; + + // Create the page group representing the source. + R_TRY(this->MakePageGroup(pg, src_address, num_pages)); + + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + // Reprotect the source as kernel-read/not mapped. - auto block_guard = detail::ScopeExit([&] { - Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, - OperationType::ChangePermissions); - }); - R_TRY(Operate(src_address, num_pages, new_src_perm, OperationType::ChangePermissions)); - R_TRY(MapPages(dst_address, page_linked_list, KMemoryPermission::UserReadWrite)); + const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>( + KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); + const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; + const KPageProperties src_properties = {new_src_perm, false, false, + DisableMergeAttribute::DisableHeadBodyTail}; + R_TRY(this->Operate(src_address, num_pages, src_properties.perm, + OperationType::ChangePermissions)); - block_guard.Cancel(); - } + // Ensure that we unprotect the source pages on failure. + ON_RESULT_FAILURE { + const KPageProperties unprotect_properties = { + KMemoryPermission::UserReadWrite, false, false, + DisableMergeAttribute::EnableHeadBodyTail}; + ASSERT(this->Operate(src_address, num_pages, unprotect_properties.perm, + OperationType::ChangePermissions) == ResultSuccess); + }; - // Apply the memory block updates. - m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, - new_src_perm, new_src_attr, - KMemoryBlockDisableMergeAttribute::Locked, - KMemoryBlockDisableMergeAttribute::None); - m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::Stack, KMemoryPermission::UserReadWrite, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + // Map the alias pages. + const KPageProperties dst_map_properties = {KMemoryPermission::UserReadWrite, false, false, + DisableMergeAttribute::DisableHead}; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties, + false)); + + // Apply the memory block updates. + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, + src_state, new_src_perm, new_src_attr, + KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update( + std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::Stack, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None); + } R_SUCCEED(); } -Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size) { +Result KPageTable::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, + size_t size) { // Lock the table. KScopedLightLock lk(m_general_lock); @@ -1970,108 +1997,208 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); // Create an update allocator for the source. - Result src_allocator_result{ResultSuccess}; + Result src_allocator_result; KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. - Result dst_allocator_result{ResultSuccess}; + Result dst_allocator_result; KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); R_TRY(dst_allocator_result); - KPageGroup src_pages{m_kernel, m_block_info_manager}; - KPageGroup dst_pages{m_kernel, m_block_info_manager}; - const size_t num_pages{size / PageSize}; + // Unmap the memory. + { + // Determine the number of pages being operated on. + const size_t num_pages = size / PageSize; - AddRegionToPages(src_address, num_pages, src_pages); - AddRegionToPages(dst_address, num_pages, dst_pages); + // Create page groups for the memory being unmapped. + KPageGroup pg{m_kernel, m_block_info_manager}; - R_UNLESS(dst_pages.IsEquivalentTo(src_pages), ResultInvalidMemoryRegion); + // Create the page group representing the destination. + R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); - { - auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); }); + // Ensure the page group is the valid for the source. + R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion); - R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap)); - R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, - OperationType::ChangePermissions)); + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); - block_guard.Cancel(); - } + // Unmap the aliased copy of the pages. + const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false, + DisableMergeAttribute::None}; + R_TRY( + this->Operate(dst_address, num_pages, dst_unmap_properties.perm, OperationType::Unmap)); + + // Ensure that we re-map the aliased pages on failure. + ON_RESULT_FAILURE { + this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg); + }; - // Apply the memory block updates. - m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, - KMemoryPermission::UserReadWrite, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Locked); - m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::None, KMemoryPermission::None, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); + // Try to set the permissions for the source pages back to what they should be. + const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false, + DisableMergeAttribute::EnableAndMergeHeadBodyTail}; + R_TRY(this->Operate(src_address, num_pages, src_properties.perm, + OperationType::ChangePermissions)); + + // Apply the memory block updates. + m_memory_block_manager.Update( + std::addressof(src_allocator), src_address, num_pages, src_state, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked); + m_memory_block_manager.Update( + std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); + } R_SUCCEED(); } -Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, - KMemoryPermission perm) { +Result KPageTable::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address, + size_t num_pages, KMemoryPermission perm) { ASSERT(this->IsLockedByCurrentThread()); - VAddr cur_addr{addr}; + // Create a page group to hold the pages we allocate. + KPageGroup pg{m_kernel, m_block_info_manager}; - for (const auto& node : page_linked_list) { - if (const auto result{ - Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())}; - result.IsError()) { - const size_t num_pages{(addr - cur_addr) / PageSize}; + // Allocate the pages. + R_TRY( + m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option)); - ASSERT(Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap) - .IsSuccess()); + // Ensure that the page group is closed when we're done working with it. + SCOPE_EXIT({ pg.Close(); }); - R_RETURN(result); + // Clear all pages. + for (const auto& it : pg) { + std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value, + it.GetSize()); + } + + // Map the pages. + R_RETURN(this->Operate(address, num_pages, pg, OperationType::MapGroup)); +} + +Result KPageTable::MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address, + const KPageGroup& pg, const KPageProperties properties, + bool reuse_ll) { + ASSERT(this->IsLockedByCurrentThread()); + + // Note the current address, so that we can iterate. + const KProcessAddress start_address = address; + KProcessAddress cur_address = address; + + // Ensure that we clean up on failure. + ON_RESULT_FAILURE { + ASSERT(!reuse_ll); + if (cur_address != start_address) { + const KPageProperties unmap_properties = {KMemoryPermission::None, false, false, + DisableMergeAttribute::None}; + ASSERT(this->Operate(start_address, (cur_address - start_address) / PageSize, + unmap_properties.perm, OperationType::Unmap) == ResultSuccess); } + }; - cur_addr += node.GetNumPages() * PageSize; + // Iterate, mapping all pages in the group. + for (const auto& block : pg) { + // Map and advance. + const KPageProperties cur_properties = + (cur_address == start_address) + ? properties + : KPageProperties{properties.perm, properties.io, properties.uncached, + DisableMergeAttribute::None}; + this->Operate(cur_address, block.GetNumPages(), cur_properties.perm, OperationType::Map, + block.GetAddress()); + cur_address += block.GetSize(); } + // We succeeded! R_SUCCEED(); } -Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state, - KMemoryPermission perm) { - // Check that the map is in range. - const size_t num_pages{page_linked_list.GetNumPages()}; - const size_t size{num_pages * PageSize}; - R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); +void KPageTable::RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size, + const KPageGroup& pg) { + ASSERT(this->IsLockedByCurrentThread()); - // Lock the table. - KScopedLightLock lk(m_general_lock); + // Note the current address, so that we can iterate. + const KProcessAddress start_address = address; + const KProcessAddress last_address = start_address + size - 1; + const KProcessAddress end_address = last_address + 1; - // Check the memory state. - R_TRY(this->CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free, - KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::None, KMemoryAttribute::None)); + // Iterate over the memory. + auto pg_it = pg.begin(); + ASSERT(pg_it != pg.end()); - // Create an update allocator. - Result allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - m_memory_block_slab_manager); + KPhysicalAddress pg_phys_addr = pg_it->GetAddress(); + size_t pg_pages = pg_it->GetNumPages(); - // Map the pages. - R_TRY(MapPages(address, page_linked_list, perm)); + auto it = m_memory_block_manager.FindIterator(start_address); + while (true) { + // Check that the iterator is valid. + ASSERT(it != m_memory_block_manager.end()); - // Update the blocks. - m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + // Get the memory info. + const KMemoryInfo info = it->GetMemoryInfo(); - R_SUCCEED(); + // Determine the range to map. + KProcessAddress map_address = std::max<VAddr>(info.GetAddress(), start_address); + const KProcessAddress map_end_address = std::min<VAddr>(info.GetEndAddress(), end_address); + ASSERT(map_end_address != map_address); + + // Determine if we should disable head merge. + const bool disable_head_merge = + info.GetAddress() >= start_address && + True(info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Normal); + const KPageProperties map_properties = { + info.GetPermission(), false, false, + disable_head_merge ? DisableMergeAttribute::DisableHead : DisableMergeAttribute::None}; + + // While we have pages to map, map them. + size_t map_pages = (map_end_address - map_address) / PageSize; + while (map_pages > 0) { + // Check if we're at the end of the physical block. + if (pg_pages == 0) { + // Ensure there are more pages to map. + ASSERT(pg_it != pg.end()); + + // Advance our physical block. + ++pg_it; + pg_phys_addr = pg_it->GetAddress(); + pg_pages = pg_it->GetNumPages(); + } + + // Map whatever we can. + const size_t cur_pages = std::min(pg_pages, map_pages); + ASSERT(this->Operate(map_address, map_pages, map_properties.perm, OperationType::Map, + pg_phys_addr) == ResultSuccess); + + // Advance. + map_address += cur_pages * PageSize; + map_pages -= cur_pages; + + pg_phys_addr += cur_pages * PageSize; + pg_pages -= cur_pages; + } + + // Check if we're done. + if (last_address <= info.GetLastAddress()) { + break; + } + + // Advance. + ++it; + } + + // Check that we re-mapped precisely the page group. + ASSERT((++pg_it) == pg.end()); } -Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, - bool is_pa_valid, VAddr region_start, size_t region_num_pages, +Result KPageTable::MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, + KPhysicalAddress phys_addr, bool is_pa_valid, + KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize); @@ -2084,26 +2211,30 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, KScopedLightLock lk(m_general_lock); // Find a random address to map at. - VAddr addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, - this->GetNumGuardPages()); + KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, + 0, this->GetNumGuardPages()); R_UNLESS(addr != 0, ResultOutOfMemory); ASSERT(Common::IsAligned(addr, alignment)); ASSERT(this->CanContain(addr, num_pages * PageSize, state)); ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::None, KMemoryAttribute::None) - .IsSuccess()); + KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess); // Create an update allocator. - Result allocator_result{ResultSuccess}; + Result allocator_result; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); // Perform mapping operation. if (is_pa_valid) { - R_TRY(this->Operate(addr, num_pages, perm, OperationType::Map, phys_addr)); + const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead}; + R_TRY(this->Operate(addr, num_pages, properties.perm, OperationType::Map, phys_addr)); } else { - UNIMPLEMENTED(); + R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, perm)); } // Update the blocks. @@ -2116,28 +2247,45 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, R_SUCCEED(); } -Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { - ASSERT(this->IsLockedByCurrentThread()); +Result KPageTable::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, + KMemoryPermission perm) { + // Check that the map is in range. + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); - VAddr cur_addr{addr}; + // Lock the table. + KScopedLightLock lk(m_general_lock); - for (const auto& node : page_linked_list) { - if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None, - OperationType::Unmap)}; - result.IsError()) { - R_RETURN(result); - } + // Check the memory state. + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, + KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryAttribute::None)); - cur_addr += node.GetNumPages() * PageSize; - } + // Create an update allocator. + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + + // Map the pages. + R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, perm)); + + // Update the blocks. + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); R_SUCCEED(); } -Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) { +Result KPageTable::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) { // Check that the unmap is in range. - const size_t num_pages{page_linked_list.GetNumPages()}; - const size_t size{num_pages * PageSize}; + const size_t size = num_pages * PageSize; R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. @@ -2151,13 +2299,18 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo KMemoryAttribute::None)); // Create an update allocator. - Result allocator_result{ResultSuccess}; + Result allocator_result; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + // Perform the unmap. - R_TRY(UnmapPages(address, page_linked_list)); + const KPageProperties unmap_properties = {KMemoryPermission::None, false, false, + DisableMergeAttribute::None}; + R_TRY(this->Operate(address, num_pages, unmap_properties.perm, OperationType::Unmap)); // Update the blocks. m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, @@ -2168,29 +2321,130 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo R_SUCCEED(); } -Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState state) { - // Check that the unmap is in range. +Result KPageTable::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg, + KProcessAddress region_start, size_t region_num_pages, + KMemoryState state, KMemoryPermission perm) { + ASSERT(!this->IsLockedByCurrentThread()); + + // Ensure this is a valid map request. + const size_t num_pages = pg.GetNumPages(); + R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state), + ResultInvalidCurrentMemory); + R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory); + + // Lock the table. + KScopedLightLock lk(m_general_lock); + + // Find a random address to map at. + KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize, + 0, this->GetNumGuardPages()); + R_UNLESS(addr != 0, ResultOutOfMemory); + ASSERT(this->CanContain(addr, num_pages * PageSize, state)); + ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, + KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess); + + // Create an update allocator. + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager); + R_TRY(allocator_result); + + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + + // Perform mapping operation. + const KPageProperties properties = {perm, state == KMemoryState::Io, false, + DisableMergeAttribute::DisableHead}; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); + + // Update the blocks. + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); + + // We successfully mapped the pages. + *out_addr = addr; + R_SUCCEED(); +} + +Result KPageTable::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state, + KMemoryPermission perm) { + ASSERT(!this->IsLockedByCurrentThread()); + + // Ensure this is a valid map request. + const size_t num_pages = pg.GetNumPages(); const size_t size = num_pages * PageSize; - R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); + R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory); // Lock the table. KScopedLightLock lk(m_general_lock); - // Check the memory state. - size_t num_allocator_blocks{}; + // Check if state allows us to map. + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size, + KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryAttribute::None)); + + // Create an update allocator. + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + + // Perform mapping operation. + const KPageProperties properties = {perm, state == KMemoryState::Io, false, + DisableMergeAttribute::DisableHead}; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); + + // Update the blocks. + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); + + // We successfully mapped the pages. + R_SUCCEED(); +} + +Result KPageTable::UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, + KMemoryState state) { + ASSERT(!this->IsLockedByCurrentThread()); + + // Ensure this is a valid unmap request. + const size_t num_pages = pg.GetNumPages(); + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); + + // Lock the table. + KScopedLightLock lk(m_general_lock); + + // Check if state allows us to unmap. + size_t num_allocator_blocks; R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState::All, state, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + // Check that the page group is valid. + R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), ResultInvalidCurrentMemory); + // Create an update allocator. - Result allocator_result{ResultSuccess}; + Result allocator_result; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); - // Perform the unmap. - R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap)); + // We're going to perform an update, so create a helper. + KScopedPageTableUpdater updater(this); + + // Perform unmapping operation. + const KPageProperties properties = {KMemoryPermission::None, false, false, + DisableMergeAttribute::None}; + R_TRY(this->Operate(address, num_pages, properties.perm, OperationType::Unmap)); // Update the blocks. m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, @@ -2550,54 +2804,6 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) { } } -ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_t align, - bool is_map_only, VAddr region_start, - size_t region_num_pages, KMemoryState state, - KMemoryPermission perm, PAddr map_addr) { - KScopedLightLock lk(m_general_lock); - - R_UNLESS(CanContain(region_start, region_num_pages * PageSize, state), - ResultInvalidCurrentMemory); - R_UNLESS(region_num_pages > needed_num_pages, ResultOutOfMemory); - const VAddr addr{ - AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)}; - R_UNLESS(addr, ResultOutOfMemory); - - // Create an update allocator. - Result allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - m_memory_block_slab_manager); - - if (is_map_only) { - R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr)); - } else { - // Create a page group tohold the pages we allocate. - KPageGroup pg{m_kernel, m_block_info_manager}; - - R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen( - &pg, needed_num_pages, - KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option))); - - // Ensure that the page group is closed when we're done working with it. - SCOPE_EXIT({ pg.Close(); }); - - // Clear all pages. - for (const auto& it : pg) { - std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), - m_heap_fill_value, it.GetSize()); - } - - R_TRY(Operate(addr, needed_num_pages, pg, OperationType::MapGroup)); - } - - // Update the blocks. - m_memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); - - return addr; -} - Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, KMemoryPermission perm, bool is_aligned, bool check_heap) { diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 0a454b05b..367dab613 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -24,12 +24,36 @@ class System; namespace Kernel { +enum class DisableMergeAttribute : u8 { + None = (0U << 0), + DisableHead = (1U << 0), + DisableHeadAndBody = (1U << 1), + EnableHeadAndBody = (1U << 2), + DisableTail = (1U << 3), + EnableTail = (1U << 4), + EnableAndMergeHeadBodyTail = (1U << 5), + EnableHeadBodyTail = EnableHeadAndBody | EnableTail, + DisableHeadBodyTail = DisableHeadAndBody | DisableTail, +}; + +struct KPageProperties { + KMemoryPermission perm; + bool io; + bool uncached; + DisableMergeAttribute disable_merge_attributes; +}; +static_assert(std::is_trivial_v<KPageProperties>); +static_assert(sizeof(KPageProperties) == sizeof(u32)); + class KBlockInfoManager; class KMemoryBlockManager; class KResourceLimit; class KSystemResource; class KPageTable final { +protected: + struct PageLinkedList; + public: enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll }; @@ -57,27 +81,12 @@ public: Result UnmapPhysicalMemory(VAddr addr, size_t size); Result MapMemory(VAddr dst_addr, VAddr src_addr, size_t size); Result UnmapMemory(VAddr dst_addr, VAddr src_addr, size_t size); - Result MapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state, - KMemoryPermission perm); - Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, - KMemoryState state, KMemoryPermission perm) { - R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, - this->GetRegionAddress(state), - this->GetRegionSize(state) / PageSize, state, perm)); - } - Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state); - Result UnmapPages(VAddr address, size_t num_pages, KMemoryState state); Result SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm); KMemoryInfo QueryInfo(VAddr addr); Result SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission perm); Result SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr); Result SetMaxHeapSize(size_t size); Result SetHeapSize(VAddr* out, size_t size); - ResultVal<VAddr> AllocateAndMapMemory(size_t needed_num_pages, size_t align, bool is_map_only, - VAddr region_start, size_t region_num_pages, - KMemoryState state, KMemoryPermission perm, - PAddr map_addr = 0); - Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, KMemoryPermission perm, bool is_aligned, bool check_heap); Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap); @@ -113,6 +122,40 @@ public: bool CanContain(VAddr addr, size_t size, KMemoryState state) const; + Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, + KPhysicalAddress phys_addr, KProcessAddress region_start, + size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start, + region_num_pages, state, perm)); + } + + Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, + KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, + this->GetRegionAddress(state), + this->GetRegionSize(state) / PageSize, state, perm)); + } + + Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state, + KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, PageSize, 0, false, + this->GetRegionAddress(state), + this->GetRegionSize(state) / PageSize, state, perm)); + } + + Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, + KMemoryPermission perm); + Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state); + + Result MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg, + KProcessAddress region_start, size_t region_num_pages, KMemoryState state, + KMemoryPermission perm); + Result MapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state, + KMemoryPermission perm); + Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state); + void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size, + const KPageGroup& pg); + protected: struct PageLinkedList { private: @@ -166,11 +209,9 @@ private: static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; - Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm); - Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, - bool is_pa_valid, VAddr region_start, size_t region_num_pages, - KMemoryState state, KMemoryPermission perm); - Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list); + Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, + KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start, + size_t region_num_pages, KMemoryState state, KMemoryPermission perm); bool IsRegionContiguous(VAddr addr, u64 size) const; void AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list); KMemoryInfo QueryInfoImpl(VAddr addr); @@ -265,6 +306,11 @@ private: void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address, size_t size, KMemoryPermission prot_perm); + Result AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address, + size_t num_pages, KMemoryPermission perm); + Result MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address, + const KPageGroup& pg, const KPageProperties properties, bool reuse_ll); + mutable KLightLock m_general_lock; mutable KLightLock m_map_physical_memory_lock; diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h index cb2512b0b..645c5b531 100644 --- a/src/core/hle/kernel/k_priority_queue.h +++ b/src/core/hle/kernel/k_priority_queue.h @@ -17,35 +17,41 @@ namespace Kernel { class KThread; template <typename T> -concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) { - { t.GetAffinityMask() } -> Common::ConvertibleTo<u64>; - {t.SetAffinityMask(0)}; +concept KPriorityQueueAffinityMask = ! +std::is_reference_v<T>&& requires(T& t) { + { t.GetAffinityMask() } -> Common::ConvertibleTo<u64>; + { t.SetAffinityMask(0) }; - { t.GetAffinity(0) } -> std::same_as<bool>; - {t.SetAffinity(0, false)}; - {t.SetAll()}; -}; + { t.GetAffinity(0) } -> std::same_as<bool>; + { t.SetAffinity(0, false) }; + { t.SetAll() }; + }; template <typename T> -concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) { - {typename T::QueueEntry()}; - {(typename T::QueueEntry()).Initialize()}; - {(typename T::QueueEntry()).SetPrev(std::addressof(t))}; - {(typename T::QueueEntry()).SetNext(std::addressof(t))}; - { (typename T::QueueEntry()).GetNext() } -> std::same_as<T*>; - { (typename T::QueueEntry()).GetPrev() } -> std::same_as<T*>; - { t.GetPriorityQueueEntry(0) } -> std::same_as<typename T::QueueEntry&>; - - {t.GetAffinityMask()}; - { std::remove_cvref_t<decltype(t.GetAffinityMask())>() } -> KPriorityQueueAffinityMask; - - { t.GetActiveCore() } -> Common::ConvertibleTo<s32>; - { t.GetPriority() } -> Common::ConvertibleTo<s32>; - { t.IsDummyThread() } -> Common::ConvertibleTo<bool>; -}; +concept KPriorityQueueMember = ! +std::is_reference_v<T>&& requires(T& t) { + { typename T::QueueEntry() }; + { (typename T::QueueEntry()).Initialize() }; + { (typename T::QueueEntry()).SetPrev(std::addressof(t)) }; + { (typename T::QueueEntry()).SetNext(std::addressof(t)) }; + { (typename T::QueueEntry()).GetNext() } -> std::same_as<T*>; + { (typename T::QueueEntry()).GetPrev() } -> std::same_as<T*>; + { + t.GetPriorityQueueEntry(0) + } -> std::same_as<typename T::QueueEntry&>; + + { t.GetAffinityMask() }; + { + std::remove_cvref_t<decltype(t.GetAffinityMask())>() + } -> KPriorityQueueAffinityMask; + + { t.GetActiveCore() } -> Common::ConvertibleTo<s32>; + { t.GetPriority() } -> Common::ConvertibleTo<s32>; + { t.IsDummyThread() } -> Common::ConvertibleTo<bool>; + }; template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority> -requires KPriorityQueueMember<Member> + requires KPriorityQueueMember<Member> class KPriorityQueue { public: using AffinityMaskType = std::remove_cv_t< diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index a1abf5d68..e201bb0cd 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -417,9 +417,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: } void KProcess::Run(s32 main_thread_priority, u64 stack_size) { - AllocateMainThreadStack(stack_size); + ASSERT(AllocateMainThreadStack(stack_size) == ResultSuccess); resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); - resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, main_thread_stack_size); const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)}; ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError()); @@ -675,20 +674,31 @@ void KProcess::ChangeState(State new_state) { } Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { - ASSERT(stack_size); - - // The kernel always ensures that the given stack size is page aligned. - main_thread_stack_size = Common::AlignUp(stack_size, PageSize); - - const VAddr start{page_table.GetStackRegionStart()}; - const std::size_t size{page_table.GetStackRegionEnd() - start}; - - CASCADE_RESULT(main_thread_stack_top, - page_table.AllocateAndMapMemory( - main_thread_stack_size / PageSize, PageSize, false, start, size / PageSize, - KMemoryState::Stack, KMemoryPermission::UserReadWrite)); + // Ensure that we haven't already allocated stack. + ASSERT(main_thread_stack_size == 0); + + // Ensure that we're allocating a valid stack. + stack_size = Common::AlignUp(stack_size, PageSize); + // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory); + R_UNLESS(stack_size + image_size >= image_size, ResultOutOfMemory); + + // Place a tentative reservation of memory for our new stack. + KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax, + stack_size); + R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached); + + // Allocate and map our stack. + if (stack_size) { + KProcessAddress stack_bottom; + R_TRY(page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize, + KMemoryState::Stack, KMemoryPermission::UserReadWrite)); + + main_thread_stack_top = stack_bottom + stack_size; + main_thread_stack_size = stack_size; + } - main_thread_stack_top += main_thread_stack_size; + // We succeeded! Commit our memory reservation. + mem_reservation.Commit(); R_SUCCEED(); } diff --git a/src/core/hle/kernel/k_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h index 857e21156..59b3e32ae 100644 --- a/src/core/hle/kernel/k_scoped_lock.h +++ b/src/core/hle/kernel/k_scoped_lock.h @@ -9,13 +9,14 @@ namespace Kernel { template <typename T> -concept KLockable = !std::is_reference_v<T> && requires(T & t) { - { t.Lock() } -> std::same_as<void>; - { t.Unlock() } -> std::same_as<void>; -}; +concept KLockable = ! +std::is_reference_v<T>&& requires(T& t) { + { t.Lock() } -> std::same_as<void>; + { t.Unlock() } -> std::same_as<void>; + }; template <typename T> -requires KLockable<T> + requires KLockable<T> class [[nodiscard]] KScopedLock { public: explicit KScopedLock(T* l) : lock_ptr(l) { diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 3cf2b5d91..df505edfe 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -94,15 +94,15 @@ Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t m R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission); } - return target_process.PageTable().MapPages(address, *page_group, KMemoryState::Shared, - ConvertToKMemoryPermission(map_perm)); + return target_process.PageTable().MapPageGroup(address, *page_group, KMemoryState::Shared, + ConvertToKMemoryPermission(map_perm)); } Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) { // Validate the size. R_UNLESS(size == unmap_size, ResultInvalidSize); - return target_process.PageTable().UnmapPages(address, *page_group, KMemoryState::Shared); + return target_process.PageTable().UnmapPageGroup(address, *page_group, KMemoryState::Shared); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 21207fe99..84ff3c64b 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -330,7 +330,7 @@ void KThread::Finalize() { KThread* const waiter = std::addressof(*it); // The thread shouldn't be a kernel waiter. - ASSERT(!IsKernelAddressKey(waiter->GetAddressKey())); + ASSERT(!waiter->GetAddressKeyIsKernel()); // Clear the lock owner. waiter->SetLockOwner(nullptr); @@ -763,19 +763,6 @@ void KThread::Continue() { KScheduler::OnThreadStateChanged(kernel, this, old_state); } -void KThread::WaitUntilSuspended() { - // Make sure we have a suspend requested. - ASSERT(IsSuspendRequested()); - - // Loop until the thread is not executing on any core. - for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) { - KThread* core_thread{}; - do { - core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread(); - } while (core_thread == this); - } -} - Result KThread::SetActivity(Svc::ThreadActivity activity) { // Lock ourselves. KScopedLightLock lk(activity_pause_lock); @@ -897,7 +884,7 @@ void KThread::AddWaiterImpl(KThread* thread) { } // Keep track of how many kernel waiters we have. - if (IsKernelAddressKey(thread->GetAddressKey())) { + if (thread->GetAddressKeyIsKernel()) { ASSERT((num_kernel_waiters++) >= 0); KScheduler::SetSchedulerUpdateNeeded(kernel); } @@ -911,7 +898,7 @@ void KThread::RemoveWaiterImpl(KThread* thread) { ASSERT(kernel.GlobalSchedulerContext().IsLocked()); // Keep track of how many kernel waiters we have. - if (IsKernelAddressKey(thread->GetAddressKey())) { + if (thread->GetAddressKeyIsKernel()) { ASSERT((num_kernel_waiters--) > 0); KScheduler::SetSchedulerUpdateNeeded(kernel); } @@ -987,7 +974,7 @@ KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) { KThread* thread = std::addressof(*it); // Keep track of how many kernel waiters we have. - if (IsKernelAddressKey(thread->GetAddressKey())) { + if (thread->GetAddressKeyIsKernel()) { ASSERT((num_kernel_waiters--) > 0); KScheduler::SetSchedulerUpdateNeeded(kernel); } diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 7cd94a340..8b8dc51be 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -214,8 +214,6 @@ public: void Continue(); - void WaitUntilSuspended(); - constexpr void SetSyncedIndex(s32 index) { synced_index = index; } @@ -607,13 +605,30 @@ public: return address_key_value; } - void SetAddressKey(VAddr key) { + [[nodiscard]] bool GetAddressKeyIsKernel() const { + return address_key_is_kernel; + } + + //! NB: intentional deviation from official kernel. + // + // Separate SetAddressKey into user and kernel versions + // to cope with arbitrary host pointers making their way + // into things. + + void SetUserAddressKey(VAddr key) { address_key = key; + address_key_is_kernel = false; } - void SetAddressKey(VAddr key, u32 val) { + void SetUserAddressKey(VAddr key, u32 val) { address_key = key; address_key_value = val; + address_key_is_kernel = false; + } + + void SetKernelAddressKey(VAddr key) { + address_key = key; + address_key_is_kernel = true; } void ClearWaitQueue() { @@ -662,7 +677,7 @@ private: union SyncObjectBuffer { std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> sync_objects{}; std::array<Handle, - Svc::ArgumentHandleCountMax*(sizeof(KSynchronizationObject*) / sizeof(Handle))> + Svc::ArgumentHandleCountMax * (sizeof(KSynchronizationObject*) / sizeof(Handle))> handles; constexpr SyncObjectBuffer() {} }; @@ -683,10 +698,8 @@ private: }; template <typename T> - requires( - std::same_as<T, KThread> || - std::same_as<T, RedBlackKeyType>) static constexpr int Compare(const T& lhs, - const KThread& rhs) { + requires(std::same_as<T, KThread> || std::same_as<T, RedBlackKeyType>) + static constexpr int Compare(const T& lhs, const KThread& rhs) { const u64 l_key = lhs.GetConditionVariableKey(); const u64 r_key = rhs.GetConditionVariableKey(); @@ -772,6 +785,7 @@ private: bool debug_attached{}; s8 priority_inheritance_count{}; bool resource_limit_release_hint{}; + bool address_key_is_kernel{}; StackParameters stack_parameters{}; Common::SpinLock context_guard{}; diff --git a/src/core/hle/kernel/k_thread_local_page.h b/src/core/hle/kernel/k_thread_local_page.h index fe0cff084..71254eb55 100644 --- a/src/core/hle/kernel/k_thread_local_page.h +++ b/src/core/hle/kernel/k_thread_local_page.h @@ -70,10 +70,8 @@ public: } template <typename T> - requires(std::same_as<T, KThreadLocalPage> || - std::same_as<T, RedBlackKeyType>) static constexpr int Compare(const T& lhs, - const KThreadLocalPage& - rhs) { + requires(std::same_as<T, KThreadLocalPage> || std::same_as<T, RedBlackKeyType>) + static constexpr int Compare(const T& lhs, const KThreadLocalPage& rhs) { const VAddr lval = GetRedBlackKey(lhs); const VAddr rval = GetRedBlackKey(rhs); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1fb25f221..d9eafe261 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -1198,28 +1198,35 @@ void KernelCore::Suspend(bool suspended) { const bool should_suspend{exception_exited || suspended}; const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; - std::vector<KScopedAutoObject<KThread>> process_threads; - { - KScopedSchedulerLock sl{*this}; + //! This refers to the application process, not the current process. + KScopedAutoObject<KProcess> process = CurrentProcess(); + if (process.IsNull()) { + return; + } - if (auto* process = CurrentProcess(); process != nullptr) { - process->SetActivity(activity); + // Set the new activity. + process->SetActivity(activity); - if (!should_suspend) { - // Runnable now; no need to wait. - return; - } + // Wait for process execution to stop. + bool must_wait{should_suspend}; + + // KernelCore::Suspend must be called from locked context, or we + // could race another call to SetActivity, interfering with waiting. + while (must_wait) { + KScopedSchedulerLock sl{*this}; + + // Assume that all threads have finished running. + must_wait = false; - for (auto* thread : process->GetThreadList()) { - process_threads.emplace_back(thread); + for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { + if (Scheduler(i).GetSchedulerCurrentThread()->GetOwnerProcess() == + process.GetPointerUnsafe()) { + // A thread has not finished running yet. + // Continue waiting. + must_wait = true; } } } - - // Wait for execution to stop. - for (auto& thread : process_threads) { - thread->WaitUntilSuspended(); - } } void KernelCore::ShutdownCores() { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 8d22f8d2c..5f52e1e95 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -35,6 +35,7 @@ class GlobalSchedulerContext; class KAutoObjectWithListContainer; class KClientSession; class KDebug; +class KDeviceAddressSpace; class KDynamicPageManager; class KEvent; class KEventInfo; @@ -359,6 +360,8 @@ public: return slab_heap_container->transfer_memory; } else if constexpr (std::is_same_v<T, KCodeMemory>) { return slab_heap_container->code_memory; + } else if constexpr (std::is_same_v<T, KDeviceAddressSpace>) { + return slab_heap_container->device_address_space; } else if constexpr (std::is_same_v<T, KPageBuffer>) { return slab_heap_container->page_buffer; } else if constexpr (std::is_same_v<T, KThreadLocalPage>) { @@ -431,6 +434,7 @@ private: KSlabHeap<KThread> thread; KSlabHeap<KTransferMemory> transfer_memory; KSlabHeap<KCodeMemory> code_memory; + KSlabHeap<KDeviceAddressSpace> device_address_space; KSlabHeap<KPageBuffer> page_buffer; KSlabHeap<KThreadLocalPage> thread_local_page; KSlabHeap<KSessionRequest> session_request; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index aca442196..4cb6f40a0 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1,2697 +1,16 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <algorithm> -#include <cinttypes> -#include <iterator> -#include <mutex> -#include <vector> - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/common_funcs.h" -#include "common/fiber.h" -#include "common/logging/log.h" -#include "common/scope_exit.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/debugger/debugger.h" -#include "core/hle/kernel/k_client_port.h" -#include "core/hle/kernel/k_client_session.h" -#include "core/hle/kernel/k_code_memory.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/kernel/k_handle_table.h" -#include "core/hle/kernel/k_memory_block.h" -#include "core/hle/kernel/k_memory_layout.h" -#include "core/hle/kernel/k_page_table.h" -#include "core/hle/kernel/k_port.h" +#include "common/common_types.h" #include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/k_readable_event.h" -#include "core/hle/kernel/k_resource_limit.h" -#include "core/hle/kernel/k_scheduler.h" -#include "core/hle/kernel/k_scoped_resource_reservation.h" -#include "core/hle/kernel/k_session.h" -#include "core/hle/kernel/k_shared_memory.h" -#include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/k_thread.h" -#include "core/hle/kernel/k_thread_queue.h" -#include "core/hle/kernel/k_transfer_memory.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/svc.h" -#include "core/hle/kernel/svc_results.h" -#include "core/hle/kernel/svc_types.h" #include "core/hle/kernel/svc_wrap.h" #include "core/hle/result.h" -#include "core/memory.h" -#include "core/reporter.h" namespace Kernel::Svc { namespace { -// Checks if address + size is greater than the given address -// This can return false if the size causes an overflow of a 64-bit type -// or if the given size is zero. -constexpr bool IsValidAddressRange(VAddr address, u64 size) { - return address + size > address; -} - -// Helper function that performs the common sanity checks for svcMapMemory -// and svcUnmapMemory. This is doable, as both functions perform their sanitizing -// in the same order. -Result MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr, - u64 size) { - if (!Common::Is4KBAligned(dst_addr)) { - LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(src_addr)) { - LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is 0"); - return ResultInvalidSize; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", - dst_addr, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_addr, size)) { - LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", - src_addr, size); - return ResultInvalidCurrentMemory; - } - - if (!manager.IsInsideAddressSpace(src_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", - src_addr, size); - return ResultInvalidCurrentMemory; - } - - if (manager.IsOutsideStackRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - if (manager.IsInsideHeapRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination does not fit within the heap region, addr=0x{:016X}, " - "size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - if (manager.IsInsideAliasRegion(dst_addr, size)) { - LOG_ERROR(Kernel_SVC, - "Destination does not fit within the map region, addr=0x{:016X}, " - "size=0x{:016X}", - dst_addr, size); - return ResultInvalidMemoryRegion; - } - - return ResultSuccess; -} - -enum class ResourceLimitValueType { - CurrentValue, - LimitValue, - PeakValue, -}; - -} // Anonymous namespace - -/// Set the process heap to a given Size. It can both extend and shrink the heap. -static Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size) { - LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size); - - // Validate size. - R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); - R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize); - - // Set the heap size. - R_TRY(system.Kernel().CurrentProcess()->PageTable().SetHeapSize(out_address, size)); - - return ResultSuccess; -} - -static Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) { - VAddr temp_heap_addr{}; - const Result result{SetHeapSize(system, &temp_heap_addr, heap_size)}; - *heap_addr = static_cast<u32>(temp_heap_addr); - return result; -} - -constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { - switch (perm) { - case MemoryPermission::None: - case MemoryPermission::Read: - case MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -static Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, - MemoryPermission perm) { - LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, - perm); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permission. - R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Validate that the region is in range for the current process. - auto& page_table = system.Kernel().CurrentProcess()->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory attribute. - return page_table.SetMemoryPermission(address, size, perm); -} - -static Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, - u32 attr) { - LOG_DEBUG(Kernel_SVC, - "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, - size, mask, attr); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the attribute and mask. - constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); - R_UNLESS((mask | attr) == mask, ResultInvalidCombination); - R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); - - // Validate that the region is in range for the current process. - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory attribute. - return page_table.SetMemoryAttribute(address, size, mask, attr); -} - -static Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, - u32 attr) { - return SetMemoryAttribute(system, address, size, mask, attr); -} - -/// Maps a memory range into a different range. -static Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, - src_addr, size); - - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - - if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; - result.IsError()) { - return result; - } - - return page_table.MapMemory(dst_addr, src_addr, size); -} - -static Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return MapMemory(system, dst_addr, src_addr, size); -} - -/// Unmaps a region that was previously mapped with svcMapMemory -static Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, - src_addr, size); - - auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; - - if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; - result.IsError()) { - return result; - } - - return page_table.UnmapMemory(dst_addr, src_addr, size); -} - -static Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return UnmapMemory(system, dst_addr, src_addr, size); -} - -template <typename T> -Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) { - auto& process = *system.CurrentProcess(); - auto& handle_table = process.GetHandleTable(); - - // Declare the session we're going to allocate. - T* session; - - // Reserve a new session from the process resource limit. - // FIXME: LimitableResource_SessionCountMax - KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax); - if (session_reservation.Succeeded()) { - session = T::Create(system.Kernel()); - } else { - return ResultLimitReached; - - // // We couldn't reserve a session. Check that we support dynamically expanding the - // // resource limit. - // R_UNLESS(process.GetResourceLimit() == - // &system.Kernel().GetSystemResourceLimit(), ResultLimitReached); - // R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached()); - - // // Try to allocate a session from unused slab memory. - // session = T::CreateFromUnusedSlabMemory(); - // R_UNLESS(session != nullptr, ResultLimitReached); - // ON_RESULT_FAILURE { session->Close(); }; - - // // If we're creating a KSession, we want to add two KSessionRequests to the heap, to - // // prevent request exhaustion. - // // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's - // // no reason to not do this statically. - // if constexpr (std::same_as<T, KSession>) { - // for (size_t i = 0; i < 2; i++) { - // KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory(); - // R_UNLESS(request != nullptr, ResultLimitReached); - // request->Close(); - // } - // } - - // We successfully allocated a session, so add the object we allocated to the resource - // limit. - // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1); - } - - // Check that we successfully created a session. - R_UNLESS(session != nullptr, ResultOutOfResource); - - // Initialize the session. - session->Initialize(nullptr, fmt::format("{}", name)); - - // Commit the session reservation. - session_reservation.Commit(); - - // Ensure that we clean up the session (and its only references are handle table) on function - // end. - SCOPE_EXIT({ - session->GetClientSession().Close(); - session->GetServerSession().Close(); - }); - - // Register the session. - T::Register(system.Kernel(), session); - - // Add the server session to the handle table. - R_TRY(handle_table.Add(out_server, &session->GetServerSession())); - - // Add the client session to the handle table. - const auto result = handle_table.Add(out_client, &session->GetClientSession()); - - if (!R_SUCCEEDED(result)) { - // Ensure that we maintaing a clean handle state on exit. - handle_table.Remove(*out_server); - } - - return result; -} - -static Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, - u32 is_light, u64 name) { - if (is_light) { - // return CreateSession<KLightSession>(system, out_server, out_client, name); - return ResultUnknown; - } else { - return CreateSession<KSession>(system, out_server, out_client, name); - } -} - -/// Connect to an OS service given the port name, returns the handle to the port to out -static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) { - auto& memory = system.Memory(); - if (!memory.IsValidVirtualAddress(port_name_address)) { - LOG_ERROR(Kernel_SVC, - "Port Name Address is not a valid virtual address, port_name_address=0x{:016X}", - port_name_address); - return ResultNotFound; - } - - static constexpr std::size_t PortNameMaxLength = 11; - // Read 1 char beyond the max allowed port name to detect names that are too long. - const std::string port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); - if (port_name.size() > PortNameMaxLength) { - LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength, - port_name.size()); - return ResultOutOfRange; - } - - LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); - - // Get the current handle table. - auto& kernel = system.Kernel(); - auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - - // Find the client port. - auto port = kernel.CreateNamedServicePort(port_name); - if (!port) { - LOG_ERROR(Kernel_SVC, "tried to connect to unknown port: {}", port_name); - return ResultNotFound; - } - - // Reserve a handle for the port. - // NOTE: Nintendo really does write directly to the output handle here. - R_TRY(handle_table.Reserve(out)); - auto handle_guard = SCOPE_GUARD({ handle_table.Unreserve(*out); }); - - // Create a session. - KClientSession* session{}; - R_TRY(port->CreateSession(std::addressof(session))); - - kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort()); - - // Register the session in the table, close the extra reference. - handle_table.Register(*out, session); - session->Close(); - - // We succeeded. - handle_guard.Cancel(); - return ResultSuccess; -} - -static Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, - u32 port_name_address) { - - return ConnectToNamedPort(system, out_handle, port_name_address); -} - -/// Makes a blocking IPC call to a service. -static Result SendSyncRequest(Core::System& system, Handle handle) { - auto& kernel = system.Kernel(); - - // Create the wait queue. - KThreadQueue wait_queue(kernel); - - // Get the client session from its handle. - KScopedAutoObject session = - kernel.CurrentProcess()->GetHandleTable().GetObject<KClientSession>(handle); - R_UNLESS(session.IsNotNull(), ResultInvalidHandle); - - LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); - - return session->SendSyncRequest(); -} - -static Result SendSyncRequest32(Core::System& system, Handle handle) { - return SendSyncRequest(system, handle); -} - -static Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, - s32 num_handles, Handle reply_target, s64 timeout_ns) { - auto& kernel = system.Kernel(); - auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable(); - - // Convert handle list to object table. - std::vector<KSynchronizationObject*> objs(num_handles); - R_UNLESS( - handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles), - ResultInvalidHandle); - - // Ensure handles are closed when we're done. - SCOPE_EXIT({ - for (auto i = 0; i < num_handles; ++i) { - objs[i]->Close(); - } - }); - - // Reply to the target, if one is specified. - if (reply_target != InvalidHandle) { - KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target); - R_UNLESS(session.IsNotNull(), ResultInvalidHandle); - - // If we fail to reply, we want to set the output index to -1. - // ON_RESULT_FAILURE { *out_index = -1; }; - - // Send the reply. - // R_TRY(session->SendReply()); - - Result rc = session->SendReply(); - if (!R_SUCCEEDED(rc)) { - *out_index = -1; - return rc; - } - } - - // Wait for a message. - while (true) { - // Wait for an object. - s32 index; - Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(), - static_cast<s32>(objs.size()), timeout_ns); - if (result == ResultTimedOut) { - return result; - } - - // Receive the request. - if (R_SUCCEEDED(result)) { - KServerSession* session = objs[index]->DynamicCast<KServerSession*>(); - if (session != nullptr) { - result = session->ReceiveRequest(); - if (result == ResultNotFound) { - continue; - } - } - } - - *out_index = index; - return result; - } -} - -/// Get the ID for the specified thread. -static Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the thread's id. - *out_thread_id = thread->GetId(); - return ResultSuccess; -} - -static Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, - Handle thread_handle) { - u64 out_thread_id{}; - const Result result{GetThreadId(system, &out_thread_id, thread_handle)}; - - *out_thread_id_low = static_cast<u32>(out_thread_id >> 32); - *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max()); - - return result; -} - -/// Gets the ID of the specified process or a specified thread's owning process. -static Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); - - // Get the object from the handle table. - KScopedAutoObject obj = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KAutoObject>( - static_cast<Handle>(handle)); - R_UNLESS(obj.IsNotNull(), ResultInvalidHandle); - - // Get the process from the object. - KProcess* process = nullptr; - if (KProcess* p = obj->DynamicCast<KProcess*>(); p != nullptr) { - // The object is a process, so we can use it directly. - process = p; - } else if (KThread* t = obj->DynamicCast<KThread*>(); t != nullptr) { - // The object is a thread, so we want to use its parent. - process = reinterpret_cast<KThread*>(obj.GetPointerUnsafe())->GetOwnerProcess(); - } else { - // TODO(bunnei): This should also handle debug objects before returning. - UNIMPLEMENTED_MSG("Debug objects not implemented"); - } - - // Make sure the target process exists. - R_UNLESS(process != nullptr, ResultInvalidHandle); - - // Get the process id. - *out_process_id = process->GetId(); - - return ResultSuccess; -} - -static Result GetProcessId32(Core::System& system, u32* out_process_id_low, - u32* out_process_id_high, Handle handle) { - u64 out_process_id{}; - const auto result = GetProcessId(system, &out_process_id, handle); - *out_process_id_low = static_cast<u32>(out_process_id); - *out_process_id_high = static_cast<u32>(out_process_id >> 32); - return result; -} - -/// Wait for the given handles to synchronize, timeout after the specified nanoseconds -static Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, - s32 num_handles, s64 nano_seconds) { - LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, num_handles={}, nano_seconds={}", - handles_address, num_handles, nano_seconds); - - // Ensure number of handles is valid. - R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange); - - auto& kernel = system.Kernel(); - std::vector<KSynchronizationObject*> objs(num_handles); - const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - Handle* handles = system.Memory().GetPointer<Handle>(handles_address); - - // Copy user handles. - if (num_handles > 0) { - // Convert the handles to objects. - R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, - num_handles), - ResultInvalidHandle); - for (const auto& obj : objs) { - kernel.RegisterInUseObject(obj); - } - } - - // Ensure handles are closed when we're done. - SCOPE_EXIT({ - for (s32 i = 0; i < num_handles; ++i) { - kernel.UnregisterInUseObject(objs[i]); - objs[i]->Close(); - } - }); - - return KSynchronizationObject::Wait(kernel, index, objs.data(), static_cast<s32>(objs.size()), - nano_seconds); -} - -static Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, - s32 num_handles, u32 timeout_high, s32* index) { - const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)}; - return WaitSynchronization(system, index, handles_address, num_handles, nano_seconds); -} - -/// Resumes a thread waiting on WaitSynchronization -static Result CancelSynchronization(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Cancel the thread's wait. - thread->WaitCancel(); - return ResultSuccess; -} - -static Result CancelSynchronization32(Core::System& system, Handle handle) { - return CancelSynchronization(system, handle); -} - -/// Attempts to locks a mutex -static Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag) { - LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}", - thread_handle, address, tag); - - // Validate the input address. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})", - address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(u32))) { - LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); - return ResultInvalidAddress; - } - - return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag); -} - -static Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag) { - return ArbitrateLock(system, thread_handle, address, tag); -} - -/// Unlock a mutex -static Result ArbitrateUnlock(Core::System& system, VAddr address) { - LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address); - - // Validate the input address. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, - "Attempting to arbitrate an unlock on a kernel address (address={:08X})", - address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(u32))) { - LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); - return ResultInvalidAddress; - } - - return system.Kernel().CurrentProcess()->SignalToAddress(address); -} - -static Result ArbitrateUnlock32(Core::System& system, u32 address) { - return ArbitrateUnlock(system, address); -} - -/// Break program execution -static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { - BreakReason break_reason = - static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag)); - bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0; - - bool has_dumped_buffer{}; - std::vector<u8> debug_buffer; - - const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { - if (sz == 0 || addr == 0 || has_dumped_buffer) { - return; - } - - auto& memory = system.Memory(); - - // This typically is an error code so we're going to assume this is the case - if (sz == sizeof(u32)) { - LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", memory.Read32(addr)); - } else { - // We don't know what's in here so we'll hexdump it - debug_buffer.resize(sz); - memory.ReadBlock(addr, debug_buffer.data(), sz); - std::string hexdump; - for (std::size_t i = 0; i < debug_buffer.size(); i++) { - hexdump += fmt::format("{:02X} ", debug_buffer[i]); - if (i != 0 && i % 16 == 0) { - hexdump += '\n'; - } - } - LOG_CRITICAL(Debug_Emulated, "debug_buffer=\n{}", hexdump); - } - has_dumped_buffer = true; - }; - switch (break_reason) { - case BreakReason::Panic: - LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, - info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::Assert: - LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}", - info1, info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::User: - LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); - handle_debug_buffer(info1, info2); - break; - case BreakReason::PreLoadDll: - LOG_INFO(Debug_Emulated, - "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PostLoadDll: - LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PreUnloadDll: - LOG_INFO(Debug_Emulated, - "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, - info2); - break; - case BreakReason::PostUnloadDll: - LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", - info1, info2); - break; - case BreakReason::CppException: - LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered."); - break; - default: - LOG_WARNING( - Debug_Emulated, - "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - handle_debug_buffer(info1, info2); - break; - } - - system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2, - has_dumped_buffer ? std::make_optional(debug_buffer) - : std::nullopt); - - if (!notification_only) { - LOG_CRITICAL( - Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - - handle_debug_buffer(info1, info2); - - auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - const auto thread_processor_id = current_thread->GetActiveCore(); - system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); - } - - if (system.DebuggerEnabled()) { - auto* thread = system.Kernel().GetCurrentEmuThread(); - system.GetDebugger().NotifyThreadStopped(thread); - thread->RequestSuspend(Kernel::SuspendType::Debug); - } -} - -static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { - Break(system, reason, info1, info2); -} - -/// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(Core::System& system, VAddr address, u64 len) { - if (len == 0) { - return; - } - - std::string str(len, '\0'); - system.Memory().ReadBlock(address, str.data(), str.size()); - LOG_DEBUG(Debug_Emulated, "{}", str); -} - -static void OutputDebugString32(Core::System& system, u32 address, u32 len) { - OutputDebugString(system, address, len); -} - -/// Gets system/memory information for the current process -static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, - u64 info_sub_id) { - LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, - info_sub_id, handle); - - const auto info_id_type = static_cast<InfoType>(info_id); - - switch (info_id_type) { - case InfoType::CoreMask: - case InfoType::PriorityMask: - case InfoType::AliasRegionAddress: - case InfoType::AliasRegionSize: - case InfoType::HeapRegionAddress: - case InfoType::HeapRegionSize: - case InfoType::AslrRegionAddress: - case InfoType::AslrRegionSize: - case InfoType::StackRegionAddress: - case InfoType::StackRegionSize: - case InfoType::TotalMemorySize: - case InfoType::UsedMemorySize: - case InfoType::SystemResourceSizeTotal: - case InfoType::SystemResourceSizeUsed: - case InfoType::ProgramId: - case InfoType::UserExceptionContextAddress: - case InfoType::TotalNonSystemMemorySize: - case InfoType::UsedNonSystemMemorySize: - case InfoType::IsApplication: - case InfoType::FreeThreadCount: { - if (info_sub_id != 0) { - LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, - info_sub_id); - return ResultInvalidEnumValue; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}", - info_id, info_sub_id, handle); - return ResultInvalidHandle; - } - - switch (info_id_type) { - case InfoType::CoreMask: - *result = process->GetCoreMask(); - return ResultSuccess; - - case InfoType::PriorityMask: - *result = process->GetPriorityMask(); - return ResultSuccess; - - case InfoType::AliasRegionAddress: - *result = process->PageTable().GetAliasRegionStart(); - return ResultSuccess; - - case InfoType::AliasRegionSize: - *result = process->PageTable().GetAliasRegionSize(); - return ResultSuccess; - - case InfoType::HeapRegionAddress: - *result = process->PageTable().GetHeapRegionStart(); - return ResultSuccess; - - case InfoType::HeapRegionSize: - *result = process->PageTable().GetHeapRegionSize(); - return ResultSuccess; - - case InfoType::AslrRegionAddress: - *result = process->PageTable().GetAliasCodeRegionStart(); - return ResultSuccess; - - case InfoType::AslrRegionSize: - *result = process->PageTable().GetAliasCodeRegionSize(); - return ResultSuccess; - - case InfoType::StackRegionAddress: - *result = process->PageTable().GetStackRegionStart(); - return ResultSuccess; - - case InfoType::StackRegionSize: - *result = process->PageTable().GetStackRegionSize(); - return ResultSuccess; - - case InfoType::TotalMemorySize: - *result = process->GetTotalPhysicalMemoryAvailable(); - return ResultSuccess; - - case InfoType::UsedMemorySize: - *result = process->GetTotalPhysicalMemoryUsed(); - return ResultSuccess; - - case InfoType::SystemResourceSizeTotal: - *result = process->GetSystemResourceSize(); - return ResultSuccess; - - case InfoType::SystemResourceSizeUsed: - LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); - *result = process->GetSystemResourceUsage(); - return ResultSuccess; - - case InfoType::ProgramId: - *result = process->GetProgramID(); - return ResultSuccess; - - case InfoType::UserExceptionContextAddress: - *result = process->GetProcessLocalRegionAddress(); - return ResultSuccess; - - case InfoType::TotalNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); - return ResultSuccess; - - case InfoType::UsedNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); - return ResultSuccess; - - case InfoType::FreeThreadCount: - *result = process->GetFreeThreadCount(); - return ResultSuccess; - - default: - break; - } - - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ResultInvalidEnumValue; - } - - case InfoType::DebuggerAttached: - *result = 0; - return ResultSuccess; - - case InfoType::ResourceLimit: { - if (handle != 0) { - LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); - return ResultInvalidHandle; - } - - if (info_sub_id != 0) { - LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, - info_sub_id); - return ResultInvalidCombination; - } - - KProcess* const current_process = system.Kernel().CurrentProcess(); - KHandleTable& handle_table = current_process->GetHandleTable(); - const auto resource_limit = current_process->GetResourceLimit(); - if (!resource_limit) { - *result = Svc::InvalidHandle; - // Yes, the kernel considers this a successful operation. - return ResultSuccess; - } - - Handle resource_handle{}; - R_TRY(handle_table.Add(&resource_handle, resource_limit)); - - *result = resource_handle; - return ResultSuccess; - } - - case InfoType::RandomEntropy: - if (handle != 0) { - LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", - handle); - return ResultInvalidHandle; - } - - if (info_sub_id >= KProcess::RANDOM_ENTROPY_SIZE) { - LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", - KProcess::RANDOM_ENTROPY_SIZE, info_sub_id); - return ResultInvalidCombination; - } - - *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); - return ResultSuccess; - - case InfoType::InitialProcessIdRange: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query privileged process id bounds, returned 0"); - *result = 0; - return ResultSuccess; - - case InfoType::ThreadTickCount: { - constexpr u64 num_cpus = 4; - if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { - LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, - info_sub_id); - return ResultInvalidCombination; - } - - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>( - static_cast<Handle>(handle)); - if (thread.IsNull()) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", - static_cast<Handle>(handle)); - return ResultInvalidHandle; - } - - const auto& core_timing = system.CoreTiming(); - const auto& scheduler = *system.Kernel().CurrentScheduler(); - const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - const bool same_thread = current_thread == thread.GetPointerUnsafe(); - - const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); - u64 out_ticks = 0; - if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { - const u64 thread_ticks = current_thread->GetCpuTime(); - - out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); - } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { - out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; - } - - *result = out_ticks; - return ResultSuccess; - } - case InfoType::IdleTickCount: { - // Verify the input handle is invalid. - R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); - - // Verify the requested core is valid. - const bool core_valid = - (info_sub_id == 0xFFFFFFFFFFFFFFFF) || - (info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex())); - R_UNLESS(core_valid, ResultInvalidCombination); - - // Get the idle tick count. - *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); - return ResultSuccess; - } - case InfoType::MesosphereCurrentProcess: { - // Verify the input handle is invalid. - R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); - - // Verify the sub-type is valid. - R_UNLESS(info_sub_id == 0, ResultInvalidCombination); - - // Get the handle table. - KProcess* current_process = system.Kernel().CurrentProcess(); - KHandleTable& handle_table = current_process->GetHandleTable(); - - // Get a new handle for the current process. - Handle tmp; - R_TRY(handle_table.Add(&tmp, current_process)); - - // Set the output. - *result = tmp; - - // We succeeded. - return ResultSuccess; - } - default: - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); - return ResultInvalidEnumValue; - } -} - -static Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, - u32 info_id, u32 handle, u32 sub_id_high) { - const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; - u64 res_value{}; - - const Result result{GetInfo(system, &res_value, info_id, handle, sub_id)}; - *result_high = static_cast<u32>(res_value >> 32); - *result_low = static_cast<u32>(res_value & std::numeric_limits<u32>::max()); - - return result; -} - -/// Maps memory at a desired address -static Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); - - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is zero"); - return ResultInvalidSize; - } - - if (!(addr < addr + size)) { - LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ResultInvalidMemoryRegion; - } - - KProcess* const current_process{system.Kernel().CurrentProcess()}; - auto& page_table{current_process->PageTable()}; - - if (current_process->GetSystemResourceSize() == 0) { - LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ResultInvalidState; - } - - if (!page_table.IsInsideAddressSpace(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - if (page_table.IsOutsideAliasRegion(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - return page_table.MapPhysicalMemory(addr, size); -} - -static Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return MapPhysicalMemory(system, addr, size); -} - -/// Unmaps memory previously mapped via MapPhysicalMemory -static Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); - - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); - return ResultInvalidSize; - } - - if (size == 0) { - LOG_ERROR(Kernel_SVC, "Size is zero"); - return ResultInvalidSize; - } - - if (!(addr < addr + size)) { - LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); - return ResultInvalidMemoryRegion; - } - - KProcess* const current_process{system.Kernel().CurrentProcess()}; - auto& page_table{current_process->PageTable()}; - - if (current_process->GetSystemResourceSize() == 0) { - LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); - return ResultInvalidState; - } - - if (!page_table.IsInsideAddressSpace(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - if (page_table.IsOutsideAliasRegion(addr, size)) { - LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, - size); - return ResultInvalidMemoryRegion; - } - - return page_table.UnmapPhysicalMemory(addr, size); -} - -static Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return UnmapPhysicalMemory(system, addr, size); -} - -/// Sets the thread activity -static Result SetThreadActivity(Core::System& system, Handle thread_handle, - ThreadActivity thread_activity) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, - thread_activity); - - // Validate the activity. - constexpr auto IsValidThreadActivity = [](ThreadActivity activity) { - return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused; - }; - R_UNLESS(IsValidThreadActivity(thread_activity), ResultInvalidEnumValue); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Check that the activity is being set on a non-current thread for the current process. - R_UNLESS(thread->GetOwnerProcess() == system.Kernel().CurrentProcess(), ResultInvalidHandle); - R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); - - // Set the activity. - R_TRY(thread->SetActivity(thread_activity)); - - return ResultSuccess; -} - -static Result SetThreadActivity32(Core::System& system, Handle thread_handle, - Svc::ThreadActivity thread_activity) { - return SetThreadActivity(system, thread_handle, thread_activity); -} - -/// Gets the thread context -static Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context, - thread_handle); - - auto& kernel = system.Kernel(); - - // Get the thread from its handle. - KScopedAutoObject thread = - kernel.CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Require the handle be to a non-current thread in the current process. - const auto* current_process = kernel.CurrentProcess(); - R_UNLESS(current_process == thread->GetOwnerProcess(), ResultInvalidId); - - // Verify that the thread isn't terminated. - R_UNLESS(thread->GetState() != ThreadState::Terminated, ResultTerminationRequested); - - /// Check that the thread is not the current one. - /// NOTE: Nintendo does not check this, and thus the following loop will deadlock. - R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(kernel), ResultInvalidId); - - // Try to get the thread context until the thread isn't current on any core. - while (true) { - KScopedSchedulerLock sl{kernel}; - - // TODO(bunnei): Enforce that thread is suspended for debug here. - - // If the thread's raw state isn't runnable, check if it's current on some core. - if (thread->GetRawState() != ThreadState::Runnable) { - bool current = false; - for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { - if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetSchedulerCurrentThread()) { - current = true; - break; - } - } - - // If the thread is current, retry until it isn't. - if (current) { - continue; - } - } - - // Get the thread context. - std::vector<u8> context; - R_TRY(thread->GetThreadContext3(context)); - - // Copy the thread context to user space. - system.Memory().WriteBlock(out_context, context.data(), context.size()); - - return ResultSuccess; - } - - return ResultSuccess; -} - -static Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) { - return GetThreadContext(system, out_context, thread_handle); -} - -/// Gets the priority for the specified thread -static Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) { - LOG_TRACE(Kernel_SVC, "called"); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the thread's priority. - *out_priority = thread->GetPriority(); - return ResultSuccess; -} - -static Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) { - return GetThreadPriority(system, out_priority, handle); -} - -/// Sets the priority for the specified thread -static Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) { - // Get the current process. - KProcess& process = *system.Kernel().CurrentProcess(); - - // Validate the priority. - R_UNLESS(HighestThreadPriority <= priority && priority <= LowestThreadPriority, - ResultInvalidPriority); - R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); - - // Get the thread from its handle. - KScopedAutoObject thread = process.GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Set the thread priority. - thread->SetBasePriority(priority); - return ResultSuccess; -} - -static Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) { - return SetThreadPriority(system, thread_handle, priority); -} - -/// Get which CPU core is executing the current thread -static u32 GetCurrentProcessorNumber(Core::System& system) { - LOG_TRACE(Kernel_SVC, "called"); - return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex()); -} - -static u32 GetCurrentProcessorNumber32(Core::System& system) { - return GetCurrentProcessorNumber(system); -} - -namespace { - -constexpr bool IsValidSharedMemoryPermission(Svc::MemoryPermission perm) { - switch (perm) { - case Svc::MemoryPermission::Read: - case Svc::MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -[[maybe_unused]] constexpr bool IsValidRemoteSharedMemoryPermission(Svc::MemoryPermission perm) { - return IsValidSharedMemoryPermission(perm) || perm == Svc::MemoryPermission::DontCare; -} - -constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) { - switch (perm) { - case Svc::MemoryPermission::None: - case Svc::MemoryPermission::Read: - case Svc::MemoryPermission::ReadWrite: - case Svc::MemoryPermission::ReadExecute: - return true; - default: - return false; - } -} - -constexpr bool IsValidMapCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::ReadWrite; -} - -constexpr bool IsValidMapToOwnerCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::Read || perm == Svc::MemoryPermission::ReadExecute; -} - -constexpr bool IsValidUnmapCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::None; -} - -constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(Svc::MemoryPermission perm) { - return perm == Svc::MemoryPermission::None; -} - -} // Anonymous namespace - -static Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, - Svc::MemoryPermission map_perm) { - LOG_TRACE(Kernel_SVC, - "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", - shmem_handle, address, size, map_perm); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permission. - R_UNLESS(IsValidSharedMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); - - // Get the current process. - auto& process = *system.Kernel().CurrentProcess(); - auto& page_table = process.PageTable(); - - // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); - R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); - - // Verify that the mapping is in range. - R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); - - // Add the shared memory to the process. - R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); - - // Ensure that we clean up the shared memory if we fail to map it. - auto guard = - SCOPE_GUARD({ process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }); - - // Map the shared memory. - R_TRY(shmem->Map(process, address, size, map_perm)); - - // We succeeded. - guard.Cancel(); - return ResultSuccess; -} - -static Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, - Svc::MemoryPermission map_perm) { - return MapSharedMemory(system, shmem_handle, address, size, map_perm); -} - -static Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, - u64 size) { - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Get the current process. - auto& process = *system.Kernel().CurrentProcess(); - auto& page_table = process.PageTable(); - - // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); - R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); - - // Verify that the mapping is in range. - R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); - - // Unmap the shared memory. - R_TRY(shmem->Unmap(process, address, size)); - - // Remove the shared memory from the process. - process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); - - return ResultSuccess; -} - -static Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, - u32 size) { - return UnmapSharedMemory(system, shmem_handle, address, size); -} - -static Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, - u64 size, Svc::MemoryPermission perm) { - LOG_TRACE(Kernel_SVC, - "called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", - process_handle, address, size, perm); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); - R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); - - // Validate the memory permission. - R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Get the process from its handle. - KScopedAutoObject process = - system.CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); - R_UNLESS(process.IsNotNull(), ResultInvalidHandle); - - // Validate that the address is in range. - auto& page_table = process->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Set the memory permission. - return page_table.SetProcessMemoryPermission(address, size, perm); -} - -static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, - VAddr src_address, u64 size) { - LOG_TRACE(Kernel_SVC, - "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", - dst_address, process_handle, src_address, size); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); - R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); - - // Get the processes. - KProcess* dst_process = system.CurrentProcess(); - KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); - R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); - - // Get the page tables. - auto& dst_pt = dst_process->PageTable(); - auto& src_pt = src_process->PageTable(); - - // Validate that the mapping is in range. - R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); - R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), - ResultInvalidMemoryRegion); - - // Create a new page group. - KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()}; - R_TRY(src_pt.MakeAndOpenPageGroup( - std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess, - KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::All, KMemoryAttribute::None)); - - // Map the group. - R_TRY(dst_pt.MapPages(dst_address, pg, KMemoryState::SharedCode, - KMemoryPermission::UserReadWrite)); - - return ResultSuccess; -} - -static Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, - VAddr src_address, u64 size) { - LOG_TRACE(Kernel_SVC, - "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", - dst_address, process_handle, src_address, size); - - // Validate the address/size. - R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); - R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); - - // Get the processes. - KProcess* dst_process = system.CurrentProcess(); - KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); - R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); - - // Get the page tables. - auto& dst_pt = dst_process->PageTable(); - auto& src_pt = src_process->PageTable(); - - // Validate that the mapping is in range. - R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); - R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), - ResultInvalidMemoryRegion); - - // Unmap the memory. - R_TRY(dst_pt.UnmapProcessMemory(dst_address, size, src_pt, src_address)); - - return ResultSuccess; -} - -static Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size); - - // Get kernel instance. - auto& kernel = system.Kernel(); - - // Validate address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Create the code memory. - - KCodeMemory* code_mem = KCodeMemory::Create(kernel); - R_UNLESS(code_mem != nullptr, ResultOutOfResource); - - // Verify that the region is in range. - R_UNLESS(system.CurrentProcess()->PageTable().Contains(address, size), - ResultInvalidCurrentMemory); - - // Initialize the code memory. - R_TRY(code_mem->Initialize(system.DeviceMemory(), address, size)); - - // Register the code memory. - KCodeMemory::Register(kernel, code_mem); - - // Add the code memory to the handle table. - R_TRY(system.CurrentProcess()->GetHandleTable().Add(out, code_mem)); - - code_mem->Close(); - - return ResultSuccess; -} - -static Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size) { - return CreateCodeMemory(system, out, address, size); -} - -static Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, - VAddr address, size_t size, Svc::MemoryPermission perm) { - - LOG_TRACE(Kernel_SVC, - "called, code_memory_handle=0x{:X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, " - "permission=0x{:X}", - code_memory_handle, operation, address, size, perm); - - // Validate the address / size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Get the code memory from its handle. - KScopedAutoObject code_mem = - system.CurrentProcess()->GetHandleTable().GetObject<KCodeMemory>(code_memory_handle); - R_UNLESS(code_mem.IsNotNull(), ResultInvalidHandle); - - // NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. - // This enables homebrew usage of these SVCs for JIT. - - // Perform the operation. - switch (static_cast<CodeMemoryOperation>(operation)) { - case CodeMemoryOperation::Map: { - // Check that the region is in range. - R_UNLESS( - system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidMapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Map the memory. - R_TRY(code_mem->Map(address, size)); - } break; - case CodeMemoryOperation::Unmap: { - // Check that the region is in range. - R_UNLESS( - system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Unmap the memory. - R_TRY(code_mem->Unmap(address, size)); - } break; - case CodeMemoryOperation::MapToOwner: { - // Check that the region is in range. - R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, - KMemoryState::GeneratedCode), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Map the memory to its owner. - R_TRY(code_mem->MapToOwner(address, size, perm)); - } break; - case CodeMemoryOperation::UnmapFromOwner: { - // Check that the region is in range. - R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, - KMemoryState::GeneratedCode), - ResultInvalidMemoryRegion); - - // Check the memory permission. - R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); - - // Unmap the memory from its owner. - R_TRY(code_mem->UnmapFromOwner(address, size)); - } break; - default: - return ResultInvalidEnumValue; - } - - return ResultSuccess; -} - -static Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, - u64 address, u64 size, Svc::MemoryPermission perm) { - return ControlCodeMemory(system, code_memory_handle, operation, address, size, perm); -} - -static Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, - VAddr page_info_address, Handle process_handle, VAddr address) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", - process_handle); - return ResultInvalidHandle; - } - - auto& memory{system.Memory()}; - const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()}; - - memory.Write64(memory_info_address + 0x00, memory_info.base_address); - memory.Write64(memory_info_address + 0x08, memory_info.size); - memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff); - memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute)); - memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission)); - memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count); - memory.Write32(memory_info_address + 0x20, memory_info.device_count); - memory.Write32(memory_info_address + 0x24, 0); - - // Page info appears to be currently unused by the kernel and is always set to zero. - memory.Write32(page_info_address, 0); - - return ResultSuccess; -} - -static Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, - VAddr query_address) { - LOG_TRACE(Kernel_SVC, - "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, " - "query_address=0x{:016X}", - memory_info_address, page_info_address, query_address); - - return QueryProcessMemory(system, memory_info_address, page_info_address, CurrentProcess, - query_address); -} - -static Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, - u32 query_address) { - return QueryMemory(system, memory_info_address, page_info_address, query_address); -} - -static Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, - u64 src_address, u64 size) { - LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, " - "src_address=0x{:016X}, size=0x{:016X}", - process_handle, dst_address, src_address, size); - - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", - src_address); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", - dst_address); - return ResultInvalidAddress; - } - - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", - process_handle); - return ResultInvalidHandle; - } - - auto& page_table = process->PageTable(); - if (!page_table.IsInsideAddressSpace(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - if (!page_table.IsInsideASLRRegion(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidMemoryRegion; - } - - return page_table.MapCodeMemory(dst_address, src_address, size); -} - -static Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, - u64 src_address, u64 size) { - LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " - "size=0x{:016X}", - process_handle, dst_address, src_address, size); - - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", - dst_address); - return ResultInvalidAddress; - } - - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", - src_address); - return ResultInvalidAddress; - } - - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); - return ResultInvalidSize; - } - - if (!IsValidAddressRange(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidCurrentMemory; - } - - if (!IsValidAddressRange(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", - process_handle); - return ResultInvalidHandle; - } - - auto& page_table = process->PageTable(); - if (!page_table.IsInsideAddressSpace(src_address, size)) { - LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", - src_address, size); - return ResultInvalidCurrentMemory; - } - - if (!page_table.IsInsideASLRRegion(dst_address, size)) { - LOG_ERROR(Kernel_SVC, - "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " - "size=0x{:016X}).", - dst_address, size); - return ResultInvalidMemoryRegion; - } - - return page_table.UnmapCodeMemory(dst_address, src_address, size, - KPageTable::ICacheInvalidationStrategy::InvalidateAll); -} - -/// Exits the current process -static void ExitProcess(Core::System& system) { - auto* current_process = system.Kernel().CurrentProcess(); - - LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); - ASSERT_MSG(current_process->GetState() == KProcess::State::Running, - "Process has already exited"); - - system.Exit(); -} - -static void ExitProcess32(Core::System& system) { - ExitProcess(system); -} - -namespace { - -constexpr bool IsValidVirtualCoreId(int32_t core_id) { - return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES)); -} - -} // Anonymous namespace - -/// Creates a new thread -static Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, - VAddr stack_bottom, u32 priority, s32 core_id) { - LOG_DEBUG(Kernel_SVC, - "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " - "priority=0x{:08X}, core_id=0x{:08X}", - entry_point, arg, stack_bottom, priority, core_id); - - // Adjust core id, if it's the default magic. - auto& kernel = system.Kernel(); - auto& process = *kernel.CurrentProcess(); - if (core_id == IdealCoreUseProcessValue) { - core_id = process.GetIdealCoreId(); - } - - // Validate arguments. - if (!IsValidVirtualCoreId(core_id)) { - LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id); - return ResultInvalidCoreId; - } - if (((1ULL << core_id) & process.GetCoreMask()) == 0) { - LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id); - return ResultInvalidCoreId; - } - - if (HighestThreadPriority > priority || priority > LowestThreadPriority) { - LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority); - return ResultInvalidPriority; - } - if (!process.CheckThreadPriority(priority)) { - LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority); - return ResultInvalidPriority; - } - - // Reserve a new thread from the process resource limit (waiting up to 100ms). - KScopedResourceReservation thread_reservation( - kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1, - system.CoreTiming().GetGlobalTimeNs().count() + 100000000); - if (!thread_reservation.Succeeded()) { - LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); - return ResultLimitReached; - } - - // Create the thread. - KThread* thread = KThread::Create(kernel); - if (!thread) { - LOG_ERROR(Kernel_SVC, "Unable to create new threads. Thread creation limit reached."); - return ResultOutOfResource; - } - SCOPE_EXIT({ thread->Close(); }); - - // Initialize the thread. - { - KScopedLightLock lk{process.GetStateLock()}; - R_TRY(KThread::InitializeUserThread(system, thread, entry_point, arg, stack_bottom, - priority, core_id, &process)); - } - - // Set the thread name for debugging purposes. - thread->SetName(fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *out_handle)); - - // Commit the thread reservation. - thread_reservation.Commit(); - - // Register the new thread. - KThread::Register(kernel, thread); - - // Add the thread to the handle table. - R_TRY(process.GetHandleTable().Add(out_handle, thread)); - - return ResultSuccess; -} - -static Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, - u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) { - return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); -} - -/// Starts the thread for the provided handle -static Result StartThread(Core::System& system, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Try to start the thread. - R_TRY(thread->Run()); - - // If we succeeded, persist a reference to the thread. - thread->Open(); - system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe()); - - return ResultSuccess; -} - -static Result StartThread32(Core::System& system, Handle thread_handle) { - return StartThread(system, thread_handle); -} - -/// Called when a thread exits -static void ExitThread(Core::System& system) { - LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); - - auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); - system.GlobalSchedulerContext().RemoveThread(current_thread); - current_thread->Exit(); - system.Kernel().UnregisterInUseObject(current_thread); -} - -static void ExitThread32(Core::System& system) { - ExitThread(system); -} - -/// Sleep the current thread -static void SleepThread(Core::System& system, s64 nanoseconds) { - auto& kernel = system.Kernel(); - const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); - - LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); - - // When the input tick is positive, sleep. - if (nanoseconds > 0) { - // Convert the timeout from nanoseconds to ticks. - // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... - - // Sleep. - // NOTE: Nintendo does not check the result of this sleep. - static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); - } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { - KScheduler::YieldWithoutCoreMigration(kernel); - } else if (yield_type == Svc::YieldType::WithCoreMigration) { - KScheduler::YieldWithCoreMigration(kernel); - } else if (yield_type == Svc::YieldType::ToAnyThread) { - KScheduler::YieldToAnyThread(kernel); - } else { - // Nintendo does nothing at all if an otherwise invalid value is passed. - ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); - } -} - -static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { - const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); - SleepThread(system, nanoseconds); -} - -/// Wait process wide key atomic -static Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, - s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, - cv_key, tag, timeout_ns); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - - // Convert timeout from nanoseconds to ticks. - s64 timeout{}; - if (timeout_ns > 0) { - const s64 offset_tick(timeout_ns); - if (offset_tick > 0) { - timeout = offset_tick + 2; - if (timeout <= 0) { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = timeout_ns; - } - - // Wait on the condition variable. - return system.Kernel().CurrentProcess()->WaitConditionVariable( - address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout); -} - -static Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, - u32 timeout_ns_low, u32 timeout_ns_high) { - const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); - return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns); -} - -/// Signal process wide key -static void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) { - LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count); - - // Signal the condition variable. - return system.Kernel().CurrentProcess()->SignalConditionVariable( - Common::AlignDown(cv_key, sizeof(u32)), count); -} - -static void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) { - SignalProcessWideKey(system, cv_key, count); -} - -namespace { - -constexpr bool IsValidSignalType(Svc::SignalType type) { - switch (type) { - case Svc::SignalType::Signal: - case Svc::SignalType::SignalAndIncrementIfEqual: - case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: - return true; - default: - return false; - } -} - -constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { - switch (type) { - case Svc::ArbitrationType::WaitIfLessThan: - case Svc::ArbitrationType::DecrementAndWaitIfLessThan: - case Svc::ArbitrationType::WaitIfEqual: - return true; - default: - return false; - } -} - -} // namespace - -// Wait for an address (via Address Arbiter) -static Result WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type, - s32 value, s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", - address, arb_type, value, timeout_ns); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - if (!IsValidArbitrationType(arb_type)) { - LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type); - return ResultInvalidEnumValue; - } - - // Convert timeout from nanoseconds to ticks. - s64 timeout{}; - if (timeout_ns > 0) { - const s64 offset_tick(timeout_ns); - if (offset_tick > 0) { - timeout = offset_tick + 2; - if (timeout <= 0) { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = std::numeric_limits<s64>::max(); - } - } else { - timeout = timeout_ns; - } - - return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout); -} - -static Result WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type, - s32 value, u32 timeout_ns_low, u32 timeout_ns_high) { - const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); - return WaitForAddress(system, address, arb_type, value, timeout); -} - -// Signals to an address (via Address Arbiter) -static Result SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type, - s32 value, s32 count) { - LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", - address, signal_type, value, count); - - // Validate input. - if (IsKernelAddress(address)) { - LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address); - return ResultInvalidCurrentMemory; - } - if (!Common::IsAligned(address, sizeof(s32))) { - LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address); - return ResultInvalidAddress; - } - if (!IsValidSignalType(signal_type)) { - LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type); - return ResultInvalidEnumValue; - } - - return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value, - count); -} - -static void SynchronizePreemptionState(Core::System& system) { - auto& kernel = system.Kernel(); - - // Lock the scheduler. - KScopedSchedulerLock sl{kernel}; - - // If the current thread is pinned, unpin it. - KProcess* cur_process = system.Kernel().CurrentProcess(); - const auto core_id = GetCurrentCoreId(kernel); - - if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) { - // Clear the current thread's interrupt flag. - GetCurrentThread(kernel).ClearInterruptFlag(); - - // Unpin the current thread. - cur_process->UnpinCurrentThread(core_id); - } -} - -static Result SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type, - s32 value, s32 count) { - return SignalToAddress(system, address, signal_type, value, count); -} - -static void KernelDebug([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 kernel_debug_type, [[maybe_unused]] u64 param1, - [[maybe_unused]] u64 param2, [[maybe_unused]] u64 param3) { - // Intentionally do nothing, as this does nothing in released kernel binaries. -} - -static void ChangeKernelTraceState([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 trace_state) { - // Intentionally do nothing, as this does nothing in released kernel binaries. -} - -/// This returns the total CPU ticks elapsed since the CPU was powered-on -static u64 GetSystemTick(Core::System& system) { - LOG_TRACE(Kernel_SVC, "called"); - - auto& core_timing = system.CoreTiming(); - - // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) - const u64 result{core_timing.GetClockTicks()}; - - if (!system.Kernel().IsMulticore()) { - core_timing.AddTicks(400U); - } - - return result; -} - -static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { - const auto time = GetSystemTick(system); - *time_low = static_cast<u32>(time); - *time_high = static_cast<u32>(time >> 32); -} - -/// Close a handle -static Result CloseHandle(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); - - // Remove the handle. - R_UNLESS(system.Kernel().CurrentProcess()->GetHandleTable().Remove(handle), - ResultInvalidHandle); - - return ResultSuccess; -} - -static Result CloseHandle32(Core::System& system, Handle handle) { - return CloseHandle(system, handle); -} - -/// Clears the signaled state of an event or process. -static Result ResetSignal(Core::System& system, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); - - // Get the current handle table. - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Try to reset as readable event. - { - KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(handle); - if (readable_event.IsNotNull()) { - return readable_event->Reset(); - } - } - - // Try to reset as process. - { - KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); - if (process.IsNotNull()) { - return process->Reset(); - } - } - - LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle); - - return ResultInvalidHandle; -} - -static Result ResetSignal32(Core::System& system, Handle handle) { - return ResetSignal(system, handle); -} - -namespace { - -constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) { - switch (perm) { - case MemoryPermission::None: - case MemoryPermission::Read: - case MemoryPermission::ReadWrite: - return true; - default: - return false; - } -} - -} // Anonymous namespace - -/// Creates a TransferMemory object -static Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, - MemoryPermission map_perm) { - auto& kernel = system.Kernel(); - - // Validate the size. - R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); - R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS((address < address + size), ResultInvalidCurrentMemory); - - // Validate the permissions. - R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); - - // Get the current process and handle table. - auto& process = *kernel.CurrentProcess(); - auto& handle_table = process.GetHandleTable(); - - // Reserve a new transfer memory from the process resource limit. - KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), - LimitableResource::TransferMemoryCountMax); - R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached); - - // Create the transfer memory. - KTransferMemory* trmem = KTransferMemory::Create(kernel); - R_UNLESS(trmem != nullptr, ResultOutOfResource); - - // Ensure the only reference is in the handle table when we're done. - SCOPE_EXIT({ trmem->Close(); }); - - // Ensure that the region is in range. - R_UNLESS(process.PageTable().Contains(address, size), ResultInvalidCurrentMemory); - - // Initialize the transfer memory. - R_TRY(trmem->Initialize(address, size, map_perm)); - - // Commit the reservation. - trmem_reservation.Commit(); - - // Register the transfer memory. - KTransferMemory::Register(kernel, trmem); - - // Add the transfer memory to the handle table. - R_TRY(handle_table.Add(out, trmem)); - - return ResultSuccess; -} - -static Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, - MemoryPermission map_perm) { - return CreateTransferMemory(system, out, address, size, map_perm); -} - -static Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, - u64* out_affinity_mask) { - LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Get the core mask. - R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask)); - - return ResultSuccess; -} - -static Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, - u32* out_affinity_mask_low, u32* out_affinity_mask_high) { - u64 out_affinity_mask{}; - const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask); - *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32); - *out_affinity_mask_low = static_cast<u32>(out_affinity_mask); - return result; -} - -static Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, - u64 affinity_mask) { - // Determine the core id/affinity mask. - if (core_id == IdealCoreUseProcessValue) { - core_id = system.Kernel().CurrentProcess()->GetIdealCoreId(); - affinity_mask = (1ULL << core_id); - } else { - // Validate the affinity mask. - const u64 process_core_mask = system.Kernel().CurrentProcess()->GetCoreMask(); - R_UNLESS((affinity_mask | process_core_mask) == process_core_mask, ResultInvalidCoreId); - R_UNLESS(affinity_mask != 0, ResultInvalidCombination); - - // Validate the core id. - if (IsValidVirtualCoreId(core_id)) { - R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, ResultInvalidCombination); - } else { - R_UNLESS(core_id == IdealCoreNoUpdate || core_id == IdealCoreDontCare, - ResultInvalidCoreId); - } - } - - // Get the thread from its handle. - KScopedAutoObject thread = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); - R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); - - // Set the core mask. - R_TRY(thread->SetCoreMask(core_id, affinity_mask)); - - return ResultSuccess; -} - -static Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, - u32 affinity_mask_low, u32 affinity_mask_high) { - const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); - return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask); -} - -static Result SignalEvent(Core::System& system, Handle event_handle) { - LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); - - // Get the current handle table. - const KHandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Get the event. - KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); - R_UNLESS(event.IsNotNull(), ResultInvalidHandle); - - return event->Signal(); -} - -static Result SignalEvent32(Core::System& system, Handle event_handle) { - return SignalEvent(system, event_handle); -} - -static Result ClearEvent(Core::System& system, Handle event_handle) { - LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); - - // Get the current handle table. - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - - // Try to clear the writable event. - { - KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); - if (event.IsNotNull()) { - return event->Clear(); - } - } - - // Try to clear the readable event. - { - KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(event_handle); - if (readable_event.IsNotNull()) { - return readable_event->Clear(); - } - } - - LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle); - - return ResultInvalidHandle; -} - -static Result ClearEvent32(Core::System& system, Handle event_handle) { - return ClearEvent(system, event_handle); -} - -static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { - LOG_DEBUG(Kernel_SVC, "called"); - - // Get the kernel reference and handle table. - auto& kernel = system.Kernel(); - auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); - - // Reserve a new event from the process resource limit - KScopedResourceReservation event_reservation(kernel.CurrentProcess(), - LimitableResource::EventCountMax); - R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); - - // Create a new event. - KEvent* event = KEvent::Create(kernel); - R_UNLESS(event != nullptr, ResultOutOfResource); - - // Initialize the event. - event->Initialize(kernel.CurrentProcess()); - - // Commit the thread reservation. - event_reservation.Commit(); - - // Ensure that we clean up the event (and its only references are handle table) on function end. - SCOPE_EXIT({ - event->GetReadableEvent().Close(); - event->Close(); - }); - - // Register the event. - KEvent::Register(kernel, event); - - // Add the event to the handle table. - R_TRY(handle_table.Add(out_write, event)); - - // Ensure that we maintaing a clean handle state on exit. - auto handle_guard = SCOPE_GUARD({ handle_table.Remove(*out_write); }); - - // Add the readable event to the handle table. - R_TRY(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); - - // We succeeded. - handle_guard.Cancel(); - return ResultSuccess; -} - -static Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) { - return CreateEvent(system, out_write, out_read); -} - -static Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); - - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); - if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", - process_handle); - return ResultInvalidHandle; - } - - const auto info_type = static_cast<ProcessInfoType>(type); - if (info_type != ProcessInfoType::ProcessState) { - LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type); - return ResultInvalidEnumValue; - } - - *out = static_cast<u64>(process->GetState()); - return ResultSuccess; -} - -static Result CreateResourceLimit(Core::System& system, Handle* out_handle) { - LOG_DEBUG(Kernel_SVC, "called"); - - // Create a new resource limit. - auto& kernel = system.Kernel(); - KResourceLimit* resource_limit = KResourceLimit::Create(kernel); - R_UNLESS(resource_limit != nullptr, ResultOutOfResource); - - // Ensure we don't leak a reference to the limit. - SCOPE_EXIT({ resource_limit->Close(); }); - - // Initialize the resource limit. - resource_limit->Initialize(&system.CoreTiming()); - - // Register the limit. - KResourceLimit::Register(kernel, resource_limit); - - // Add the limit to the handle table. - R_TRY(kernel.CurrentProcess()->GetHandleTable().Add(out_handle, resource_limit)); - - return ResultSuccess; -} - -static Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, - Handle resource_limit_handle, LimitableResource which) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, - which); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Get the limit value. - *out_limit_value = resource_limit->GetLimitValue(which); - - return ResultSuccess; -} - -static Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, - Handle resource_limit_handle, LimitableResource which) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, - which); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Get the current value. - *out_current_value = resource_limit->GetCurrentValue(which); - - return ResultSuccess; -} - -static Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, - LimitableResource which, u64 limit_value) { - LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}, limit_value={}", - resource_limit_handle, which, limit_value); - - // Validate the resource. - R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); - - // Get the resource limit. - auto& kernel = system.Kernel(); - KScopedAutoObject resource_limit = - kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); - R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); - - // Set the limit value. - R_TRY(resource_limit->SetLimitValue(which, limit_value)); - - return ResultSuccess; -} - -static Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, - u32 out_process_ids_size) { - LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}", - out_process_ids, out_process_ids_size); - - // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail. - if ((out_process_ids_size & 0xF0000000) != 0) { - LOG_ERROR(Kernel_SVC, - "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}", - out_process_ids_size); - return ResultOutOfRange; - } - - const auto& kernel = system.Kernel(); - const auto total_copy_size = out_process_ids_size * sizeof(u64); - - if (out_process_ids_size > 0 && !kernel.CurrentProcess()->PageTable().IsInsideAddressSpace( - out_process_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", - out_process_ids, out_process_ids + total_copy_size); - return ResultInvalidCurrentMemory; - } - - auto& memory = system.Memory(); - const auto& process_list = kernel.GetProcessList(); - const auto num_processes = process_list.size(); - const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes); - - for (std::size_t i = 0; i < copy_amount; ++i) { - memory.Write64(out_process_ids, process_list[i]->GetProcessID()); - out_process_ids += sizeof(u64); - } - - *out_num_processes = static_cast<u32>(num_processes); - return ResultSuccess; -} - -static Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, - u32 out_thread_ids_size, Handle debug_handle) { - // TODO: Handle this case when debug events are supported. - UNIMPLEMENTED_IF(debug_handle != InvalidHandle); - - LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}", - out_thread_ids, out_thread_ids_size); - - // If the size is negative or larger than INT32_MAX / sizeof(u64) - if ((out_thread_ids_size & 0xF0000000) != 0) { - LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}", - out_thread_ids_size); - return ResultOutOfRange; - } - - auto* const current_process = system.Kernel().CurrentProcess(); - const auto total_copy_size = out_thread_ids_size * sizeof(u64); - - if (out_thread_ids_size > 0 && - !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", - out_thread_ids, out_thread_ids + total_copy_size); - return ResultInvalidCurrentMemory; - } - - auto& memory = system.Memory(); - const auto& thread_list = current_process->GetThreadList(); - const auto num_threads = thread_list.size(); - const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads); - - auto list_iter = thread_list.cbegin(); - for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { - memory.Write64(out_thread_ids, (*list_iter)->GetThreadID()); - out_thread_ids += sizeof(u64); - } - - *out_num_threads = static_cast<u32>(num_threads); - return ResultSuccess; -} - -static Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, - u64 size) { - // Validate address/size. - R_UNLESS(size > 0, ResultInvalidSize); - R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); - R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); - - // Get the process from its handle. - KScopedAutoObject process = - system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); - R_UNLESS(process.IsNotNull(), ResultInvalidHandle); - - // Verify the region is within range. - auto& page_table = process->PageTable(); - R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); - - // Perform the operation. - R_RETURN(system.Memory().FlushDataCache(*process, address, size)); -} - -namespace { struct FunctionDef { using Func = void(Core::System&); @@ -2699,6 +18,7 @@ struct FunctionDef { Func* func; const char* name; }; + } // namespace static const FunctionDef SVC_Table_32[] = { diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 13f061b83..b599f9a3d 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -4,6 +4,8 @@ #pragma once #include "common/common_types.h" +#include "core/hle/kernel/svc_types.h" +#include "core/hle/result.h" namespace Core { class System; @@ -13,4 +15,158 @@ namespace Kernel::Svc { void Call(Core::System& system, u32 immediate); +Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size); +Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, MemoryPermission perm); +Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, u32 attr); +Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size); +Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size); +Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + VAddr query_address); +void ExitProcess(Core::System& system); +Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, + VAddr stack_bottom, u32 priority, s32 core_id); +Result StartThread(Core::System& system, Handle thread_handle); +void ExitThread(Core::System& system); +void SleepThread(Core::System& system, s64 nanoseconds); +Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle); +Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority); +Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, + u64* out_affinity_mask); +Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, + u64 affinity_mask); +u32 GetCurrentProcessorNumber(Core::System& system); +Result SignalEvent(Core::System& system, Handle event_handle); +Result ClearEvent(Core::System& system, Handle event_handle); +Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, + MemoryPermission map_perm); +Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size); +Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, + MemoryPermission map_perm); +Result CloseHandle(Core::System& system, Handle handle); +Result ResetSignal(Core::System& system, Handle handle); +Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, s32 num_handles, + s64 nano_seconds); +Result CancelSynchronization(Core::System& system, Handle handle); +Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag); +Result ArbitrateUnlock(Core::System& system, VAddr address); +Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, + s64 timeout_ns); +void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count); +u64 GetSystemTick(Core::System& system); +Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address); +Result SendSyncRequest(Core::System& system, Handle handle); +Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle); +Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle); +void Break(Core::System& system, u32 reason, u64 info1, u64 info2); +void OutputDebugString(Core::System& system, VAddr address, u64 len); +Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, u64 info_sub_id); +Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size); +Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size); +Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, + Handle resource_limit_handle, LimitableResource which); +Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, + Handle resource_limit_handle, LimitableResource which); +Result SetThreadActivity(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity); +Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle); +Result WaitForAddress(Core::System& system, VAddr address, ArbitrationType arb_type, s32 value, + s64 timeout_ns); +Result SignalToAddress(Core::System& system, VAddr address, SignalType signal_type, s32 value, + s32 count); +void SynchronizePreemptionState(Core::System& system); +void KernelDebug(Core::System& system, u32 kernel_debug_type, u64 param1, u64 param2, u64 param3); +void ChangeKernelTraceState(Core::System& system, u32 trace_state); +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u32 is_light, + u64 name); +Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, s32 num_handles, + Handle reply_target, s64 timeout_ns); +Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read); +Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size); +Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, + VAddr address, size_t size, MemoryPermission perm); +Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, + u32 out_process_ids_size); +Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, + u32 out_thread_ids_size, Handle debug_handle); +Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, + u64 size, MemoryPermission perm); +Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size); +Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size); +Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + Handle process_handle, VAddr address); +Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size); +Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size); +Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type); +Result CreateResourceLimit(Core::System& system, Handle* out_handle); +Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, + LimitableResource which, u64 limit_value); + +// + +Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size); +Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attr); +Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size); +Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size); +Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, + u32 query_address); +void ExitProcess32(Core::System& system); +Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, + u32 arg, u32 stack_top, s32 processor_id); +Result StartThread32(Core::System& system, Handle thread_handle); +void ExitThread32(Core::System& system); +void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high); +Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle); +Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority); +Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, + u32* out_affinity_mask_low, u32* out_affinity_mask_high); +Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, + u32 affinity_mask_low, u32 affinity_mask_high); +u32 GetCurrentProcessorNumber32(Core::System& system); +Result SignalEvent32(Core::System& system, Handle event_handle); +Result ClearEvent32(Core::System& system, Handle event_handle); +Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, + MemoryPermission map_perm); +Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size); +Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, + MemoryPermission map_perm); +Result CloseHandle32(Core::System& system, Handle handle); +Result ResetSignal32(Core::System& system, Handle handle); +Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, + s32 num_handles, u32 timeout_high, s32* index); +Result CancelSynchronization32(Core::System& system, Handle handle); +Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag); +Result ArbitrateUnlock32(Core::System& system, u32 address); +Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, + u32 timeout_ns_low, u32 timeout_ns_high); +void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count); +void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high); +Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, u32 port_name_address); +Result SendSyncRequest32(Core::System& system, Handle handle); +Result GetProcessId32(Core::System& system, u32* out_process_id_low, u32* out_process_id_high, + Handle handle); +Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, + Handle thread_handle); +void Break32(Core::System& system, u32 reason, u32 info1, u32 info2); +void OutputDebugString32(Core::System& system, u32 address, u32 len); +Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, + u32 info_id, u32 handle, u32 sub_id_high); +Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size); +Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size); +Result SetThreadActivity32(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity); +Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle); +Result WaitForAddress32(Core::System& system, u32 address, ArbitrationType arb_type, s32 value, + u32 timeout_ns_low, u32 timeout_ns_high); +Result SignalToAddress32(Core::System& system, u32 address, SignalType signal_type, s32 value, + s32 count); +Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read); +Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size); +Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, + u64 address, u64 size, MemoryPermission perm); +Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, u64 size); + } // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_activity.cpp b/src/core/hle/kernel/svc/svc_activity.cpp new file mode 100644 index 000000000..8774a5c98 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_activity.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel::Svc { + +/// Sets the thread activity +Result SetThreadActivity(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, + thread_activity); + + // Validate the activity. + constexpr auto IsValidThreadActivity = [](ThreadActivity activity) { + return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused; + }; + R_UNLESS(IsValidThreadActivity(thread_activity), ResultInvalidEnumValue); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Check that the activity is being set on a non-current thread for the current process. + R_UNLESS(thread->GetOwnerProcess() == system.Kernel().CurrentProcess(), ResultInvalidHandle); + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); + + // Set the activity. + R_TRY(thread->SetActivity(thread_activity)); + + return ResultSuccess; +} + +Result SetThreadActivity32(Core::System& system, Handle thread_handle, + ThreadActivity thread_activity) { + return SetThreadActivity(system, thread_handle, thread_activity); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp new file mode 100644 index 000000000..842107726 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_types.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSignalType(Svc::SignalType type) { + switch (type) { + case Svc::SignalType::Signal: + case Svc::SignalType::SignalAndIncrementIfEqual: + case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: + return true; + default: + return false; + } +} + +constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { + switch (type) { + case Svc::ArbitrationType::WaitIfLessThan: + case Svc::ArbitrationType::DecrementAndWaitIfLessThan: + case Svc::ArbitrationType::WaitIfEqual: + return true; + default: + return false; + } +} + +} // namespace + +// Wait for an address (via Address Arbiter) +Result WaitForAddress(Core::System& system, VAddr address, ArbitrationType arb_type, s32 value, + s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", + address, arb_type, value, timeout_ns); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidArbitrationType(arb_type)) { + LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type); + return ResultInvalidEnumValue; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout); +} + +Result WaitForAddress32(Core::System& system, u32 address, ArbitrationType arb_type, s32 value, + u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitForAddress(system, address, arb_type, value, timeout); +} + +// Signals to an address (via Address Arbiter) +Result SignalToAddress(Core::System& system, VAddr address, SignalType signal_type, s32 value, + s32 count) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", + address, signal_type, value, count); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + if (!IsValidSignalType(signal_type)) { + LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type); + return ResultInvalidEnumValue; + } + + return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value, + count); +} + +Result SignalToAddress32(Core::System& system, u32 address, SignalType signal_type, s32 value, + s32 count) { + return SignalToAddress(system, address, signal_type, value, count); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_address_translation.cpp b/src/core/hle/kernel/svc/svc_address_translation.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_address_translation.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_cache.cpp b/src/core/hle/kernel/svc/svc_cache.cpp new file mode 100644 index 000000000..42167d35b --- /dev/null +++ b/src/core/hle/kernel/svc/svc_cache.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_types.h" + +namespace Kernel::Svc { + +Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address, u64 size) { + // Validate address/size. + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); + R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); + + // Get the process from its handle. + KScopedAutoObject process = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + + // Verify the region is within range. + auto& page_table = process->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Perform the operation. + R_RETURN(system.Memory().FlushDataCache(*process, address, size)); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_code_memory.cpp b/src/core/hle/kernel/svc/svc_code_memory.cpp new file mode 100644 index 000000000..4cb21e101 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_code_memory.cpp @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_code_memory.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidMapCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::ReadWrite; +} + +constexpr bool IsValidMapToOwnerCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::Read || perm == MemoryPermission::ReadExecute; +} + +constexpr bool IsValidUnmapCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::None; +} + +constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(MemoryPermission perm) { + return perm == MemoryPermission::None; +} + +} // namespace + +Result CreateCodeMemory(Core::System& system, Handle* out, VAddr address, size_t size) { + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, size=0x{:X}", address, size); + + // Get kernel instance. + auto& kernel = system.Kernel(); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Create the code memory. + + KCodeMemory* code_mem = KCodeMemory::Create(kernel); + R_UNLESS(code_mem != nullptr, ResultOutOfResource); + + // Verify that the region is in range. + R_UNLESS(system.CurrentProcess()->PageTable().Contains(address, size), + ResultInvalidCurrentMemory); + + // Initialize the code memory. + R_TRY(code_mem->Initialize(system.DeviceMemory(), address, size)); + + // Register the code memory. + KCodeMemory::Register(kernel, code_mem); + + // Add the code memory to the handle table. + R_TRY(system.CurrentProcess()->GetHandleTable().Add(out, code_mem)); + + code_mem->Close(); + + return ResultSuccess; +} + +Result CreateCodeMemory32(Core::System& system, Handle* out, u32 address, u32 size) { + return CreateCodeMemory(system, out, address, size); +} + +Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, u32 operation, + VAddr address, size_t size, MemoryPermission perm) { + + LOG_TRACE(Kernel_SVC, + "called, code_memory_handle=0x{:X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, " + "permission=0x{:X}", + code_memory_handle, operation, address, size, perm); + + // Validate the address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Get the code memory from its handle. + KScopedAutoObject code_mem = + system.CurrentProcess()->GetHandleTable().GetObject<KCodeMemory>(code_memory_handle); + R_UNLESS(code_mem.IsNotNull(), ResultInvalidHandle); + + // NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. + // This enables homebrew usage of these SVCs for JIT. + + // Perform the operation. + switch (static_cast<CodeMemoryOperation>(operation)) { + case CodeMemoryOperation::Map: { + // Check that the region is in range. + R_UNLESS( + system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidMapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Map the memory. + R_TRY(code_mem->Map(address, size)); + } break; + case CodeMemoryOperation::Unmap: { + // Check that the region is in range. + R_UNLESS( + system.CurrentProcess()->PageTable().CanContain(address, size, KMemoryState::CodeOut), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Unmap the memory. + R_TRY(code_mem->Unmap(address, size)); + } break; + case CodeMemoryOperation::MapToOwner: { + // Check that the region is in range. + R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, + KMemoryState::GeneratedCode), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Map the memory to its owner. + R_TRY(code_mem->MapToOwner(address, size, perm)); + } break; + case CodeMemoryOperation::UnmapFromOwner: { + // Check that the region is in range. + R_UNLESS(code_mem->GetOwner()->PageTable().CanContain(address, size, + KMemoryState::GeneratedCode), + ResultInvalidMemoryRegion); + + // Check the memory permission. + R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Unmap the memory from its owner. + R_TRY(code_mem->UnmapFromOwner(address, size)); + } break; + default: + return ResultInvalidEnumValue; + } + + return ResultSuccess; +} + +Result ControlCodeMemory32(Core::System& system, Handle code_memory_handle, u32 operation, + u64 address, u64 size, MemoryPermission perm) { + return ControlCodeMemory(system, code_memory_handle, operation, address, size, perm); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp new file mode 100644 index 000000000..d6cfc87c5 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel::Svc { + +/// Wait process wide key atomic +Result WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key, u32 tag, + s64 timeout_ns) { + LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, + cv_key, tag, timeout_ns); + + // Validate input. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(s32))) { + LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address); + return ResultInvalidAddress; + } + + // Convert timeout from nanoseconds to ticks. + s64 timeout{}; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + // Wait on the condition variable. + return system.Kernel().CurrentProcess()->WaitConditionVariable( + address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout); +} + +Result WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag, + u32 timeout_ns_low, u32 timeout_ns_high) { + const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32)); + return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns); +} + +/// Signal process wide key +void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) { + LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count); + + // Signal the condition variable. + return system.Kernel().CurrentProcess()->SignalConditionVariable( + Common::AlignDown(cv_key, sizeof(u32)), count); +} + +void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) { + SignalProcessWideKey(system, cv_key, count); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_debug.cpp b/src/core/hle/kernel/svc/svc_debug.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_debug.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp new file mode 100644 index 000000000..486e62cc4 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/svc.h" +#include "core/memory.h" + +namespace Kernel::Svc { + +/// Used to output a message on a debug hardware unit - does nothing on a retail unit +void OutputDebugString(Core::System& system, VAddr address, u64 len) { + if (len == 0) { + return; + } + + std::string str(len, '\0'); + system.Memory().ReadBlock(address, str.data(), str.size()); + LOG_DEBUG(Debug_Emulated, "{}", str); +} + +void OutputDebugString32(Core::System& system, u32 address, u32 len) { + OutputDebugString(system, address, len); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_device_address_space.cpp b/src/core/hle/kernel/svc/svc_device_address_space.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_device_address_space.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp new file mode 100644 index 000000000..885f02f50 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_event.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result SignalEvent(Core::System& system, Handle event_handle) { + LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + + // Get the current handle table. + const KHandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Get the event. + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + R_UNLESS(event.IsNotNull(), ResultInvalidHandle); + + return event->Signal(); +} + +Result SignalEvent32(Core::System& system, Handle event_handle) { + return SignalEvent(system, event_handle); +} + +Result ClearEvent(Core::System& system, Handle event_handle) { + LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + + // Get the current handle table. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Try to clear the writable event. + { + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + if (event.IsNotNull()) { + return event->Clear(); + } + } + + // Try to clear the readable event. + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(event_handle); + if (readable_event.IsNotNull()) { + return readable_event->Clear(); + } + } + + LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle); + + return ResultInvalidHandle; +} + +Result ClearEvent32(Core::System& system, Handle event_handle) { + return ClearEvent(system, event_handle); +} + +Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { + LOG_DEBUG(Kernel_SVC, "called"); + + // Get the kernel reference and handle table. + auto& kernel = system.Kernel(); + auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + + // Reserve a new event from the process resource limit + KScopedResourceReservation event_reservation(kernel.CurrentProcess(), + LimitableResource::EventCountMax); + R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); + + // Create a new event. + KEvent* event = KEvent::Create(kernel); + R_UNLESS(event != nullptr, ResultOutOfResource); + + // Initialize the event. + event->Initialize(kernel.CurrentProcess()); + + // Commit the thread reservation. + event_reservation.Commit(); + + // Ensure that we clean up the event (and its only references are handle table) on function end. + SCOPE_EXIT({ + event->GetReadableEvent().Close(); + event->Close(); + }); + + // Register the event. + KEvent::Register(kernel, event); + + // Add the event to the handle table. + R_TRY(handle_table.Add(out_write, event)); + + // Ensure that we maintaing a clean handle state on exit. + auto handle_guard = SCOPE_GUARD({ handle_table.Remove(*out_write); }); + + // Add the readable event to the handle table. + R_TRY(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); + + // We succeeded. + handle_guard.Cancel(); + return ResultSuccess; +} + +Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) { + return CreateEvent(system, out_write, out_read); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp new file mode 100644 index 000000000..fb9f133c1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_exception.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/debugger/debugger.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_types.h" +#include "core/memory.h" +#include "core/reporter.h" + +namespace Kernel::Svc { + +/// Break program execution +void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { + BreakReason break_reason = + static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag)); + bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0; + + bool has_dumped_buffer{}; + std::vector<u8> debug_buffer; + + const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { + if (sz == 0 || addr == 0 || has_dumped_buffer) { + return; + } + + auto& memory = system.Memory(); + + // This typically is an error code so we're going to assume this is the case + if (sz == sizeof(u32)) { + LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", memory.Read32(addr)); + } else { + // We don't know what's in here so we'll hexdump it + debug_buffer.resize(sz); + memory.ReadBlock(addr, debug_buffer.data(), sz); + std::string hexdump; + for (std::size_t i = 0; i < debug_buffer.size(); i++) { + hexdump += fmt::format("{:02X} ", debug_buffer[i]); + if (i != 0 && i % 16 == 0) { + hexdump += '\n'; + } + } + LOG_CRITICAL(Debug_Emulated, "debug_buffer=\n{}", hexdump); + } + has_dumped_buffer = true; + }; + switch (break_reason) { + case BreakReason::Panic: + LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, + info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::Assert: + LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}", + info1, info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::User: + LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); + handle_debug_buffer(info1, info2); + break; + case BreakReason::PreLoadDll: + LOG_INFO(Debug_Emulated, + "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PostLoadDll: + LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PreUnloadDll: + LOG_INFO(Debug_Emulated, + "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakReason::PostUnloadDll: + LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", + info1, info2); + break; + case BreakReason::CppException: + LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered."); + break; + default: + LOG_WARNING( + Debug_Emulated, + "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + handle_debug_buffer(info1, info2); + break; + } + + system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2, + has_dumped_buffer ? std::make_optional(debug_buffer) + : std::nullopt); + + if (!notification_only) { + LOG_CRITICAL( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + + handle_debug_buffer(info1, info2); + + auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + const auto thread_processor_id = current_thread->GetActiveCore(); + system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); + } + + if (system.DebuggerEnabled()) { + auto* thread = system.Kernel().GetCurrentEmuThread(); + system.GetDebugger().NotifyThreadStopped(thread); + thread->RequestSuspend(Kernel::SuspendType::Debug); + } +} + +void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { + Break(system, reason, info1, info2); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp new file mode 100644 index 000000000..df5dd85a4 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_info.cpp @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Gets system/memory information for the current process +Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle handle, u64 info_sub_id) { + LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, + info_sub_id, handle); + + const auto info_id_type = static_cast<InfoType>(info_id); + + switch (info_id_type) { + case InfoType::CoreMask: + case InfoType::PriorityMask: + case InfoType::AliasRegionAddress: + case InfoType::AliasRegionSize: + case InfoType::HeapRegionAddress: + case InfoType::HeapRegionSize: + case InfoType::AslrRegionAddress: + case InfoType::AslrRegionSize: + case InfoType::StackRegionAddress: + case InfoType::StackRegionSize: + case InfoType::TotalMemorySize: + case InfoType::UsedMemorySize: + case InfoType::SystemResourceSizeTotal: + case InfoType::SystemResourceSizeUsed: + case InfoType::ProgramId: + case InfoType::UserExceptionContextAddress: + case InfoType::TotalNonSystemMemorySize: + case InfoType::UsedNonSystemMemorySize: + case InfoType::IsApplication: + case InfoType::FreeThreadCount: { + if (info_sub_id != 0) { + LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, + info_sub_id); + return ResultInvalidEnumValue; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}", + info_id, info_sub_id, handle); + return ResultInvalidHandle; + } + + switch (info_id_type) { + case InfoType::CoreMask: + *result = process->GetCoreMask(); + return ResultSuccess; + + case InfoType::PriorityMask: + *result = process->GetPriorityMask(); + return ResultSuccess; + + case InfoType::AliasRegionAddress: + *result = process->PageTable().GetAliasRegionStart(); + return ResultSuccess; + + case InfoType::AliasRegionSize: + *result = process->PageTable().GetAliasRegionSize(); + return ResultSuccess; + + case InfoType::HeapRegionAddress: + *result = process->PageTable().GetHeapRegionStart(); + return ResultSuccess; + + case InfoType::HeapRegionSize: + *result = process->PageTable().GetHeapRegionSize(); + return ResultSuccess; + + case InfoType::AslrRegionAddress: + *result = process->PageTable().GetAliasCodeRegionStart(); + return ResultSuccess; + + case InfoType::AslrRegionSize: + *result = process->PageTable().GetAliasCodeRegionSize(); + return ResultSuccess; + + case InfoType::StackRegionAddress: + *result = process->PageTable().GetStackRegionStart(); + return ResultSuccess; + + case InfoType::StackRegionSize: + *result = process->PageTable().GetStackRegionSize(); + return ResultSuccess; + + case InfoType::TotalMemorySize: + *result = process->GetTotalPhysicalMemoryAvailable(); + return ResultSuccess; + + case InfoType::UsedMemorySize: + *result = process->GetTotalPhysicalMemoryUsed(); + return ResultSuccess; + + case InfoType::SystemResourceSizeTotal: + *result = process->GetSystemResourceSize(); + return ResultSuccess; + + case InfoType::SystemResourceSizeUsed: + LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); + *result = process->GetSystemResourceUsage(); + return ResultSuccess; + + case InfoType::ProgramId: + *result = process->GetProgramID(); + return ResultSuccess; + + case InfoType::UserExceptionContextAddress: + *result = process->GetProcessLocalRegionAddress(); + return ResultSuccess; + + case InfoType::TotalNonSystemMemorySize: + *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); + return ResultSuccess; + + case InfoType::UsedNonSystemMemorySize: + *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); + return ResultSuccess; + + case InfoType::FreeThreadCount: + *result = process->GetFreeThreadCount(); + return ResultSuccess; + + default: + break; + } + + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ResultInvalidEnumValue; + } + + case InfoType::DebuggerAttached: + *result = 0; + return ResultSuccess; + + case InfoType::ResourceLimit: { + if (handle != 0) { + LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); + return ResultInvalidHandle; + } + + if (info_sub_id != 0) { + LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, + info_sub_id); + return ResultInvalidCombination; + } + + KProcess* const current_process = system.Kernel().CurrentProcess(); + KHandleTable& handle_table = current_process->GetHandleTable(); + const auto resource_limit = current_process->GetResourceLimit(); + if (!resource_limit) { + *result = Svc::InvalidHandle; + // Yes, the kernel considers this a successful operation. + return ResultSuccess; + } + + Handle resource_handle{}; + R_TRY(handle_table.Add(&resource_handle, resource_limit)); + + *result = resource_handle; + return ResultSuccess; + } + + case InfoType::RandomEntropy: + if (handle != 0) { + LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", + handle); + return ResultInvalidHandle; + } + + if (info_sub_id >= KProcess::RANDOM_ENTROPY_SIZE) { + LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", + KProcess::RANDOM_ENTROPY_SIZE, info_sub_id); + return ResultInvalidCombination; + } + + *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); + return ResultSuccess; + + case InfoType::InitialProcessIdRange: + LOG_WARNING(Kernel_SVC, + "(STUBBED) Attempted to query privileged process id bounds, returned 0"); + *result = 0; + return ResultSuccess; + + case InfoType::ThreadTickCount: { + constexpr u64 num_cpus = 4; + if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { + LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, + info_sub_id); + return ResultInvalidCombination; + } + + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>( + static_cast<Handle>(handle)); + if (thread.IsNull()) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", + static_cast<Handle>(handle)); + return ResultInvalidHandle; + } + + const auto& core_timing = system.CoreTiming(); + const auto& scheduler = *system.Kernel().CurrentScheduler(); + const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + const bool same_thread = current_thread == thread.GetPointerUnsafe(); + + const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime(); + u64 out_ticks = 0; + if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { + const u64 thread_ticks = current_thread->GetCpuTime(); + + out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); + } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { + out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; + } + + *result = out_ticks; + return ResultSuccess; + } + case InfoType::IdleTickCount: { + // Verify the input handle is invalid. + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + + // Verify the requested core is valid. + const bool core_valid = + (info_sub_id == 0xFFFFFFFFFFFFFFFF) || + (info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex())); + R_UNLESS(core_valid, ResultInvalidCombination); + + // Get the idle tick count. + *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); + return ResultSuccess; + } + case InfoType::MesosphereCurrentProcess: { + // Verify the input handle is invalid. + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + + // Verify the sub-type is valid. + R_UNLESS(info_sub_id == 0, ResultInvalidCombination); + + // Get the handle table. + KProcess* current_process = system.Kernel().CurrentProcess(); + KHandleTable& handle_table = current_process->GetHandleTable(); + + // Get a new handle for the current process. + Handle tmp; + R_TRY(handle_table.Add(&tmp, current_process)); + + // Set the output. + *result = tmp; + + // We succeeded. + return ResultSuccess; + } + default: + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ResultInvalidEnumValue; + } +} + +Result GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, + u32 info_id, u32 handle, u32 sub_id_high) { + const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; + u64 res_value{}; + + const Result result{GetInfo(system, &res_value, info_id, handle, sub_id)}; + *result_high = static_cast<u32>(res_value >> 32); + *result_low = static_cast<u32>(res_value & std::numeric_limits<u32>::max()); + + return result; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_interrupt_event.cpp b/src/core/hle/kernel/svc/svc_interrupt_event.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_interrupt_event.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_io_pool.cpp b/src/core/hle/kernel/svc/svc_io_pool.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_io_pool.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp new file mode 100644 index 000000000..dbb68e89a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_server_session.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Makes a blocking IPC call to a service. +Result SendSyncRequest(Core::System& system, Handle handle) { + auto& kernel = system.Kernel(); + + // Get the client session from its handle. + KScopedAutoObject session = + kernel.CurrentProcess()->GetHandleTable().GetObject<KClientSession>(handle); + R_UNLESS(session.IsNotNull(), ResultInvalidHandle); + + LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); + + return session->SendSyncRequest(); +} + +Result SendSyncRequest32(Core::System& system, Handle handle) { + return SendSyncRequest(system, handle); +} + +Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles, s32 num_handles, + Handle reply_target, s64 timeout_ns) { + auto& kernel = system.Kernel(); + auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable(); + + // Convert handle list to object table. + std::vector<KSynchronizationObject*> objs(num_handles); + R_UNLESS( + handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles), + ResultInvalidHandle); + + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (auto i = 0; i < num_handles; ++i) { + objs[i]->Close(); + } + }); + + // Reply to the target, if one is specified. + if (reply_target != InvalidHandle) { + KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target); + R_UNLESS(session.IsNotNull(), ResultInvalidHandle); + + // If we fail to reply, we want to set the output index to -1. + ON_RESULT_FAILURE { + *out_index = -1; + }; + + // Send the reply. + R_TRY(session->SendReply()); + } + + // Wait for a message. + while (true) { + // Wait for an object. + s32 index; + Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(), + static_cast<s32>(objs.size()), timeout_ns); + if (result == ResultTimedOut) { + return result; + } + + // Receive the request. + if (R_SUCCEEDED(result)) { + KServerSession* session = objs[index]->DynamicCast<KServerSession*>(); + if (session != nullptr) { + result = session->ReceiveRequest(); + if (result == ResultNotFound) { + continue; + } + } + } + + *out_index = index; + return result; + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_kernel_debug.cpp b/src/core/hle/kernel/svc/svc_kernel_debug.cpp new file mode 100644 index 000000000..454255e7a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_kernel_debug.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +void KernelDebug([[maybe_unused]] Core::System& system, [[maybe_unused]] u32 kernel_debug_type, + [[maybe_unused]] u64 param1, [[maybe_unused]] u64 param2, + [[maybe_unused]] u64 param3) { + // Intentionally do nothing, as this does nothing in released kernel binaries. +} + +void ChangeKernelTraceState([[maybe_unused]] Core::System& system, + [[maybe_unused]] u32 trace_state) { + // Intentionally do nothing, as this does nothing in released kernel binaries. +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_light_ipc.cpp b/src/core/hle/kernel/svc/svc_light_ipc.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_light_ipc.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp new file mode 100644 index 000000000..45f2a6553 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_lock.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_memory_layout.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Attempts to locks a mutex +Result ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address, u32 tag) { + LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}", + thread_handle, address, tag); + + // Validate the input address. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; + } + + return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag); +} + +Result ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address, u32 tag) { + return ArbitrateLock(system, thread_handle, address, tag); +} + +/// Unlock a mutex +Result ArbitrateUnlock(Core::System& system, VAddr address) { + LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address); + + // Validate the input address. + if (IsKernelAddress(address)) { + LOG_ERROR(Kernel_SVC, + "Attempting to arbitrate an unlock on a kernel address (address={:08X})", + address); + return ResultInvalidCurrentMemory; + } + if (!Common::IsAligned(address, sizeof(u32))) { + LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address); + return ResultInvalidAddress; + } + + return system.Kernel().CurrentProcess()->SignalToAddress(address); +} + +Result ArbitrateUnlock32(Core::System& system, u32 address) { + return ArbitrateUnlock(system, address); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp new file mode 100644 index 000000000..f78b1239b --- /dev/null +++ b/src/core/hle/kernel/svc/svc_memory.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::None: + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +// Checks if address + size is greater than the given address +// This can return false if the size causes an overflow of a 64-bit type +// or if the given size is zero. +constexpr bool IsValidAddressRange(VAddr address, u64 size) { + return address + size > address; +} + +// Helper function that performs the common sanity checks for svcMapMemory +// and svcUnmapMemory. This is doable, as both functions perform their sanitizing +// in the same order. +Result MapUnmapMemorySanityChecks(const KPageTable& manager, VAddr dst_addr, VAddr src_addr, + u64 size) { + if (!Common::Is4KBAligned(dst_addr)) { + LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(src_addr)) { + LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ResultInvalidSize; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + dst_addr, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_addr, size)) { + LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + src_addr, size); + return ResultInvalidCurrentMemory; + } + + if (!manager.IsInsideAddressSpace(src_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", + src_addr, size); + return ResultInvalidCurrentMemory; + } + + if (manager.IsOutsideStackRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + if (manager.IsInsideHeapRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the heap region, addr=0x{:016X}, " + "size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + if (manager.IsInsideAliasRegion(dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the map region, addr=0x{:016X}, " + "size=0x{:016X}", + dst_addr, size); + return ResultInvalidMemoryRegion; + } + + return ResultSuccess; +} + +} // namespace + +Result SetMemoryPermission(Core::System& system, VAddr address, u64 size, MemoryPermission perm) { + LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, + perm); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permission. + R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Validate that the region is in range for the current process. + auto& page_table = system.Kernel().CurrentProcess()->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory attribute. + return page_table.SetMemoryPermission(address, size, perm); +} + +Result SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, u32 attr) { + LOG_DEBUG(Kernel_SVC, + "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, + size, mask, attr); + + // Validate address / size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the attribute and mask. + constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); + R_UNLESS((mask | attr) == mask, ResultInvalidCombination); + R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); + + // Validate that the region is in range for the current process. + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory attribute. + return page_table.SetMemoryAttribute(address, size, mask, attr); +} + +Result SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attr) { + return SetMemoryAttribute(system, address, size, mask, attr); +} + +/// Maps a memory range into a different range. +Result MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { + LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + + if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; + result.IsError()) { + return result; + } + + return page_table.MapMemory(dst_addr, src_addr, size); +} + +Result MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { + return MapMemory(system, dst_addr, src_addr, size); +} + +/// Unmaps a region that was previously mapped with svcMapMemory +Result UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { + LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + src_addr, size); + + auto& page_table{system.Kernel().CurrentProcess()->PageTable()}; + + if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)}; + result.IsError()) { + return result; + } + + return page_table.UnmapMemory(dst_addr, src_addr, size); +} + +Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { + return UnmapMemory(system, dst_addr, src_addr, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp new file mode 100644 index 000000000..0fc262203 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Set the process heap to a given Size. It can both extend and shrink the heap. +Result SetHeapSize(Core::System& system, VAddr* out_address, u64 size) { + LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", size); + + // Validate size. + R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); + R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize); + + // Set the heap size. + R_TRY(system.Kernel().CurrentProcess()->PageTable().SetHeapSize(out_address, size)); + + return ResultSuccess; +} + +Result SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_size) { + VAddr temp_heap_addr{}; + const Result result{SetHeapSize(system, &temp_heap_addr, heap_size)}; + *heap_addr = static_cast<u32>(temp_heap_addr); + return result; +} + +/// Maps memory at a desired address +Result MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ResultInvalidSize; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ResultInvalidMemoryRegion; + } + + KProcess* const current_process{system.Kernel().CurrentProcess()}; + auto& page_table{current_process->PageTable()}; + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ResultInvalidState; + } + + if (!page_table.IsInsideAddressSpace(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + if (page_table.IsOutsideAliasRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + return page_table.MapPhysicalMemory(addr, size); +} + +Result MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { + return MapPhysicalMemory(system, addr, size); +} + +/// Unmaps memory previously mapped via MapPhysicalMemory +Result UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ResultInvalidSize; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ResultInvalidSize; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ResultInvalidMemoryRegion; + } + + KProcess* const current_process{system.Kernel().CurrentProcess()}; + auto& page_table{current_process->PageTable()}; + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ResultInvalidState; + } + + if (!page_table.IsInsideAddressSpace(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + if (page_table.IsOutsideAliasRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, + "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + size); + return ResultInvalidMemoryRegion; + } + + return page_table.UnmapPhysicalMemory(addr, size); +} + +Result UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { + return UnmapPhysicalMemory(system, addr, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_port.cpp b/src/core/hle/kernel/svc/svc_port.cpp new file mode 100644 index 000000000..cdfe0dd16 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_port.cpp @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Connect to an OS service given the port name, returns the handle to the port to out +Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) { + auto& memory = system.Memory(); + if (!memory.IsValidVirtualAddress(port_name_address)) { + LOG_ERROR(Kernel_SVC, + "Port Name Address is not a valid virtual address, port_name_address=0x{:016X}", + port_name_address); + return ResultNotFound; + } + + static constexpr std::size_t PortNameMaxLength = 11; + // Read 1 char beyond the max allowed port name to detect names that are too long. + const std::string port_name = memory.ReadCString(port_name_address, PortNameMaxLength + 1); + if (port_name.size() > PortNameMaxLength) { + LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength, + port_name.size()); + return ResultOutOfRange; + } + + LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); + + // Get the current handle table. + auto& kernel = system.Kernel(); + auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + + // Find the client port. + auto port = kernel.CreateNamedServicePort(port_name); + if (!port) { + LOG_ERROR(Kernel_SVC, "tried to connect to unknown port: {}", port_name); + return ResultNotFound; + } + + // Reserve a handle for the port. + // NOTE: Nintendo really does write directly to the output handle here. + R_TRY(handle_table.Reserve(out)); + auto handle_guard = SCOPE_GUARD({ handle_table.Unreserve(*out); }); + + // Create a session. + KClientSession* session{}; + R_TRY(port->CreateSession(std::addressof(session))); + + kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort()); + + // Register the session in the table, close the extra reference. + handle_table.Register(*out, session); + session->Close(); + + // We succeeded. + handle_guard.Cancel(); + return ResultSuccess; +} + +Result ConnectToNamedPort32(Core::System& system, Handle* out_handle, u32 port_name_address) { + + return ConnectToNamedPort(system, out_handle, port_name_address); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_power_management.cpp b/src/core/hle/kernel/svc/svc_power_management.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_power_management.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp new file mode 100644 index 000000000..d6c8b4561 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_process.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Exits the current process +void ExitProcess(Core::System& system) { + auto* current_process = system.Kernel().CurrentProcess(); + + LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); + ASSERT_MSG(current_process->GetState() == KProcess::State::Running, + "Process has already exited"); + + system.Exit(); +} + +void ExitProcess32(Core::System& system) { + ExitProcess(system); +} + +/// Gets the ID of the specified process or a specified thread's owning process. +Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { + LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); + + // Get the object from the handle table. + KScopedAutoObject obj = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KAutoObject>( + static_cast<Handle>(handle)); + R_UNLESS(obj.IsNotNull(), ResultInvalidHandle); + + // Get the process from the object. + KProcess* process = nullptr; + if (KProcess* p = obj->DynamicCast<KProcess*>(); p != nullptr) { + // The object is a process, so we can use it directly. + process = p; + } else if (KThread* t = obj->DynamicCast<KThread*>(); t != nullptr) { + // The object is a thread, so we want to use its parent. + process = reinterpret_cast<KThread*>(obj.GetPointerUnsafe())->GetOwnerProcess(); + } else { + // TODO(bunnei): This should also handle debug objects before returning. + UNIMPLEMENTED_MSG("Debug objects not implemented"); + } + + // Make sure the target process exists. + R_UNLESS(process != nullptr, ResultInvalidHandle); + + // Get the process id. + *out_process_id = process->GetId(); + + return ResultSuccess; +} + +Result GetProcessId32(Core::System& system, u32* out_process_id_low, u32* out_process_id_high, + Handle handle) { + u64 out_process_id{}; + const auto result = GetProcessId(system, &out_process_id, handle); + *out_process_id_low = static_cast<u32>(out_process_id); + *out_process_id_high = static_cast<u32>(out_process_id >> 32); + return result; +} + +Result GetProcessList(Core::System& system, u32* out_num_processes, VAddr out_process_ids, + u32 out_process_ids_size) { + LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}", + out_process_ids, out_process_ids_size); + + // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail. + if ((out_process_ids_size & 0xF0000000) != 0) { + LOG_ERROR(Kernel_SVC, + "Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}", + out_process_ids_size); + return ResultOutOfRange; + } + + const auto& kernel = system.Kernel(); + const auto total_copy_size = out_process_ids_size * sizeof(u64); + + if (out_process_ids_size > 0 && !kernel.CurrentProcess()->PageTable().IsInsideAddressSpace( + out_process_ids, total_copy_size)) { + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + out_process_ids, out_process_ids + total_copy_size); + return ResultInvalidCurrentMemory; + } + + auto& memory = system.Memory(); + const auto& process_list = kernel.GetProcessList(); + const auto num_processes = process_list.size(); + const auto copy_amount = std::min(std::size_t{out_process_ids_size}, num_processes); + + for (std::size_t i = 0; i < copy_amount; ++i) { + memory.Write64(out_process_ids, process_list[i]->GetProcessID()); + out_process_ids += sizeof(u64); + } + + *out_num_processes = static_cast<u32>(num_processes); + return ResultSuccess; +} + +Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); + return ResultInvalidHandle; + } + + const auto info_type = static_cast<ProcessInfoType>(type); + if (info_type != ProcessInfoType::ProcessState) { + LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type); + return ResultInvalidEnumValue; + } + + *out = static_cast<u64>(process->GetState()); + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_process_memory.cpp b/src/core/hle/kernel/svc/svc_process_memory.cpp new file mode 100644 index 000000000..b6ac43af2 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_process_memory.cpp @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidAddressRange(VAddr address, u64 size) { + return address + size > address; +} + +constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) { + switch (perm) { + case Svc::MemoryPermission::None: + case Svc::MemoryPermission::Read: + case Svc::MemoryPermission::ReadWrite: + case Svc::MemoryPermission::ReadExecute: + return true; + default: + return false; + } +} + +} // namespace + +Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, VAddr address, + u64 size, Svc::MemoryPermission perm) { + LOG_TRACE(Kernel_SVC, + "called, process_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + process_handle, address, size, perm); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory); + R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory); + + // Validate the memory permission. + R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission); + + // Get the process from its handle. + KScopedAutoObject process = + system.CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), ResultInvalidHandle); + + // Validate that the address is in range. + auto& page_table = process->PageTable(); + R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); + + // Set the memory permission. + return page_table.SetProcessMemoryPermission(address, size, perm); +} + +Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size) { + LOG_TRACE(Kernel_SVC, + "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + dst_address, process_handle, src_address, size); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); + R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); + + // Get the processes. + KProcess* dst_process = system.CurrentProcess(); + KScopedAutoObject src_process = + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); + + // Get the page tables. + auto& dst_pt = dst_process->PageTable(); + auto& src_pt = src_process->PageTable(); + + // Validate that the mapping is in range. + R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), + ResultInvalidMemoryRegion); + + // Create a new page group. + KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()}; + R_TRY(src_pt.MakeAndOpenPageGroup( + std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess, + KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::All, KMemoryAttribute::None)); + + // Map the group. + R_TRY(dst_pt.MapPageGroup(dst_address, pg, KMemoryState::SharedCode, + KMemoryPermission::UserReadWrite)); + + return ResultSuccess; +} + +Result UnmapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle, + VAddr src_address, u64 size) { + LOG_TRACE(Kernel_SVC, + "called, dst_address=0x{:X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + dst_address, process_handle, src_address, size); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(dst_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(src_address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((dst_address < dst_address + size), ResultInvalidCurrentMemory); + R_UNLESS((src_address < src_address + size), ResultInvalidCurrentMemory); + + // Get the processes. + KProcess* dst_process = system.CurrentProcess(); + KScopedAutoObject src_process = + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); + + // Get the page tables. + auto& dst_pt = dst_process->PageTable(); + auto& src_pt = src_process->PageTable(); + + // Validate that the mapping is in range. + R_UNLESS(src_pt.Contains(src_address, size), ResultInvalidCurrentMemory); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState::SharedCode), + ResultInvalidMemoryRegion); + + // Unmap the memory. + R_TRY(dst_pt.UnmapProcessMemory(dst_address, size, src_pt, src_address)); + + return ResultSuccess; +} + +Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size) { + LOG_DEBUG(Kernel_SVC, + "called. process_handle=0x{:08X}, dst_address=0x{:016X}, " + "src_address=0x{:016X}, size=0x{:016X}", + process_handle, dst_address, src_address, size); + + if (!Common::Is4KBAligned(src_address)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + src_address); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(dst_address)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + dst_address); + return ResultInvalidAddress; + } + + if (size == 0 || !Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range overflows the address space (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range overflows the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + process_handle); + return ResultInvalidHandle; + } + + auto& page_table = process->PageTable(); + if (!page_table.IsInsideAddressSpace(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range is not within the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + if (!page_table.IsInsideASLRRegion(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidMemoryRegion; + } + + return page_table.MapCodeMemory(dst_address, src_address, size); +} + +Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, + u64 src_address, u64 size) { + LOG_DEBUG(Kernel_SVC, + "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " + "size=0x{:016X}", + process_handle, dst_address, src_address, size); + + if (!Common::Is4KBAligned(dst_address)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + dst_address); + return ResultInvalidAddress; + } + + if (!Common::Is4KBAligned(src_address)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + src_address); + return ResultInvalidAddress; + } + + if (size == 0 || !Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); + return ResultInvalidSize; + } + + if (!IsValidAddressRange(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range overflows the address space (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidCurrentMemory; + } + + if (!IsValidAddressRange(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range overflows the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + process_handle); + return ResultInvalidHandle; + } + + auto& page_table = process->PageTable(); + if (!page_table.IsInsideAddressSpace(src_address, size)) { + LOG_ERROR(Kernel_SVC, + "Source address range is not within the address space (src_address=0x{:016X}, " + "size=0x{:016X}).", + src_address, size); + return ResultInvalidCurrentMemory; + } + + if (!page_table.IsInsideASLRRegion(dst_address, size)) { + LOG_ERROR(Kernel_SVC, + "Destination address range is not within the ASLR region (dst_address=0x{:016X}, " + "size=0x{:016X}).", + dst_address, size); + return ResultInvalidMemoryRegion; + } + + return page_table.UnmapCodeMemory(dst_address, src_address, size, + KPageTable::ICacheInvalidationStrategy::InvalidateAll); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_processor.cpp b/src/core/hle/kernel/svc/svc_processor.cpp new file mode 100644 index 000000000..8561cf74f --- /dev/null +++ b/src/core/hle/kernel/svc/svc_processor.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/kernel/physical_core.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Get which CPU core is executing the current thread +u32 GetCurrentProcessorNumber(Core::System& system) { + LOG_TRACE(Kernel_SVC, "called"); + return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex()); +} + +u32 GetCurrentProcessorNumber32(Core::System& system) { + return GetCurrentProcessorNumber(system); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_query_memory.cpp b/src/core/hle/kernel/svc/svc_query_memory.cpp new file mode 100644 index 000000000..aac3b2eca --- /dev/null +++ b/src/core/hle/kernel/svc/svc_query_memory.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result QueryMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + VAddr query_address) { + LOG_TRACE(Kernel_SVC, + "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, " + "query_address=0x{:016X}", + memory_info_address, page_info_address, query_address); + + return QueryProcessMemory(system, memory_info_address, page_info_address, CurrentProcess, + query_address); +} + +Result QueryMemory32(Core::System& system, u32 memory_info_address, u32 page_info_address, + u32 query_address) { + return QueryMemory(system, memory_info_address, page_info_address, query_address); +} + +Result QueryProcessMemory(Core::System& system, VAddr memory_info_address, VAddr page_info_address, + Handle process_handle, VAddr address) { + LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle); + if (process.IsNull()) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); + return ResultInvalidHandle; + } + + auto& memory{system.Memory()}; + const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()}; + + memory.Write64(memory_info_address + 0x00, memory_info.base_address); + memory.Write64(memory_info_address + 0x08, memory_info.size); + memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff); + memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute)); + memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission)); + memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count); + memory.Write32(memory_info_address + 0x20, memory_info.device_count); + memory.Write32(memory_info_address + 0x24, 0); + + // Page info appears to be currently unused by the kernel and is always set to zero. + memory.Write32(page_info_address, 0); + + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_register.cpp b/src/core/hle/kernel/svc/svc_register.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_register.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp new file mode 100644 index 000000000..679ba10fa --- /dev/null +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_resource_limit.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +Result CreateResourceLimit(Core::System& system, Handle* out_handle) { + LOG_DEBUG(Kernel_SVC, "called"); + + // Create a new resource limit. + auto& kernel = system.Kernel(); + KResourceLimit* resource_limit = KResourceLimit::Create(kernel); + R_UNLESS(resource_limit != nullptr, ResultOutOfResource); + + // Ensure we don't leak a reference to the limit. + SCOPE_EXIT({ resource_limit->Close(); }); + + // Initialize the resource limit. + resource_limit->Initialize(&system.CoreTiming()); + + // Register the limit. + KResourceLimit::Register(kernel, resource_limit); + + // Add the limit to the handle table. + R_TRY(kernel.CurrentProcess()->GetHandleTable().Add(out_handle, resource_limit)); + + return ResultSuccess; +} + +Result GetResourceLimitLimitValue(Core::System& system, u64* out_limit_value, + Handle resource_limit_handle, LimitableResource which) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, + which); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Get the limit value. + *out_limit_value = resource_limit->GetLimitValue(which); + + return ResultSuccess; +} + +Result GetResourceLimitCurrentValue(Core::System& system, u64* out_current_value, + Handle resource_limit_handle, LimitableResource which) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}", resource_limit_handle, + which); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Get the current value. + *out_current_value = resource_limit->GetCurrentValue(which); + + return ResultSuccess; +} + +Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_handle, + LimitableResource which, u64 limit_value) { + LOG_DEBUG(Kernel_SVC, "called, resource_limit_handle={:08X}, which={}, limit_value={}", + resource_limit_handle, which, limit_value); + + // Validate the resource. + R_UNLESS(IsValidResourceType(which), ResultInvalidEnumValue); + + // Get the resource limit. + auto& kernel = system.Kernel(); + KScopedAutoObject resource_limit = + kernel.CurrentProcess()->GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); + + // Set the limit value. + R_TRY(resource_limit->SetLimitValue(which, limit_value)); + + return ResultSuccess; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp b/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_secure_monitor_call.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_session.cpp b/src/core/hle/kernel/svc/svc_session.cpp new file mode 100644 index 000000000..dac8ce33c --- /dev/null +++ b/src/core/hle/kernel/svc/svc_session.cpp @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +template <typename T> +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) { + auto& process = *system.CurrentProcess(); + auto& handle_table = process.GetHandleTable(); + + // Declare the session we're going to allocate. + T* session; + + // Reserve a new session from the process resource limit. + // FIXME: LimitableResource_SessionCountMax + KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax); + if (session_reservation.Succeeded()) { + session = T::Create(system.Kernel()); + } else { + return ResultLimitReached; + + // // We couldn't reserve a session. Check that we support dynamically expanding the + // // resource limit. + // R_UNLESS(process.GetResourceLimit() == + // &system.Kernel().GetSystemResourceLimit(), ResultLimitReached); + // R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached()); + + // // Try to allocate a session from unused slab memory. + // session = T::CreateFromUnusedSlabMemory(); + // R_UNLESS(session != nullptr, ResultLimitReached); + // ON_RESULT_FAILURE { session->Close(); }; + + // // If we're creating a KSession, we want to add two KSessionRequests to the heap, to + // // prevent request exhaustion. + // // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's + // // no reason to not do this statically. + // if constexpr (std::same_as<T, KSession>) { + // for (size_t i = 0; i < 2; i++) { + // KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory(); + // R_UNLESS(request != nullptr, ResultLimitReached); + // request->Close(); + // } + // } + + // We successfully allocated a session, so add the object we allocated to the resource + // limit. + // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1); + } + + // Check that we successfully created a session. + R_UNLESS(session != nullptr, ResultOutOfResource); + + // Initialize the session. + session->Initialize(nullptr, fmt::format("{}", name)); + + // Commit the session reservation. + session_reservation.Commit(); + + // Ensure that we clean up the session (and its only references are handle table) on function + // end. + SCOPE_EXIT({ + session->GetClientSession().Close(); + session->GetServerSession().Close(); + }); + + // Register the session. + T::Register(system.Kernel(), session); + + // Add the server session to the handle table. + R_TRY(handle_table.Add(out_server, &session->GetServerSession())); + + // Add the client session to the handle table. + const auto result = handle_table.Add(out_client, &session->GetClientSession()); + + if (!R_SUCCEEDED(result)) { + // Ensure that we maintaing a clean handle state on exit. + handle_table.Remove(*out_server); + } + + return result; +} + +} // namespace + +Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u32 is_light, + u64 name) { + if (is_light) { + // return CreateSession<KLightSession>(system, out_server, out_client, name); + return ResultUnknown; + } else { + return CreateSession<KSession>(system, out_server, out_client, name); + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_shared_memory.cpp b/src/core/hle/kernel/svc/svc_shared_memory.cpp new file mode 100644 index 000000000..d465bcbe7 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_shared_memory.cpp @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidSharedMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +[[maybe_unused]] constexpr bool IsValidRemoteSharedMemoryPermission(MemoryPermission perm) { + return IsValidSharedMemoryPermission(perm) || perm == MemoryPermission::DontCare; +} + +} // namespace + +Result MapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size, + Svc::MemoryPermission map_perm) { + LOG_TRACE(Kernel_SVC, + "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + shmem_handle, address, size, map_perm); + + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permission. + R_UNLESS(IsValidSharedMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); + + // Get the current process. + auto& process = *system.Kernel().CurrentProcess(); + auto& page_table = process.PageTable(); + + // Get the shared memory. + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); + + // Add the shared memory to the process. + R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); + + // Ensure that we clean up the shared memory if we fail to map it. + auto guard = + SCOPE_GUARD({ process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }); + + // Map the shared memory. + R_TRY(shmem->Map(process, address, size, map_perm)); + + // We succeeded. + guard.Cancel(); + return ResultSuccess; +} + +Result MapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size, + Svc::MemoryPermission map_perm) { + return MapSharedMemory(system, shmem_handle, address, size, map_perm); +} + +Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, VAddr address, u64 size) { + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Get the current process. + auto& process = *system.Kernel().CurrentProcess(); + auto& page_table = process.PageTable(); + + // Get the shared memory. + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); + + // Unmap the shared memory. + R_TRY(shmem->Unmap(process, address, size)); + + // Remove the shared memory from the process. + process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + + return ResultSuccess; +} + +Result UnmapSharedMemory32(Core::System& system, Handle shmem_handle, u32 address, u32 size) { + return UnmapSharedMemory(system, shmem_handle, address, size); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp new file mode 100644 index 000000000..1bf6a612a --- /dev/null +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// Close a handle +Result CloseHandle(Core::System& system, Handle handle) { + LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); + + // Remove the handle. + R_UNLESS(system.Kernel().CurrentProcess()->GetHandleTable().Remove(handle), + ResultInvalidHandle); + + return ResultSuccess; +} + +Result CloseHandle32(Core::System& system, Handle handle) { + return CloseHandle(system, handle); +} + +/// Clears the signaled state of an event or process. +Result ResetSignal(Core::System& system, Handle handle) { + LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); + + // Get the current handle table. + const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + + // Try to reset as readable event. + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(handle); + if (readable_event.IsNotNull()) { + return readable_event->Reset(); + } + } + + // Try to reset as process. + { + KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); + if (process.IsNotNull()) { + return process->Reset(); + } + } + + LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle); + + return ResultInvalidHandle; +} + +Result ResetSignal32(Core::System& system, Handle handle) { + return ResetSignal(system, handle); +} + +/// Wait for the given handles to synchronize, timeout after the specified nanoseconds +Result WaitSynchronization(Core::System& system, s32* index, VAddr handles_address, s32 num_handles, + s64 nano_seconds) { + LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, num_handles={}, nano_seconds={}", + handles_address, num_handles, nano_seconds); + + // Ensure number of handles is valid. + R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange); + + auto& kernel = system.Kernel(); + std::vector<KSynchronizationObject*> objs(num_handles); + const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); + Handle* handles = system.Memory().GetPointer<Handle>(handles_address); + + // Copy user handles. + if (num_handles > 0) { + // Convert the handles to objects. + R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, + num_handles), + ResultInvalidHandle); + for (const auto& obj : objs) { + kernel.RegisterInUseObject(obj); + } + } + + // Ensure handles are closed when we're done. + SCOPE_EXIT({ + for (s32 i = 0; i < num_handles; ++i) { + kernel.UnregisterInUseObject(objs[i]); + objs[i]->Close(); + } + }); + + return KSynchronizationObject::Wait(kernel, index, objs.data(), static_cast<s32>(objs.size()), + nano_seconds); +} + +Result WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address, + s32 num_handles, u32 timeout_high, s32* index) { + const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)}; + return WaitSynchronization(system, index, handles_address, num_handles, nano_seconds); +} + +/// Resumes a thread waiting on WaitSynchronization +Result CancelSynchronization(Core::System& system, Handle handle) { + LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Cancel the thread's wait. + thread->WaitCancel(); + return ResultSuccess; +} + +Result CancelSynchronization32(Core::System& system, Handle handle) { + return CancelSynchronization(system, handle); +} + +void SynchronizePreemptionState(Core::System& system) { + auto& kernel = system.Kernel(); + + // Lock the scheduler. + KScopedSchedulerLock sl{kernel}; + + // If the current thread is pinned, unpin it. + KProcess* cur_process = system.Kernel().CurrentProcess(); + const auto core_id = GetCurrentCoreId(kernel); + + if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) { + // Clear the current thread's interrupt flag. + GetCurrentThread(kernel).ClearInterruptFlag(); + + // Unpin the current thread. + cur_process->UnpinCurrentThread(core_id); + } +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp new file mode 100644 index 000000000..dd9f8e8b1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -0,0 +1,396 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidVirtualCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES)); +} + +} // Anonymous namespace + +/// Creates a new thread +Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg, + VAddr stack_bottom, u32 priority, s32 core_id) { + LOG_DEBUG(Kernel_SVC, + "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " + "priority=0x{:08X}, core_id=0x{:08X}", + entry_point, arg, stack_bottom, priority, core_id); + + // Adjust core id, if it's the default magic. + auto& kernel = system.Kernel(); + auto& process = *kernel.CurrentProcess(); + if (core_id == IdealCoreUseProcessValue) { + core_id = process.GetIdealCoreId(); + } + + // Validate arguments. + if (!IsValidVirtualCoreId(core_id)) { + LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id); + return ResultInvalidCoreId; + } + if (((1ULL << core_id) & process.GetCoreMask()) == 0) { + LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id); + return ResultInvalidCoreId; + } + + if (HighestThreadPriority > priority || priority > LowestThreadPriority) { + LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority); + return ResultInvalidPriority; + } + if (!process.CheckThreadPriority(priority)) { + LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority); + return ResultInvalidPriority; + } + + // Reserve a new thread from the process resource limit (waiting up to 100ms). + KScopedResourceReservation thread_reservation( + kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1, + system.CoreTiming().GetGlobalTimeNs().count() + 100000000); + if (!thread_reservation.Succeeded()) { + LOG_ERROR(Kernel_SVC, "Could not reserve a new thread"); + return ResultLimitReached; + } + + // Create the thread. + KThread* thread = KThread::Create(kernel); + if (!thread) { + LOG_ERROR(Kernel_SVC, "Unable to create new threads. Thread creation limit reached."); + return ResultOutOfResource; + } + SCOPE_EXIT({ thread->Close(); }); + + // Initialize the thread. + { + KScopedLightLock lk{process.GetStateLock()}; + R_TRY(KThread::InitializeUserThread(system, thread, entry_point, arg, stack_bottom, + priority, core_id, &process)); + } + + // Set the thread name for debugging purposes. + thread->SetName(fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *out_handle)); + + // Commit the thread reservation. + thread_reservation.Commit(); + + // Register the new thread. + KThread::Register(kernel, thread); + + // Add the thread to the handle table. + R_TRY(process.GetHandleTable().Add(out_handle, thread)); + + return ResultSuccess; +} + +Result CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, + u32 arg, u32 stack_top, s32 processor_id) { + return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); +} + +/// Starts the thread for the provided handle +Result StartThread(Core::System& system, Handle thread_handle) { + LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Try to start the thread. + R_TRY(thread->Run()); + + // If we succeeded, persist a reference to the thread. + thread->Open(); + system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe()); + + return ResultSuccess; +} + +Result StartThread32(Core::System& system, Handle thread_handle) { + return StartThread(system, thread_handle); +} + +/// Called when a thread exits +void ExitThread(Core::System& system) { + LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); + + auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); + system.GlobalSchedulerContext().RemoveThread(current_thread); + current_thread->Exit(); + system.Kernel().UnregisterInUseObject(current_thread); +} + +void ExitThread32(Core::System& system) { + ExitThread(system); +} + +/// Sleep the current thread +void SleepThread(Core::System& system, s64 nanoseconds) { + auto& kernel = system.Kernel(); + const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); + + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); + + // When the input tick is positive, sleep. + if (nanoseconds > 0) { + // Convert the timeout from nanoseconds to ticks. + // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + + // Sleep. + // NOTE: Nintendo does not check the result of this sleep. + static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); + } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { + KScheduler::YieldWithoutCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::WithCoreMigration) { + KScheduler::YieldWithCoreMigration(kernel); + } else if (yield_type == Svc::YieldType::ToAnyThread) { + KScheduler::YieldToAnyThread(kernel); + } else { + // Nintendo does nothing at all if an otherwise invalid value is passed. + ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); + } +} + +void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { + const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); + SleepThread(system, nanoseconds); +} + +/// Gets the thread context +Result GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) { + LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context, + thread_handle); + + auto& kernel = system.Kernel(); + + // Get the thread from its handle. + KScopedAutoObject thread = + kernel.CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Require the handle be to a non-current thread in the current process. + const auto* current_process = kernel.CurrentProcess(); + R_UNLESS(current_process == thread->GetOwnerProcess(), ResultInvalidId); + + // Verify that the thread isn't terminated. + R_UNLESS(thread->GetState() != ThreadState::Terminated, ResultTerminationRequested); + + /// Check that the thread is not the current one. + /// NOTE: Nintendo does not check this, and thus the following loop will deadlock. + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(kernel), ResultInvalidId); + + // Try to get the thread context until the thread isn't current on any core. + while (true) { + KScopedSchedulerLock sl{kernel}; + + // TODO(bunnei): Enforce that thread is suspended for debug here. + + // If the thread's raw state isn't runnable, check if it's current on some core. + if (thread->GetRawState() != ThreadState::Runnable) { + bool current = false; + for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { + if (thread.GetPointerUnsafe() == kernel.Scheduler(i).GetSchedulerCurrentThread()) { + current = true; + break; + } + } + + // If the thread is current, retry until it isn't. + if (current) { + continue; + } + } + + // Get the thread context. + std::vector<u8> context; + R_TRY(thread->GetThreadContext3(context)); + + // Copy the thread context to user space. + system.Memory().WriteBlock(out_context, context.data(), context.size()); + + return ResultSuccess; + } + + return ResultSuccess; +} + +Result GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) { + return GetThreadContext(system, out_context, thread_handle); +} + +/// Gets the priority for the specified thread +Result GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) { + LOG_TRACE(Kernel_SVC, "called"); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the thread's priority. + *out_priority = thread->GetPriority(); + return ResultSuccess; +} + +Result GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) { + return GetThreadPriority(system, out_priority, handle); +} + +/// Sets the priority for the specified thread +Result SetThreadPriority(Core::System& system, Handle thread_handle, u32 priority) { + // Get the current process. + KProcess& process = *system.Kernel().CurrentProcess(); + + // Validate the priority. + R_UNLESS(HighestThreadPriority <= priority && priority <= LowestThreadPriority, + ResultInvalidPriority); + R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); + + // Get the thread from its handle. + KScopedAutoObject thread = process.GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Set the thread priority. + thread->SetBasePriority(priority); + return ResultSuccess; +} + +Result SetThreadPriority32(Core::System& system, Handle thread_handle, u32 priority) { + return SetThreadPriority(system, thread_handle, priority); +} + +Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr out_thread_ids, + u32 out_thread_ids_size, Handle debug_handle) { + // TODO: Handle this case when debug events are supported. + UNIMPLEMENTED_IF(debug_handle != InvalidHandle); + + LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}", + out_thread_ids, out_thread_ids_size); + + // If the size is negative or larger than INT32_MAX / sizeof(u64) + if ((out_thread_ids_size & 0xF0000000) != 0) { + LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}", + out_thread_ids_size); + return ResultOutOfRange; + } + + auto* const current_process = system.Kernel().CurrentProcess(); + const auto total_copy_size = out_thread_ids_size * sizeof(u64); + + if (out_thread_ids_size > 0 && + !current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) { + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + out_thread_ids, out_thread_ids + total_copy_size); + return ResultInvalidCurrentMemory; + } + + auto& memory = system.Memory(); + const auto& thread_list = current_process->GetThreadList(); + const auto num_threads = thread_list.size(); + const auto copy_amount = std::min(std::size_t{out_thread_ids_size}, num_threads); + + auto list_iter = thread_list.cbegin(); + for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { + memory.Write64(out_thread_ids, (*list_iter)->GetThreadID()); + out_thread_ids += sizeof(u64); + } + + *out_num_threads = static_cast<u32>(num_threads); + return ResultSuccess; +} + +Result GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id, + u64* out_affinity_mask) { + LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the core mask. + R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask)); + + return ResultSuccess; +} + +Result GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id, + u32* out_affinity_mask_low, u32* out_affinity_mask_high) { + u64 out_affinity_mask{}; + const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask); + *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32); + *out_affinity_mask_low = static_cast<u32>(out_affinity_mask); + return result; +} + +Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, + u64 affinity_mask) { + // Determine the core id/affinity mask. + if (core_id == IdealCoreUseProcessValue) { + core_id = system.Kernel().CurrentProcess()->GetIdealCoreId(); + affinity_mask = (1ULL << core_id); + } else { + // Validate the affinity mask. + const u64 process_core_mask = system.Kernel().CurrentProcess()->GetCoreMask(); + R_UNLESS((affinity_mask | process_core_mask) == process_core_mask, ResultInvalidCoreId); + R_UNLESS(affinity_mask != 0, ResultInvalidCombination); + + // Validate the core id. + if (IsValidVirtualCoreId(core_id)) { + R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, ResultInvalidCombination); + } else { + R_UNLESS(core_id == IdealCoreNoUpdate || core_id == IdealCoreDontCare, + ResultInvalidCoreId); + } + } + + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Set the core mask. + R_TRY(thread->SetCoreMask(core_id, affinity_mask)); + + return ResultSuccess; +} + +Result SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id, + u32 affinity_mask_low, u32 affinity_mask_high) { + const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); + return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask); +} + +/// Get the ID for the specified thread. +Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { + // Get the thread from its handle. + KScopedAutoObject thread = + system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); + + // Get the thread's id. + *out_thread_id = thread->GetId(); + return ResultSuccess; +} + +Result GetThreadId32(Core::System& system, u32* out_thread_id_low, u32* out_thread_id_high, + Handle thread_handle) { + u64 out_thread_id{}; + const Result result{GetThreadId(system, &out_thread_id, thread_handle)}; + + *out_thread_id_low = static_cast<u32>(out_thread_id >> 32); + *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max()); + + return result; +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_thread_profiler.cpp b/src/core/hle/kernel/svc/svc_thread_profiler.cpp new file mode 100644 index 000000000..299e22ae6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_thread_profiler.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc {} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_tick.cpp b/src/core/hle/kernel/svc/svc_tick.cpp new file mode 100644 index 000000000..e9b4fd5a6 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_tick.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { + +/// This returns the total CPU ticks elapsed since the CPU was powered-on +u64 GetSystemTick(Core::System& system) { + LOG_TRACE(Kernel_SVC, "called"); + + auto& core_timing = system.CoreTiming(); + + // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) + const u64 result{core_timing.GetClockTicks()}; + + if (!system.Kernel().IsMulticore()) { + core_timing.AddTicks(400U); + } + + return result; +} + +void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { + const auto time = GetSystemTick(system); + *time_low = static_cast<u32>(time); + *time_high = static_cast<u32>(time >> 32); +} + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp new file mode 100644 index 000000000..b14ae24a1 --- /dev/null +++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/kernel/svc.h" + +namespace Kernel::Svc { +namespace { + +constexpr bool IsValidTransferMemoryPermission(MemoryPermission perm) { + switch (perm) { + case MemoryPermission::None: + case MemoryPermission::Read: + case MemoryPermission::ReadWrite: + return true; + default: + return false; + } +} + +} // Anonymous namespace + +/// Creates a TransferMemory object +Result CreateTransferMemory(Core::System& system, Handle* out, VAddr address, u64 size, + MemoryPermission map_perm) { + auto& kernel = system.Kernel(); + + // Validate the size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permissions. + R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidNewMemoryPermission); + + // Get the current process and handle table. + auto& process = *kernel.CurrentProcess(); + auto& handle_table = process.GetHandleTable(); + + // Reserve a new transfer memory from the process resource limit. + KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(), + LimitableResource::TransferMemoryCountMax); + R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached); + + // Create the transfer memory. + KTransferMemory* trmem = KTransferMemory::Create(kernel); + R_UNLESS(trmem != nullptr, ResultOutOfResource); + + // Ensure the only reference is in the handle table when we're done. + SCOPE_EXIT({ trmem->Close(); }); + + // Ensure that the region is in range. + R_UNLESS(process.PageTable().Contains(address, size), ResultInvalidCurrentMemory); + + // Initialize the transfer memory. + R_TRY(trmem->Initialize(address, size, map_perm)); + + // Commit the reservation. + trmem_reservation.Commit(); + + // Register the transfer memory. + KTransferMemory::Register(kernel, trmem); + + // Add the transfer memory to the handle table. + R_TRY(handle_table.Add(out, trmem)); + + return ResultSuccess; +} + +Result CreateTransferMemory32(Core::System& system, Handle* out, u32 address, u32 size, + MemoryPermission map_perm) { + return CreateTransferMemory(system, out, address, size, map_perm); +} +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 33eebcef6..e90c35601 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -3,6 +3,9 @@ #pragma once +#include <bitset> + +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" @@ -496,6 +499,19 @@ enum class MemoryMapping : u32 { Memory = 2, }; +enum class MapDeviceAddressSpaceFlag : u32 { + None = (0U << 0), + NotIoRegister = (1U << 0), +}; +DECLARE_ENUM_FLAG_OPERATORS(MapDeviceAddressSpaceFlag); + +union MapDeviceAddressSpaceOption { + u32 raw; + BitField<0, 16, MemoryPermission> permission; + BitField<16, 1, MapDeviceAddressSpaceFlag> flags; + BitField<17, 15, u32> reserved; +}; + enum class KernelDebugType : u32 { Thread = 0, ThreadCallStack = 1, @@ -592,4 +608,7 @@ struct CreateProcessParameter { }; static_assert(sizeof(CreateProcessParameter) == 0x30); +constexpr size_t NumSupervisorCalls = 0xC0; +using SvcAccessFlagSet = std::bitset<NumSupervisorCalls>; + } // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_version.h b/src/core/hle/kernel/svc_version.h new file mode 100644 index 000000000..e4f47b34b --- /dev/null +++ b/src/core/hle/kernel/svc_version.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/literals.h" + +namespace Kernel::Svc { + +constexpr inline u32 ConvertToSvcMajorVersion(u32 sdk) { + return sdk + 4; +} +constexpr inline u32 ConvertToSdkMajorVersion(u32 svc) { + return svc - 4; +} + +constexpr inline u32 ConvertToSvcMinorVersion(u32 sdk) { + return sdk; +} +constexpr inline u32 ConvertToSdkMinorVersion(u32 svc) { + return svc; +} + +union KernelVersion { + u32 value; + BitField<0, 4, u32> minor_version; + BitField<4, 13, u32> major_version; +}; + +constexpr inline u32 EncodeKernelVersion(u32 major, u32 minor) { + return decltype(KernelVersion::minor_version)::FormatValue(minor) | + decltype(KernelVersion::major_version)::FormatValue(major); +} + +constexpr inline u32 GetKernelMajorVersion(u32 encoded) { + return std::bit_cast<decltype(KernelVersion::major_version)>(encoded).Value(); +} + +constexpr inline u32 GetKernelMinorVersion(u32 encoded) { + return std::bit_cast<decltype(KernelVersion::minor_version)>(encoded).Value(); +} + +// Nintendo doesn't support programs targeting SVC versions < 3.0. +constexpr inline u32 RequiredKernelMajorVersion = 3; +constexpr inline u32 RequiredKernelMinorVersion = 0; +constexpr inline u32 RequiredKernelVersion = + EncodeKernelVersion(RequiredKernelMajorVersion, RequiredKernelMinorVersion); + +// This is the highest SVC version supported, to be updated on new kernel releases. +// NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor. +constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(15); +constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(3); +constexpr inline u32 SupportedKernelVersion = + EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion); + +} // namespace Kernel::Svc diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 1ea8c7fbc..052be40dd 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -172,11 +172,11 @@ void SvcWrap64(Core::System& system) { } // Used by GetResourceLimitLimitValue. -template <Result func(Core::System&, u64*, Handle, LimitableResource)> +template <Result func(Core::System&, u64*, Handle, Svc::LimitableResource)> void SvcWrap64(Core::System& system) { u64 param_1 = 0; const u32 retval = func(system, ¶m_1, static_cast<Handle>(Param(system, 1)), - static_cast<LimitableResource>(Param(system, 2))) + static_cast<Svc::LimitableResource>(Param(system, 2))) .raw; system.CurrentArmInterface().SetReg(1, param_1); @@ -189,10 +189,10 @@ void SvcWrap64(Core::System& system) { } // Used by SetResourceLimitLimitValue -template <Result func(Core::System&, Handle, LimitableResource, u64)> +template <Result func(Core::System&, Handle, Svc::LimitableResource, u64)> void SvcWrap64(Core::System& system) { FuncReturn(system, func(system, static_cast<Handle>(Param(system, 0)), - static_cast<LimitableResource>(Param(system, 1)), Param(system, 2)) + static_cast<Svc::LimitableResource>(Param(system, 1)), Param(system, 2)) .raw); } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 22999c942..ebcf6e164 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1124,7 +1124,7 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 offset{rp.Pop<u64>()}; - const std::vector<u8> data{ctx.ReadBuffer()}; + const auto data{ctx.ReadBuffer()}; const std::size_t size{std::min<u64>(data.size(), backing.GetSize() - offset)}; LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 3a1c231b6..0ee28752c 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -112,7 +112,7 @@ private: void RequestUpdate(Kernel::HLERequestContext& ctx) { LOG_TRACE(Service_Audio, "called"); - std::vector<u8> input{ctx.ReadBuffer(0)}; + const auto input{ctx.ReadBuffer(0)}; // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for // checking size 0. Performance size is 0 for most games. diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 825fb8bcc..e01f87356 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -93,7 +93,7 @@ private: ctx.WriteBuffer(samples); } - bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input, + bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, std::vector<opus_int16>& output, u64* out_performance_time) const { const auto start_time = std::chrono::steady_clock::now(); const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index d183e5829..fb8686859 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -122,7 +122,7 @@ private: void ImportTicket(Kernel::HLERequestContext& ctx) { const auto ticket = ctx.ReadBuffer(); - const auto cert = ctx.ReadBuffer(1); + [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); if (ticket.size() < sizeof(Core::Crypto::Ticket)) { LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index fbb16a7da..447d624e1 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -190,7 +190,7 @@ private: return; } - const std::vector<u8> data = ctx.ReadBuffer(); + const auto data = ctx.ReadBuffer(); ASSERT_MSG( static_cast<s64>(data.size()) <= length, @@ -401,11 +401,8 @@ public: } void RenameFile(Kernel::HLERequestContext& ctx) { - std::vector<u8> buffer = ctx.ReadBuffer(0); - const std::string src_name = Common::StringFromBuffer(buffer); - - buffer = ctx.ReadBuffer(1); - const std::string dst_name = Common::StringFromBuffer(buffer); + const std::string src_name = Common::StringFromBuffer(ctx.ReadBuffer(0)); + const std::string dst_name = Common::StringFromBuffer(ctx.ReadBuffer(1)); LOG_DEBUG(Service_FS, "called. file '{}' to file '{}'", src_name, dst_name); @@ -1086,7 +1083,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { } void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { - const auto raw = ctx.ReadBuffer(); + const auto raw = ctx.ReadBufferCopy(); auto log = Common::StringFromFixedZeroTerminatedBuffer( reinterpret_cast<const char*>(raw.data()), raw.size()); diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index 49b6d45fe..ce21b69e3 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -228,7 +228,8 @@ private: return; } - control = ctx.ReadBuffer(); + // TODO: Can this be a span? + control = ctx.ReadBufferCopy(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 2f871de31..513ea485a 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { } break; case Core::HID::NpadStyleIndex::JoyconLeft: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.left; shared_memory->joycon_color.attribute = ColorAttribute::Ok; shared_memory->joycon_color.left = body_colors.left; shared_memory->battery_level_dual = battery_level.left.battery_level; @@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconRight: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.right; shared_memory->joycon_color.attribute = ColorAttribute::Ok; shared_memory->joycon_color.right = body_colors.right; shared_memory->battery_level_right = battery_level.right.battery_level; @@ -332,6 +336,20 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { controller.is_connected = true; controller.device->Connect(); + controller.device->SetLedPattern(); + if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) { + if (controller.is_dual_left_connected) { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex, + Common::Input::PollingMode::Active); + } + if (controller.is_dual_right_connected) { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); + } + } else { + controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices, + Common::Input::PollingMode::Active); + } SignalStyleSetChangedEvent(npad_id); WriteEmptyEntry(controller.shared_memory); } @@ -410,6 +428,9 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { return; } + // This function is unique to yuzu for the turbo buttons to work properly + controller.device->TurboButtonUpdate(); + auto& pad_entry = controller.npad_pad_state; auto& trigger_entry = controller.npad_trigger_state; const auto button_state = controller.device->GetNpadButtons(); @@ -737,11 +758,12 @@ Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { return hid_core.GetSupportedStyleTag(); } -void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) { +void Controller_NPad::SetSupportedNpadIdTypes(std::span<const u8> data) { + const auto length = data.size(); ASSERT(length > 0 && (length % sizeof(u32)) == 0); supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); - std::memcpy(supported_npad_id_types.data(), data, length); + std::memcpy(supported_npad_id_types.data(), data.data(), length); } void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) { diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 1a589cca2..1f7d33459 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -6,6 +6,7 @@ #include <array> #include <atomic> #include <mutex> +#include <span> #include "common/bit_field.h" #include "common/common_types.h" @@ -95,7 +96,7 @@ public: void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set); Core::HID::NpadStyleTag GetSupportedStyleSet() const; - void SetSupportedNpadIdTypes(u8* data, std::size_t length); + void SetSupportedNpadIdTypes(std::span<const u8> data); void GetSupportedNpadIdTypes(u32* data, std::size_t max_length); std::size_t GetSupportedNpadIdTypesSize() const; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index bf28440c6..f15f1a6bb 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -1026,7 +1026,7 @@ void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { const auto applet_resource_user_id{rp.Pop<u64>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + .SetSupportedNpadIdTypes(ctx.ReadBuffer()); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); @@ -2104,7 +2104,7 @@ void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) { const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; const auto unknown{rp.Pop<u64>()}; - const auto buffer = ctx.ReadBuffer(); + [[maybe_unused]] const auto buffer = ctx.ReadBuffer(); LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}", connection_handle.npad_id, unknown); diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp index e5e50845f..17252a84a 100644 --- a/src/core/hle/service/hid/hidbus.cpp +++ b/src/core/hle/service/hid/hidbus.cpp @@ -297,13 +297,13 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) { const auto parameters{rp.PopRaw<Parameters>()}; - LOG_INFO(Service_HID, - "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " - "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", - parameters.enable, parameters.bus_handle.abstracted_pad_id, - parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, - parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, - parameters.applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", + parameters.enable, parameters.bus_handle.abstracted_pad_id, + parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, + parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, + parameters.applet_resource_user_id); const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); @@ -326,11 +326,11 @@ void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto bus_handle_{rp.PopRaw<BusHandle>()}; - LOG_INFO(Service_HID, - "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " - "is_valid={}", - bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, - bus_handle_.player_number, bus_handle_.is_valid); + LOG_DEBUG(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); const auto device_index = GetDeviceIndexFromHandle(bus_handle_); diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h index d3960f506..65e301137 100644 --- a/src/core/hle/service/hid/hidbus/hidbus_base.h +++ b/src/core/hle/service/hid/hidbus/hidbus_base.h @@ -4,6 +4,7 @@ #pragma once #include <array> +#include <span> #include "common/common_types.h" #include "core/hle/result.h" @@ -150,7 +151,7 @@ public: } // Assigns a command from data - virtual bool SetCommand(const std::vector<u8>& data) { + virtual bool SetCommand(std::span<const u8> data) { return {}; } diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp index 57f1a2a26..35847cbdd 100644 --- a/src/core/hle/service/hid/hidbus/ringcon.cpp +++ b/src/core/hle/service/hid/hidbus/ringcon.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/hid/emulated_devices.h" +#include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_readable_event.h" @@ -12,16 +12,20 @@ namespace Service::HID { RingController::RingController(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) : HidbusBase(service_context_) { - input = hid_core_.GetEmulatedDevices(); + input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1); } RingController::~RingController() = default; void RingController::OnInit() { + input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Ring); return; } void RingController::OnRelease() { + input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); return; }; @@ -112,7 +116,7 @@ std::vector<u8> RingController::GetReply() const { } } -bool RingController::SetCommand(const std::vector<u8>& data) { +bool RingController::SetCommand(std::span<const u8> data) { if (data.size() < 4) { LOG_ERROR(Service_HID, "Command size not supported {}", data.size()); command = RingConCommands::Error; diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h index b37df50ac..c2fb386b1 100644 --- a/src/core/hle/service/hid/hidbus/ringcon.h +++ b/src/core/hle/service/hid/hidbus/ringcon.h @@ -4,12 +4,13 @@ #pragma once #include <array> +#include <span> #include "common/common_types.h" #include "core/hle/service/hid/hidbus/hidbus_base.h" namespace Core::HID { -class EmulatedDevices; +class EmulatedController; } // namespace Core::HID namespace Service::HID { @@ -31,7 +32,7 @@ public: u8 GetDeviceId() const override; // Assigns a command from data - bool SetCommand(const std::vector<u8>& data) override; + bool SetCommand(std::span<const u8> data) override; // Returns a reply from a command std::vector<u8> GetReply() const override; @@ -248,6 +249,6 @@ private: .zero = {.value = idle_value, .crc = 225}, }; - Core::HID::EmulatedDevices* input; + Core::HID::EmulatedController* input; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp index dd439f60a..d0e760314 100644 --- a/src/core/hle/service/hid/hidbus/starlink.cpp +++ b/src/core/hle/service/hid/hidbus/starlink.cpp @@ -42,7 +42,7 @@ std::vector<u8> Starlink::GetReply() const { return {}; } -bool Starlink::SetCommand(const std::vector<u8>& data) { +bool Starlink::SetCommand(std::span<const u8> data) { LOG_ERROR(Service_HID, "Command not implemented"); return false; } diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h index 0b1b7ba49..07c800e6e 100644 --- a/src/core/hle/service/hid/hidbus/starlink.h +++ b/src/core/hle/service/hid/hidbus/starlink.h @@ -29,7 +29,7 @@ public: u8 GetDeviceId() const override; // Assigns a command from data - bool SetCommand(const std::vector<u8>& data) override; + bool SetCommand(std::span<const u8> data) override; // Returns a reply from a command std::vector<u8> GetReply() const override; diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp index e477443e3..07632c872 100644 --- a/src/core/hle/service/hid/hidbus/stubbed.cpp +++ b/src/core/hle/service/hid/hidbus/stubbed.cpp @@ -43,7 +43,7 @@ std::vector<u8> HidbusStubbed::GetReply() const { return {}; } -bool HidbusStubbed::SetCommand(const std::vector<u8>& data) { +bool HidbusStubbed::SetCommand(std::span<const u8> data) { LOG_ERROR(Service_HID, "Command not implemented"); return false; } diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h index 91165ceff..38eaa0ecc 100644 --- a/src/core/hle/service/hid/hidbus/stubbed.h +++ b/src/core/hle/service/hid/hidbus/stubbed.h @@ -29,7 +29,7 @@ public: u8 GetDeviceId() const override; // Assigns a command from data - bool SetCommand(const std::vector<u8>& data) override; + bool SetCommand(std::span<const u8> data) override; // Returns a reply from a command std::vector<u8> GetReply() const override; diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 6a3453457..52f402c56 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -108,6 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { auto result = IsIrCameraHandleValid(parameters.camera_handle); if (result.IsSuccess()) { // TODO: Stop Image processor + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); result = ResultSuccess; } @@ -139,6 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor<MomentProcessor>(parameters.camera_handle, device); auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -170,6 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { auto& image_transfer_processor = GetProcessor<ClusteringProcessor>(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -219,6 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { GetProcessor<ImageTransferProcessor>(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -294,6 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { auto& image_transfer_processor = GetProcessor<TeraPluginProcessor>(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -343,6 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor<PointingProcessor>(camera_handle, device); auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle); image_transfer_processor.SetConfig(processor_config); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -453,6 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { GetProcessor<ImageTransferProcessor>(parameters.camera_handle); image_transfer_processor.SetConfig(parameters.processor_config); image_transfer_processor.SetTransferMemoryPointer(transfer_memory); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -479,6 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { MakeProcessor<IrLedProcessor>(camera_handle, device); auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle); image_transfer_processor.SetConfig(processor_config); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::IR); } IPC::ResponseBuilder rb{ctx, 2}; @@ -504,6 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { auto result = IsIrCameraHandleValid(parameters.camera_handle); if (result.IsSuccess()) { // TODO: Stop image processor async + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); result = ResultSuccess; } diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index 8f2920c51..1295a44c7 100644 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp @@ -62,7 +62,7 @@ public: const auto parameters{rp.PopRaw<InputParameters>()}; // Optional input/output buffers - std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; + const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span<const u8>()}; std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); // Function call prototype: @@ -132,7 +132,7 @@ public: const auto command{rp.PopRaw<u64>()}; // Optional input/output buffers - std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; + const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span<const u8>()}; std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); // Function call prototype: diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index c49c61cff..e5099d61f 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -412,7 +412,7 @@ public: } void SetAdvertiseData(Kernel::HLERequestContext& ctx) { - std::vector<u8> read_buffer = ctx.ReadBuffer(); + const auto read_buffer = ctx.ReadBuffer(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(lan_discovery.SetAdvertiseData(read_buffer)); @@ -464,7 +464,7 @@ public: parameters.security_config.passphrase_size, parameters.security_config.security_mode, parameters.local_communication_version); - const std::vector<u8> read_buffer = ctx.ReadBuffer(); + const auto read_buffer = ctx.ReadBuffer(); if (read_buffer.size() != sizeof(NetworkInfo)) { LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp index 78578f723..9a3234e8c 100644 --- a/src/core/hle/service/nfc/nfc_device.cpp +++ b/src/core/hle/service/nfc/nfc_device.cpp @@ -130,7 +130,9 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) { return WrongDeviceState; } - if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { + if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::NFC) != + Common::Input::DriverResult::Success) { LOG_ERROR(Service_NFC, "Nfc not supported"); return NfcDisabled; } @@ -141,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) { } Result NfcDevice::StopDetection() { - npad_device->SetPollingMode(Common::Input::PollingMode::Active); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); if (device_state == NFP::DeviceState::Initialized) { return ResultSuccess; diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index c860fd1a1..e67a76f55 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -152,7 +152,9 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) { return WrongDeviceState; } - if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) { + if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::NFC) != + Common::Input::DriverResult::Success) { LOG_ERROR(Service_NFP, "Nfc not supported"); return NfcDisabled; } @@ -163,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) { } Result NfpDevice::StopDetection() { - npad_device->SetPollingMode(Common::Input::PollingMode::Active); + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); if (device_state == DeviceState::Initialized) { return ResultSuccess; diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 204b0e757..c562e04d2 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -3,7 +3,9 @@ #pragma once +#include <span> #include <vector> + #include "common/common_types.h" #include "core/hle/service/nvdrv/nvdata.h" @@ -31,7 +33,7 @@ public: * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + virtual NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) = 0; /** @@ -42,8 +44,8 @@ public: * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) = 0; + virtual NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) = 0; /** * Handles an ioctl3 request. @@ -53,7 +55,7 @@ public: * @param inline_output A buffer where the inlined output data will be written to. * @returns The result code of the ioctl. */ - virtual NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + virtual NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) = 0; /** diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 4122fc98d..5a5b2e305 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -17,19 +17,19 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core) : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {} nvdisp_disp0::~nvdisp_disp0() = default; -NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvdisp_disp0::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvdisp_disp0::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvdisp_disp0::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvdisp_disp0::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 04217ab12..81bd7960a 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -25,12 +25,12 @@ public: explicit nvdisp_disp0(Core::System& system_, NvCore::Container& core); ~nvdisp_disp0() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index b635e6ed1..681bd0867 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -27,7 +27,7 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Con nvhost_as_gpu::~nvhost_as_gpu() = default; -NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 'A': @@ -60,13 +60,13 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8> return NvResult::NotImplemented; } -NvResult nvhost_as_gpu::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { switch (command.group) { case 'A': @@ -87,7 +87,7 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8> void nvhost_as_gpu::OnOpen(DeviceFD fd) {} void nvhost_as_gpu::OnClose(DeviceFD fd) {} -NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::AllocAsEx(std::span<const u8> input, std::vector<u8>& output) { IoctlAllocAsEx params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -141,7 +141,7 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>& return NvResult::Success; } -NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::AllocateSpace(std::span<const u8> input, std::vector<u8>& output) { IoctlAllocSpace params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -220,7 +220,7 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) { mapping_map.erase(offset); } -NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::FreeSpace(std::span<const u8> input, std::vector<u8>& output) { IoctlFreeSpace params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -266,7 +266,7 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& return NvResult::Success; } -NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::Remap(std::span<const u8> input, std::vector<u8>& output) { const auto num_entries = input.size() / sizeof(IoctlRemapEntry); LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries); @@ -320,7 +320,7 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out return NvResult::Success; } -NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::MapBufferEx(std::span<const u8> input, std::vector<u8>& output) { IoctlMapBufferEx params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -424,7 +424,7 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8 return NvResult::Success; } -NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::UnmapBuffer(std::span<const u8> input, std::vector<u8>& output) { IoctlUnmapBuffer params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -463,7 +463,7 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8 return NvResult::Success; } -NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::BindChannel(std::span<const u8> input, std::vector<u8>& output) { IoctlBindChannel params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); @@ -492,7 +492,7 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) { }; } -NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::vector<u8>& output) { IoctlGetVaRegions params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -511,7 +511,7 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u return NvResult::Success; } -NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output, +NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { IoctlGetVaRegions params{}; std::memcpy(¶ms, input.data(), input.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 86fe71c75..1aba8d579 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -47,12 +47,12 @@ public: explicit nvhost_as_gpu(Core::System& system_, Module& module, NvCore::Container& core); ~nvhost_as_gpu() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -138,17 +138,17 @@ private: static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2, "IoctlGetVaRegions is incorrect size"); - NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output); - NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output); - NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output); - NvResult MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output); - NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); - NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output); - NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output); + NvResult AllocAsEx(std::span<const u8> input, std::vector<u8>& output); + NvResult AllocateSpace(std::span<const u8> input, std::vector<u8>& output); + NvResult Remap(std::span<const u8> input, std::vector<u8>& output); + NvResult MapBufferEx(std::span<const u8> input, std::vector<u8>& output); + NvResult UnmapBuffer(std::span<const u8> input, std::vector<u8>& output); + NvResult FreeSpace(std::span<const u8> input, std::vector<u8>& output); + NvResult BindChannel(std::span<const u8> input, std::vector<u8>& output); void GetVARegionsImpl(IoctlGetVaRegions& params); - NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output, + NvResult GetVARegions(std::span<const u8> input, std::vector<u8>& output); + NvResult GetVARegions(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output); void FreeMappingLocked(u64 offset); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index eee11fab8..0cdde82a7 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -34,7 +34,7 @@ nvhost_ctrl::~nvhost_ctrl() { } } -NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 0x0: @@ -63,13 +63,13 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& return NvResult::NotImplemented; } -NvResult nvhost_ctrl::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_ctrl::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_outpu) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; @@ -79,7 +79,7 @@ void nvhost_ctrl::OnOpen(DeviceFD fd) {} void nvhost_ctrl::OnClose(DeviceFD fd) {} -NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::NvOsGetConfigU32(std::span<const u8> input, std::vector<u8>& output) { IocGetConfigParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(), @@ -87,7 +87,7 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector return NvResult::ConfigVarNotFound; // Returns error on production mode } -NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, +NvResult nvhost_ctrl::IocCtrlEventWait(std::span<const u8> input, std::vector<u8>& output, bool is_allocation) { IocCtrlEventWaitParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -231,7 +231,7 @@ NvResult nvhost_ctrl::FreeEvent(u32 slot) { return NvResult::Success; } -NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlEventRegister(std::span<const u8> input, std::vector<u8>& output) { IocCtrlEventRegisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id; @@ -252,8 +252,7 @@ NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::ve return NvResult::Success; } -NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, - std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlEventUnregister(std::span<const u8> input, std::vector<u8>& output) { IocCtrlEventUnregisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id & 0x00FF; @@ -263,7 +262,7 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, return FreeEvent(event_id); } -NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input, +NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(std::span<const u8> input, std::vector<u8>& output) { IocCtrlEventUnregisterBatchParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -282,7 +281,7 @@ NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input, return NvResult::Success; } -NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl::IocCtrlClearEventWait(std::span<const u8> input, std::vector<u8>& output) { IocCtrlEventClearParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 0b56d7070..dd2e7888a 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -25,12 +25,12 @@ public: NvCore::Container& core); ~nvhost_ctrl() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -186,13 +186,13 @@ private: static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8, "IocCtrlEventKill is incorrect size"); - NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, + NvResult NvOsGetConfigU32(std::span<const u8> input, std::vector<u8>& output); + NvResult IocCtrlEventWait(std::span<const u8> input, std::vector<u8>& output, bool is_allocation); - NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocCtrlEventUnregisterBatch(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCtrlEventRegister(std::span<const u8> input, std::vector<u8>& output); + NvResult IocCtrlEventUnregister(std::span<const u8> input, std::vector<u8>& output); + NvResult IocCtrlEventUnregisterBatch(std::span<const u8> input, std::vector<u8>& output); + NvResult IocCtrlClearEventWait(std::span<const u8> input, std::vector<u8>& output); NvResult FreeEvent(u32 slot); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index b97813fbc..be3c083db 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -21,7 +21,7 @@ nvhost_ctrl_gpu::~nvhost_ctrl_gpu() { events_interface.FreeEvent(unknown_event); } -NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 'G': @@ -53,13 +53,13 @@ NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u return NvResult::NotImplemented; } -NvResult nvhost_ctrl_gpu::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { switch (command.group) { case 'G': @@ -82,8 +82,7 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u void nvhost_ctrl_gpu::OnOpen(DeviceFD fd) {} void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {} -NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, - std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -128,7 +127,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, return NvResult::Success; } -NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, +NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; @@ -176,7 +175,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std:: return NvResult::Success; } -NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetTPCMasks(std::span<const u8> input, std::vector<u8>& output) { IoctlGpuGetTpcMasksArgs params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size); @@ -187,7 +186,7 @@ NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector< return NvResult::Success; } -NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, +NvResult nvhost_ctrl_gpu::GetTPCMasks(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { IoctlGpuGetTpcMasksArgs params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -200,7 +199,7 @@ NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector< return NvResult::Success; } -NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetActiveSlotMask(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlActiveSlotMask params{}; @@ -213,7 +212,7 @@ NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::v return NvResult::Success; } -NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlZcullGetCtxSize params{}; @@ -225,7 +224,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vec return NvResult::Success; } -NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZCullGetInfo(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlNvgpuGpuZcullGetInfoArgs params{}; @@ -248,7 +247,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector return NvResult::Success; } -NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZBCSetTable(std::span<const u8> input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcSetTable params{}; @@ -264,7 +263,7 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector< return NvResult::Success; } -NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::ZBCQueryTable(std::span<const u8> input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcQueryTable params{}; @@ -274,7 +273,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vecto return NvResult::Success; } -NvResult nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::FlushL2(std::span<const u8> input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlFlushL2 params{}; @@ -284,7 +283,7 @@ NvResult nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& return NvResult::Success; } -NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_ctrl_gpu::GetGpuTime(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlGetGpuTime params{}; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index 1e8f254e2..b9333d9d3 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -21,12 +21,12 @@ public: explicit nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_); ~nvhost_ctrl_gpu() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -151,21 +151,21 @@ private: }; static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size"); - NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + NvResult GetCharacteristics(std::span<const u8> input, std::vector<u8>& output); + NvResult GetCharacteristics(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output); - NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, + NvResult GetTPCMasks(std::span<const u8> input, std::vector<u8>& output); + NvResult GetTPCMasks(std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output); - NvResult GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output); - NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetActiveSlotMask(std::span<const u8> input, std::vector<u8>& output); + NvResult ZCullGetCtxSize(std::span<const u8> input, std::vector<u8>& output); + NvResult ZCullGetInfo(std::span<const u8> input, std::vector<u8>& output); + NvResult ZBCSetTable(std::span<const u8> input, std::vector<u8>& output); + NvResult ZBCQueryTable(std::span<const u8> input, std::vector<u8>& output); + NvResult FlushL2(std::span<const u8> input, std::vector<u8>& output); + NvResult GetGpuTime(std::span<const u8> input, std::vector<u8>& output); EventInterface& events_interface; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index e123564c6..d2308fffc 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -46,7 +46,7 @@ nvhost_gpu::~nvhost_gpu() { syncpoint_manager.FreeSyncpoint(channel_syncpoint); } -NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 0x0: @@ -98,8 +98,8 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i return NvResult::NotImplemented; }; -NvResult nvhost_gpu::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { switch (command.group) { case 'H': switch (command.cmd) { @@ -112,7 +112,7 @@ NvResult nvhost_gpu::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& i return NvResult::NotImplemented; } -NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; @@ -121,7 +121,7 @@ NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i void nvhost_gpu::OnOpen(DeviceFD fd) {} void nvhost_gpu::OnClose(DeviceFD fd) {} -NvResult nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetNVMAPfd(std::span<const u8> input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); @@ -130,7 +130,7 @@ NvResult nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& o return NvResult::Success; } -NvResult nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetClientData(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; @@ -139,7 +139,7 @@ NvResult nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8> return NvResult::Success; } -NvResult nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::GetClientData(std::span<const u8> input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; @@ -149,7 +149,7 @@ NvResult nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8> return NvResult::Success; } -NvResult nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ZCullBind(std::span<const u8> input, std::vector<u8>& output) { std::memcpy(&zcull_params, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va, zcull_params.mode); @@ -158,7 +158,7 @@ NvResult nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& ou return NvResult::Success; } -NvResult nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetErrorNotifier(std::span<const u8> input, std::vector<u8>& output) { IoctlSetErrorNotifier params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset, @@ -168,14 +168,14 @@ NvResult nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector< return NvResult::Success; } -NvResult nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::SetChannelPriority(std::span<const u8> input, std::vector<u8>& output) { std::memcpy(&channel_priority, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority); return NvResult::Success; } -NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::AllocGPFIFOEx2(std::span<const u8> input, std::vector<u8>& output) { IoctlAllocGpfifoEx2 params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, @@ -197,7 +197,7 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8 return NvResult::Success; } -NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::AllocateObjectContext(std::span<const u8> input, std::vector<u8>& output) { IoctlAllocObjCtx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num, @@ -293,7 +293,7 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8> return NvResult::Success; } -NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output, +NvResult nvhost_gpu::SubmitGPFIFOBase(std::span<const u8> input, std::vector<u8>& output, bool kickoff) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); @@ -314,8 +314,7 @@ NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, std::vector< return SubmitGPFIFOImpl(params, output, std::move(entries)); } -NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, - const std::vector<u8>& input_inline, +NvResult nvhost_gpu::SubmitGPFIFOBase(std::span<const u8> input, std::span<const u8> input_inline, std::vector<u8>& output) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); @@ -328,7 +327,7 @@ NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, return SubmitGPFIFOImpl(params, output, std::move(entries)); } -NvResult nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::GetWaitbase(std::span<const u8> input, std::vector<u8>& output) { IoctlGetWaitbase params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown); @@ -338,7 +337,7 @@ NvResult nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& return NvResult::Success; } -NvResult nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ChannelSetTimeout(std::span<const u8> input, std::vector<u8>& output) { IoctlChannelSetTimeout params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlChannelSetTimeout)); LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout); @@ -346,7 +345,7 @@ NvResult nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector return NvResult::Success; } -NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_gpu::ChannelSetTimeslice(std::span<const u8> input, std::vector<u8>& output) { IoctlSetTimeslice params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSetTimeslice)); LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 1e4ecd55b..3ca58202d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -40,12 +40,12 @@ public: NvCore::Container& core); ~nvhost_gpu() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -186,23 +186,23 @@ private: u32_le channel_priority{}; u32_le channel_timeslice{}; - NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); - NvResult SetClientData(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetClientData(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ZCullBind(const std::vector<u8>& input, std::vector<u8>& output); - NvResult SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output); - NvResult SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output); - NvResult AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); - NvResult AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetNVMAPfd(std::span<const u8> input, std::vector<u8>& output); + NvResult SetClientData(std::span<const u8> input, std::vector<u8>& output); + NvResult GetClientData(std::span<const u8> input, std::vector<u8>& output); + NvResult ZCullBind(std::span<const u8> input, std::vector<u8>& output); + NvResult SetErrorNotifier(std::span<const u8> input, std::vector<u8>& output); + NvResult SetChannelPriority(std::span<const u8> input, std::vector<u8>& output); + NvResult AllocGPFIFOEx2(std::span<const u8> input, std::vector<u8>& output); + NvResult AllocateObjectContext(std::span<const u8> input, std::vector<u8>& output); NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output, Tegra::CommandList&& entries); - NvResult SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output, + NvResult SubmitGPFIFOBase(std::span<const u8> input, std::vector<u8>& output, bool kickoff = false); - NvResult SubmitGPFIFOBase(const std::vector<u8>& input, const std::vector<u8>& input_inline, + NvResult SubmitGPFIFOBase(std::span<const u8> input, std::span<const u8> input_inline, std::vector<u8>& output); - NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); - NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output); + NvResult GetWaitbase(std::span<const u8> input, std::vector<u8>& output); + NvResult ChannelSetTimeout(std::span<const u8> input, std::vector<u8>& output); + NvResult ChannelSetTimeslice(std::span<const u8> input, std::vector<u8>& output); EventInterface& events_interface; NvCore::Container& core; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 1703f9cc3..0c7aee1b8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -15,7 +15,7 @@ nvhost_nvdec::nvhost_nvdec(Core::System& system_, NvCore::Container& core_) : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::NvDec} {} nvhost_nvdec::~nvhost_nvdec() = default; -NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 0x0: @@ -55,13 +55,13 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& return NvResult::NotImplemented; } -NvResult nvhost_nvdec::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_nvdec::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index c1b4e53e8..0d615bbcb 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -13,12 +13,12 @@ public: explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core); ~nvhost_nvdec() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp index 99eede702..7bcef105b 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp @@ -23,7 +23,7 @@ namespace { // Copies count amount of type T from the input vector into the dst vector. // Returns the number of bytes written into dst. template <typename T> -std::size_t SliceVectors(const std::vector<u8>& input, std::vector<T>& dst, std::size_t count, +std::size_t SliceVectors(std::span<const u8> input, std::vector<T>& dst, std::size_t count, std::size_t offset) { if (dst.empty()) { return 0; @@ -63,7 +63,7 @@ nvhost_nvdec_common::~nvhost_nvdec_common() { core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint); } -NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) { +NvResult nvhost_nvdec_common::SetNVMAPfd(std::span<const u8> input) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); @@ -72,7 +72,7 @@ NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) { return NvResult::Success; } -NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input, +NvResult nvhost_nvdec_common::Submit(DeviceFD fd, std::span<const u8> input, std::vector<u8>& output) { IoctlSubmit params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); @@ -121,7 +121,7 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input, return NvResult::Success; } -NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvdec_common::GetSyncpoint(std::span<const u8> input, std::vector<u8>& output) { IoctlGetSyncpoint params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param); @@ -133,7 +133,7 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve return NvResult::Success; } -NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvdec_common::GetWaitbase(std::span<const u8> input, std::vector<u8>& output) { IoctlGetWaitbase params{}; LOG_CRITICAL(Service_NVDRV, "called WAITBASE"); std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); @@ -142,7 +142,7 @@ NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vec return NvResult::Success; } -NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvdec_common::MapBuffer(std::span<const u8> input, std::vector<u8>& output) { IoctlMapBuffer params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); @@ -159,7 +159,7 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto return NvResult::Success; } -NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvdec_common::UnmapBuffer(std::span<const u8> input, std::vector<u8>& output) { IoctlMapBuffer params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); @@ -173,8 +173,7 @@ NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vec return NvResult::Success; } -NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input, - std::vector<u8>& output) { +NvResult nvhost_nvdec_common::SetSubmitTimeout(std::span<const u8> input, std::vector<u8>& output) { std::memcpy(&submit_timeout, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called"); return NvResult::Success; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h index fe76100c8..5af26a26f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -107,13 +107,13 @@ protected: static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); /// Ioctl command implementations - NvResult SetNVMAPfd(const std::vector<u8>& input); - NvResult Submit(DeviceFD fd, const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output); - NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); - NvResult MapBuffer(const std::vector<u8>& input, std::vector<u8>& output); - NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); - NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetNVMAPfd(std::span<const u8> input); + NvResult Submit(DeviceFD fd, std::span<const u8> input, std::vector<u8>& output); + NvResult GetSyncpoint(std::span<const u8> input, std::vector<u8>& output); + NvResult GetWaitbase(std::span<const u8> input, std::vector<u8>& output); + NvResult MapBuffer(std::span<const u8> input, std::vector<u8>& output); + NvResult UnmapBuffer(std::span<const u8> input, std::vector<u8>& output); + NvResult SetSubmitTimeout(std::span<const u8> input, std::vector<u8>& output); Kernel::KEvent* QueryEvent(u32 event_id) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index bdbc2f9e1..39f30e7c8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -12,7 +12,7 @@ namespace Service::Nvidia::Devices { nvhost_nvjpg::nvhost_nvjpg(Core::System& system_) : nvdevice{system_} {} nvhost_nvjpg::~nvhost_nvjpg() = default; -NvResult nvhost_nvjpg::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_nvjpg::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 'H': @@ -31,13 +31,13 @@ NvResult nvhost_nvjpg::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& return NvResult::NotImplemented; } -NvResult nvhost_nvjpg::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_nvjpg::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; @@ -46,7 +46,7 @@ NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& void nvhost_nvjpg::OnOpen(DeviceFD fd) {} void nvhost_nvjpg::OnClose(DeviceFD fd) {} -NvResult nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvhost_nvjpg::SetNVMAPfd(std::span<const u8> input, std::vector<u8>& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 440e7d371..41b57e872 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -15,12 +15,12 @@ public: explicit nvhost_nvjpg(Core::System& system_); ~nvhost_nvjpg() override; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -33,7 +33,7 @@ private: s32_le nvmap_fd{}; - NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); + NvResult SetNVMAPfd(std::span<const u8> input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index 73f97136e..b0ea402a7 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -15,7 +15,7 @@ nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_) nvhost_vic::~nvhost_vic() = default; -NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 0x0: @@ -55,13 +55,13 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i return NvResult::NotImplemented; } -NvResult nvhost_vic::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvhost_vic::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index f164caafb..b5e350a83 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -12,12 +12,12 @@ public: explicit nvhost_vic(Core::System& system_, NvCore::Container& core); ~nvhost_vic(); - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index fa29db758..29c1e0f01 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -25,7 +25,7 @@ nvmap::nvmap(Core::System& system_, NvCore::Container& container_) nvmap::~nvmap() = default; -NvResult nvmap::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvmap::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { switch (command.group) { case 0x1: @@ -54,13 +54,13 @@ NvResult nvmap::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, return NvResult::NotImplemented; } -NvResult nvmap::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult nvmap::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; } -NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); return NvResult::NotImplemented; @@ -69,7 +69,7 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, void nvmap::OnOpen(DeviceFD fd) {} void nvmap::OnClose(DeviceFD fd) {} -NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocCreate(std::span<const u8> input, std::vector<u8>& output) { IocCreateParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size); @@ -89,7 +89,7 @@ NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) return NvResult::Success; } -NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocAlloc(std::span<const u8> input, std::vector<u8>& output) { IocAllocParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address); @@ -137,7 +137,7 @@ NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) return result; } -NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocGetId(std::span<const u8> input, std::vector<u8>& output) { IocGetIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -161,7 +161,7 @@ NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) return NvResult::Success; } -NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocFromId(std::span<const u8> input, std::vector<u8>& output) { IocFromIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -192,7 +192,7 @@ NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) return NvResult::Success; } -NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocParam(std::span<const u8> input, std::vector<u8>& output) { enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 }; IocParamParams params; @@ -241,7 +241,7 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) return NvResult::Success; } -NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { +NvResult nvmap::IocFree(std::span<const u8> input, std::vector<u8>& output) { IocFreeParams params; std::memcpy(¶ms, input.data(), sizeof(params)); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index e9bfd0358..82bd3b118 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -26,12 +26,12 @@ public: nvmap(const nvmap&) = delete; nvmap& operator=(const nvmap&) = delete; - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) override; - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) override; - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output) override; + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) override; + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output) override; void OnOpen(DeviceFD fd) override; void OnClose(DeviceFD fd) override; @@ -106,12 +106,12 @@ private: }; static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); - NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output); - NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output); + NvResult IocCreate(std::span<const u8> input, std::vector<u8>& output); + NvResult IocAlloc(std::span<const u8> input, std::vector<u8>& output); + NvResult IocGetId(std::span<const u8> input, std::vector<u8>& output); + NvResult IocFromId(std::span<const u8> input, std::vector<u8>& output); + NvResult IocParam(std::span<const u8> input, std::vector<u8>& output); + NvResult IocFree(std::span<const u8> input, std::vector<u8>& output); NvCore::Container& container; NvCore::NvMap& file; diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 6fc8565c0..52d27e755 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -124,7 +124,7 @@ DeviceFD Module::Open(const std::string& device_name) { return fd; } -NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output) { if (fd < 0) { LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); @@ -141,8 +141,8 @@ NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input return itr->second->Ioctl1(fd, command, input, output); } -NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output) { +NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output) { if (fd < 0) { LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); return NvResult::InvalidState; @@ -158,7 +158,7 @@ NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input return itr->second->Ioctl2(fd, command, input, inline_input, output); } -NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, +NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, std::vector<u8>& inline_output) { if (fd < 0) { LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index f3c81bd88..b09b6e585 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -7,6 +7,7 @@ #include <functional> #include <list> #include <memory> +#include <span> #include <string> #include <unordered_map> #include <vector> @@ -79,14 +80,13 @@ public: DeviceFD Open(const std::string& device_name); /// Sends an ioctl command to the specified file descriptor. - NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output); + NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output); - NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - const std::vector<u8>& inline_input, std::vector<u8>& output); + NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input, + std::span<const u8> inline_input, std::vector<u8>& output); - NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, - std::vector<u8>& output, std::vector<u8>& inline_output); + NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::vector<u8>& output, + std::vector<u8>& inline_output); /// Closes a device file descriptor and returns operation success. NvResult Close(DeviceFD fd); diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp index e601b5da1..bcbe05b0d 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp @@ -815,8 +815,8 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot, void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) { Status status{Status::NoError}; - Parcel parcel_in{ctx.ReadBuffer()}; - Parcel parcel_out{}; + InputParcel parcel_in{ctx.ReadBuffer()}; + OutputParcel parcel_out{}; switch (code) { case TransactionId::Connect: { diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp index 4043c91f1..769e8c0a3 100644 --- a/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp +++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.cpp @@ -9,7 +9,7 @@ namespace Service::android { -QueueBufferInput::QueueBufferInput(Parcel& parcel) { +QueueBufferInput::QueueBufferInput(InputParcel& parcel) { parcel.ReadFlattened(*this); } diff --git a/src/core/hle/service/nvflinger/graphic_buffer_producer.h b/src/core/hle/service/nvflinger/graphic_buffer_producer.h index 6ea327bbe..2969f0fd5 100644 --- a/src/core/hle/service/nvflinger/graphic_buffer_producer.h +++ b/src/core/hle/service/nvflinger/graphic_buffer_producer.h @@ -14,11 +14,11 @@ namespace Service::android { -class Parcel; +class InputParcel; #pragma pack(push, 1) struct QueueBufferInput final { - explicit QueueBufferInput(Parcel& parcel); + explicit QueueBufferInput(InputParcel& parcel); void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, diff --git a/src/core/hle/service/nvflinger/parcel.h b/src/core/hle/service/nvflinger/parcel.h index f3fa2587d..d1b6201e0 100644 --- a/src/core/hle/service/nvflinger/parcel.h +++ b/src/core/hle/service/nvflinger/parcel.h @@ -4,6 +4,7 @@ #pragma once #include <memory> +#include <span> #include <vector> #include "common/alignment.h" @@ -12,18 +13,17 @@ namespace Service::android { -class Parcel final { -public: - static constexpr std::size_t DefaultBufferSize = 0x40; - - Parcel() : buffer(DefaultBufferSize) {} - - template <typename T> - explicit Parcel(const T& out_data) : buffer(DefaultBufferSize) { - Write(out_data); - } +struct ParcelHeader { + u32 data_size; + u32 data_offset; + u32 objects_size; + u32 objects_offset; +}; +static_assert(sizeof(ParcelHeader) == 16, "ParcelHeader has wrong size"); - explicit Parcel(std::vector<u8> in_data) : buffer(std::move(in_data)) { +class InputParcel final { +public: + explicit InputParcel(std::span<const u8> in_data) : read_buffer(std::move(in_data)) { DeserializeHeader(); [[maybe_unused]] const std::u16string token = ReadInterfaceToken(); } @@ -31,9 +31,9 @@ public: template <typename T> void Read(T& val) { static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); - ASSERT(read_index + sizeof(T) <= buffer.size()); + ASSERT(read_index + sizeof(T) <= read_buffer.size()); - std::memcpy(&val, buffer.data() + read_index, sizeof(T)); + std::memcpy(&val, read_buffer.data() + read_index, sizeof(T)); read_index += sizeof(T); read_index = Common::AlignUp(read_index, 4); } @@ -62,10 +62,10 @@ public: template <typename T> T ReadUnaligned() { static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); - ASSERT(read_index + sizeof(T) <= buffer.size()); + ASSERT(read_index + sizeof(T) <= read_buffer.size()); T val; - std::memcpy(&val, buffer.data() + read_index, sizeof(T)); + std::memcpy(&val, read_buffer.data() + read_index, sizeof(T)); read_index += sizeof(T); return val; } @@ -101,6 +101,31 @@ public: return token; } + void DeserializeHeader() { + ASSERT(read_buffer.size() > sizeof(ParcelHeader)); + + ParcelHeader header{}; + std::memcpy(&header, read_buffer.data(), sizeof(ParcelHeader)); + + read_index = header.data_offset; + } + +private: + std::span<const u8> read_buffer; + std::size_t read_index = 0; +}; + +class OutputParcel final { +public: + static constexpr std::size_t DefaultBufferSize = 0x40; + + OutputParcel() : buffer(DefaultBufferSize) {} + + template <typename T> + explicit OutputParcel(const T& out_data) : buffer(DefaultBufferSize) { + Write(out_data); + } + template <typename T> void Write(const T& val) { static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable."); @@ -133,40 +158,20 @@ public: WriteObject(ptr.get()); } - void DeserializeHeader() { - ASSERT(buffer.size() > sizeof(Header)); - - Header header{}; - std::memcpy(&header, buffer.data(), sizeof(Header)); - - read_index = header.data_offset; - } - std::vector<u8> Serialize() const { - ASSERT(read_index == 0); - - Header header{}; - header.data_size = static_cast<u32>(write_index - sizeof(Header)); - header.data_offset = sizeof(Header); + ParcelHeader header{}; + header.data_size = static_cast<u32>(write_index - sizeof(ParcelHeader)); + header.data_offset = sizeof(ParcelHeader); header.objects_size = 4; - header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size); - std::memcpy(buffer.data(), &header, sizeof(Header)); + header.objects_offset = static_cast<u32>(sizeof(ParcelHeader) + header.data_size); + std::memcpy(buffer.data(), &header, sizeof(ParcelHeader)); return buffer; } private: - struct Header { - u32 data_size; - u32 data_offset; - u32 objects_size; - u32 objects_offset; - }; - static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size"); - mutable std::vector<u8> buffer; - std::size_t read_index = 0; - std::size_t write_index = sizeof(Header); + std::size_t write_index = sizeof(ParcelHeader); }; } // namespace Service::android diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 78f897d3e..01040b32a 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -63,7 +63,7 @@ private: return ctx.ReadBuffer(1); } - return std::vector<u8>{}; + return std::span<const u8>{}; }(); LOG_DEBUG(Service_PREPO, @@ -90,7 +90,7 @@ private: return ctx.ReadBuffer(1); } - return std::vector<u8>{}; + return std::span<const u8>{}; }(); LOG_DEBUG(Service_PREPO, @@ -142,7 +142,7 @@ private: return ctx.ReadBuffer(1); } - return std::vector<u8>{}; + return std::span<const u8>{}; }(); LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", @@ -166,7 +166,7 @@ private: return ctx.ReadBuffer(1); } - return std::vector<u8>{}; + return std::span<const u8>{}; }(); LOG_DEBUG(Service_PREPO, diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 9e94a462f..bdb499268 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -208,7 +208,6 @@ void BSD::Bind(Kernel::HLERequestContext& ctx) { const s32 fd = rp.Pop<s32>(); LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); - BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer())); } @@ -312,7 +311,7 @@ void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) { const u32 level = rp.Pop<u32>(); const OptName optname = static_cast<OptName>(rp.Pop<u32>()); - const std::vector<u8> buffer = ctx.ReadBuffer(); + const auto buffer = ctx.ReadBuffer(); const u8* optval = buffer.empty() ? nullptr : buffer.data(); size_t optlen = buffer.size(); @@ -489,7 +488,7 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco return {fd, Errno::SUCCESS}; } -std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer, +std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout) { if (write_buffer.size() < nfds * sizeof(PollFD)) { return {-1, Errno::INVAL}; @@ -584,7 +583,7 @@ std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) { return {new_fd, Errno::SUCCESS}; } -Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) { +Errno BSD::BindImpl(s32 fd, std::span<const u8> addr) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } @@ -595,7 +594,7 @@ Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) { return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); } -Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) { +Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } @@ -800,15 +799,15 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess return {ret, bsd_errno}; } -std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) { +std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } return Translate(file_descriptors[fd]->socket->Send(message, flags)); } -std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message, - const std::vector<u8>& addr) { +std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message, + std::span<const u8> addr) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 81e855e0f..56bb3f8b1 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -4,6 +4,7 @@ #pragma once #include <memory> +#include <span> #include <vector> #include "common/common_types.h" @@ -44,7 +45,7 @@ private: s32 nfds; s32 timeout; - std::vector<u8> read_buffer; + std::span<const u8> read_buffer; std::vector<u8> write_buffer; s32 ret{}; Errno bsd_errno{}; @@ -65,7 +66,7 @@ private: void Response(Kernel::HLERequestContext& ctx); s32 fd; - std::vector<u8> addr; + std::span<const u8> addr; Errno bsd_errno{}; }; @@ -98,7 +99,7 @@ private: s32 fd; u32 flags; - std::vector<u8> message; + std::span<const u8> message; s32 ret{}; Errno bsd_errno{}; }; @@ -109,8 +110,8 @@ private: s32 fd; u32 flags; - std::vector<u8> message; - std::vector<u8> addr; + std::span<const u8> message; + std::span<const u8> addr; s32 ret{}; Errno bsd_errno{}; }; @@ -143,11 +144,11 @@ private: void ExecuteWork(Kernel::HLERequestContext& ctx, Work work); std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol); - std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer, + std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout); std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer); - Errno BindImpl(s32 fd, const std::vector<u8>& addr); - Errno ConnectImpl(s32 fd, const std::vector<u8>& addr); + Errno BindImpl(s32 fd, std::span<const u8> addr); + Errno ConnectImpl(s32 fd, std::span<const u8> addr); Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer); Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer); Errno ListenImpl(s32 fd, s32 backlog); @@ -157,9 +158,9 @@ private: std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message); std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, std::vector<u8>& addr); - std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message); - std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message, - const std::vector<u8>& addr); + std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, std::span<const u8> message); + std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, std::span<const u8> message, + std::span<const u8> addr); Errno CloseImpl(s32 fd); s32 FindFreeFileDescriptorHandle() noexcept; diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 097c37d7a..e96eda7f3 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -243,4 +243,4 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(Kernel::HLERequestContext& ctx) { rb.Push(0); } -} // namespace Service::Sockets
\ No newline at end of file +} // namespace Service::Sockets diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 3735e0452..dcf47083f 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -101,7 +101,7 @@ private: void ImportServerPki(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto certificate_format = rp.PopEnum<CertificateFormat>(); - const auto pkcs_12_certificates = ctx.ReadBuffer(0); + [[maybe_unused]] const auto pkcs_12_certificates = ctx.ReadBuffer(0); constexpr u64 server_id = 0; @@ -113,13 +113,13 @@ private: } void ImportClientPki(Kernel::HLERequestContext& ctx) { - const auto pkcs_12_certificate = ctx.ReadBuffer(0); - const auto ascii_password = [&ctx] { + [[maybe_unused]] const auto pkcs_12_certificate = ctx.ReadBuffer(0); + [[maybe_unused]] const auto ascii_password = [&ctx] { if (ctx.CanReadBuffer(1)) { return ctx.ReadBuffer(1); } - return std::vector<u8>{}; + return std::span<const u8>{}; }(); constexpr u64 client_id = 0; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index bb283e74e..2fb631183 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -603,7 +603,7 @@ private: return; } - const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}}; + const auto parcel = android::OutputParcel{NativeWindow{*buffer_queue_id}}; const auto buffer_size = ctx.WriteBuffer(parcel.Serialize()); IPC::ResponseBuilder rb{ctx, 4}; @@ -649,7 +649,7 @@ private: return; } - const auto parcel = android::Parcel{NativeWindow{*buffer_queue_id}}; + const auto parcel = android::OutputParcel{NativeWindow{*buffer_queue_id}}; const auto buffer_size = ctx.WriteBuffer(parcel.Serialize()); IPC::ResponseBuilder rb{ctx, 6}; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 282ea1ff9..7494fb62d 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -550,7 +550,7 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, Sock return {-1, GetAndLogLastError()}; } -std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { +std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); @@ -563,7 +563,7 @@ std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) { return {-1, GetAndLogLastError()}; } -std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message, +std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, const SockAddrIn* addr) { ASSERT(flags == 0); diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index 1e1c42cea..7a77171c2 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -182,7 +182,7 @@ std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& mes return {static_cast<u32>(read_bytes), Errno::SUCCESS}; } -std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) { +std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags) { LOG_WARNING(Network, "(STUBBED) called"); ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); @@ -200,7 +200,7 @@ void ProxySocket::SendPacket(ProxyPacket& packet) { } } -std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message, +std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message, const SockAddrIn* addr) { ASSERT(flags == 0); diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h index f12b5f567..9421492bc 100644 --- a/src/core/internal_network/socket_proxy.h +++ b/src/core/internal_network/socket_proxy.h @@ -4,6 +4,7 @@ #pragma once #include <mutex> +#include <span> #include <vector> #include <queue> @@ -48,11 +49,11 @@ public: std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr, std::size_t max_length); - std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; + std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override; void SendPacket(ProxyPacket& packet); - std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const SockAddrIn* addr) override; Errno SetLinger(bool enable, u32 linger) override; diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h index 2e328c645..4c7489258 100644 --- a/src/core/internal_network/sockets.h +++ b/src/core/internal_network/sockets.h @@ -5,6 +5,7 @@ #include <map> #include <memory> +#include <span> #include <utility> #if defined(_WIN32) @@ -66,9 +67,9 @@ public: virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) = 0; - virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0; + virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0; - virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + virtual std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const SockAddrIn* addr) = 0; virtual Errno SetLinger(bool enable, u32 linger) = 0; @@ -138,9 +139,9 @@ public: std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override; - std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; + std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override; - std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const SockAddrIn* addr) override; Errno SetLinger(bool enable, u32 linger) override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index a1e41faff..af9660b55 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -383,6 +383,10 @@ struct Memory::Impl { return; } + if (Settings::IsFastmemEnabled()) { + system.DeviceMemory().buffer.Protect(vaddr, size, !debug, !debug); + } + // Iterate over a contiguous CPU address space, marking/unmarking the region. // The region is at a granularity of CPU pages. @@ -436,7 +440,7 @@ struct Memory::Impl { } if (Settings::IsFastmemEnabled()) { - const bool is_read_enable = !Settings::IsGPULevelExtreme() || !cached; + const bool is_read_enable = Settings::IsGPULevelHigh() || !cached; system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); } diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 77821e047..59dfb8767 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -312,7 +312,7 @@ void Reporter::SaveUnimplementedAppletReport( } void Reporter::SavePlayReport(PlayReportType type, u64 title_id, - const std::vector<std::vector<u8>>& data, + const std::vector<std::span<const u8>>& data, std::optional<u64> process_id, std::optional<u128> user_id) const { if (!IsReportingEnabled()) { return; diff --git a/src/core/reporter.h b/src/core/reporter.h index 9fdb9d6c1..bb11f8e7c 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -5,6 +5,7 @@ #include <array> #include <optional> +#include <span> #include <string> #include <vector> #include "common/common_types.h" @@ -56,7 +57,8 @@ public: System, }; - void SavePlayReport(PlayReportType type, u64 title_id, const std::vector<std::vector<u8>>& data, + void SavePlayReport(PlayReportType type, u64 title_id, + const std::vector<std::span<const u8>>& data, std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const; // Used by error applet diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index cef2c4d52..e3b627e4f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -51,8 +51,29 @@ endif() if (ENABLE_SDL2) target_sources(input_common PRIVATE + drivers/joycon.cpp + drivers/joycon.h drivers/sdl_driver.cpp drivers/sdl_driver.h + helpers/joycon_driver.cpp + helpers/joycon_driver.h + helpers/joycon_protocol/calibration.cpp + helpers/joycon_protocol/calibration.h + helpers/joycon_protocol/common_protocol.cpp + helpers/joycon_protocol/common_protocol.h + helpers/joycon_protocol/generic_functions.cpp + helpers/joycon_protocol/generic_functions.h + helpers/joycon_protocol/joycon_types.h + helpers/joycon_protocol/irs.cpp + helpers/joycon_protocol/irs.h + helpers/joycon_protocol/nfc.cpp + helpers/joycon_protocol/nfc.h + helpers/joycon_protocol/poller.cpp + helpers/joycon_protocol/poller.h + helpers/joycon_protocol/ringcon.cpp + helpers/joycon_protocol/ringcon.h + helpers/joycon_protocol/rumble.cpp + helpers/joycon_protocol/rumble.h ) target_link_libraries(input_common PRIVATE SDL2::SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp index fad9177dc..04970f635 100644 --- a/src/input_common/drivers/camera.cpp +++ b/src/input_common/drivers/camera.cpp @@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const { } } -Common::Input::CameraError Camera::SetCameraFormat( +Common::Input::DriverResult Camera::SetCameraFormat( [[maybe_unused]] const PadIdentifier& identifier_, const Common::Input::CameraFormat camera_format) { status.format = camera_format; - return Common::Input::CameraError::None; + return Common::Input::DriverResult::Success; } } // namespace InputCommon diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h index ead3e0fde..24b27e325 100644 --- a/src/input_common/drivers/camera.h +++ b/src/input_common/drivers/camera.h @@ -22,8 +22,8 @@ public: std::size_t getImageWidth() const; std::size_t getImageHeight() const; - Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_, - Common::Input::CameraFormat camera_format) override; + Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) override; private: Common::Input::CameraStatus status{}; diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 826fa2109..d09ff178b 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/param_package.h" +#include "common/polyfill_thread.h" #include "common/settings_input.h" #include "common/thread.h" #include "input_common/drivers/gc_adapter.h" @@ -217,8 +218,7 @@ void GCAdapter::AdapterScanThread(std::stop_token stop_token) { Common::SetCurrentThreadName("ScanGCAdapter"); usb_adapter_handle = nullptr; pads = {}; - while (!stop_token.stop_requested() && !Setup()) { - std::this_thread::sleep_for(std::chrono::seconds(2)); + while (!Setup() && Common::StoppableTimedWait(stop_token, std::chrono::seconds{2})) { } } @@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -Common::Input::VibrationError GCAdapter::SetVibration( +Common::Input::DriverResult GCAdapter::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; const auto processed_amplitude = @@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration( pads[identifier.port].rumble_amplitude = processed_amplitude; if (!rumble_enabled) { - return Common::Input::VibrationError::Disabled; + return Common::Input::DriverResult::Disabled; } - return Common::Input::VibrationError::None; + return Common::Input::DriverResult::Success; } bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index b5270fd0b..3c2eb376d 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -25,7 +25,7 @@ public: explicit GCAdapter(std::string input_engine_); ~GCAdapter() override; - Common::Input::VibrationError SetVibration( + Common::Input::DriverResult SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp new file mode 100644 index 000000000..4fcfb4510 --- /dev/null +++ b/src/input_common/drivers/joycon.cpp @@ -0,0 +1,677 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "common/polyfill_ranges.h" +#include "common/polyfill_thread.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/joycon.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon { + +Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { + // Avoid conflicting with SDL driver + if (!Settings::values.enable_joycon_driver) { + return; + } + LOG_INFO(Input, "Joycon driver Initialization started"); + const int init_res = SDL_hid_init(); + if (init_res == 0) { + Setup(); + } else { + LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); + } +} + +Joycons::~Joycons() { + Reset(); +} + +void Joycons::Reset() { + scan_thread = {}; + for (const auto& device : left_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + for (const auto& device : right_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + SDL_hid_exit(); +} + +void Joycons::Setup() { + u32 port = 0; + PreSetController(GetIdentifier(0, Joycon::ControllerType::None)); + for (auto& device : left_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); + device = std::make_shared<Joycon::JoyconDriver>(port++); + } + port = 0; + for (auto& device : right_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); + device = std::make_shared<Joycon::JoyconDriver>(port++); + } + + scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); +} + +void Joycons::ScanThread(std::stop_token stop_token) { + constexpr u16 nintendo_vendor_id = 0x057e; + Common::SetCurrentThreadName("JoyconScanThread"); + + do { + SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); + SDL_hid_device_info* cur_dev = devs; + + while (cur_dev) { + if (IsDeviceNew(cur_dev)) { + LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, + cur_dev->product_id); + RegisterNewDevice(cur_dev); + } + cur_dev = cur_dev->next; + } + + SDL_hid_free_enumeration(devs); + } while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5})); +} + +bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { + Joycon::ControllerType type{}; + Joycon::SerialNumber serial_number{}; + + const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + if (result != Joycon::DriverResult::Success) { + return false; + } + + const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); + if (result2 != Joycon::DriverResult::Success) { + return false; + } + + auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetHandleSerialNumber() != serial_number) { + return false; + } + return true; + }; + + // Check if device already exist + switch (type) { + case Joycon::ControllerType::Left: + for (const auto& device : left_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + case Joycon::ControllerType::Right: + for (const auto& device : right_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + default: + return false; + } + + return true; +} + +void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { + Joycon::ControllerType type{}; + auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + auto handle = GetNextFreeHandle(type); + if (handle == nullptr) { + LOG_WARNING(Input, "No free handles available"); + return; + } + if (result == Joycon::DriverResult::Success) { + result = handle->RequestDeviceAccess(device_info); + } + if (result == Joycon::DriverResult::Success) { + LOG_WARNING(Input, "Initialize device"); + + const std::size_t port = handle->GetDevicePort(); + const Joycon::JoyconCallbacks callbacks{ + .on_battery_data = {[this, port, type](Joycon::Battery value) { + OnBatteryUpdate(port, type, value); + }}, + .on_color_data = {[this, port, type](Joycon::Color value) { + OnColorUpdate(port, type, value); + }}, + .on_button_data = {[this, port, type](int id, bool value) { + OnButtonUpdate(port, type, id, value); + }}, + .on_stick_data = {[this, port, type](int id, f32 value) { + OnStickUpdate(port, type, id, value); + }}, + .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) { + OnMotionUpdate(port, type, id, value); + }}, + .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, + .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { + OnAmiiboUpdate(port, amiibo_data); + }}, + .on_camera_data = {[this, port](const std::vector<u8>& camera_data, + Joycon::IrsResolution format) { + OnCameraUpdate(port, camera_data, format); + }}, + }; + + handle->InitializeDevice(); + handle->SetCallbacks(callbacks); + } +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle( + Joycon::ControllerType type) const { + if (type == Joycon::ControllerType::Left) { + const auto unconnected_device = + std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); }); + if (unconnected_device != left_joycons.end()) { + return *unconnected_device; + } + } + if (type == Joycon::ControllerType::Right) { + const auto unconnected_device = std::ranges::find_if( + right_joycons, [](auto& device) { return !device->IsConnected(); }); + + if (unconnected_device != right_joycons.end()) { + return *unconnected_device; + } + } + return nullptr; +} + +bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { + const auto handle = GetHandle(identifier); + if (handle == nullptr) { + return false; + } + return handle->IsVibrationEnabled(); +} + +Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier, + const Common::Input::VibrationStatus& vibration) { + const Joycon::VibrationValue native_vibration{ + .low_amplitude = vibration.low_amplitude, + .low_frequency = vibration.low_frequency, + .high_amplitude = vibration.high_amplitude, + .high_frequency = vibration.high_frequency, + }; + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + + handle->SetVibration(native_vibration); + return Common::Input::DriverResult::Success; +} + +Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier, + const Common::Input::LedStatus& led_status) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + int led_config = led_status.led_1 ? 1 : 0; + led_config += led_status.led_2 ? 2 : 0; + led_config += led_status.led_3 ? 4 : 0; + led_config += led_status.led_4 ? 8 : 0; + + return static_cast<Common::Input::DriverResult>( + handle->SetLedConfig(static_cast<u8>(led_config))); +} + +Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier, + Common::Input::CameraFormat camera_format) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::DriverResult::InvalidHandle; + } + return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig( + Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format))); +}; + +Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { + return Common::Input::NfcState::Success; +}; + +Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, + const std::vector<u8>& data) { + return Common::Input::NfcState::NotSupported; +}; + +Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, + const Common::Input::PollingMode polling_mode) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + LOG_ERROR(Input, "Invalid handle {}", identifier.port); + return Common::Input::DriverResult::InvalidHandle; + } + + switch (polling_mode) { + case Common::Input::PollingMode::Active: + return static_cast<Common::Input::DriverResult>(handle->SetActiveMode()); + case Common::Input::PollingMode::Pasive: + return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode()); + case Common::Input::PollingMode::IR: + return static_cast<Common::Input::DriverResult>(handle->SetIrMode()); + case Common::Input::PollingMode::NFC: + return static_cast<Common::Input::DriverResult>(handle->SetNfcMode()); + case Common::Input::PollingMode::Ring: + return static_cast<Common::Input::DriverResult>(handle->SetRingConMode()); + default: + return Common::Input::DriverResult::NotSupported; + } +} + +void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, + Joycon::Battery value) { + const auto identifier = GetIdentifier(port, type); + if (value.charging != 0) { + SetBattery(identifier, Common::Input::BatteryLevel::Charging); + return; + } + + Common::Input::BatteryLevel battery{}; + switch (value.status) { + case 0: + battery = Common::Input::BatteryLevel::Empty; + break; + case 1: + battery = Common::Input::BatteryLevel::Critical; + break; + case 2: + battery = Common::Input::BatteryLevel::Low; + break; + case 3: + battery = Common::Input::BatteryLevel::Medium; + break; + case 4: + default: + battery = Common::Input::BatteryLevel::Full; + break; + } + SetBattery(identifier, battery); +} + +void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, + const Joycon::Color& value) { + const auto identifier = GetIdentifier(port, type); + Common::Input::BodyColorStatus color{ + .body = value.body, + .buttons = value.buttons, + .left_grip = value.left_grip, + .right_grip = value.right_grip, + }; + SetColor(identifier, color); +} + +void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { + const auto identifier = GetIdentifier(port, type); + SetButton(identifier, id, value); +} + +void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { + const auto identifier = GetIdentifier(port, type); + SetAxis(identifier, id, value); +} + +void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, + const Joycon::MotionData& value) { + const auto identifier = GetIdentifier(port, type); + BasicMotion motion_data{ + .gyro_x = value.gyro_x, + .gyro_y = value.gyro_y, + .gyro_z = value.gyro_z, + .accel_x = value.accel_x, + .accel_y = value.accel_y, + .accel_z = value.accel_z, + .delta_timestamp = 15000, + }; + SetMotion(identifier, id, motion_data); +} + +void Joycons::OnRingConUpdate(f32 ring_data) { + // To simplify ring detection it will always be mapped to an empty identifier for all + // controllers + constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, + }; + SetAxis(identifier, 100, ring_data); +} + +void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) { + const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); + const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved + : Common::Input::NfcState::NewAmiibo; + SetNfc(identifier, {nfc_state, amiibo_data}); +} + +void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, + Joycon::IrsResolution format) { + const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); + SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data}); +} + +std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const { + auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetDevicePort() == identifier.port) { + return true; + } + return false; + }; + const auto type = static_cast<Joycon::ControllerType>(identifier.pad); + + if (type == Joycon::ControllerType::Left) { + const auto matching_device = std::ranges::find_if( + left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + + if (matching_device != left_joycons.end()) { + return *matching_device; + } + } + + if (type == Joycon::ControllerType::Right) { + const auto matching_device = std::ranges::find_if( + right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); }); + + if (matching_device != right_joycons.end()) { + return *matching_device; + } + } + + return nullptr; +} + +PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { + const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)}; + return { + .guid = Common::UUID{guid}, + .port = port, + .pad = static_cast<std::size_t>(type), + }; +} + +Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const { + const auto identifier = GetIdentifier(port, type); + return { + {"engine", GetEngineName()}, + {"guid", identifier.guid.RawString()}, + {"port", std::to_string(identifier.port)}, + {"pad", std::to_string(identifier.pad)}, + }; +} + +std::vector<Common::ParamPackage> Joycons::GetInputDevices() const { + std::vector<Common::ParamPackage> devices{}; + + auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) { + if (!device) { + return; + } + if (!device->IsConnected()) { + return; + } + auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType()); + std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), + device->GetDevicePort() + 1); + param.Set("display", std::move(name)); + devices.emplace_back(param); + }; + + for (const auto& controller : left_joycons) { + add_entry(controller); + } + for (const auto& controller : right_joycons) { + add_entry(controller); + } + + // List dual joycon pairs + for (std::size_t i = 0; i < MaxSupportedControllers; i++) { + if (!left_joycons[i] || !right_joycons[i]) { + continue; + } + if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) { + continue; + } + auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType()); + const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType()); + const auto type = Joycon::ControllerType::Dual; + std::string name = fmt::format("{} {}", JoyconName(type), i + 1); + + main_param.Set("display", std::move(name)); + main_param.Set("guid2", second_param.Get("guid", "")); + main_param.Set("pad", std::to_string(static_cast<size_t>(type))); + devices.emplace_back(main_param); + } + + return devices; +} + +ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { + static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>, + 18> + switch_to_joycon_button = { + std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true}, + {Settings::NativeButton::B, Joycon::PadButton::B, true}, + {Settings::NativeButton::X, Joycon::PadButton::X, true}, + {Settings::NativeButton::Y, Joycon::PadButton::Y, true}, + {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false}, + {Settings::NativeButton::DUp, Joycon::PadButton::Up, false}, + {Settings::NativeButton::DRight, Joycon::PadButton::Right, false}, + {Settings::NativeButton::DDown, Joycon::PadButton::Down, false}, + {Settings::NativeButton::L, Joycon::PadButton::L, false}, + {Settings::NativeButton::R, Joycon::PadButton::R, true}, + {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false}, + {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true}, + {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true}, + {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false}, + {Settings::NativeButton::Home, Joycon::PadButton::Home, true}, + {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false}, + {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false}, + {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true}, + }; + + if (!params.Has("port")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + if (pad == Joycon::ControllerType::Dual) { + pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left; + } + + Common::ParamPackage button_params = GetParamPackage(port, pad); + button_params.Set("button", static_cast<int>(joycon_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Map SL and SR buttons for left joycons + if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left); + + Common::ParamPackage sl_button_params = button_params; + Common::ParamPackage sr_button_params = button_params; + sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL)); + sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR)); + mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); + mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); + } + + // Map SL and SR buttons for right joycons + if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) { + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right); + + Common::ParamPackage sl_button_params = button_params; + Common::ParamPackage sr_button_params = button_params; + sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL)); + sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR)); + mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params)); + mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params)); + } + + return mapping; +} + +AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + auto pad_right = pad_left; + if (pad_left == Joycon::ControllerType::Dual) { + pad_left = Joycon::ControllerType::Left; + pad_right = Joycon::ControllerType::Right; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left); + left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right); + right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + const std::size_t port = static_cast<std::size_t>(params.Get("port", 0)); + auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0)); + auto pad_right = pad_left; + if (pad_left == Joycon::ControllerType::Dual) { + pad_left = Joycon::ControllerType::Left; + pad_right = Joycon::ControllerType::Right; + } + + MotionMapping mapping = {}; + Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left); + left_motion_params.Set("motion", 0); + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); + Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right); + right_Motion_params.Set("motion", 1); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); + return mapping; +} + +Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { + const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0)); + switch (button) { + case Joycon::PadButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case Joycon::PadButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case Joycon::PadButton::Down: + return Common::Input::ButtonNames::ButtonDown; + case Joycon::PadButton::Up: + return Common::Input::ButtonNames::ButtonUp; + case Joycon::PadButton::LeftSL: + case Joycon::PadButton::RightSL: + return Common::Input::ButtonNames::TriggerSL; + case Joycon::PadButton::LeftSR: + case Joycon::PadButton::RightSR: + return Common::Input::ButtonNames::TriggerSR; + case Joycon::PadButton::L: + return Common::Input::ButtonNames::TriggerL; + case Joycon::PadButton::R: + return Common::Input::ButtonNames::TriggerR; + case Joycon::PadButton::ZL: + return Common::Input::ButtonNames::TriggerZL; + case Joycon::PadButton::ZR: + return Common::Input::ButtonNames::TriggerZR; + case Joycon::PadButton::A: + return Common::Input::ButtonNames::ButtonA; + case Joycon::PadButton::B: + return Common::Input::ButtonNames::ButtonB; + case Joycon::PadButton::X: + return Common::Input::ButtonNames::ButtonX; + case Joycon::PadButton::Y: + return Common::Input::ButtonNames::ButtonY; + case Joycon::PadButton::Plus: + return Common::Input::ButtonNames::ButtonPlus; + case Joycon::PadButton::Minus: + return Common::Input::ButtonNames::ButtonMinus; + case Joycon::PadButton::Home: + return Common::Input::ButtonNames::ButtonHome; + case Joycon::PadButton::Capture: + return Common::Input::ButtonNames::ButtonCapture; + case Joycon::PadButton::StickL: + return Common::Input::ButtonNames::ButtonStickL; + case Joycon::PadButton::StickR: + return Common::Input::ButtonNames::ButtonStickR; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +std::string Joycons::JoyconName(Joycon::ControllerType type) const { + switch (type) { + case Joycon::ControllerType::Left: + return "Left Joycon"; + case Joycon::ControllerType::Right: + return "Right Joycon"; + case Joycon::ControllerType::Pro: + return "Pro Controller"; + case Joycon::ControllerType::Dual: + return "Dual Joycon"; + default: + return "Unknown Switch Controller"; + } +} +} // namespace InputCommon diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h new file mode 100644 index 000000000..2149ab7fd --- /dev/null +++ b/src/input_common/drivers/joycon.h @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> +#include <thread> +#include <SDL_hidapi.h> + +#include "input_common/input_engine.h" + +namespace InputCommon::Joycon { +using SerialNumber = std::array<u8, 15>; +struct Battery; +struct Color; +struct MotionData; +enum class ControllerType : u8; +enum class DriverResult; +enum class IrsResolution; +class JoyconDriver; +} // namespace InputCommon::Joycon + +namespace InputCommon { + +class Joycons final : public InputCommon::InputEngine { +public: + explicit Joycons(const std::string& input_engine_); + + ~Joycons(); + + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + Common::Input::DriverResult SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + + Common::Input::DriverResult SetLeds(const PadIdentifier& identifier, + const Common::Input::LedStatus& led_status) override; + + Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, + Common::Input::CameraFormat camera_format) override; + + Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; + Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, + const std::vector<u8>& data) override; + + Common::Input::DriverResult SetPollingMode( + const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; + + /// Used for automapping features + std::vector<Common::ParamPackage> GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + static constexpr std::size_t MaxSupportedControllers = 8; + + /// For shutting down, clear all data, join all threads, release usb devices + void Reset(); + + /// Registers controllers, clears all data and starts the scan thread + void Setup(); + + /// Actively searchs for new devices + void ScanThread(std::stop_token stop_token); + + /// Returns true if device is valid and not registered + bool IsDeviceNew(SDL_hid_device_info* device_info) const; + + /// Tries to connect to the new device + void RegisterNewDevice(SDL_hid_device_info* device_info); + + /// Returns the next free handle + std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const; + + void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value); + void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value); + void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value); + void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value); + void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, + const Joycon::MotionData& value); + void OnRingConUpdate(f32 ring_data); + void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data); + void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, + Joycon::IrsResolution format); + + /// Returns a JoyconHandle corresponding to a PadIdentifier + std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const; + + /// Returns a PadIdentifier corresponding to the port number and joycon type + PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const; + + /// Returns a ParamPackage corresponding to the port number and joycon type + Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const; + + std::string JoyconName(std::size_t port) const; + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + /// Returns the name of the device in text format + std::string JoyconName(Joycon::ControllerType type) const; + + std::jthread scan_thread; + + // Joycon types are split by type to ease supporting dualjoycon configurations + std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{}; + std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 4818bb744..d975eb815 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -40,25 +40,26 @@ public: } void EnableMotion() { - if (sdl_controller) { - SDL_GameController* controller = sdl_controller.get(); - has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; - has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; - if (has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); - } - if (has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); - } + if (!sdl_controller) { + return; + } + SDL_GameController* controller = sdl_controller.get(); + if (HasMotion()) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); + } + has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; + has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; + if (has_accel) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + } + if (has_gyro) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); } } - bool HasGyro() const { - return has_gyro; - } - - bool HasAccel() const { - return has_accel; + bool HasMotion() const { + return has_gyro || has_accel; } bool UpdateMotion(SDL_ControllerSensorEvent event) { @@ -85,6 +86,20 @@ public: if (time_difference == 0) { return false; } + + // Motion data is invalid + if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 && + motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) { + if (motion_error_count++ < 200) { + return false; + } + // Try restarting the sensor + motion_error_count = 0; + EnableMotion(); + return false; + } + + motion_error_count = 0; motion.delta_timestamp = time_difference * 1000; return true; } @@ -250,6 +265,7 @@ private: mutable std::mutex mutex; u64 last_motion_update{}; + std::size_t motion_error_count{}; bool has_gyro{false}; bool has_accel{false}; bool has_vibration{false}; @@ -318,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) { const auto guid = GetGUID(sdl_joystick); + if (Settings::values.enable_joycon_driver) { + if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && + (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); + SDL_JoystickClose(sdl_joystick); + return; + } + } + std::scoped_lock lock{joystick_map_mutex}; if (joystick_map.find(guid) == joystick_map.end()) { auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); @@ -440,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); - // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and - // not a generic one - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled + if (Settings::values.enable_joycon_driver) { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0"); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + } + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. @@ -532,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { return devices; } -Common::Input::VibrationError SDLDriver::SetVibration( +Common::Input::DriverResult SDLDriver::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port)); @@ -566,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration( .vibration = new_vibration, }); - return Common::Input::VibrationError::None; + return Common::Input::DriverResult::Success; } bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { @@ -942,18 +971,18 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p MotionMapping mapping = {}; joystick->EnableMotion(); - if (joystick->HasGyro() || joystick->HasAccel()) { + if (joystick->HasMotion()) { mapping.insert_or_assign(Settings::NativeMotion::MotionRight, BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); } if (params.Has("guid2")) { joystick2->EnableMotion(); - if (joystick2->HasGyro() || joystick2->HasAccel()) { + if (joystick2->HasMotion()) { mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); } } else { - if (joystick->HasGyro() || joystick->HasAccel()) { + if (joystick->HasMotion()) { mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); } diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 366bcc496..ffde169b3 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -63,7 +63,7 @@ public: bool IsStickInverted(const Common::ParamPackage& params) override; - Common::Input::VibrationError SetVibration( + Common::Input::DriverResult SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; bool IsVibrationEnabled(const PadIdentifier& identifier) override; diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 63ffaca67..4a0268a4d 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp @@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move( VirtualAmiibo::~VirtualAmiibo() = default; -Common::Input::PollingError VirtualAmiibo::SetPollingMode( +Common::Input::DriverResult VirtualAmiibo::SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) { polling_mode = polling_mode_; - if (polling_mode == Common::Input::PollingMode::NFC) { + switch (polling_mode) { + case Common::Input::PollingMode::NFC: if (state == State::Initialized) { state = State::WaitingForAmiibo; } - } else { + return Common::Input::DriverResult::Success; + default: if (state == State::AmiiboIsOpen) { CloseAmiibo(); } + return Common::Input::DriverResult::NotSupported; } - - return Common::Input::PollingError::None; } Common::Input::NfcState VirtualAmiibo::SupportsNfc( diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 0f9dad333..13cacfc0a 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h @@ -36,7 +36,7 @@ public: ~VirtualAmiibo() override; // Sets polling mode to a controller - Common::Input::PollingError SetPollingMode( + Common::Input::DriverResult SetPollingMode( const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp new file mode 100644 index 000000000..8f94c9f45 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.cpp @@ -0,0 +1,575 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "common/swap.h" +#include "common/thread.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/calibration.h" +#include "input_common/helpers/joycon_protocol/generic_functions.h" +#include "input_common/helpers/joycon_protocol/irs.h" +#include "input_common/helpers/joycon_protocol/nfc.h" +#include "input_common/helpers/joycon_protocol/poller.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" +#include "input_common/helpers/joycon_protocol/rumble.h" + +namespace InputCommon::Joycon { +JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { + hidapi_handle = std::make_shared<JoyconHandle>(); +} + +JoyconDriver::~JoyconDriver() { + Stop(); +} + +void JoyconDriver::Stop() { + is_connected = false; + input_thread = {}; +} + +DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { + std::scoped_lock lock{mutex}; + + handle_device_type = ControllerType::None; + GetDeviceType(device_info, handle_device_type); + if (handle_device_type == ControllerType::None) { + return DriverResult::UnsupportedControllerType; + } + + hidapi_handle->handle = + SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); + std::memcpy(&handle_serial_number, device_info->serial_number, 15); + if (!hidapi_handle->handle) { + LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", + device_info->vendor_id, device_info->product_id); + return DriverResult::HandleInUse; + } + SDL_hid_set_nonblocking(hidapi_handle->handle, 1); + return DriverResult::Success; +} + +DriverResult JoyconDriver::InitializeDevice() { + if (!hidapi_handle->handle) { + return DriverResult::InvalidHandle; + } + std::scoped_lock lock{mutex}; + disable_input_thread = true; + + // Reset Counters + error_counter = 0; + hidapi_handle->packet_counter = 0; + + // Reset external device status + starlink_connected = false; + ring_connected = false; + amiibo_detected = false; + + // Set HW default configuration + vibration_enabled = true; + motion_enabled = true; + hidbus_enabled = false; + nfc_enabled = false; + passive_enabled = false; + irs_enabled = false; + gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; + gyro_performance = Joycon::GyroPerformance::HZ833; + accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; + accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; + + // Initialize HW Protocols + calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); + generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); + irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); + nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); + ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); + rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); + + // Get fixed joycon info + generic_protocol->GetVersionNumber(version); + generic_protocol->SetLowPowerMode(false); + generic_protocol->GetColor(color); + if (handle_device_type == ControllerType::Pro) { + // Some 3rd party controllers aren't pro controllers + generic_protocol->GetControllerType(device_type); + } else { + device_type = handle_device_type; + } + generic_protocol->GetSerialNumber(serial_number); + supported_features = GetSupportedFeatures(); + + // Get Calibration data + calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); + calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); + calibration_protocol->GetImuCalibration(motion_calibration); + + // Set led status + generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); + + // Apply HW configuration + SetPollingMode(); + + // Initialize joycon poller + joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, + right_stick_calibration, motion_calibration); + + // Start pooling for data + is_connected = true; + if (!input_thread_running) { + input_thread = + std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); + } + + disable_input_thread = false; + return DriverResult::Success; +} + +void JoyconDriver::InputThread(std::stop_token stop_token) { + LOG_INFO(Input, "Joycon Adapter input thread started"); + Common::SetCurrentThreadName("JoyconInput"); + input_thread_running = true; + + // Max update rate is 5ms, ensure we are always able to read a bit faster + constexpr int ThreadDelay = 2; + std::vector<u8> buffer(MaxBufferSize); + + while (!stop_token.stop_requested()) { + int status = 0; + + if (!IsInputThreadValid()) { + input_thread.request_stop(); + continue; + } + + // By disabling the input thread we can ensure custom commands will succeed as no package is + // skipped + if (!disable_input_thread) { + status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), + ThreadDelay); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); + } + + if (IsPayloadCorrect(status, buffer)) { + OnNewData(buffer); + } + + std::this_thread::yield(); + } + + is_connected = false; + input_thread_running = false; + LOG_INFO(Input, "Joycon Adapter input thread stopped"); +} + +void JoyconDriver::OnNewData(std::span<u8> buffer) { + const auto report_mode = static_cast<ReportMode>(buffer[0]); + + // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion + // experience + switch (report_mode) { + case ReportMode::STANDARD_FULL_60HZ: + case ReportMode::NFC_IR_MODE_60HZ: + case ReportMode::SIMPLE_HID_MODE: { + const auto now = std::chrono::steady_clock::now(); + const auto new_delta_time = static_cast<u64>( + std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); + delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; + last_update = now; + joycon_poller->UpdateColor(color); + break; + } + default: + break; + } + + const MotionStatus motion_status{ + .is_enabled = motion_enabled, + .delta_time = delta_time, + .gyro_sensitivity = gyro_sensitivity, + .accelerometer_sensitivity = accelerometer_sensitivity, + }; + + // TODO: Remove this when calibration is properly loaded and not calculated + if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) { + InputReportActive data{}; + memcpy(&data, buffer.data(), sizeof(InputReportActive)); + calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); + } + + const RingStatus ring_status{ + .is_enabled = ring_connected, + .default_value = ring_calibration.default_value, + .max_value = ring_calibration.max_value, + .min_value = ring_calibration.min_value, + }; + + if (irs_protocol->IsEnabled()) { + irs_protocol->RequestImage(buffer); + joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); + } + + if (nfc_protocol->IsEnabled()) { + if (amiibo_detected) { + if (!nfc_protocol->HasAmiibo()) { + joycon_poller->UpdateAmiibo({}); + amiibo_detected = false; + return; + } + } + + if (!amiibo_detected) { + std::vector<u8> data(0x21C); + const auto result = nfc_protocol->ScanAmiibo(data); + if (result == DriverResult::Success) { + joycon_poller->UpdateAmiibo(data); + amiibo_detected = true; + } + } + } + + switch (report_mode) { + case ReportMode::STANDARD_FULL_60HZ: + joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); + break; + case ReportMode::NFC_IR_MODE_60HZ: + joycon_poller->ReadNfcIRMode(buffer, motion_status); + break; + case ReportMode::SIMPLE_HID_MODE: + joycon_poller->ReadPassiveMode(buffer); + break; + case ReportMode::SUBCMD_REPLY: + LOG_DEBUG(Input, "Unhandled command reply"); + break; + default: + LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); + break; + } +} + +DriverResult JoyconDriver::SetPollingMode() { + disable_input_thread = true; + + rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); + + if (motion_enabled && supported_features.motion) { + generic_protocol->EnableImu(true); + generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, + accelerometer_sensitivity, accelerometer_performance); + } else { + generic_protocol->EnableImu(false); + } + + if (irs_protocol->IsEnabled()) { + irs_protocol->DisableIrs(); + } + + if (nfc_protocol->IsEnabled()) { + amiibo_detected = false; + nfc_protocol->DisableNfc(); + } + + if (ring_protocol->IsEnabled()) { + ring_connected = false; + ring_protocol->DisableRingCon(); + } + + if (irs_enabled && supported_features.irs) { + auto result = irs_protocol->EnableIrs(); + if (result == DriverResult::Success) { + disable_input_thread = false; + return result; + } + irs_protocol->DisableIrs(); + LOG_ERROR(Input, "Error enabling IRS"); + } + + if (nfc_enabled && supported_features.nfc) { + auto result = nfc_protocol->EnableNfc(); + if (result == DriverResult::Success) { + result = nfc_protocol->StartNFCPollingMode(); + } + if (result == DriverResult::Success) { + disable_input_thread = false; + return result; + } + nfc_protocol->DisableNfc(); + LOG_ERROR(Input, "Error enabling NFC"); + } + + if (hidbus_enabled && supported_features.hidbus) { + auto result = ring_protocol->EnableRingCon(); + if (result == DriverResult::Success) { + result = ring_protocol->StartRingconPolling(); + } + if (result == DriverResult::Success) { + ring_connected = true; + disable_input_thread = false; + return result; + } + ring_connected = false; + ring_protocol->DisableRingCon(); + LOG_ERROR(Input, "Error enabling Ringcon"); + } + + if (passive_enabled && supported_features.passive) { + const auto result = generic_protocol->EnablePassiveMode(); + if (result == DriverResult::Success) { + disable_input_thread = false; + return result; + } + LOG_ERROR(Input, "Error enabling passive mode"); + } + + // Default Mode + const auto result = generic_protocol->EnableActiveMode(); + if (result != DriverResult::Success) { + LOG_ERROR(Input, "Error enabling active mode"); + } + // Switch calls this function after enabling active mode + generic_protocol->TriggersElapsed(); + + disable_input_thread = false; + return result; +} + +JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { + SupportedFeatures features{ + .passive = true, + .motion = true, + .vibration = true, + }; + + if (device_type == ControllerType::Right) { + features.nfc = true; + features.irs = true; + features.hidbus = true; + } + + if (device_type == ControllerType::Pro) { + features.nfc = true; + } + return features; +} + +bool JoyconDriver::IsInputThreadValid() const { + if (!is_connected.load()) { + return false; + } + if (hidapi_handle->handle == nullptr) { + return false; + } + // Controller is not responding. Terminate connection + if (error_counter > MaxErrorCount) { + return false; + } + return true; +} + +bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { + if (status <= -1) { + error_counter++; + return false; + } + // There's no new data + if (status == 0) { + return false; + } + // No reply ever starts with zero + if (buffer[0] == 0x00) { + error_counter++; + return false; + } + error_counter = 0; + return true; +} + +DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { + std::scoped_lock lock{mutex}; + if (disable_input_thread) { + return DriverResult::HandleInUse; + } + return rumble_protocol->SendVibration(vibration); +} + +DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { + std::scoped_lock lock{mutex}; + if (disable_input_thread) { + return DriverResult::HandleInUse; + } + return generic_protocol->SetLedPattern(led_pattern); +} + +DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { + std::scoped_lock lock{mutex}; + if (disable_input_thread) { + return DriverResult::HandleInUse; + } + disable_input_thread = true; + const auto result = irs_protocol->SetIrsConfig(mode_, format_); + disable_input_thread = false; + return result; +} + +DriverResult JoyconDriver::SetPasiveMode() { + std::scoped_lock lock{mutex}; + motion_enabled = false; + hidbus_enabled = false; + nfc_enabled = false; + passive_enabled = true; + irs_enabled = false; + return SetPollingMode(); +} + +DriverResult JoyconDriver::SetActiveMode() { + if (is_ring_disabled_by_irs) { + is_ring_disabled_by_irs = false; + SetActiveMode(); + return SetRingConMode(); + } + + std::scoped_lock lock{mutex}; + motion_enabled = true; + hidbus_enabled = false; + nfc_enabled = false; + passive_enabled = false; + irs_enabled = false; + return SetPollingMode(); +} + +DriverResult JoyconDriver::SetIrMode() { + std::scoped_lock lock{mutex}; + + if (!supported_features.irs) { + return DriverResult::NotSupported; + } + + if (ring_connected) { + is_ring_disabled_by_irs = true; + } + + motion_enabled = false; + hidbus_enabled = false; + nfc_enabled = false; + passive_enabled = false; + irs_enabled = true; + return SetPollingMode(); +} + +DriverResult JoyconDriver::SetNfcMode() { + std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + + motion_enabled = true; + hidbus_enabled = false; + nfc_enabled = true; + passive_enabled = false; + irs_enabled = false; + return SetPollingMode(); +} + +DriverResult JoyconDriver::SetRingConMode() { + std::scoped_lock lock{mutex}; + + if (!supported_features.hidbus) { + return DriverResult::NotSupported; + } + + motion_enabled = true; + hidbus_enabled = true; + nfc_enabled = false; + passive_enabled = false; + irs_enabled = false; + + const auto result = SetPollingMode(); + + if (!ring_connected) { + return DriverResult::NoDeviceDetected; + } + + return result; +} + +bool JoyconDriver::IsConnected() const { + std::scoped_lock lock{mutex}; + return is_connected.load(); +} + +bool JoyconDriver::IsVibrationEnabled() const { + std::scoped_lock lock{mutex}; + return vibration_enabled; +} + +FirmwareVersion JoyconDriver::GetDeviceVersion() const { + std::scoped_lock lock{mutex}; + return version; +} + +Color JoyconDriver::GetDeviceColor() const { + std::scoped_lock lock{mutex}; + return color; +} + +std::size_t JoyconDriver::GetDevicePort() const { + std::scoped_lock lock{mutex}; + return port; +} + +ControllerType JoyconDriver::GetDeviceType() const { + std::scoped_lock lock{mutex}; + return device_type; +} + +ControllerType JoyconDriver::GetHandleDeviceType() const { + std::scoped_lock lock{mutex}; + return handle_device_type; +} + +SerialNumber JoyconDriver::GetSerialNumber() const { + std::scoped_lock lock{mutex}; + return serial_number; +} + +SerialNumber JoyconDriver::GetHandleSerialNumber() const { + std::scoped_lock lock{mutex}; + return handle_serial_number; +} + +void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { + joycon_poller->SetCallbacks(callbacks); +} + +DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, + ControllerType& controller_type) { + static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{ + std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, + {0x2007, ControllerType::Right}, + }; + constexpr u16 nintendo_vendor_id = 0x057e; + + controller_type = ControllerType::None; + if (device_info->vendor_id != nintendo_vendor_id) { + return DriverResult::UnsupportedControllerType; + } + + for (const auto& [product_id, type] : supported_devices) { + if (device_info->product_id == static_cast<u16>(product_id)) { + controller_type = type; + return Joycon::DriverResult::Success; + } + } + return Joycon::DriverResult::UnsupportedControllerType; +} + +DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, + SerialNumber& serial_number) { + if (device_info->serial_number == nullptr) { + return DriverResult::Unknown; + } + std::memcpy(&serial_number, device_info->serial_number, 15); + return Joycon::DriverResult::Success; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h new file mode 100644 index 000000000..c1e189fa5 --- /dev/null +++ b/src/input_common/helpers/joycon_driver.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <atomic> +#include <functional> +#include <mutex> +#include <span> +#include <thread> + +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { +class CalibrationProtocol; +class GenericProtocol; +class IrsProtocol; +class NfcProtocol; +class JoyconPoller; +class RingConProtocol; +class RumbleProtocol; + +class JoyconDriver final { +public: + explicit JoyconDriver(std::size_t port_); + + ~JoyconDriver(); + + DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info); + DriverResult InitializeDevice(); + void Stop(); + + bool IsConnected() const; + bool IsVibrationEnabled() const; + + FirmwareVersion GetDeviceVersion() const; + Color GetDeviceColor() const; + std::size_t GetDevicePort() const; + ControllerType GetDeviceType() const; + ControllerType GetHandleDeviceType() const; + SerialNumber GetSerialNumber() const; + SerialNumber GetHandleSerialNumber() const; + + DriverResult SetVibration(const VibrationValue& vibration); + DriverResult SetLedConfig(u8 led_pattern); + DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_); + DriverResult SetPasiveMode(); + DriverResult SetActiveMode(); + DriverResult SetIrMode(); + DriverResult SetNfcMode(); + DriverResult SetRingConMode(); + + void SetCallbacks(const JoyconCallbacks& callbacks); + + // Returns device type from hidapi handle + static DriverResult GetDeviceType(SDL_hid_device_info* device_info, + ControllerType& controller_type); + + // Returns serial number from hidapi handle + static DriverResult GetSerialNumber(SDL_hid_device_info* device_info, + SerialNumber& serial_number); + +private: + struct SupportedFeatures { + bool passive{}; + bool hidbus{}; + bool irs{}; + bool motion{}; + bool nfc{}; + bool vibration{}; + }; + + /// Main thread, actively request new data from the handle + void InputThread(std::stop_token stop_token); + + /// Called everytime a valid package arrives + void OnNewData(std::span<u8> buffer); + + /// Updates device configuration to enable or disable features + DriverResult SetPollingMode(); + + /// Returns true if input thread is valid and doesn't need to be stopped + bool IsInputThreadValid() const; + + /// Returns true if the data should be interpreted. Otherwise the error counter is incremented + bool IsPayloadCorrect(int status, std::span<const u8> buffer); + + /// Returns a list of supported features that can be enabled on this device + SupportedFeatures GetSupportedFeatures(); + + // Protocol Features + std::unique_ptr<CalibrationProtocol> calibration_protocol; + std::unique_ptr<GenericProtocol> generic_protocol; + std::unique_ptr<IrsProtocol> irs_protocol; + std::unique_ptr<NfcProtocol> nfc_protocol; + std::unique_ptr<JoyconPoller> joycon_poller; + std::unique_ptr<RingConProtocol> ring_protocol; + std::unique_ptr<RumbleProtocol> rumble_protocol; + + // Connection status + std::atomic<bool> is_connected{}; + u64 delta_time; + std::size_t error_counter{}; + std::shared_ptr<JoyconHandle> hidapi_handle; + std::chrono::time_point<std::chrono::steady_clock> last_update; + + // External device status + bool starlink_connected{}; + bool ring_connected{}; + bool amiibo_detected{}; + bool is_ring_disabled_by_irs{}; + + // Harware configuration + u8 leds{}; + ReportMode mode{}; + bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time + bool hidbus_enabled{}; // External device support + bool irs_enabled{}; // Infrared camera input + bool motion_enabled{}; // Enables motion input + bool nfc_enabled{}; // Enables Amiibo detection + bool vibration_enabled{}; // Allows vibrations + + // Calibration data + GyroSensitivity gyro_sensitivity{}; + GyroPerformance gyro_performance{}; + AccelerometerSensitivity accelerometer_sensitivity{}; + AccelerometerPerformance accelerometer_performance{}; + JoyStickCalibration left_stick_calibration{}; + JoyStickCalibration right_stick_calibration{}; + MotionCalibration motion_calibration{}; + RingCalibration ring_calibration{}; + + // Fixed joycon info + FirmwareVersion version{}; + Color color{}; + std::size_t port{}; + ControllerType device_type{}; // Device type reported by controller + ControllerType handle_device_type{}; // Device type reported by hidapi + SerialNumber serial_number{}; // Serial number reported by controller + SerialNumber handle_serial_number{}; // Serial number type reported by hidapi + SupportedFeatures supported_features{}; + + // Thread related + mutable std::mutex mutex; + std::jthread input_thread; + bool input_thread_running{}; + bool disable_input_thread{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp new file mode 100644 index 000000000..d8f040f75 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.cpp @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cstring> + +#include "input_common/helpers/joycon_protocol/calibration.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) { + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + JoystickLeftSpiCalibration spi_calibration{}; + bool has_user_calibration = false; + calibration = {}; + + if (result == DriverResult::Success) { + result = HasUserCalibration(SpiAddress::USER_LEFT_MAGIC, has_user_calibration); + } + + // Read User defined calibration + if (result == DriverResult::Success && has_user_calibration) { + result = ReadSPI(SpiAddress::USER_LEFT_DATA, spi_calibration); + } + + // Read Factory calibration + if (result == DriverResult::Success && !has_user_calibration) { + result = ReadSPI(SpiAddress::FACT_LEFT_DATA, spi_calibration); + } + + if (result == DriverResult::Success) { + calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center); + calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center); + calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min); + calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min); + calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max); + calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max); + } + + // Set a valid default calibration if data is missing + ValidateCalibration(calibration); + + return result; +} + +DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) { + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + JoystickRightSpiCalibration spi_calibration{}; + bool has_user_calibration = false; + calibration = {}; + + if (result == DriverResult::Success) { + result = HasUserCalibration(SpiAddress::USER_RIGHT_MAGIC, has_user_calibration); + } + + // Read User defined calibration + if (result == DriverResult::Success && has_user_calibration) { + result = ReadSPI(SpiAddress::USER_RIGHT_DATA, spi_calibration); + } + + // Read Factory calibration + if (result == DriverResult::Success && !has_user_calibration) { + result = ReadSPI(SpiAddress::FACT_RIGHT_DATA, spi_calibration); + } + + if (result == DriverResult::Success) { + calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center); + calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center); + calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min); + calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min); + calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max); + calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max); + } + + // Set a valid default calibration if data is missing + ValidateCalibration(calibration); + + return result; +} + +DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) { + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + ImuSpiCalibration spi_calibration{}; + bool has_user_calibration = false; + calibration = {}; + + if (result == DriverResult::Success) { + result = HasUserCalibration(SpiAddress::USER_IMU_MAGIC, has_user_calibration); + } + + // Read User defined calibration + if (result == DriverResult::Success && has_user_calibration) { + result = ReadSPI(SpiAddress::USER_IMU_DATA, spi_calibration); + } + + // Read Factory calibration + if (result == DriverResult::Success && !has_user_calibration) { + result = ReadSPI(SpiAddress::FACT_IMU_DATA, spi_calibration); + } + + if (result == DriverResult::Success) { + calibration.accelerometer[0].offset = spi_calibration.accelerometer_offset[0]; + calibration.accelerometer[1].offset = spi_calibration.accelerometer_offset[1]; + calibration.accelerometer[2].offset = spi_calibration.accelerometer_offset[2]; + + calibration.accelerometer[0].scale = spi_calibration.accelerometer_scale[0]; + calibration.accelerometer[1].scale = spi_calibration.accelerometer_scale[1]; + calibration.accelerometer[2].scale = spi_calibration.accelerometer_scale[2]; + + calibration.gyro[0].offset = spi_calibration.gyroscope_offset[0]; + calibration.gyro[1].offset = spi_calibration.gyroscope_offset[1]; + calibration.gyro[2].offset = spi_calibration.gyroscope_offset[2]; + + calibration.gyro[0].scale = spi_calibration.gyroscope_scale[0]; + calibration.gyro[1].scale = spi_calibration.gyroscope_scale[1]; + calibration.gyro[2].scale = spi_calibration.gyroscope_scale[2]; + } + + ValidateCalibration(calibration); + + return result; +} + +DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration, + s16 current_value) { + constexpr s16 DefaultRingRange{800}; + + // TODO: Get default calibration form ring itself + if (ring_data_max == 0 && ring_data_min == 0) { + ring_data_max = current_value + DefaultRingRange; + ring_data_min = current_value - DefaultRingRange; + ring_data_default = current_value; + } + ring_data_max = std::max(ring_data_max, current_value); + ring_data_min = std::min(ring_data_min, current_value); + calibration = { + .default_value = ring_data_default, + .max_value = ring_data_max, + .min_value = ring_data_min, + }; + return DriverResult::Success; +} + +DriverResult CalibrationProtocol::HasUserCalibration(SpiAddress address, + bool& has_user_calibration) { + MagicSpiCalibration spi_magic{}; + const DriverResult result{ReadSPI(address, spi_magic)}; + has_user_calibration = false; + if (result == DriverResult::Success) { + has_user_calibration = spi_magic.first == CalibrationMagic::USR_MAGIC_0 && + spi_magic.second == CalibrationMagic::USR_MAGIC_1; + } + return result; +} + +u16 CalibrationProtocol::GetXAxisCalibrationValue(std::span<u8> block) const { + return static_cast<u16>(((block[1] & 0x0F) << 8) | block[0]); +} + +u16 CalibrationProtocol::GetYAxisCalibrationValue(std::span<u8> block) const { + return static_cast<u16>((block[2] << 4) | (block[1] >> 4)); +} + +void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) { + constexpr u16 DefaultStickCenter{0x800}; + constexpr u16 DefaultStickRange{0x6cc}; + + calibration.x.center = ValidateValue(calibration.x.center, DefaultStickCenter); + calibration.x.max = ValidateValue(calibration.x.max, DefaultStickRange); + calibration.x.min = ValidateValue(calibration.x.min, DefaultStickRange); + + calibration.y.center = ValidateValue(calibration.y.center, DefaultStickCenter); + calibration.y.max = ValidateValue(calibration.y.max, DefaultStickRange); + calibration.y.min = ValidateValue(calibration.y.min, DefaultStickRange); +} + +void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) { + constexpr s16 DefaultAccelerometerScale{0x4000}; + constexpr s16 DefaultGyroScale{0x3be7}; + constexpr s16 DefaultOffset{0}; + + for (auto& sensor : calibration.accelerometer) { + sensor.scale = ValidateValue(sensor.scale, DefaultAccelerometerScale); + sensor.offset = ValidateValue(sensor.offset, DefaultOffset); + } + for (auto& sensor : calibration.gyro) { + sensor.scale = ValidateValue(sensor.scale, DefaultGyroScale); + sensor.offset = ValidateValue(sensor.offset, DefaultOffset); + } +} + +u16 CalibrationProtocol::ValidateValue(u16 value, u16 default_value) const { + if (value == 0) { + return default_value; + } + if (value == 0xFFF) { + return default_value; + } + return value; +} + +s16 CalibrationProtocol::ValidateValue(s16 value, s16 default_value) const { + if (value == 0) { + return default_value; + } + if (value == 0xFFF) { + return default_value; + } + return value; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h new file mode 100644 index 000000000..c6fd0f729 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/calibration.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" + +namespace InputCommon::Joycon { +enum class DriverResult; +struct JoyStickCalibration; +struct IMUCalibration; +struct JoyconHandle; +} // namespace InputCommon::Joycon + +namespace InputCommon::Joycon { + +/// Driver functions related to retrieving calibration data from the device +class CalibrationProtocol final : private JoyconCommonProtocol { +public: + explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle); + + /** + * Sends a request to obtain the left stick calibration from memory + * @param is_factory_calibration if true factory values will be returned + * @returns JoyStickCalibration of the left joystick + */ + DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration); + + /** + * Sends a request to obtain the right stick calibration from memory + * @param is_factory_calibration if true factory values will be returned + * @returns JoyStickCalibration of the right joystick + */ + DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration); + + /** + * Sends a request to obtain the motion calibration from memory + * @returns ImuCalibration of the motion sensor + */ + DriverResult GetImuCalibration(MotionCalibration& calibration); + + /** + * Calculates on run time the proper calibration of the ring controller + * @returns RingCalibration of the ring sensor + */ + DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value); + +private: + /// Returns true if the specified address corresponds to the magic value of user calibration + DriverResult HasUserCalibration(SpiAddress address, bool& has_user_calibration); + + /// Converts a raw calibration block to an u16 value containing the x axis value + u16 GetXAxisCalibrationValue(std::span<u8> block) const; + + /// Converts a raw calibration block to an u16 value containing the y axis value + u16 GetYAxisCalibrationValue(std::span<u8> block) const; + + /// Ensures that all joystick calibration values are set + void ValidateCalibration(JoyStickCalibration& calibration); + + /// Ensures that all motion calibration values are set + void ValidateCalibration(MotionCalibration& calibration); + + /// Returns the default value if the value is either zero or 0xFFF + u16 ValidateValue(u16 value, u16 default_value) const; + + /// Returns the default value if the value is either zero or 0xFFF + s16 ValidateValue(s16 value, s16 default_value) const; + + s16 ring_data_max = 0; + s16 ring_data_default = 0; + s16 ring_data_min = 0; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp new file mode 100644 index 000000000..2b42a4555 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp @@ -0,0 +1,316 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/common_protocol.h" + +namespace InputCommon::Joycon { +JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_) + : hidapi_handle{std::move(hidapi_handle_)} {} + +u8 JoyconCommonProtocol::GetCounter() { + hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F; + return hidapi_handle->packet_counter; +} + +void JoyconCommonProtocol::SetBlocking() { + SDL_hid_set_nonblocking(hidapi_handle->handle, 0); +} + +void JoyconCommonProtocol::SetNonBlocking() { + SDL_hid_set_nonblocking(hidapi_handle->handle, 1); +} + +DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) { + const auto result = ReadSPI(SpiAddress::DEVICE_TYPE, controller_type); + + if (result == DriverResult::Success) { + // Fallback to 3rd party pro controllers + if (controller_type == ControllerType::None) { + controller_type = ControllerType::Pro; + } + } + + return result; +} + +DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) { + ControllerType controller_type{ControllerType::None}; + const auto result = GetDeviceType(controller_type); + + if (result != DriverResult::Success || controller_type == ControllerType::None) { + return DriverResult::UnsupportedControllerType; + } + + hidapi_handle->handle = + SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); + + if (!hidapi_handle->handle) { + LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", + device_info->vendor_id, device_info->product_id); + return DriverResult::HandleInUse; + } + + SetNonBlocking(); + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) { + const std::array<u8, 1> buffer{static_cast<u8>(report_mode)}; + return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer); +} + +DriverResult JoyconCommonProtocol::SendRawData(std::span<const u8> buffer) { + const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size()); + + if (result == -1) { + return DriverResult::ErrorWritingData; + } + + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, + SubCommandResponse& output) { + constexpr int timeout_mili = 66; + constexpr int MaxTries = 15; + int tries = 0; + + do { + int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output), + sizeof(SubCommandResponse), timeout_mili); + + if (result < 1) { + LOG_ERROR(Input, "No response from joycon"); + } + if (tries++ > MaxTries) { + return DriverResult::Timeout; + } + } while (output.input_report.report_mode != ReportMode::SUBCMD_REPLY && + output.sub_command != sc); + + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer, + SubCommandResponse& output) { + SubCommandPacket packet{ + .output_report = OutputReport::RUMBLE_AND_SUBCMD, + .packet_counter = GetCounter(), + .sub_command = sc, + .command_data = {}, + }; + + if (buffer.size() > packet.command_data.size()) { + return DriverResult::InvalidParameters; + } + + memcpy(packet.command_data.data(), buffer.data(), buffer.size()); + + auto result = SendData(packet); + + if (result != DriverResult::Success) { + return result; + } + + result = GetSubCommandResponse(sc, output); + + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) { + SubCommandResponse output{}; + return SendSubCommand(sc, buffer, output); +} + +DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) { + SubCommandPacket packet{ + .output_report = OutputReport::MCU_DATA, + .packet_counter = GetCounter(), + .sub_command = sc, + .command_data = {}, + }; + + if (buffer.size() > packet.command_data.size()) { + return DriverResult::InvalidParameters; + } + + memcpy(packet.command_data.data(), buffer.data(), buffer.size()); + + return SendData(packet); +} + +DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) { + VibrationPacket packet{ + .output_report = OutputReport::RUMBLE_ONLY, + .packet_counter = GetCounter(), + .vibration_data = {}, + }; + + if (buffer.size() > packet.vibration_data.size()) { + return DriverResult::InvalidParameters; + } + + memcpy(packet.vibration_data.data(), buffer.data(), buffer.size()); + + return SendData(packet); +} + +DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) { + constexpr std::size_t HeaderSize = 5; + constexpr std::size_t MaxTries = 10; + std::size_t tries = 0; + SubCommandResponse response{}; + std::array<u8, sizeof(ReadSpiPacket)> buffer{}; + const ReadSpiPacket packet_data{ + .spi_address = addr, + .size = static_cast<u8>(output.size()), + }; + + memcpy(buffer.data(), &packet_data, sizeof(ReadSpiPacket)); + do { + const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, response); + if (result != DriverResult::Success) { + return result; + } + + if (tries++ > MaxTries) { + return DriverResult::Timeout; + } + } while (response.spi_address != addr); + + if (response.command_data.size() < packet_data.size + HeaderSize) { + return DriverResult::WrongReply; + } + + // Remove header from output + memcpy(output.data(), response.command_data.data() + HeaderSize, packet_data.size); + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::EnableMCU(bool enable) { + const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)}; + const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state); + + if (result != DriverResult::Success) { + LOG_ERROR(Input, "Failed with error {}", result); + } + + return result; +} + +DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) { + LOG_DEBUG(Input, "ConfigureMCU"); + std::array<u8, sizeof(MCUConfig)> config_buffer; + memcpy(config_buffer.data(), &config, sizeof(MCUConfig)); + config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36); + + const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer); + + if (result != DriverResult::Success) { + LOG_ERROR(Input, "Failed with error {}", result); + } + + return result; +} + +DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode, + MCUCommandResponse& output) { + constexpr int TimeoutMili = 200; + constexpr int MaxTries = 9; + int tries = 0; + + do { + int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output), + sizeof(MCUCommandResponse), TimeoutMili); + + if (result < 1) { + LOG_ERROR(Input, "No response from joycon attempt {}", tries); + } + if (tries++ > MaxTries) { + return DriverResult::Timeout; + } + } while (output.input_report.report_mode != report_mode || + output.mcu_report == MCUReport::EmptyAwaitingCmd); + + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc, + std::span<const u8> buffer, + MCUCommandResponse& output) { + SubCommandPacket packet{ + .output_report = OutputReport::MCU_DATA, + .packet_counter = GetCounter(), + .sub_command = sc, + .command_data = {}, + }; + + if (buffer.size() > packet.command_data.size()) { + return DriverResult::InvalidParameters; + } + + memcpy(packet.command_data.data(), buffer.data(), buffer.size()); + + auto result = SendData(packet); + + if (result != DriverResult::Success) { + return result; + } + + result = GetMCUDataResponse(report_mode, output); + + return DriverResult::Success; +} + +DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { + MCUCommandResponse output{}; + constexpr std::size_t MaxTries{8}; + std::size_t tries{}; + + do { + const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)}; + const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output); + + if (result != DriverResult::Success) { + return result; + } + + if (tries++ > MaxTries) { + return DriverResult::WrongReply; + } + } while (output.mcu_report != MCUReport::StateReport || + output.mcu_data[6] != static_cast<u8>(mode)); + + return DriverResult::Success; +} + +// crc-8-ccitt / polynomial 0x07 look up table +constexpr std::array<u8, 256> mcu_crc8_table = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3}; + +u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const { + u8 crc8 = 0x0; + + for (int i = 0; i < size; ++i) { + crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])]; + } + return crc8; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h new file mode 100644 index 000000000..f44f73ba4 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/common_protocol.h @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <memory> +#include <span> +#include <vector> + +#include "common/common_types.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +/// Joycon driver functions that handle low level communication +class JoyconCommonProtocol { +public: + explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_); + + /** + * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is + * data to read before returning. + */ + void SetBlocking(); + + /** + * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return + * immediately with a value of 0 if there is no data to be read + */ + void SetNonBlocking(); + + /** + * Sends a request to obtain the joycon type from device + * @returns controller type of the joycon + */ + DriverResult GetDeviceType(ControllerType& controller_type); + + /** + * Verifies and sets the joycon_handle if device is valid + * @param device info from the driver + * @returns success if the device is valid + */ + DriverResult CheckDeviceAccess(SDL_hid_device_info* device); + + /** + * Sends a request to set the polling mode of the joycon + * @param report_mode polling mode to be set + */ + DriverResult SetReportMode(Joycon::ReportMode report_mode); + + /** + * Sends data to the joycon device + * @param buffer data to be send + */ + DriverResult SendRawData(std::span<const u8> buffer); + + template <typename Output> + requires std::is_trivially_copyable_v<Output> + DriverResult SendData(const Output& output) { + std::array<u8, sizeof(Output)> buffer; + std::memcpy(buffer.data(), &output, sizeof(Output)); + return SendRawData(buffer); + } + + /** + * Waits for incoming data of the joycon device that matchs the subcommand + * @param sub_command type of data to be returned + * @returns a buffer containing the response + */ + DriverResult GetSubCommandResponse(SubCommand sub_command, SubCommandResponse& output); + + /** + * Sends a sub command to the device and waits for it's reply + * @param sc sub command to be send + * @param buffer data to be send + * @returns output buffer containing the response + */ + DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, + SubCommandResponse& output); + + /** + * Sends a sub command to the device and waits for it's reply and ignores the output + * @param sc sub command to be send + * @param buffer data to be send + */ + DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer); + + /** + * Sends a mcu command to the device + * @param sc sub command to be send + * @param buffer data to be send + */ + DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer); + + /** + * Sends vibration data to the joycon + * @param buffer data to be send + */ + DriverResult SendVibrationReport(std::span<const u8> buffer); + + /** + * Reads the SPI memory stored on the joycon + * @param Initial address location + * @returns output buffer containing the response + */ + DriverResult ReadRawSPI(SpiAddress addr, std::span<u8> output); + + /** + * Reads the SPI memory stored on the joycon + * @param Initial address location + * @returns output object containing the response + */ + template <typename Output> + requires std::is_trivially_copyable_v<Output> + DriverResult ReadSPI(SpiAddress addr, Output& output) { + std::array<u8, sizeof(Output)> buffer; + output = {}; + + const auto result = ReadRawSPI(addr, buffer); + if (result != DriverResult::Success) { + return result; + } + + std::memcpy(&output, buffer.data(), sizeof(Output)); + return DriverResult::Success; + } + + /** + * Enables MCU chip on the joycon + * @param enable if true the chip will be enabled + */ + DriverResult EnableMCU(bool enable); + + /** + * Configures the MCU to the correspoinding mode + * @param MCUConfig configuration + */ + DriverResult ConfigureMCU(const MCUConfig& config); + + /** + * Waits until there's MCU data available. On timeout returns error + * @param report mode of the expected reply + * @returns a buffer containing the response + */ + DriverResult GetMCUDataResponse(ReportMode report_mode_, MCUCommandResponse& output); + + /** + * Sends data to the MCU chip and waits for it's reply + * @param report mode of the expected reply + * @param sub command to be send + * @param buffer data to be send + * @returns output buffer containing the response + */ + DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer, + MCUCommandResponse& output); + + /** + * Wait's until the MCU chip is on the specified mode + * @param report mode of the expected reply + * @param MCUMode configuration + */ + DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode); + + /** + * Calculates the checksum from the MCU data + * @param buffer containing the data to be send + * @param size of the buffer in bytes + * @returns byte with the correct checksum + */ + u8 CalculateMCU_CRC8(u8* buffer, u8 size) const; + +private: + /** + * Increments and returns the packet counter of the handle + * @param joycon_handle device to send the data + * @returns packet counter value + */ + u8 GetCounter(); + + std::shared_ptr<JoyconHandle> hidapi_handle; +}; + +class ScopedSetBlocking { +public: + explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} { + m_self->SetBlocking(); + } + + ~ScopedSetBlocking() { + m_self->SetNonBlocking(); + } + +private: + JoyconCommonProtocol* m_self{}; +}; +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp new file mode 100644 index 000000000..548a4b9e3 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/generic_functions.h" + +namespace InputCommon::Joycon { + +GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult GenericProtocol::EnablePassiveMode() { + ScopedSetBlocking sb(this); + return SetReportMode(ReportMode::SIMPLE_HID_MODE); +} + +DriverResult GenericProtocol::EnableActiveMode() { + ScopedSetBlocking sb(this); + return SetReportMode(ReportMode::STANDARD_FULL_60HZ); +} + +DriverResult GenericProtocol::SetLowPowerMode(bool enable) { + ScopedSetBlocking sb(this); + const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; + return SendSubCommand(SubCommand::LOW_POWER_MODE, buffer); +} + +DriverResult GenericProtocol::TriggersElapsed() { + ScopedSetBlocking sb(this); + return SendSubCommand(SubCommand::TRIGGERS_ELAPSED, {}); +} + +DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { + ScopedSetBlocking sb(this); + SubCommandResponse output{}; + + const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output); + + device_info = {}; + if (result == DriverResult::Success) { + device_info = output.device_info; + } + + return result; +} + +DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) { + return GetDeviceType(controller_type); +} + +DriverResult GenericProtocol::EnableImu(bool enable) { + ScopedSetBlocking sb(this); + const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; + return SendSubCommand(SubCommand::ENABLE_IMU, buffer); +} + +DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, + AccelerometerSensitivity asen, + AccelerometerPerformance afrec) { + ScopedSetBlocking sb(this); + const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen), + static_cast<u8>(gfrec), static_cast<u8>(afrec)}; + return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer); +} + +DriverResult GenericProtocol::GetBattery(u32& battery_level) { + // This function is meant to request the high resolution battery status + battery_level = 0; + return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::GetColor(Color& color) { + ScopedSetBlocking sb(this); + std::array<u8, 12> buffer{}; + const auto result = ReadRawSPI(SpiAddress::COLOR_DATA, buffer); + + color = {}; + if (result == DriverResult::Success) { + color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]); + color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]); + color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]); + color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]); + } + + return result; +} + +DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) { + ScopedSetBlocking sb(this); + std::array<u8, 16> buffer{}; + const auto result = ReadRawSPI(SpiAddress::SERIAL_NUMBER, buffer); + + serial_number = {}; + if (result == DriverResult::Success) { + memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber)); + } + + return result; +} + +DriverResult GenericProtocol::GetTemperature(u32& temperature) { + // Not all devices have temperature sensor + temperature = 25; + return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) { + DeviceInfo device_info{}; + + const auto result = GetDeviceInfo(device_info); + version = device_info.firmware; + + return result; +} + +DriverResult GenericProtocol::SetHomeLight() { + ScopedSetBlocking sb(this); + static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00}; + return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer); +} + +DriverResult GenericProtocol::SetLedBusy() { + return DriverResult::NotSupported; +} + +DriverResult GenericProtocol::SetLedPattern(u8 leds) { + ScopedSetBlocking sb(this); + const std::array<u8, 1> buffer{leds}; + return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer); +} + +DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) { + return SetLedPattern(static_cast<u8>(leds << 4)); +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h new file mode 100644 index 000000000..424831e81 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/generic_functions.h @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +/// Joycon driver functions that easily implemented +class GenericProtocol final : private JoyconCommonProtocol { +public: + explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle); + + /// Enables passive mode. This mode only sends button data on change. Sticks will return digital + /// data instead of analog. Motion will be disabled + DriverResult EnablePassiveMode(); + + /// Enables active mode. This mode will return the current status every 5-15ms + DriverResult EnableActiveMode(); + + /// Enables or disables the low power mode + DriverResult SetLowPowerMode(bool enable); + + /// Unknown function used by the switch + DriverResult TriggersElapsed(); + + /** + * Sends a request to obtain the joycon firmware and mac from handle + * @returns controller device info + */ + DriverResult GetDeviceInfo(DeviceInfo& controller_type); + + /** + * Sends a request to obtain the joycon type from handle + * @returns controller type of the joycon + */ + DriverResult GetControllerType(ControllerType& controller_type); + + /** + * Enables motion input + * @param enable if true motion data will be enabled + */ + DriverResult EnableImu(bool enable); + + /** + * Configures the motion sensor with the specified parameters + * @param gsen gyroscope sensor sensitvity in degrees per second + * @param gfrec gyroscope sensor frequency in hertz + * @param asen accelerometer sensitivity in G force + * @param afrec accelerometer frequency in hertz + */ + DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec, + AccelerometerSensitivity asen, AccelerometerPerformance afrec); + + /** + * Request battery level from the device + * @returns battery level + */ + DriverResult GetBattery(u32& battery_level); + + /** + * Request joycon colors from the device + * @returns colors of the body and buttons + */ + DriverResult GetColor(Color& color); + + /** + * Request joycon serial number from the device + * @returns 16 byte serial number + */ + DriverResult GetSerialNumber(SerialNumber& serial_number); + + /** + * Request joycon serial number from the device + * @returns 16 byte serial number + */ + DriverResult GetTemperature(u32& temperature); + + /** + * Request joycon serial number from the device + * @returns 16 byte serial number + */ + DriverResult GetVersionNumber(FirmwareVersion& version); + + /** + * Sets home led behaviour + */ + DriverResult SetHomeLight(); + + /** + * Sets home led into a slow breathing state + */ + DriverResult SetLedBusy(); + + /** + * Sets the 4 player leds on the joycon on a solid state + * @params bit flag containing the led state + */ + DriverResult SetLedPattern(u8 leds); + + /** + * Sets the 4 player leds on the joycon on a blinking state + * @returns bit flag containing the led state + */ + DriverResult SetLedBlinkPattern(u8 leds); +}; +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp new file mode 100644 index 000000000..731fd5981 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.cpp @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/irs.h" + +namespace InputCommon::Joycon { + +IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult IrsProtocol::EnableIrs() { + LOG_INFO(Input, "Enable IRS"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); + } + if (result == DriverResult::Success) { + result = EnableMCU(true); + } + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); + } + if (result == DriverResult::Success) { + const MCUConfig config{ + .command = MCUCommand::ConfigureMCU, + .sub_command = MCUSubCommand::SetMCUMode, + .mode = MCUMode::IR, + .crc = {}, + }; + + result = ConfigureMCU(config); + } + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR); + } + if (result == DriverResult::Success) { + result = ConfigureIrs(); + } + if (result == DriverResult::Success) { + result = WriteRegistersStep1(); + } + if (result == DriverResult::Success) { + result = WriteRegistersStep2(); + } + + is_enabled = true; + + return result; +} + +DriverResult IrsProtocol::DisableIrs() { + LOG_DEBUG(Input, "Disable IRS"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = EnableMCU(false); + } + + is_enabled = false; + + return result; +} + +DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) { + irs_mode = mode; + switch (format) { + case IrsResolution::Size320x240: + resolution_code = IrsResolutionCode::Size320x240; + fragments = IrsFragments::Size320x240; + resolution = IrsResolution::Size320x240; + break; + case IrsResolution::Size160x120: + resolution_code = IrsResolutionCode::Size160x120; + fragments = IrsFragments::Size160x120; + resolution = IrsResolution::Size160x120; + break; + case IrsResolution::Size80x60: + resolution_code = IrsResolutionCode::Size80x60; + fragments = IrsFragments::Size80x60; + resolution = IrsResolution::Size80x60; + break; + case IrsResolution::Size20x15: + resolution_code = IrsResolutionCode::Size20x15; + fragments = IrsFragments::Size20x15; + resolution = IrsResolution::Size20x15; + break; + case IrsResolution::Size40x30: + default: + resolution_code = IrsResolutionCode::Size40x30; + fragments = IrsFragments::Size40x30; + resolution = IrsResolution::Size40x30; + break; + } + + // Restart feature + if (is_enabled) { + DisableIrs(); + return EnableIrs(); + } + + return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) { + const u8 next_packet_fragment = + static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1)); + + if (buffer[0] == 0x31 && buffer[49] == 0x03) { + u8 new_packet_fragment = buffer[52]; + if (new_packet_fragment == next_packet_fragment) { + packet_fragment = next_packet_fragment; + memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300); + + return RequestFrame(packet_fragment); + } + + if (new_packet_fragment == packet_fragment) { + return RequestFrame(packet_fragment); + } + + return ResendFrame(next_packet_fragment); + } + + return RequestFrame(packet_fragment); +} + +DriverResult IrsProtocol::ConfigureIrs() { + LOG_DEBUG(Input, "Configure IRS"); + constexpr std::size_t max_tries = 28; + SubCommandResponse output{}; + std::size_t tries = 0; + + const IrsConfigure irs_configuration{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::SetDeviceMode, + .irs_mode = IrsMode::ImageTransfer, + .number_of_fragments = fragments, + .mcu_major_version = 0x0500, + .mcu_minor_version = 0x1800, + .crc = {}, + }; + buf_image.resize((static_cast<u8>(fragments) + 1) * 300); + + std::array<u8, sizeof(IrsConfigure)> request_data{}; + memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + do { + const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (output.command_data[0] != 0x0b); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep1() { + LOG_DEBUG(Input, "WriteRegistersStep1"); + DriverResult result{DriverResult::Success}; + constexpr std::size_t max_tries = 28; + SubCommandResponse output{}; + std::size_t tries = 0; + + const IrsWriteRegisters irs_registers{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::WriteDeviceRegisters, + .number_of_registers = 0x9, + .registers = + { + IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)}, + {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)}, + {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)}, + {IrRegistersAddress::ExposureTime, 0x00}, + {IrRegistersAddress::Leds, static_cast<u8>(leds)}, + {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)}, + {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)}, + {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)}, + {IrRegistersAddress::WhitePixelThreshold, 0xc8}, + }, + .crc = {}, + }; + + std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; + memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + + std::array<u8, 38> mcu_request{0x02}; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + + if (result != DriverResult::Success) { + return result; + } + + do { + result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + // First time we need to set the report mode + if (result == DriverResult::Success && tries == 0) { + result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); + } + if (result == DriverResult::Success && tries == 0) { + GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output); + } + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (!(output.command_data[0] == 0x13 && output.command_data[2] == 0x07) && + output.command_data[0] != 0x23); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::WriteRegistersStep2() { + LOG_DEBUG(Input, "WriteRegistersStep2"); + constexpr std::size_t max_tries = 28; + SubCommandResponse output{}; + std::size_t tries = 0; + + const IrsWriteRegisters irs_registers{ + .command = MCUCommand::ConfigureIR, + .sub_command = MCUSubCommand::WriteDeviceRegisters, + .number_of_registers = 0x8, + .registers = + { + IrsRegister{IrRegistersAddress::LedIntensitiyMSB, + static_cast<u8>(led_intensity >> 8)}, + {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)}, + {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)}, + {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)}, + {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)}, + {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)}, + {IrRegistersAddress::UpdateTime, 0x2d}, + {IrRegistersAddress::FinalizeConfig, 0x01}, + }, + .crc = {}, + }; + + std::array<u8, sizeof(IrsWriteRegisters)> request_data{}; + memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + do { + const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ >= max_tries) { + return DriverResult::WrongReply; + } + } while (output.command_data[0] != 0x13 && output.command_data[0] != 0x23); + + return DriverResult::Success; +} + +DriverResult IrsProtocol::RequestFrame(u8 frame) { + std::array<u8, 38> mcu_request{}; + mcu_request[3] = frame; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +DriverResult IrsProtocol::ResendFrame(u8 frame) { + std::array<u8, 38> mcu_request{}; + mcu_request[1] = 0x1; + mcu_request[2] = frame; + mcu_request[3] = 0x0; + mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36); + mcu_request[37] = 0xFF; + return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request); +} + +std::vector<u8> IrsProtocol::GetImage() const { + return buf_image; +} + +IrsResolution IrsProtocol::GetIrsFormat() const { + return resolution; +} + +bool IrsProtocol::IsEnabled() const { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h new file mode 100644 index 000000000..76dfa02ea --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/irs.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class IrsProtocol final : private JoyconCommonProtocol { +public: + explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle); + + DriverResult EnableIrs(); + + DriverResult DisableIrs(); + + DriverResult SetIrsConfig(IrsMode mode, IrsResolution format); + + DriverResult RequestImage(std::span<u8> buffer); + + std::vector<u8> GetImage() const; + + IrsResolution GetIrsFormat() const; + + bool IsEnabled() const; + +private: + DriverResult ConfigureIrs(); + + DriverResult WriteRegistersStep1(); + DriverResult WriteRegistersStep2(); + + DriverResult RequestFrame(u8 frame); + DriverResult ResendFrame(u8 frame); + + IrsMode irs_mode{IrsMode::ImageTransfer}; + IrsResolution resolution{IrsResolution::Size40x30}; + IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30}; + IrsFragments fragments{IrsFragments::Size40x30}; + IrLeds leds{IrLeds::BrightAndDim}; + IrExLedFilter led_filter{IrExLedFilter::Enabled}; + IrImageFlip image_flip{IrImageFlip::Normal}; + u8 digital_gain{0x01}; + u16 exposure{0x2490}; + u16 led_intensity{0x0f10}; + u32 denoise{0x012344}; + + u8 packet_fragment{}; + std::vector<u8> buf_image; // 8bpp greyscale image. + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h new file mode 100644 index 000000000..b91934990 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -0,0 +1,697 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <array> +#include <functional> +#include <SDL_hidapi.h> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace InputCommon::Joycon { +constexpr u32 MaxErrorCount = 50; +constexpr u32 MaxBufferSize = 368; +constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40}; + +using MacAddress = std::array<u8, 6>; +using SerialNumber = std::array<u8, 15>; + +enum class ControllerType : u8 { + None = 0x00, + Left = 0x01, + Right = 0x02, + Pro = 0x03, + Dual = 0x05, // TODO: Verify this id + LarkHvc1 = 0x07, + LarkHvc2 = 0x08, + LarkNesLeft = 0x09, + LarkNesRight = 0x0A, + Lucia = 0x0B, + Lagon = 0x0C, + Lager = 0x0D, +}; + +enum class PadAxes { + LeftStickX, + LeftStickY, + RightStickX, + RightStickY, + Undefined, +}; + +enum class PadMotion { + LeftMotion, + RightMotion, + Undefined, +}; + +enum class PadButton : u32 { + Down = 0x000001, + Up = 0x000002, + Right = 0x000004, + Left = 0x000008, + LeftSR = 0x000010, + LeftSL = 0x000020, + L = 0x000040, + ZL = 0x000080, + Y = 0x000100, + X = 0x000200, + B = 0x000400, + A = 0x000800, + RightSR = 0x001000, + RightSL = 0x002000, + R = 0x004000, + ZR = 0x008000, + Minus = 0x010000, + Plus = 0x020000, + StickR = 0x040000, + StickL = 0x080000, + Home = 0x100000, + Capture = 0x200000, +}; + +enum class PasivePadButton : u32 { + Down_A = 0x0001, + Right_X = 0x0002, + Left_B = 0x0004, + Up_Y = 0x0008, + SL = 0x0010, + SR = 0x0020, + Minus = 0x0100, + Plus = 0x0200, + StickL = 0x0400, + StickR = 0x0800, + Home = 0x1000, + Capture = 0x2000, + L_R = 0x4000, + ZL_ZR = 0x8000, +}; + +enum class OutputReport : u8 { + RUMBLE_AND_SUBCMD = 0x01, + FW_UPDATE_PKT = 0x03, + RUMBLE_ONLY = 0x10, + MCU_DATA = 0x11, + USB_CMD = 0x80, +}; + +enum class FeatureReport : u8 { + Last_SUBCMD = 0x02, + OTA_GW_UPGRADE = 0x70, + SETUP_MEM_READ = 0x71, + MEM_READ = 0x72, + ERASE_MEM_SECTOR = 0x73, + MEM_WRITE = 0x74, + LAUNCH = 0x75, +}; + +enum class SubCommand : u8 { + STATE = 0x00, + MANUAL_BT_PAIRING = 0x01, + REQ_DEV_INFO = 0x02, + SET_REPORT_MODE = 0x03, + TRIGGERS_ELAPSED = 0x04, + GET_PAGE_LIST_STATE = 0x05, + SET_HCI_STATE = 0x06, + RESET_PAIRING_INFO = 0x07, + LOW_POWER_MODE = 0x08, + SPI_FLASH_READ = 0x10, + SPI_FLASH_WRITE = 0x11, + SPI_SECTOR_ERASE = 0x12, + RESET_MCU = 0x20, + SET_MCU_CONFIG = 0x21, + SET_MCU_STATE = 0x22, + SET_PLAYER_LIGHTS = 0x30, + GET_PLAYER_LIGHTS = 0x31, + SET_HOME_LIGHT = 0x38, + ENABLE_IMU = 0x40, + SET_IMU_SENSITIVITY = 0x41, + WRITE_IMU_REG = 0x42, + READ_IMU_REG = 0x43, + ENABLE_VIBRATION = 0x48, + GET_REGULATED_VOLTAGE = 0x50, + SET_EXTERNAL_CONFIG = 0x58, + GET_EXTERNAL_DEVICE_INFO = 0x59, + ENABLE_EXTERNAL_POLLING = 0x5A, + DISABLE_EXTERNAL_POLLING = 0x5B, + SET_EXTERNAL_FORMAT_CONFIG = 0x5C, +}; + +enum class UsbSubCommand : u8 { + CONN_STATUS = 0x01, + HADSHAKE = 0x02, + BAUDRATE_3M = 0x03, + NO_TIMEOUT = 0x04, + EN_TIMEOUT = 0x05, + RESET = 0x06, + PRE_HANDSHAKE = 0x91, + SEND_UART = 0x92, +}; + +enum class CalibrationMagic : u8 { + USR_MAGIC_0 = 0xB2, + USR_MAGIC_1 = 0xA1, +}; + +enum class SpiAddress : u16 { + MAGIC = 0x0000, + MAC_ADDRESS = 0x0015, + PAIRING_INFO = 0x2000, + SHIPMENT = 0x5000, + SERIAL_NUMBER = 0x6000, + DEVICE_TYPE = 0x6012, + FORMAT_VERSION = 0x601B, + FACT_IMU_DATA = 0x6020, + FACT_LEFT_DATA = 0x603d, + FACT_RIGHT_DATA = 0x6046, + COLOR_DATA = 0x6050, + DESIGN_VARIATION = 0x605C, + SENSOR_DATA = 0x6080, + USER_LEFT_MAGIC = 0x8010, + USER_LEFT_DATA = 0x8012, + USER_RIGHT_MAGIC = 0x801B, + USER_RIGHT_DATA = 0x801D, + USER_IMU_MAGIC = 0x8026, + USER_IMU_DATA = 0x8028, +}; + +enum class ReportMode : u8 { + ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00, + ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01, + ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02, + ACTIVE_POLLING_IR_CAMERA_DATA = 0x03, + SUBCMD_REPLY = 0x21, + MCU_UPDATE_STATE = 0x23, + STANDARD_FULL_60HZ = 0x30, + NFC_IR_MODE_60HZ = 0x31, + SIMPLE_HID_MODE = 0x3F, + INPUT_USB_RESPONSE = 0x81, +}; + +enum class GyroSensitivity : u8 { + DPS250, + DPS500, + DPS1000, + DPS2000, // Default +}; + +enum class AccelerometerSensitivity : u8 { + G8, // Default + G4, + G2, + G16, +}; + +enum class GyroPerformance : u8 { + HZ833, + HZ208, // Default +}; + +enum class AccelerometerPerformance : u8 { + HZ200, + HZ100, // Default +}; + +enum class MCUCommand : u8 { + ConfigureMCU = 0x21, + ConfigureIR = 0x23, +}; + +enum class MCUSubCommand : u8 { + SetMCUMode = 0x0, + SetDeviceMode = 0x1, + ReadDeviceMode = 0x02, + WriteDeviceRegisters = 0x4, +}; + +enum class MCUMode : u8 { + Suspend = 0, + Standby = 1, + Ringcon = 3, + NFC = 4, + IR = 5, + MaybeFWUpdate = 6, +}; + +enum class MCURequest : u8 { + GetMCUStatus = 1, + GetNFCData = 2, + GetIRData = 3, +}; + +enum class MCUReport : u8 { + Empty = 0x00, + StateReport = 0x01, + IRData = 0x03, + BusyInitializing = 0x0b, + IRStatus = 0x13, + IRRegisters = 0x1b, + NFCState = 0x2a, + NFCReadData = 0x3a, + EmptyAwaitingCmd = 0xff, +}; + +enum class MCUPacketFlag : u8 { + MorePacketsRemaining = 0x00, + LastCommandPacket = 0x08, +}; + +enum class NFCReadCommand : u8 { + CancelAll = 0x00, + StartPolling = 0x01, + StopPolling = 0x02, + StartWaitingRecieve = 0x04, + Ntag = 0x06, + Mifare = 0x0F, +}; + +enum class NFCTagType : u8 { + AllTags = 0x00, + Ntag215 = 0x01, +}; + +enum class NFCPages { + Block0 = 0, + Block45 = 45, + Block135 = 135, + Block231 = 231, +}; + +enum class NFCStatus : u8 { + LastPackage = 0x04, + TagLost = 0x07, +}; + +enum class IrsMode : u8 { + None = 0x02, + Moment = 0x03, + Dpd = 0x04, + Clustering = 0x06, + ImageTransfer = 0x07, + Silhouette = 0x08, + TeraImage = 0x09, + SilhouetteTeraImage = 0x0A, +}; + +enum class IrsResolution { + Size320x240, + Size160x120, + Size80x60, + Size40x30, + Size20x15, + None, +}; + +enum class IrsResolutionCode : u8 { + Size320x240 = 0x00, // Full pixel array + Size160x120 = 0x50, // Sensor Binning [2 X 2] + Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2] + Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4] + Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4] +}; + +// Size of image divided by 300 +enum class IrsFragments : u8 { + Size20x15 = 0x00, + Size40x30 = 0x03, + Size80x60 = 0x0f, + Size160x120 = 0x3f, + Size320x240 = 0xFF, +}; + +enum class IrLeds : u8 { + BrightAndDim = 0x00, + Bright = 0x20, + Dim = 0x10, + None = 0x30, +}; + +enum class IrExLedFilter : u8 { + Disabled = 0x00, + Enabled = 0x03, +}; + +enum class IrImageFlip : u8 { + Normal = 0x00, + Inverted = 0x02, +}; + +enum class IrRegistersAddress : u16 { + UpdateTime = 0x0400, + FinalizeConfig = 0x0700, + LedFilter = 0x0e00, + Leds = 0x1000, + LedIntensitiyMSB = 0x1100, + LedIntensitiyLSB = 0x1200, + ImageFlip = 0x2d00, + Resolution = 0x2e00, + DigitalGainLSB = 0x2e01, + DigitalGainMSB = 0x2f01, + ExposureLSB = 0x3001, + ExposureMSB = 0x3101, + ExposureTime = 0x3201, + WhitePixelThreshold = 0x4301, + DenoiseSmoothing = 0x6701, + DenoiseEdge = 0x6801, + DenoiseColor = 0x6901, +}; + +enum class ExternalDeviceId : u16 { + RingController = 0x2000, + Starlink = 0x2800, +}; + +enum class DriverResult { + Success, + WrongReply, + Timeout, + InvalidParameters, + UnsupportedControllerType, + HandleInUse, + ErrorReadingData, + ErrorWritingData, + NoDeviceDetected, + InvalidHandle, + NotSupported, + Disabled, + Unknown, +}; + +struct MotionSensorCalibration { + s16 offset; + s16 scale; +}; + +struct MotionCalibration { + std::array<MotionSensorCalibration, 3> accelerometer; + std::array<MotionSensorCalibration, 3> gyro; +}; + +// Basic motion data containing data from the sensors and a timestamp in microseconds +struct MotionData { + float gyro_x{}; + float gyro_y{}; + float gyro_z{}; + float accel_x{}; + float accel_y{}; + float accel_z{}; + u64 delta_timestamp{}; +}; + +// Output from SPI read command containing user calibration magic +struct MagicSpiCalibration { + CalibrationMagic first; + CalibrationMagic second; +}; +static_assert(sizeof(MagicSpiCalibration) == 0x2, "MagicSpiCalibration is an invalid size"); + +// Output from SPI read command containing left joystick calibration +struct JoystickLeftSpiCalibration { + std::array<u8, 3> max; + std::array<u8, 3> center; + std::array<u8, 3> min; +}; +static_assert(sizeof(JoystickLeftSpiCalibration) == 0x9, + "JoystickLeftSpiCalibration is an invalid size"); + +// Output from SPI read command containing right joystick calibration +struct JoystickRightSpiCalibration { + std::array<u8, 3> center; + std::array<u8, 3> min; + std::array<u8, 3> max; +}; +static_assert(sizeof(JoystickRightSpiCalibration) == 0x9, + "JoystickRightSpiCalibration is an invalid size"); + +struct JoyStickAxisCalibration { + u16 max; + u16 min; + u16 center; +}; + +struct JoyStickCalibration { + JoyStickAxisCalibration x; + JoyStickAxisCalibration y; +}; + +struct ImuSpiCalibration { + std::array<s16, 3> accelerometer_offset; + std::array<s16, 3> accelerometer_scale; + std::array<s16, 3> gyroscope_offset; + std::array<s16, 3> gyroscope_scale; +}; +static_assert(sizeof(ImuSpiCalibration) == 0x18, "ImuSpiCalibration is an invalid size"); + +struct RingCalibration { + s16 default_value; + s16 max_value; + s16 min_value; +}; + +struct Color { + u32 body; + u32 buttons; + u32 left_grip; + u32 right_grip; +}; + +struct Battery { + union { + u8 raw{}; + + BitField<0, 4, u8> unknown; + BitField<4, 1, u8> charging; + BitField<5, 3, u8> status; + }; +}; + +struct VibrationValue { + f32 low_amplitude; + f32 low_frequency; + f32 high_amplitude; + f32 high_frequency; +}; + +struct JoyconHandle { + SDL_hid_device* handle = nullptr; + u8 packet_counter{}; +}; + +struct MCUConfig { + MCUCommand command; + MCUSubCommand sub_command; + MCUMode mode; + INSERT_PADDING_BYTES(0x22); + u8 crc; +}; +static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size"); + +#pragma pack(push, 1) +struct InputReportPassive { + ReportMode report_mode; + u16 button_input; + u8 stick_state; + std::array<u8, 10> unknown_data; +}; +static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size"); + +struct InputReportActive { + ReportMode report_mode; + u8 packet_id; + Battery battery_status; + std::array<u8, 3> button_input; + std::array<u8, 3> left_stick_state; + std::array<u8, 3> right_stick_state; + u8 vibration_code; + std::array<s16, 6 * 2> motion_input; + INSERT_PADDING_BYTES(0x2); + s16 ring_input; +}; +static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size"); + +struct InputReportNfcIr { + ReportMode report_mode; + u8 packet_id; + Battery battery_status; + std::array<u8, 3> button_input; + std::array<u8, 3> left_stick_state; + std::array<u8, 3> right_stick_state; + u8 vibration_code; + std::array<s16, 6 * 2> motion_input; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size"); +#pragma pack(pop) + +struct NFCReadBlock { + u8 start; + u8 end; +}; +static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size"); + +struct NFCReadBlockCommand { + u8 block_count{}; + std::array<NFCReadBlock, 4> blocks{}; +}; +static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size"); + +struct NFCReadCommandData { + u8 unknown; + u8 uuid_length; + u8 unknown_2; + std::array<u8, 6> uid; + NFCTagType tag_type; + NFCReadBlockCommand read_block; +}; +static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); + +struct NFCPollingCommandData { + u8 enable_mifare; + u8 unknown_1; + u8 unknown_2; + u8 unknown_3; + u8 unknown_4; +}; +static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); + +struct NFCRequestState { + MCUSubCommand sub_command; + NFCReadCommand command_argument; + u8 packet_id; + INSERT_PADDING_BYTES(0x1); + MCUPacketFlag packet_flag; + u8 data_length; + union { + std::array<u8, 0x1F> raw_data; + NFCReadCommandData nfc_read; + NFCPollingCommandData nfc_polling; + }; + u8 crc; +}; +static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); + +struct IrsConfigure { + MCUCommand command; + MCUSubCommand sub_command; + IrsMode irs_mode; + IrsFragments number_of_fragments; + u16 mcu_major_version; + u16 mcu_minor_version; + INSERT_PADDING_BYTES(0x1D); + u8 crc; +}; +static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size"); + +#pragma pack(push, 1) +struct IrsRegister { + IrRegistersAddress address; + u8 value; +}; +static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size"); + +struct IrsWriteRegisters { + MCUCommand command; + MCUSubCommand sub_command; + u8 number_of_registers; + std::array<IrsRegister, 9> registers; + INSERT_PADDING_BYTES(0x7); + u8 crc; +}; +static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size"); +#pragma pack(pop) + +struct FirmwareVersion { + u8 major; + u8 minor; +}; +static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); + +struct DeviceInfo { + FirmwareVersion firmware; + std::array<u8, 2> unknown_1; + MacAddress mac_address; + std::array<u8, 2> unknown_2; +}; +static_assert(sizeof(DeviceInfo) == 0xC, "DeviceInfo is an invalid size"); + +struct MotionStatus { + bool is_enabled; + u64 delta_time; + GyroSensitivity gyro_sensitivity; + AccelerometerSensitivity accelerometer_sensitivity; +}; + +struct RingStatus { + bool is_enabled; + s16 default_value; + s16 max_value; + s16 min_value; +}; + +struct VibrationPacket { + OutputReport output_report; + u8 packet_counter; + std::array<u8, 0x8> vibration_data; +}; +static_assert(sizeof(VibrationPacket) == 0xA, "VibrationPacket is an invalid size"); + +struct SubCommandPacket { + OutputReport output_report; + u8 packet_counter; + INSERT_PADDING_BYTES(0x8); // This contains vibration data + SubCommand sub_command; + std::array<u8, 0x26> command_data; +}; +static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size"); + +#pragma pack(push, 1) +struct ReadSpiPacket { + SpiAddress spi_address; + INSERT_PADDING_BYTES(0x2); + u8 size; +}; +static_assert(sizeof(ReadSpiPacket) == 0x5, "ReadSpiPacket is an invalid size"); + +struct SubCommandResponse { + InputReportPassive input_report; + SubCommand sub_command; + union { + std::array<u8, 0x30> command_data; + SpiAddress spi_address; // Reply from SPI_FLASH_READ subcommand + ExternalDeviceId external_device_id; // Reply from GET_EXTERNAL_DEVICE_INFO subcommand + DeviceInfo device_info; // Reply from REQ_DEV_INFO subcommand + }; + u8 crc; // This is never used +}; +static_assert(sizeof(SubCommandResponse) == 0x40, "SubCommandResponse is an invalid size"); +#pragma pack(pop) + +struct MCUCommandResponse { + InputReportNfcIr input_report; + INSERT_PADDING_BYTES(0x8); + MCUReport mcu_report; + std::array<u8, 0x13D> mcu_data; + u8 crc; +}; +static_assert(sizeof(MCUCommandResponse) == 0x170, "MCUCommandResponse is an invalid size"); + +struct JoyconCallbacks { + std::function<void(Battery)> on_battery_data; + std::function<void(Color)> on_color_data; + std::function<void(int, bool)> on_button_data; + std::function<void(int, f32)> on_stick_data; + std::function<void(int, const MotionData&)> on_motion_data; + std::function<void(f32)> on_ring_data; + std::function<void(const std::vector<u8>&)> on_amiibo_data; + std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp new file mode 100644 index 000000000..eeba82986 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp @@ -0,0 +1,406 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <thread> +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/nfc.h" + +namespace InputCommon::Joycon { + +NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult NfcProtocol::EnableNfc() { + LOG_INFO(Input, "Enable NFC"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ); + } + if (result == DriverResult::Success) { + result = EnableMCU(true); + } + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby); + } + if (result == DriverResult::Success) { + const MCUConfig config{ + .command = MCUCommand::ConfigureMCU, + .sub_command = MCUSubCommand::SetMCUMode, + .mode = MCUMode::NFC, + .crc = {}, + }; + + result = ConfigureMCU(config); + } + + return result; +} + +DriverResult NfcProtocol::DisableNfc() { + LOG_DEBUG(Input, "Disable NFC"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = EnableMCU(false); + } + + is_enabled = false; + + return result; +} + +DriverResult NfcProtocol::StartNFCPollingMode() { + LOG_DEBUG(Input, "Start NFC pooling Mode"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + + if (result == DriverResult::Success) { + result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIsReady(); + } + if (result == DriverResult::Success) { + is_enabled = true; + } + + return result; +} + +DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { + LOG_DEBUG(Input, "Start NFC pooling Mode"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + + if (result == DriverResult::Success) { + result = StartPolling(tag_data); + } + if (result == DriverResult::Success) { + result = ReadTag(tag_data); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIsReady(); + } + if (result == DriverResult::Success) { + result = StartPolling(tag_data); + } + if (result == DriverResult::Success) { + result = GetAmiiboData(data); + } + + return result; +} + +bool NfcProtocol::HasAmiibo() { + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + + if (result == DriverResult::Success) { + result = StartPolling(tag_data); + } + + return result == DriverResult::Success; +} + +DriverResult NfcProtocol::WaitUntilNfcIsReady() { + constexpr std::size_t timeout_limit = 10; + MCUCommandResponse output{}; + std::size_t tries = 0; + + do { + auto result = SendStartWaitingRecieveRequest(output); + + if (result != DriverResult::Success) { + return result; + } + if (tries++ > timeout_limit) { + return DriverResult::Timeout; + } + } while (output.mcu_report != MCUReport::NFCState || + (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || + output.mcu_data[5] != 0x31 || output.mcu_data[6] != 0x00); + + return DriverResult::Success; +} + +DriverResult NfcProtocol::StartPolling(TagFoundData& data) { + LOG_DEBUG(Input, "Start Polling for tag"); + constexpr std::size_t timeout_limit = 7; + MCUCommandResponse output{}; + std::size_t tries = 0; + + do { + const auto result = SendStartPollingRequest(output); + if (result != DriverResult::Success) { + return result; + } + if (tries++ > timeout_limit) { + return DriverResult::Timeout; + } + } while (output.mcu_report != MCUReport::NFCState || + (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || + output.mcu_data[6] != 0x09); + + data.type = output.mcu_data[12]; + data.uuid.resize(output.mcu_data[14]); + memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size()); + + return DriverResult::Success; +} + +DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { + constexpr std::size_t timeout_limit = 10; + MCUCommandResponse output{}; + std::size_t tries = 0; + + std::string uuid_string; + for (auto& content : data.uuid) { + uuid_string += fmt::format(" {:02x}", content); + } + + LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string); + + tries = 0; + NFCPages ntag_pages = NFCPages::Block0; + // Read Tag data + while (true) { + auto result = SendReadAmiiboRequest(output, ntag_pages); + const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); + + if (result != DriverResult::Success) { + return result; + } + + if ((output.mcu_report == MCUReport::NFCReadData || + output.mcu_report == MCUReport::NFCState) && + nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07 && + output.mcu_data[2] == 0x01) { + if (data.type != 2) { + continue; + } + switch (output.mcu_data[24]) { + case 0: + ntag_pages = NFCPages::Block135; + break; + case 3: + ntag_pages = NFCPages::Block45; + break; + case 4: + ntag_pages = NFCPages::Block231; + break; + default: + return DriverResult::ErrorReadingData; + } + continue; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { + // finished + SendStopPollingRequest(output); + return DriverResult::Success; + } + + // Ignore other state reports + if (output.mcu_report == MCUReport::NFCState) { + continue; + } + + if (tries++ > timeout_limit) { + return DriverResult::Timeout; + } + } + + return DriverResult::Success; +} + +DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { + constexpr std::size_t timeout_limit = 10; + MCUCommandResponse output{}; + std::size_t tries = 0; + + NFCPages ntag_pages = NFCPages::Block135; + std::size_t ntag_buffer_pos = 0; + // Read Tag data + while (true) { + auto result = SendReadAmiiboRequest(output, ntag_pages); + const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); + + if (result != DriverResult::Success) { + return result; + } + + if ((output.mcu_report == MCUReport::NFCReadData || + output.mcu_report == MCUReport::NFCState) && + nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) { + std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF; + if (output.mcu_data[2] == 0x01) { + memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66, + payload_size - 60); + ntag_buffer_pos += payload_size - 60; + } else { + memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6, + payload_size); + } + continue; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { + LOG_INFO(Input, "Finished reading amiibo"); + return DriverResult::Success; + } + + // Ignore other state reports + if (output.mcu_report == MCUReport::NFCState) { + continue; + } + + if (tries++ > timeout_limit) { + return DriverResult::Timeout; + } + } + + return DriverResult::Success; +} + +DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { + NFCRequestState request{ + .sub_command = MCUSubCommand::ReadDeviceMode, + .command_argument = NFCReadCommand::StartPolling, + .packet_id = 0x0, + .packet_flag = MCUPacketFlag::LastCommandPacket, + .data_length = sizeof(NFCPollingCommandData), + .nfc_polling = + { + .enable_mifare = 0x01, + .unknown_1 = 0x00, + .unknown_2 = 0x00, + .unknown_3 = 0x2c, + .unknown_4 = 0x01, + }, + .crc = {}, + }; + + std::array<u8, sizeof(NFCRequestState)> request_data{}; + memcpy(request_data.data(), &request, sizeof(NFCRequestState)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { + NFCRequestState request{ + .sub_command = MCUSubCommand::ReadDeviceMode, + .command_argument = NFCReadCommand::StopPolling, + .packet_id = 0x0, + .packet_flag = MCUPacketFlag::LastCommandPacket, + .data_length = 0, + .raw_data = {}, + .crc = {}, + }; + + std::array<u8, sizeof(NFCRequestState)> request_data{}; + memcpy(request_data.data(), &request, sizeof(NFCRequestState)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) { + NFCRequestState request{ + .sub_command = MCUSubCommand::ReadDeviceMode, + .command_argument = NFCReadCommand::StartWaitingRecieve, + .packet_id = 0x0, + .packet_flag = MCUPacketFlag::LastCommandPacket, + .data_length = 0, + .raw_data = {}, + .crc = {}, + }; + + std::vector<u8> request_data(sizeof(NFCRequestState)); + memcpy(request_data.data(), &request, sizeof(NFCRequestState)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) { + NFCRequestState request{ + .sub_command = MCUSubCommand::ReadDeviceMode, + .command_argument = NFCReadCommand::Ntag, + .packet_id = 0x0, + .packet_flag = MCUPacketFlag::LastCommandPacket, + .data_length = sizeof(NFCReadCommandData), + .nfc_read = + { + .unknown = 0xd0, + .uuid_length = 0x07, + .unknown_2 = 0x00, + .uid = {}, + .tag_type = NFCTagType::AllTags, + .read_block = GetReadBlockCommand(ntag_pages), + }, + .crc = {}, + }; + + std::array<u8, sizeof(NFCRequestState)> request_data{}; + memcpy(request_data.data(), &request, sizeof(NFCRequestState)); + request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); +} + +NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { + switch (pages) { + case NFCPages::Block0: + return { + .block_count = 1, + }; + case NFCPages::Block45: + return { + .block_count = 1, + .blocks = + { + NFCReadBlock{0x00, 0x2C}, + }, + }; + case NFCPages::Block135: + return { + .block_count = 3, + .blocks = + { + NFCReadBlock{0x00, 0x3b}, + {0x3c, 0x77}, + {0x78, 0x86}, + }, + }; + case NFCPages::Block231: + return { + .block_count = 4, + .blocks = + { + NFCReadBlock{0x00, 0x3b}, + {0x3c, 0x77}, + {0x78, 0x83}, + {0xb4, 0xe6}, + }, + }; + default: + return {}; + }; +} + +bool NfcProtocol::IsEnabled() const { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h new file mode 100644 index 000000000..11e263e07 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class NfcProtocol final : private JoyconCommonProtocol { +public: + explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle); + + DriverResult EnableNfc(); + + DriverResult DisableNfc(); + + DriverResult StartNFCPollingMode(); + + DriverResult ScanAmiibo(std::vector<u8>& data); + + bool HasAmiibo(); + + bool IsEnabled() const; + +private: + struct TagFoundData { + u8 type; + std::vector<u8> uuid; + }; + + DriverResult WaitUntilNfcIsReady(); + + DriverResult StartPolling(TagFoundData& data); + + DriverResult ReadTag(const TagFoundData& data); + + DriverResult GetAmiiboData(std::vector<u8>& data); + + DriverResult SendStartPollingRequest(MCUCommandResponse& output); + + DriverResult SendStopPollingRequest(MCUCommandResponse& output); + + DriverResult SendStartWaitingRecieveRequest(MCUCommandResponse& output); + + DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages); + + NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp new file mode 100644 index 000000000..9bb15e935 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -0,0 +1,337 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/poller.h" + +namespace InputCommon::Joycon { + +JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, + JoyStickCalibration right_stick_calibration_, + MotionCalibration motion_calibration_) + : device_type{device_type_}, left_stick_calibration{left_stick_calibration_}, + right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {} + +void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) { + callbacks = std::move(callbacks_); +} + +void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, + const RingStatus& ring_status) { + InputReportActive data{}; + memcpy(&data, buffer.data(), sizeof(InputReportActive)); + + switch (device_type) { + case Joycon::ControllerType::Left: + UpdateActiveLeftPadInput(data, motion_status); + break; + case Joycon::ControllerType::Right: + UpdateActiveRightPadInput(data, motion_status); + break; + case Joycon::ControllerType::Pro: + UpdateActiveProPadInput(data, motion_status); + break; + default: + break; + } + + if (ring_status.is_enabled) { + UpdateRing(data.ring_input, ring_status); + } + + callbacks.on_battery_data(data.battery_status); +} + +void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) { + InputReportPassive data{}; + memcpy(&data, buffer.data(), sizeof(InputReportPassive)); + + switch (device_type) { + case Joycon::ControllerType::Left: + UpdatePasiveLeftPadInput(data); + break; + case Joycon::ControllerType::Right: + UpdatePasiveRightPadInput(data); + break; + case Joycon::ControllerType::Pro: + UpdatePasiveProPadInput(data); + break; + default: + break; + } +} + +void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) { + // This mode is compatible with the active mode + ReadActiveMode(buffer, motion_status, {}); +} + +void JoyconPoller::UpdateColor(const Color& color) { + callbacks.on_color_data(color); +} + +void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) { + callbacks.on_amiibo_data(amiibo_data); +} + +void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) { + callbacks.on_camera_data(camera_data, format); +} + +void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { + float normalized_value = static_cast<float>(value - ring_status.default_value); + if (normalized_value > 0) { + normalized_value = normalized_value / + static_cast<float>(ring_status.max_value - ring_status.default_value); + } + if (normalized_value < 0) { + normalized_value = normalized_value / + static_cast<float>(ring_status.default_value - ring_status.min_value); + } + callbacks.on_ring_data(normalized_value); +} + +void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input, + const MotionStatus& motion_status) { + static constexpr std::array<Joycon::PadButton, 11> left_buttons{ + Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, + Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR, + Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus, + Joycon::PadButton::Capture, Joycon::PadButton::StickL, + }; + + const u32 raw_button = + static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16)); + for (std::size_t i = 0; i < left_buttons.size(); ++i) { + const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0; + const int button = static_cast<int>(left_buttons[i]); + callbacks.on_button_data(button, button_status); + } + + const u16 raw_left_axis_x = + static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); + const u16 raw_left_axis_y = + static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); + const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); + const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); + callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); + callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); + + if (motion_status.is_enabled) { + auto left_motion = GetMotionInput(input, motion_status); + // Rotate motion axis to the correct direction + left_motion.accel_y = -left_motion.accel_y; + left_motion.accel_z = -left_motion.accel_z; + left_motion.gyro_x = -left_motion.gyro_x; + callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion); + } +} + +void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input, + const MotionStatus& motion_status) { + static constexpr std::array<Joycon::PadButton, 11> right_buttons{ + Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B, + Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR, + Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, + Joycon::PadButton::Home, Joycon::PadButton::StickR, + }; + + const u32 raw_button = + static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16)); + for (std::size_t i = 0; i < right_buttons.size(); ++i) { + const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0; + const int button = static_cast<int>(right_buttons[i]); + callbacks.on_button_data(button, button_status); + } + + const u16 raw_right_axis_x = + static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); + const u16 raw_right_axis_y = + static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); + const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); + const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); + callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); + callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); + + if (motion_status.is_enabled) { + auto right_motion = GetMotionInput(input, motion_status); + // Rotate motion axis to the correct direction + right_motion.accel_x = -right_motion.accel_x; + right_motion.accel_y = -right_motion.accel_y; + right_motion.gyro_z = -right_motion.gyro_z; + callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion); + } +} + +void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input, + const MotionStatus& motion_status) { + static constexpr std::array<Joycon::PadButton, 18> pro_buttons{ + Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, + Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL, + Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y, + Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A, + Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, + Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR, + }; + + const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) | + (input.button_input[1] << 16)); + for (std::size_t i = 0; i < pro_buttons.size(); ++i) { + const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0; + const int button = static_cast<int>(pro_buttons[i]); + callbacks.on_button_data(button, button_status); + } + + const u16 raw_left_axis_x = + static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); + const u16 raw_left_axis_y = + static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); + const u16 raw_right_axis_x = + static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); + const u16 raw_right_axis_y = + static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); + + const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); + const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); + const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); + const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); + callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); + callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); + callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); + callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); + + if (motion_status.is_enabled) { + auto pro_motion = GetMotionInput(input, motion_status); + pro_motion.gyro_x = -pro_motion.gyro_x; + pro_motion.accel_y = -pro_motion.accel_y; + pro_motion.accel_z = -pro_motion.accel_z; + callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion); + callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion); + } +} + +void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) { + static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{ + Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, + Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, + Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, + Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, + Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture, + Joycon::PasivePadButton::StickL, + }; + + for (auto left_button : left_buttons) { + const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0; + const int button = static_cast<int>(left_button); + callbacks.on_button_data(button, button_status); + } +} + +void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) { + static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{ + Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, + Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, + Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, + Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, + Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home, + Joycon::PasivePadButton::StickR, + }; + + for (auto right_button : right_buttons) { + const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0; + const int button = static_cast<int>(right_button); + callbacks.on_button_data(button, button_status); + } +} + +void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) { + static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{ + Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, + Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, + Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, + Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, + Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus, + Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home, + Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR, + }; + + for (auto pro_button : pro_buttons) { + const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0; + const int button = static_cast<int>(pro_button); + callbacks.on_button_data(button, button_status); + } +} + +f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const { + const f32 value = static_cast<f32>(raw_value - calibration.center); + if (value > 0.0f) { + return value / calibration.max; + } + return value / calibration.min; +} + +f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, + AccelerometerSensitivity sensitivity) const { + const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4; + switch (sensitivity) { + case Joycon::AccelerometerSensitivity::G2: + return value / 4.0f; + case Joycon::AccelerometerSensitivity::G4: + return value / 2.0f; + case Joycon::AccelerometerSensitivity::G8: + return value; + case Joycon::AccelerometerSensitivity::G16: + return value * 2.0f; + } + return value; +} + +f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal, + GyroSensitivity sensitivity) const { + const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f; + switch (sensitivity) { + case Joycon::GyroSensitivity::DPS250: + return value / 8.0f; + case Joycon::GyroSensitivity::DPS500: + return value / 4.0f; + case Joycon::GyroSensitivity::DPS1000: + return value / 2.0f; + case Joycon::GyroSensitivity::DPS2000: + return value; + } + return value; +} + +s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis, + const InputReportActive& input) const { + return input.motion_input[(sensor * 3) + axis]; +} + +MotionData JoyconPoller::GetMotionInput(const InputReportActive& input, + const MotionStatus& motion_status) const { + MotionData motion{}; + const auto& accel_cal = motion_calibration.accelerometer; + const auto& gyro_cal = motion_calibration.gyro; + const s16 raw_accel_x = input.motion_input[1]; + const s16 raw_accel_y = input.motion_input[0]; + const s16 raw_accel_z = input.motion_input[2]; + const s16 raw_gyro_x = input.motion_input[4]; + const s16 raw_gyro_y = input.motion_input[3]; + const s16 raw_gyro_z = input.motion_input[5]; + + motion.delta_timestamp = motion_status.delta_time; + motion.accel_x = + GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity); + motion.accel_y = + GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity); + motion.accel_z = + GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity); + motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity); + motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity); + motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity); + + // TODO(German77): Return all three samples data + return motion; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h new file mode 100644 index 000000000..354d41dad --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <functional> +#include <span> + +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +// Handles input packages and triggers the corresponding input events +class JoyconPoller { +public: + JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, + JoyStickCalibration right_stick_calibration_, + MotionCalibration motion_calibration_); + + void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_); + + /// Handles data from passive packages + void ReadPassiveMode(std::span<u8> buffer); + + /// Handles data from active packages + void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status, + const RingStatus& ring_status); + + /// Handles data from nfc or ir packages + void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status); + + void UpdateColor(const Color& color); + void UpdateRing(s16 value, const RingStatus& ring_status); + void UpdateAmiibo(const std::vector<u8>& amiibo_data); + void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format); + +private: + void UpdateActiveLeftPadInput(const InputReportActive& input, + const MotionStatus& motion_status); + void UpdateActiveRightPadInput(const InputReportActive& input, + const MotionStatus& motion_status); + void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status); + + void UpdatePasiveLeftPadInput(const InputReportPassive& buffer); + void UpdatePasiveRightPadInput(const InputReportPassive& buffer); + void UpdatePasiveProPadInput(const InputReportPassive& buffer); + + /// Returns a calibrated joystick axis from raw axis data + f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const; + + /// Returns a calibrated accelerometer axis from raw motion data + f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, + AccelerometerSensitivity sensitivity) const; + + /// Returns a calibrated gyro axis from raw motion data + f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal, + GyroSensitivity sensitivity) const; + + /// Returns a raw motion value from a buffer + s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const; + + /// Returns motion data from a buffer + MotionData GetMotionInput(const InputReportActive& input, + const MotionStatus& motion_status) const; + + ControllerType device_type{}; + + // Device calibration + JoyStickCalibration left_stick_calibration{}; + JoyStickCalibration right_stick_calibration{}; + MotionCalibration motion_calibration{}; + + Joycon::JoyconCallbacks callbacks{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp new file mode 100644 index 000000000..190cef812 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" + +namespace InputCommon::Joycon { + +RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult RingConProtocol::EnableRingCon() { + LOG_DEBUG(Input, "Enable Ringcon"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = SetReportMode(ReportMode::STANDARD_FULL_60HZ); + } + if (result == DriverResult::Success) { + result = EnableMCU(true); + } + if (result == DriverResult::Success) { + const MCUConfig config{ + .command = MCUCommand::ConfigureMCU, + .sub_command = MCUSubCommand::SetDeviceMode, + .mode = MCUMode::Standby, + .crc = {}, + }; + result = ConfigureMCU(config); + } + + return result; +} + +DriverResult RingConProtocol::DisableRingCon() { + LOG_DEBUG(Input, "Disable RingCon"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + result = EnableMCU(false); + } + + is_enabled = false; + + return result; +} + +DriverResult RingConProtocol::StartRingconPolling() { + LOG_DEBUG(Input, "Enable Ringcon"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + bool is_connected = false; + + if (result == DriverResult::Success) { + result = IsRingConnected(is_connected); + } + if (result == DriverResult::Success && is_connected) { + LOG_INFO(Input, "Ringcon detected"); + result = ConfigureRing(); + } + if (result == DriverResult::Success) { + is_enabled = true; + } + + return result; +} + +DriverResult RingConProtocol::IsRingConnected(bool& is_connected) { + LOG_DEBUG(Input, "IsRingConnected"); + constexpr std::size_t max_tries = 28; + SubCommandResponse output{}; + std::size_t tries = 0; + is_connected = false; + + do { + const auto result = SendSubCommand(SubCommand::GET_EXTERNAL_DEVICE_INFO, {}, output); + + if (result != DriverResult::Success) { + return result; + } + + if (tries++ >= max_tries) { + return DriverResult::NoDeviceDetected; + } + } while (output.external_device_id != ExternalDeviceId::RingController); + + is_connected = true; + return DriverResult::Success; +} + +DriverResult RingConProtocol::ConfigureRing() { + LOG_DEBUG(Input, "ConfigureRing"); + + static constexpr std::array<u8, 37> ring_config{ + 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36, + 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36}; + + const DriverResult result = SendSubCommand(SubCommand::SET_EXTERNAL_FORMAT_CONFIG, ring_config); + + if (result != DriverResult::Success) { + return result; + } + + static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02}; + return SendSubCommand(SubCommand::ENABLE_EXTERNAL_POLLING, ringcon_data); +} + +bool RingConProtocol::IsEnabled() const { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h new file mode 100644 index 000000000..6e858f3fc --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/ringcon.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class RingConProtocol final : private JoyconCommonProtocol { +public: + explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle); + + DriverResult EnableRingCon(); + + DriverResult DisableRingCon(); + + DriverResult StartRingconPolling(); + + bool IsEnabled() const; + +private: + DriverResult IsRingConnected(bool& is_connected); + + DriverResult ConfigureRing(); + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp new file mode 100644 index 000000000..63b60c946 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.cpp @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <cmath> + +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/rumble.h" + +namespace InputCommon::Joycon { + +RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle) + : JoyconCommonProtocol(std::move(handle)) {} + +DriverResult RumbleProtocol::EnableRumble(bool is_enabled) { + LOG_DEBUG(Input, "Enable Rumble"); + ScopedSetBlocking sb(this); + const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)}; + return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer); +} + +DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) { + std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{}; + + if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) { + return SendVibrationReport(DefaultVibrationBuffer); + } + + // Protect joycons from damage from strong vibrations + const f32 clamp_amplitude = + 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude); + + const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency); + const u8 encoded_high_amplitude = + EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude); + const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency); + const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude); + + buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF); + buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01)); + buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80)); + buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF); + + // Duplicate rumble for now + buffer[4] = buffer[0]; + buffer[5] = buffer[1]; + buffer[6] = buffer[2]; + buffer[7] = buffer[3]; + + return SendVibrationReport(buffer); +} + +u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const { + const u8 new_frequency = + static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); + return static_cast<u16>((new_frequency - 0x60) * 4); +} + +u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const { + const u8 new_frequency = + static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); + return static_cast<u8>(new_frequency - 0x40); +} + +u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const { + // More information about these values can be found here: + // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + + static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ + std::pair<f32, int>{0.0f, 0x0}, + {0.01f, 0x2}, + {0.012f, 0x4}, + {0.014f, 0x6}, + {0.017f, 0x8}, + {0.02f, 0x0a}, + {0.024f, 0x0c}, + {0.028f, 0x0e}, + {0.033f, 0x10}, + {0.04f, 0x12}, + {0.047f, 0x14}, + {0.056f, 0x16}, + {0.067f, 0x18}, + {0.08f, 0x1a}, + {0.095f, 0x1c}, + {0.112f, 0x1e}, + {0.117f, 0x20}, + {0.123f, 0x22}, + {0.128f, 0x24}, + {0.134f, 0x26}, + {0.14f, 0x28}, + {0.146f, 0x2a}, + {0.152f, 0x2c}, + {0.159f, 0x2e}, + {0.166f, 0x30}, + {0.173f, 0x32}, + {0.181f, 0x34}, + {0.189f, 0x36}, + {0.198f, 0x38}, + {0.206f, 0x3a}, + {0.215f, 0x3c}, + {0.225f, 0x3e}, + {0.23f, 0x40}, + {0.235f, 0x42}, + {0.24f, 0x44}, + {0.245f, 0x46}, + {0.251f, 0x48}, + {0.256f, 0x4a}, + {0.262f, 0x4c}, + {0.268f, 0x4e}, + {0.273f, 0x50}, + {0.279f, 0x52}, + {0.286f, 0x54}, + {0.292f, 0x56}, + {0.298f, 0x58}, + {0.305f, 0x5a}, + {0.311f, 0x5c}, + {0.318f, 0x5e}, + {0.325f, 0x60}, + {0.332f, 0x62}, + {0.34f, 0x64}, + {0.347f, 0x66}, + {0.355f, 0x68}, + {0.362f, 0x6a}, + {0.37f, 0x6c}, + {0.378f, 0x6e}, + {0.387f, 0x70}, + {0.395f, 0x72}, + {0.404f, 0x74}, + {0.413f, 0x76}, + {0.422f, 0x78}, + {0.431f, 0x7a}, + {0.44f, 0x7c}, + {0.45f, 0x7e}, + {0.46f, 0x80}, + {0.47f, 0x82}, + {0.48f, 0x84}, + {0.491f, 0x86}, + {0.501f, 0x88}, + {0.512f, 0x8a}, + {0.524f, 0x8c}, + {0.535f, 0x8e}, + {0.547f, 0x90}, + {0.559f, 0x92}, + {0.571f, 0x94}, + {0.584f, 0x96}, + {0.596f, 0x98}, + {0.609f, 0x9a}, + {0.623f, 0x9c}, + {0.636f, 0x9e}, + {0.65f, 0xa0}, + {0.665f, 0xa2}, + {0.679f, 0xa4}, + {0.694f, 0xa6}, + {0.709f, 0xa8}, + {0.725f, 0xaa}, + {0.741f, 0xac}, + {0.757f, 0xae}, + {0.773f, 0xb0}, + {0.79f, 0xb2}, + {0.808f, 0xb4}, + {0.825f, 0xb6}, + {0.843f, 0xb8}, + {0.862f, 0xba}, + {0.881f, 0xbc}, + {0.9f, 0xbe}, + {0.92f, 0xc0}, + {0.94f, 0xc2}, + {0.96f, 0xc4}, + {0.981f, 0xc6}, + {1.003f, 0xc8}, + }; + + for (const auto& [amplitude_value, code] : high_fequency_amplitude) { + if (amplitude <= amplitude_value) { + return static_cast<u8>(code); + } + } + + return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); +} + +u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const { + // More information about these values can be found here: + // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + + static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ + std::pair<f32, int>{0.0f, 0x0040}, + {0.01f, 0x8040}, + {0.012f, 0x0041}, + {0.014f, 0x8041}, + {0.017f, 0x0042}, + {0.02f, 0x8042}, + {0.024f, 0x0043}, + {0.028f, 0x8043}, + {0.033f, 0x0044}, + {0.04f, 0x8044}, + {0.047f, 0x0045}, + {0.056f, 0x8045}, + {0.067f, 0x0046}, + {0.08f, 0x8046}, + {0.095f, 0x0047}, + {0.112f, 0x8047}, + {0.117f, 0x0048}, + {0.123f, 0x8048}, + {0.128f, 0x0049}, + {0.134f, 0x8049}, + {0.14f, 0x004a}, + {0.146f, 0x804a}, + {0.152f, 0x004b}, + {0.159f, 0x804b}, + {0.166f, 0x004c}, + {0.173f, 0x804c}, + {0.181f, 0x004d}, + {0.189f, 0x804d}, + {0.198f, 0x004e}, + {0.206f, 0x804e}, + {0.215f, 0x004f}, + {0.225f, 0x804f}, + {0.23f, 0x0050}, + {0.235f, 0x8050}, + {0.24f, 0x0051}, + {0.245f, 0x8051}, + {0.251f, 0x0052}, + {0.256f, 0x8052}, + {0.262f, 0x0053}, + {0.268f, 0x8053}, + {0.273f, 0x0054}, + {0.279f, 0x8054}, + {0.286f, 0x0055}, + {0.292f, 0x8055}, + {0.298f, 0x0056}, + {0.305f, 0x8056}, + {0.311f, 0x0057}, + {0.318f, 0x8057}, + {0.325f, 0x0058}, + {0.332f, 0x8058}, + {0.34f, 0x0059}, + {0.347f, 0x8059}, + {0.355f, 0x005a}, + {0.362f, 0x805a}, + {0.37f, 0x005b}, + {0.378f, 0x805b}, + {0.387f, 0x005c}, + {0.395f, 0x805c}, + {0.404f, 0x005d}, + {0.413f, 0x805d}, + {0.422f, 0x005e}, + {0.431f, 0x805e}, + {0.44f, 0x005f}, + {0.45f, 0x805f}, + {0.46f, 0x0060}, + {0.47f, 0x8060}, + {0.48f, 0x0061}, + {0.491f, 0x8061}, + {0.501f, 0x0062}, + {0.512f, 0x8062}, + {0.524f, 0x0063}, + {0.535f, 0x8063}, + {0.547f, 0x0064}, + {0.559f, 0x8064}, + {0.571f, 0x0065}, + {0.584f, 0x8065}, + {0.596f, 0x0066}, + {0.609f, 0x8066}, + {0.623f, 0x0067}, + {0.636f, 0x8067}, + {0.65f, 0x0068}, + {0.665f, 0x8068}, + {0.679f, 0x0069}, + {0.694f, 0x8069}, + {0.709f, 0x006a}, + {0.725f, 0x806a}, + {0.741f, 0x006b}, + {0.757f, 0x806b}, + {0.773f, 0x006c}, + {0.79f, 0x806c}, + {0.808f, 0x006d}, + {0.825f, 0x806d}, + {0.843f, 0x006e}, + {0.862f, 0x806e}, + {0.881f, 0x006f}, + {0.9f, 0x806f}, + {0.92f, 0x0070}, + {0.94f, 0x8070}, + {0.96f, 0x0071}, + {0.981f, 0x8071}, + {1.003f, 0x0072}, + }; + + for (const auto& [amplitude_value, code] : high_fequency_amplitude) { + if (amplitude <= amplitude_value) { + return static_cast<u16>(code); + } + } + + return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h new file mode 100644 index 000000000..6c12b7925 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/rumble.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse +// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c +// https://github.com/CTCaer/jc_toolkit +// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering + +#pragma once + +#include <vector> + +#include "input_common/helpers/joycon_protocol/common_protocol.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon::Joycon { + +class RumbleProtocol final : private JoyconCommonProtocol { +public: + explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle); + + DriverResult EnableRumble(bool is_enabled); + + DriverResult SendVibration(const VibrationValue& vibration); + +private: + u16 EncodeHighFrequency(f32 frequency) const; + u8 EncodeLowFrequency(f32 frequency) const; + u8 EncodeHighAmplitude(f32 amplitude) const; + u16 EncodeLowAmplitude(f32 amplitude) const; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp index f3a0b3419..a6be6dac1 100644 --- a/src/input_common/helpers/stick_from_buttons.cpp +++ b/src/input_common/helpers/stick_from_buttons.cpp @@ -11,6 +11,14 @@ namespace InputCommon { class Stick final : public Common::Input::InputDevice { public: + // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS + // do not play nicely with the theoretical maximum range. + // Using a value one lower from the maximum emulates real stick behavior. + static constexpr float MAX_RANGE = 32766.0f / 32767.0f; + static constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + static constexpr float APERTURE = TAU * 0.15f; + using Button = std::unique_ptr<Common::Input::InputDevice>; Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, @@ -56,30 +64,23 @@ public: } bool IsAngleGreater(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float top_limit = new_angle + aperture; + const float top_limit = new_angle + APERTURE; return (old_angle > new_angle && old_angle <= top_limit) || (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); } bool IsAngleSmaller(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float bottom_limit = new_angle - aperture; + const float bottom_limit = new_angle - APERTURE; return (old_angle >= bottom_limit && old_angle < new_angle) || (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); } float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { - constexpr float TAU = Common::PI * 2.0f; float new_angle = angle; auto time_difference = static_cast<float>( - std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); - time_difference /= 1000.0f * 1000.0f; + std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); + time_difference /= 1000.0f; if (time_difference > 0.5f) { time_difference = 0.5f; } @@ -196,8 +197,6 @@ public: } void UpdateStatus() { - const float coef = modifier_status.value ? modifier_scale : 1.0f; - bool r = right_status; bool l = left_status; bool u = up_status; @@ -215,7 +214,7 @@ public: // Move if a key is pressed if (r || l || u || d) { - amplitude = coef; + amplitude = modifier_status.value ? modifier_scale : MAX_RANGE; } else { amplitude = 0; } @@ -269,30 +268,17 @@ public: Common::Input::StickStatus status{}; status.x.properties = properties; status.y.properties = properties; + if (Settings::values.emulate_analog_keyboard) { const auto now = std::chrono::steady_clock::now(); - float angle_ = GetAngle(now); + const float angle_ = GetAngle(now); status.x.raw_value = std::cos(angle_) * amplitude; status.y.raw_value = std::sin(angle_) * amplitude; return status; } - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - if (right_status) { - ++x; - } - if (left_status) { - --x; - } - if (up_status) { - ++y; - } - if (down_status) { - --y; - } - const float coef = modifier_status.value ? modifier_scale : 1.0f; - status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); - status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); + + status.x.raw_value = std::cos(goal_angle) * amplitude; + status.y.raw_value = std::sin(goal_angle) * amplitude; return status; } diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 61cfd0911..91aa96aa7 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat TriggerOnBatteryChange(identifier, value); } +void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) { + { + std::scoped_lock lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.color = value; + } + } + TriggerOnColorChange(identifier, value); +} + void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { { std::scoped_lock lock{mutex}; @@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif return controller.battery; } +Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const { + std::scoped_lock lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.color; +} + BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { std::scoped_lock lock{mutex}; const auto controller_iter = controller_list.find(identifier); @@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier, } } +void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier, + [[maybe_unused]] Common::Input::BodyColorStatus value) { + std::scoped_lock lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, const BasicMotion& value) { std::scoped_lock lock{mutex_callback}; diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 6cbcf5207..50b5a3dc8 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -40,6 +40,7 @@ enum class EngineInputType { Battery, Button, Camera, + Color, HatButton, Motion, Nfc, @@ -104,14 +105,17 @@ public: void EndConfiguration(); // Sets a led pattern for a controller - virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, - [[maybe_unused]] const Common::Input::LedStatus& led_status) {} + virtual Common::Input::DriverResult SetLeds( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::LedStatus& led_status) { + return Common::Input::DriverResult::NotSupported; + } // Sets rumble to a controller - virtual Common::Input::VibrationError SetVibration( + virtual Common::Input::DriverResult SetVibration( [[maybe_unused]] const PadIdentifier& identifier, [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { - return Common::Input::VibrationError::NotSupported; + return Common::Input::DriverResult::NotSupported; } // Returns true if device supports vibrations @@ -120,17 +124,17 @@ public: } // Sets polling mode to a controller - virtual Common::Input::PollingError SetPollingMode( + virtual Common::Input::DriverResult SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier, [[maybe_unused]] const Common::Input::PollingMode polling_mode) { - return Common::Input::PollingError::NotSupported; + return Common::Input::DriverResult::NotSupported; } // Sets camera format to a controller - virtual Common::Input::CameraError SetCameraFormat( + virtual Common::Input::DriverResult SetCameraFormat( [[maybe_unused]] const PadIdentifier& identifier, [[maybe_unused]] Common::Input::CameraFormat camera_format) { - return Common::Input::CameraError::NotSupported; + return Common::Input::DriverResult::NotSupported; } // Returns success if nfc is supported @@ -199,6 +203,7 @@ public: bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; f32 GetAxis(const PadIdentifier& identifier, int axis) const; Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; + Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const; BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const; @@ -212,6 +217,7 @@ protected: void SetHatButton(const PadIdentifier& identifier, int button, u8 value); void SetAxis(const PadIdentifier& identifier, int axis, f32 value); void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); + void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value); void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value); @@ -227,6 +233,7 @@ private: std::unordered_map<int, float> axes; std::unordered_map<int, BasicMotion> motions; Common::Input::BatteryLevel battery{}; + Common::Input::BodyColorStatus color{}; Common::Input::CameraStatus camera{}; Common::Input::NfcStatus nfc{}; }; @@ -235,6 +242,8 @@ private: void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); + void TriggerOnColorChange(const PadIdentifier& identifier, + Common::Input::BodyColorStatus value); void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, const BasicMotion& value); void TriggerOnCameraChange(const PadIdentifier& identifier, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index fb8be42e2..8c6a6521a 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -16,10 +16,10 @@ public: class InputFromButton final : public Common::Input::InputDevice { public: - explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, - InputEngine* input_engine_) - : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), - input_engine(input_engine_) { + explicit InputFromButton(PadIdentifier identifier_, int button_, bool turbo_, bool toggle_, + bool inverted_, InputEngine* input_engine_) + : identifier(identifier_), button(button_), turbo(turbo_), toggle(toggle_), + inverted(inverted_), input_engine(input_engine_) { UpdateCallback engine_callback{[this]() { OnChange(); }}; const InputIdentifier input_identifier{ .identifier = identifier, @@ -40,6 +40,7 @@ public: .value = input_engine->GetButton(identifier, button), .inverted = inverted, .toggle = toggle, + .turbo = turbo, }; } @@ -68,6 +69,7 @@ public: private: const PadIdentifier identifier; const int button; + const bool turbo; const bool toggle; const bool inverted; int callback_key; @@ -77,10 +79,10 @@ private: class InputFromHatButton final : public Common::Input::InputDevice { public: - explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_, - bool inverted_, InputEngine* input_engine_) - : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_), - inverted(inverted_), input_engine(input_engine_) { + explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool turbo_, + bool toggle_, bool inverted_, InputEngine* input_engine_) + : identifier(identifier_), button(button_), direction(direction_), turbo(turbo_), + toggle(toggle_), inverted(inverted_), input_engine(input_engine_) { UpdateCallback engine_callback{[this]() { OnChange(); }}; const InputIdentifier input_identifier{ .identifier = identifier, @@ -101,6 +103,7 @@ public: .value = input_engine->GetHatButton(identifier, button, direction), .inverted = inverted, .toggle = toggle, + .turbo = turbo, }; } @@ -130,6 +133,7 @@ private: const PadIdentifier identifier; const int button; const u8 direction; + const bool turbo; const bool toggle; const bool inverted; int callback_key; @@ -498,6 +502,58 @@ private: InputEngine* input_engine; }; +class InputFromColor final : public Common::Input::InputDevice { +public: + explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Color, + .index = 0, + .callback = engine_callback, + }; + last_color_value = {}; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromColor() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::BodyColorStatus GetStatus() const { + return input_engine->GetColor(identifier); + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Color, + .color_status = GetStatus(), + }; + + last_color_value = status.color_status; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Color, + .color_status = GetStatus(), + }; + + if (status.color_status.body != last_color_value.body) { + last_color_value = status.color_status; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + int callback_key; + Common::Input::BodyColorStatus last_color_value; + InputEngine* input_engine; +}; + class InputFromMotion final : public Common::Input::InputDevice { public: explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, @@ -754,11 +810,11 @@ public: explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) : identifier(identifier_), input_engine(input_engine_) {} - void SetLED(const Common::Input::LedStatus& led_status) override { - input_engine->SetLeds(identifier, led_status); + Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override { + return input_engine->SetLeds(identifier, led_status); } - Common::Input::VibrationError SetVibration( + Common::Input::DriverResult SetVibration( const Common::Input::VibrationStatus& vibration_status) override { return input_engine->SetVibration(identifier, vibration_status); } @@ -767,11 +823,12 @@ public: return input_engine->IsVibrationEnabled(identifier); } - Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { + Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override { return input_engine->SetPollingMode(identifier, polling_mode); } - Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override { + Common::Input::DriverResult SetCameraFormat( + Common::Input::CameraFormat camera_format) override { return input_engine->SetCameraFormat(identifier, camera_format); } @@ -800,14 +857,15 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice( const auto keyboard_key = params.Get("code", 0); const auto toggle = params.Get("toggle", false) != 0; const auto inverted = params.Get("inverted", false) != 0; + const auto turbo = params.Get("turbo", false) != 0; input_engine->PreSetController(identifier); input_engine->PreSetButton(identifier, button_id); input_engine->PreSetButton(identifier, keyboard_key); if (keyboard_key != 0) { - return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted, + return std::make_unique<InputFromButton>(identifier, keyboard_key, turbo, toggle, inverted, input_engine.get()); } - return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted, + return std::make_unique<InputFromButton>(identifier, button_id, turbo, toggle, inverted, input_engine.get()); } @@ -823,11 +881,12 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice( const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); const auto toggle = params.Get("toggle", false) != 0; const auto inverted = params.Get("inverted", false) != 0; + const auto turbo = params.Get("turbo", false) != 0; input_engine->PreSetController(identifier); input_engine->PreSetHatButton(identifier, button_id); - return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted, - input_engine.get()); + return std::make_unique<InputFromHatButton>(identifier, button_id, direction, turbo, toggle, + inverted, input_engine.get()); } std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice( @@ -966,6 +1025,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice( return std::make_unique<InputFromBattery>(identifier, input_engine.get()); } +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique<InputFromColor>(identifier, input_engine.get()); +} + std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( Common::ParamPackage params) { const PadIdentifier identifier = { @@ -1053,6 +1124,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create( if (params.Has("battery")) { return CreateBatteryDevice(params); } + if (params.Has("color")) { + return CreateColorDevice(params); + } if (params.Has("camera")) { return CreateCameraDevice(params); } diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h index d7db13ce4..e097e254c 100644 --- a/src/input_common/input_poller.h +++ b/src/input_common/input_poller.h @@ -191,6 +191,17 @@ private: const Common::ParamPackage& params); /** + * Creates a color device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateColorDevice( + const Common::ParamPackage& params); + + /** * Creates a motion device from the parameters given. * @param params contains parameters for creating the device: * - "axis_x": the controller horizontal axis id to bind with the input diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index e0b2131ed..c77fc04ee 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -23,6 +23,7 @@ #include "input_common/drivers/gc_adapter.h" #endif #ifdef HAVE_SDL2 +#include "input_common/drivers/joycon.h" #include "input_common/drivers/sdl_driver.h" #endif @@ -81,6 +82,7 @@ struct InputSubsystem::Impl { RegisterEngine("virtual_gamepad", virtual_gamepad); #ifdef HAVE_SDL2 RegisterEngine("sdl", sdl); + RegisterEngine("joycon", joycon); #endif Common::Input::RegisterInputFactory("touch_from_button", @@ -111,6 +113,7 @@ struct InputSubsystem::Impl { UnregisterEngine(virtual_gamepad); #ifdef HAVE_SDL2 UnregisterEngine(sdl); + UnregisterEngine(joycon); #endif Common::Input::UnregisterInputFactory("touch_from_button"); @@ -133,6 +136,8 @@ struct InputSubsystem::Impl { auto udp_devices = udp_client->GetInputDevices(); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); #ifdef HAVE_SDL2 + auto joycon_devices = joycon->GetInputDevices(); + devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); #endif @@ -164,6 +169,9 @@ struct InputSubsystem::Impl { if (engine == sdl->GetEngineName()) { return sdl; } + if (engine == joycon->GetEngineName()) { + return joycon; + } #endif return nullptr; } @@ -247,6 +255,9 @@ struct InputSubsystem::Impl { if (engine == sdl->GetEngineName()) { return true; } + if (engine == joycon->GetEngineName()) { + return true; + } #endif return false; } @@ -260,6 +271,7 @@ struct InputSubsystem::Impl { udp_client->BeginConfiguration(); #ifdef HAVE_SDL2 sdl->BeginConfiguration(); + joycon->BeginConfiguration(); #endif } @@ -272,6 +284,7 @@ struct InputSubsystem::Impl { udp_client->EndConfiguration(); #ifdef HAVE_SDL2 sdl->EndConfiguration(); + joycon->EndConfiguration(); #endif } @@ -304,6 +317,7 @@ struct InputSubsystem::Impl { #ifdef HAVE_SDL2 std::shared_ptr<SDLDriver> sdl; + std::shared_ptr<Joycons> joycon; #endif }; diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp index 0cb1e193e..fd4a61a4d 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp @@ -279,6 +279,8 @@ void SetupOptions(const IR::Program& program, const Profile& profile, header += "OPTION NV_internal;" "OPTION NV_shader_storage_buffer;" "OPTION NV_gpu_program_fp64;"; + // TODO: Enable only when MS is used + header += "OPTION NV_texture_multisample;"; if (info.uses_int64_bit_atomics) { header += "OPTION NV_shader_atomic_int64;"; } diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp index 5bfdecc09..2fc2a0ac6 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp @@ -43,10 +43,6 @@ void EmitBitCastU64F64(EmitContext&, IR::Inst& inst, const IR::Value& value) { Alias(inst, value); } -void EmitBitCastS32F32(EmitContext&, IR::Inst& inst, const IR::Value& value) { - Alias(inst, value); -} - void EmitBitCastF16U16(EmitContext&, IR::Inst& inst, const IR::Value& value) { Alias(inst, value); } diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp index e67e80fac..85ee27333 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp @@ -59,7 +59,14 @@ std::string Image(EmitContext& ctx, IR::TextureInstInfo info, } } -std::string_view TextureType(IR::TextureInstInfo info) { +bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.info.texture_descriptors.at(info.descriptor_index).is_multisample; +} + +std::string_view TextureType(IR::TextureInstInfo info, bool is_ms = false) { if (info.is_depth) { switch (info.type) { case TextureType::Color1D: @@ -88,9 +95,9 @@ std::string_view TextureType(IR::TextureInstInfo info) { return "ARRAY1D"; case TextureType::Color2D: case TextureType::Color2DRect: - return "2D"; + return is_ms ? "2DMS" : "2D"; case TextureType::ColorArray2D: - return "ARRAY2D"; + return is_ms ? "ARRAY2DMS" : "ARRAY2D"; case TextureType::Color3D: return "3D"; case TextureType::ColorCube: @@ -510,15 +517,16 @@ void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, const IR::Value& coord, const IR::Value& offset, ScalarS32 lod, ScalarS32 ms) { const auto info{inst.Flags<IR::TextureInstInfo>()}; const auto sparse_inst{PrepareSparse(inst)}; + const bool is_multisample{ms.type != Type::Void}; const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; - const std::string_view type{TextureType(info)}; + const std::string_view type{TextureType(info, is_multisample)}; const std::string texture{Texture(ctx, info, index)}; const std::string offset_vec{Offset(ctx, offset)}; const auto [coord_vec, coord_alloc]{Coord(ctx, coord)}; const Register ret{ctx.reg_alloc.Define(inst)}; if (info.type == TextureType::Buffer) { ctx.Add("TXF.F{} {},{},{},{}{};", sparse_mod, ret, coord_vec, texture, type, offset_vec); - } else if (ms.type != Type::Void) { + } else if (is_multisample) { ctx.Add("MOV.S {}.w,{};" "TXFMS.F{} {},{},{},{}{};", coord_vec, ms, sparse_mod, ret, coord_vec, texture, type, offset_vec); @@ -531,10 +539,11 @@ void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, } void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, - ScalarS32 lod) { + ScalarS32 lod, [[maybe_unused]] const IR::Value& skip_mips) { const auto info{inst.Flags<IR::TextureInstInfo>()}; const std::string texture{Texture(ctx, info, index)}; - const std::string_view type{TextureType(info)}; + const bool is_msaa{IsTextureMsaa(ctx, info)}; + const std::string_view type{TextureType(info, is_msaa)}; ctx.Add("TXQ {},{},{},{};", inst, lod, texture, type); } diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h index eaaf9ba39..1a1ea61d5 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h +++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h @@ -197,7 +197,6 @@ void EmitSelectF64(EmitContext& ctx, ScalarS32 cond, Register true_value, Regist void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); -void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value); @@ -582,7 +581,7 @@ void EmitImageGatherDref(EmitContext& ctx, IR::Inst& inst, const IR::Value& inde void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, const IR::Value& coord, const IR::Value& offset, ScalarS32 lod, ScalarS32 ms); void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, - ScalarS32 lod); + ScalarS32 lod, const IR::Value& skip_mips); void EmitImageQueryLod(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, Register coord); void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, const IR::Value& coord, const IR::Value& derivatives, diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp index 8e5e6cf1f..1be4a0f59 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp @@ -48,10 +48,6 @@ void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value) ctx.AddU64("{}=doubleBitsToUint64({});", inst, value); } -void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value) { - ctx.AddF32("{}=ftoi({});", inst, value); -} - void EmitBitCastF16U16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst) { NotImplemented(); } diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp index cecdbb9d6..f335c8af0 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp @@ -25,6 +25,13 @@ std::string Image(EmitContext& ctx, const IR::TextureInstInfo& info, const IR::V return fmt::format("img{}{}", def.binding, index_offset); } +bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.info.texture_descriptors.at(info.descriptor_index).is_multisample; +} + std::string CastToIntVec(std::string_view value, const IR::TextureInstInfo& info) { switch (info.type) { case TextureType::Color1D: @@ -414,7 +421,7 @@ void EmitImageGatherDref(EmitContext& ctx, IR::Inst& inst, const IR::Value& inde void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, std::string_view coords, std::string_view offset, std::string_view lod, - [[maybe_unused]] std::string_view ms) { + std::string_view ms) { const auto info{inst.Flags<IR::TextureInstInfo>()}; if (info.has_bias) { throw NotImplementedException("EmitImageFetch Bias texture samples"); @@ -431,19 +438,24 @@ void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, ctx.AddU1("{}=true;", *sparse_inst); } if (!sparse_inst || !supports_sparse) { - if (!offset.empty()) { - ctx.Add("{}=texelFetchOffset({},{},int({}),{});", texel, texture, - CoordsCastToInt(coords, info), lod, CoordsCastToInt(offset, info)); + const auto int_coords{CoordsCastToInt(coords, info)}; + if (!ms.empty()) { + ctx.Add("{}=texelFetch({},{},int({}));", texel, texture, int_coords, ms); + } else if (!offset.empty()) { + ctx.Add("{}=texelFetchOffset({},{},int({}),{});", texel, texture, int_coords, lod, + CoordsCastToInt(offset, info)); } else { if (info.type == TextureType::Buffer) { ctx.Add("{}=texelFetch({},int({}));", texel, texture, coords); } else { - ctx.Add("{}=texelFetch({},{},int({}));", texel, texture, - CoordsCastToInt(coords, info), lod); + ctx.Add("{}=texelFetch({},{},int({}));", texel, texture, int_coords, lod); } } return; } + if (!ms.empty()) { + throw NotImplementedException("EmitImageFetch Sparse MSAA samples"); + } if (!offset.empty()) { ctx.AddU1("{}=sparseTexelsResidentARB(sparseTexelFetchOffsetARB({},{},int({}),{},{}));", *sparse_inst, texture, CastToIntVec(coords, info), lod, @@ -455,29 +467,36 @@ void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, } void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, - std::string_view lod) { + std::string_view lod, const IR::Value& skip_mips_val) { const auto info{inst.Flags<IR::TextureInstInfo>()}; const auto texture{Texture(ctx, info, index)}; + const bool is_msaa{IsTextureMsaa(ctx, info)}; + const bool skip_mips{skip_mips_val.U1()}; + const auto mips{skip_mips ? "0u" : fmt::format("uint(textureQueryLevels({}))", texture)}; + if (is_msaa && !skip_mips) { + throw NotImplementedException("EmitImageQueryDimensions MSAA QueryLevels"); + } + if (info.type == TextureType::Buffer && !skip_mips) { + throw NotImplementedException("EmitImageQueryDimensions TextureType::Buffer QueryLevels"); + } + const bool uses_lod{!is_msaa && info.type != TextureType::Buffer}; + const auto lod_str{uses_lod ? fmt::format(",int({})", lod) : ""}; switch (info.type) { case TextureType::Color1D: - return ctx.AddU32x4( - "{}=uvec4(uint(textureSize({},int({}))),0u,0u,uint(textureQueryLevels({})));", inst, - texture, lod, texture); + return ctx.AddU32x4("{}=uvec4(uint(textureSize({}{})),0u,0u,{});", inst, texture, lod_str, + mips); case TextureType::ColorArray1D: case TextureType::Color2D: case TextureType::ColorCube: case TextureType::Color2DRect: - return ctx.AddU32x4( - "{}=uvec4(uvec2(textureSize({},int({}))),0u,uint(textureQueryLevels({})));", inst, - texture, lod, texture); + return ctx.AddU32x4("{}=uvec4(uvec2(textureSize({}{})),0u,{});", inst, texture, lod_str, + mips); case TextureType::ColorArray2D: case TextureType::Color3D: case TextureType::ColorArrayCube: - return ctx.AddU32x4( - "{}=uvec4(uvec3(textureSize({},int({}))),uint(textureQueryLevels({})));", inst, texture, - lod, texture); + return ctx.AddU32x4("{}=uvec4(uvec3(textureSize({}{})),{});", inst, texture, lod_str, mips); case TextureType::Buffer: - throw NotImplementedException("EmitImageQueryDimensions Texture buffers"); + return ctx.AddU32x4("{}=uvec4(uint(textureSize({})),0u,0u,{});", inst, texture, mips); } throw LogicError("Unspecified image type {}", info.type.Value()); } diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h index 4151c89de..8d0a65047 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h +++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h @@ -231,7 +231,6 @@ void EmitSelectF64(EmitContext& ctx, IR::Inst& inst, std::string_view cond, void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst); void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value); void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value); -void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value); void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst); void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, std::string_view value); void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, std::string_view value); @@ -655,7 +654,7 @@ void EmitImageFetch(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, std::string_view coords, std::string_view offset, std::string_view lod, std::string_view ms); void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, - std::string_view lod); + std::string_view lod, const IR::Value& skip_mips); void EmitImageQueryLod(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, std::string_view coords); void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp index 5d01ec0cd..1b006e811 100644 --- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp +++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp @@ -61,24 +61,28 @@ std::string OutputDecorator(Stage stage, u32 size) { } } -std::string_view SamplerType(TextureType type, bool is_depth) { - if (is_depth) { - switch (type) { - case TextureType::Color1D: - return "sampler1DShadow"; - case TextureType::ColorArray1D: - return "sampler1DArrayShadow"; - case TextureType::Color2D: - return "sampler2DShadow"; - case TextureType::ColorArray2D: - return "sampler2DArrayShadow"; - case TextureType::ColorCube: - return "samplerCubeShadow"; - case TextureType::ColorArrayCube: - return "samplerCubeArrayShadow"; - default: - throw NotImplementedException("Texture type: {}", type); - } +std::string_view DepthSamplerType(TextureType type) { + switch (type) { + case TextureType::Color1D: + return "sampler1DShadow"; + case TextureType::ColorArray1D: + return "sampler1DArrayShadow"; + case TextureType::Color2D: + return "sampler2DShadow"; + case TextureType::ColorArray2D: + return "sampler2DArrayShadow"; + case TextureType::ColorCube: + return "samplerCubeShadow"; + case TextureType::ColorArrayCube: + return "samplerCubeArrayShadow"; + default: + throw NotImplementedException("Texture type: {}", type); + } +} + +std::string_view ColorSamplerType(TextureType type, bool is_multisample = false) { + if (is_multisample) { + ASSERT(type == TextureType::Color2D || type == TextureType::ColorArray2D); } switch (type) { case TextureType::Color1D: @@ -87,9 +91,9 @@ std::string_view SamplerType(TextureType type, bool is_depth) { return "sampler1DArray"; case TextureType::Color2D: case TextureType::Color2DRect: - return "sampler2D"; + return is_multisample ? "sampler2DMS" : "sampler2D"; case TextureType::ColorArray2D: - return "sampler2DArray"; + return is_multisample ? "sampler2DMSArray" : "sampler2DArray"; case TextureType::Color3D: return "sampler3D"; case TextureType::ColorCube: @@ -677,7 +681,7 @@ void EmitContext::SetupTextures(Bindings& bindings) { texture_buffers.reserve(info.texture_buffer_descriptors.size()); for (const auto& desc : info.texture_buffer_descriptors) { texture_buffers.push_back({bindings.texture, desc.count}); - const auto sampler_type{SamplerType(TextureType::Buffer, false)}; + const auto sampler_type{ColorSamplerType(TextureType::Buffer)}; const auto array_decorator{desc.count > 1 ? fmt::format("[{}]", desc.count) : ""}; header += fmt::format("layout(binding={}) uniform {} tex{}{};", bindings.texture, sampler_type, bindings.texture, array_decorator); @@ -686,7 +690,8 @@ void EmitContext::SetupTextures(Bindings& bindings) { textures.reserve(info.texture_descriptors.size()); for (const auto& desc : info.texture_descriptors) { textures.push_back({bindings.texture, desc.count}); - const auto sampler_type{SamplerType(desc.type, desc.is_depth)}; + const auto sampler_type{desc.is_depth ? DepthSamplerType(desc.type) + : ColorSamplerType(desc.type, desc.is_multisample)}; const auto array_decorator{desc.count > 1 ? fmt::format("[{}]", desc.count) : ""}; header += fmt::format("layout(binding={}) uniform {} tex{}{};", bindings.texture, sampler_type, bindings.texture, array_decorator); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp index 50daacd95..c4ca28d11 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp @@ -18,10 +18,6 @@ void EmitBitCastU64F64(EmitContext&) { throw NotImplementedException("SPIR-V Instruction"); } -void EmitBitCastS32F32(EmitContext&) { - throw NotImplementedException("SPIR-V Instruction"); -} - void EmitBitCastF16U16(EmitContext&) { throw NotImplementedException("SPIR-V Instruction"); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index fb5799c42..02073c420 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -201,6 +201,13 @@ Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) { } } +bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.textures.at(info.descriptor_index).is_multisample; +} + Id Decorate(EmitContext& ctx, IR::Inst* inst, Id sample) { const auto info{inst->Flags<IR::TextureInstInfo>()}; if (info.relaxed_precision != 0) { @@ -436,34 +443,42 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c if (info.type == TextureType::Buffer) { lod = Id{}; } + if (Sirit::ValidId(ms)) { + // This image is multisampled, lod must be implicit + lod = Id{}; + } const ImageOperands operands(offset, lod, ms); return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); } -Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod) { +Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, + const IR::Value& skip_mips_val) { const auto info{inst->Flags<IR::TextureInstInfo>()}; const Id image{TextureImage(ctx, info, index)}; const Id zero{ctx.u32_zero_value}; - const auto mips{[&] { return ctx.OpImageQueryLevels(ctx.U32[1], image); }}; + const bool skip_mips{skip_mips_val.U1()}; + const auto mips{[&] { return skip_mips ? zero : ctx.OpImageQueryLevels(ctx.U32[1], image); }}; + const bool is_msaa{IsTextureMsaa(ctx, info)}; + const bool uses_lod{!is_msaa && info.type != TextureType::Buffer}; + const auto query{[&](Id type) { + return uses_lod ? ctx.OpImageQuerySizeLod(type, image, lod) + : ctx.OpImageQuerySize(type, image); + }}; switch (info.type) { case TextureType::Color1D: - return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[1], image, lod), - zero, zero, mips()); + return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[1]), zero, zero, mips()); case TextureType::ColorArray1D: case TextureType::Color2D: case TextureType::ColorCube: case TextureType::Color2DRect: - return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[2], image, lod), - zero, mips()); + return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[2]), zero, mips()); case TextureType::ColorArray2D: case TextureType::Color3D: case TextureType::ColorArrayCube: - return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[3], image, lod), - mips()); + return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[3]), mips()); case TextureType::Buffer: - return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySize(ctx.U32[1], image), zero, - zero, mips()); + return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[1]), zero, zero, mips()); } throw LogicError("Unspecified image type {}", info.type.Value()); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index e31cdc5e8..a440b557d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -179,7 +179,6 @@ Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value); void EmitBitCastU16F16(EmitContext& ctx); Id EmitBitCastU32F32(EmitContext& ctx, Id value); void EmitBitCastU64F64(EmitContext& ctx); -void EmitBitCastS32F32(EmitContext& ctx); void EmitBitCastF16U16(EmitContext&); Id EmitBitCastF32U32(EmitContext& ctx, Id value); void EmitBitCastF64U64(EmitContext& ctx); @@ -540,7 +539,8 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, const IR::Value& offset, const IR::Value& offset2, Id dref); Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id lod, Id ms); -Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod); +Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, + const IR::Value& skip_mips); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords); Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id derivates, Id offset, Id lod_clamp); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index a0c155fdb..d48d4860e 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -35,6 +35,7 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { const spv::ImageFormat format{spv::ImageFormat::Unknown}; const Id type{ctx.F32[1]}; const bool depth{desc.is_depth}; + const bool ms{desc.is_multisample}; switch (desc.type) { case TextureType::Color1D: return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format); @@ -42,9 +43,9 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format); case TextureType::Color2D: case TextureType::Color2DRect: - return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format); + return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format); case TextureType::ColorArray2D: - return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format); + return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format); case TextureType::Color3D: return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format); case TextureType::ColorCube: @@ -1287,6 +1288,7 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in .pointer_type = pointer_type, .image_type = image_type, .count = desc.count, + .is_multisample = desc.is_multisample, }); if (profile.supported_spirv >= 0x00010400) { interfaces.push_back(id); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index dbc5c55b9..768a4fbb5 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -35,6 +35,7 @@ struct TextureDefinition { Id pointer_type; Id image_type; u32 count; + bool is_multisample; }; struct TextureBufferDefinition { diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp index eb2e49a68..b7caa4246 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp +++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp @@ -704,11 +704,6 @@ IR::U32 IREmitter::BitCast<IR::U32, IR::F32>(const IR::F32& value) { } template <> -IR::S32 IREmitter::BitCast<IR::S32, IR::F32>(const IR::F32& value) { - return Inst<IR::S32>(Opcode::BitCastS32F32, value); -} - -template <> IR::F32 IREmitter::BitCast<IR::F32, IR::U32>(const IR::U32& value) { return Inst<IR::F32>(Opcode::BitCastF32U32, value); } @@ -1851,15 +1846,16 @@ Value IREmitter::ImageFetch(const Value& handle, const Value& coords, const Valu return Inst(op, Flags{info}, handle, coords, offset, lod, multisampling); } -Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod) { +Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod, + const IR::U1& skip_mips) { const Opcode op{handle.IsImmediate() ? Opcode::BoundImageQueryDimensions : Opcode::BindlessImageQueryDimensions}; - return Inst(op, handle, lod); + return Inst(op, handle, lod, skip_mips); } Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod, - TextureInstInfo info) { - return Inst(Opcode::ImageQueryDimensions, Flags{info}, handle, lod); + const IR::U1& skip_mips, TextureInstInfo info) { + return Inst(Opcode::ImageQueryDimensions, Flags{info}, handle, lod, skip_mips); } Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info) { diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h index 7aaaa4ab0..f3c81dbe1 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.h +++ b/src/shader_recompiler/frontend/ir/ir_emitter.h @@ -320,9 +320,10 @@ public: [[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& coords, const F32& dref, const F32& lod, const Value& offset, TextureInstInfo info); - [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod); [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, - TextureInstInfo info); + const IR::U1& skip_mips); + [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod, + const IR::U1& skip_mips, TextureInstInfo info); [[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info); @@ -408,7 +409,8 @@ private: } template <typename T> - requires(sizeof(T) <= sizeof(u32) && std::is_trivially_copyable_v<T>) struct Flags { + requires(sizeof(T) <= sizeof(u32) && std::is_trivially_copyable_v<T>) + struct Flags { Flags() = default; Flags(T proxy_) : proxy{proxy_} {} diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h index d155afd0f..e300714f3 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.h +++ b/src/shader_recompiler/frontend/ir/opcodes.h @@ -38,7 +38,6 @@ constexpr Type U8{Type::U8}; constexpr Type U16{Type::U16}; constexpr Type U32{Type::U32}; constexpr Type U64{Type::U64}; -constexpr Type S32{Type::S32}; constexpr Type F16{Type::F16}; constexpr Type F32{Type::F32}; constexpr Type F64{Type::F64}; diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc index 1fe3749cc..4447d67b0 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.inc +++ b/src/shader_recompiler/frontend/ir/opcodes.inc @@ -175,7 +175,6 @@ OPCODE(SelectF64, F64, U1, OPCODE(BitCastU16F16, U16, F16, ) OPCODE(BitCastU32F32, U32, F32, ) OPCODE(BitCastU64F64, U64, F64, ) -OPCODE(BitCastS32F32, S32, F32, ) OPCODE(BitCastF16U16, F16, U16, ) OPCODE(BitCastF32U32, F32, U32, ) OPCODE(BitCastF64U64, F64, U64, ) @@ -483,7 +482,7 @@ OPCODE(BindlessImageSampleDrefExplicitLod, F32, U32, OPCODE(BindlessImageGather, F32x4, U32, Opaque, Opaque, Opaque, ) OPCODE(BindlessImageGatherDref, F32x4, U32, Opaque, Opaque, Opaque, F32, ) OPCODE(BindlessImageFetch, F32x4, U32, Opaque, Opaque, U32, Opaque, ) -OPCODE(BindlessImageQueryDimensions, U32x4, U32, U32, ) +OPCODE(BindlessImageQueryDimensions, U32x4, U32, U32, U1, ) OPCODE(BindlessImageQueryLod, F32x4, U32, Opaque, ) OPCODE(BindlessImageGradient, F32x4, U32, Opaque, Opaque, Opaque, Opaque, ) OPCODE(BindlessImageRead, U32x4, U32, Opaque, ) @@ -496,7 +495,7 @@ OPCODE(BoundImageSampleDrefExplicitLod, F32, U32, OPCODE(BoundImageGather, F32x4, U32, Opaque, Opaque, Opaque, ) OPCODE(BoundImageGatherDref, F32x4, U32, Opaque, Opaque, Opaque, F32, ) OPCODE(BoundImageFetch, F32x4, U32, Opaque, Opaque, U32, Opaque, ) -OPCODE(BoundImageQueryDimensions, U32x4, U32, U32, ) +OPCODE(BoundImageQueryDimensions, U32x4, U32, U32, U1, ) OPCODE(BoundImageQueryLod, F32x4, U32, Opaque, ) OPCODE(BoundImageGradient, F32x4, U32, Opaque, Opaque, Opaque, Opaque, ) OPCODE(BoundImageRead, U32x4, U32, Opaque, ) @@ -509,7 +508,7 @@ OPCODE(ImageSampleDrefExplicitLod, F32, Opaq OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, Opaque, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) -OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, ) +OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, ) OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, ) OPCODE(ImageRead, U32x4, Opaque, Opaque, ) diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h index 5a7c706ad..04c8c4ddb 100644 --- a/src/shader_recompiler/frontend/ir/type.h +++ b/src/shader_recompiler/frontend/ir/type.h @@ -24,22 +24,21 @@ enum class Type { U16 = 1 << 7, U32 = 1 << 8, U64 = 1 << 9, - S32 = 1 << 10, - F16 = 1 << 11, - F32 = 1 << 12, - F64 = 1 << 13, - U32x2 = 1 << 14, - U32x3 = 1 << 15, - U32x4 = 1 << 16, - F16x2 = 1 << 17, - F16x3 = 1 << 18, - F16x4 = 1 << 19, - F32x2 = 1 << 20, - F32x3 = 1 << 21, - F32x4 = 1 << 22, - F64x2 = 1 << 23, - F64x3 = 1 << 24, - F64x4 = 1 << 25, + F16 = 1 << 10, + F32 = 1 << 11, + F64 = 1 << 12, + U32x2 = 1 << 13, + U32x3 = 1 << 14, + U32x4 = 1 << 15, + F16x2 = 1 << 16, + F16x3 = 1 << 17, + F16x4 = 1 << 18, + F32x2 = 1 << 19, + F32x3 = 1 << 20, + F32x4 = 1 << 21, + F64x2 = 1 << 22, + F64x3 = 1 << 23, + F64x4 = 1 << 24, }; DECLARE_ENUM_FLAG_OPERATORS(Type) diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp index 30ba12316..346169328 100644 --- a/src/shader_recompiler/frontend/ir/value.cpp +++ b/src/shader_recompiler/frontend/ir/value.cpp @@ -23,8 +23,6 @@ Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {} Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {} -Value::Value(s32 value) noexcept : type{Type::S32}, imm_s32{value} {} - Value::Value(f32 value) noexcept : type{Type::F32}, imm_f32{value} {} Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {} @@ -71,7 +69,6 @@ bool Value::operator==(const Value& other) const { return imm_u16 == other.imm_u16; case Type::U32: case Type::F32: - case Type::S32: return imm_u32 == other.imm_u32; case Type::U64: case Type::F64: diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h index 8b34356fd..c27546b0e 100644 --- a/src/shader_recompiler/frontend/ir/value.h +++ b/src/shader_recompiler/frontend/ir/value.h @@ -43,7 +43,6 @@ public: explicit Value(u8 value) noexcept; explicit Value(u16 value) noexcept; explicit Value(u32 value) noexcept; - explicit Value(s32 value) noexcept; explicit Value(f32 value) noexcept; explicit Value(u64 value) noexcept; explicit Value(f64 value) noexcept; @@ -66,7 +65,6 @@ public: [[nodiscard]] u8 U8() const; [[nodiscard]] u16 U16() const; [[nodiscard]] u32 U32() const; - [[nodiscard]] s32 S32() const; [[nodiscard]] f32 F32() const; [[nodiscard]] u64 U64() const; [[nodiscard]] f64 F64() const; @@ -86,7 +84,6 @@ private: u8 imm_u8; u16 imm_u16; u32 imm_u32; - s32 imm_s32; f32 imm_f32; u64 imm_u64; f64 imm_f64; @@ -101,9 +98,8 @@ public: TypedValue() = default; template <IR::Type other_type> - requires((other_type & type_) != IR::Type::Void) explicit(false) - TypedValue(const TypedValue<other_type>& value) - : Value(value) {} + requires((other_type & type_) != IR::Type::Void) + explicit(false) TypedValue(const TypedValue<other_type>& value) : Value(value) {} explicit TypedValue(const Value& value) : Value(value) { if ((value.Type() & type_) == IR::Type::Void) { @@ -194,16 +190,16 @@ public: void ReplaceOpcode(IR::Opcode opcode); template <typename FlagsType> - requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) - [[nodiscard]] FlagsType Flags() const noexcept { + requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) + [[nodiscard]] FlagsType Flags() const noexcept { FlagsType ret; std::memcpy(reinterpret_cast<char*>(&ret), &flags, sizeof(ret)); return ret; } template <typename FlagsType> - requires(sizeof(FlagsType) <= sizeof(u32) && - std::is_trivially_copyable_v<FlagsType>) void SetFlags(FlagsType value) noexcept { + requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) + void SetFlags(FlagsType value) noexcept { std::memcpy(&flags, &value, sizeof(value)); } @@ -268,7 +264,6 @@ using U8 = TypedValue<Type::U8>; using U16 = TypedValue<Type::U16>; using U32 = TypedValue<Type::U32>; using U64 = TypedValue<Type::U64>; -using S32 = TypedValue<Type::S32>; using F16 = TypedValue<Type::F16>; using F32 = TypedValue<Type::F32>; using F64 = TypedValue<Type::F64>; @@ -380,14 +375,6 @@ inline u32 Value::U32() const { return imm_u32; } -inline s32 Value::S32() const { - if (IsIdentity()) { - return inst->Arg(0).S32(); - } - DEBUG_ASSERT(type == Type::S32); - return imm_s32; -} - inline f32 Value::F32() const { if (IsIdentity()) { return inst->Arg(0).F32(); diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp index f8cfd4ab6..39af62559 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_query.cpp @@ -15,11 +15,13 @@ enum class Mode : u64 { SamplePos = 5, }; -IR::Value Query(TranslatorVisitor& v, const IR::U32& handle, Mode mode, IR::Reg src_reg) { +IR::Value Query(TranslatorVisitor& v, const IR::U32& handle, Mode mode, IR::Reg src_reg, u64 mask) { switch (mode) { case Mode::Dimension: { + const bool needs_num_mips{((mask >> 3) & 1) != 0}; + const IR::U1 skip_mips{v.ir.Imm1(!needs_num_mips)}; const IR::U32 lod{v.X(src_reg)}; - return v.ir.ImageQueryDimension(handle, lod); + return v.ir.ImageQueryDimension(handle, lod, skip_mips); } case Mode::TextureType: case Mode::SamplePos: @@ -46,7 +48,7 @@ void Impl(TranslatorVisitor& v, u64 insn, std::optional<u32> cbuf_offset) { handle = v.X(src_reg); ++src_reg; } - const IR::Value query{Query(v, handle, txq.mode, src_reg)}; + const IR::Value query{Query(v, handle, txq.mode, src_reg, txq.mask)}; IR::Reg dest_reg{txq.dest_reg}; for (int element = 0; element < 4; ++element) { if (((txq.mask >> element) & 1) == 0) { diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index f5c86fcb1..d374c976a 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -355,21 +355,21 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { }; } -TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { +u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) { const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index}; const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset}; const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left}; const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset) << cbuf.secondary_shift_left}; - return env.ReadTextureType(lhs_raw | rhs_raw); + return lhs_raw | rhs_raw; +} + +TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { + return env.ReadTextureType(GetTextureHandle(env, cbuf)); } TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { - const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index}; - const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset}; - const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)}; - const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)}; - return env.ReadTexturePixelFormat(lhs_raw | rhs_raw); + return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf)); } class Descriptors { @@ -386,8 +386,10 @@ public: return Add(texture_buffer_descriptors, desc, [&desc](const auto& existing) { return desc.cbuf_index == existing.cbuf_index && desc.cbuf_offset == existing.cbuf_offset && + desc.shift_left == existing.shift_left && desc.secondary_cbuf_index == existing.secondary_cbuf_index && desc.secondary_cbuf_offset == existing.secondary_cbuf_offset && + desc.secondary_shift_left == existing.secondary_shift_left && desc.count == existing.count && desc.size_shift == existing.size_shift && desc.has_secondary == existing.has_secondary; }); @@ -405,15 +407,20 @@ public: } u32 Add(const TextureDescriptor& desc) { - return Add(texture_descriptors, desc, [&desc](const auto& existing) { + const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) { return desc.type == existing.type && desc.is_depth == existing.is_depth && desc.has_secondary == existing.has_secondary && desc.cbuf_index == existing.cbuf_index && desc.cbuf_offset == existing.cbuf_offset && + desc.shift_left == existing.shift_left && desc.secondary_cbuf_index == existing.secondary_cbuf_index && desc.secondary_cbuf_offset == existing.secondary_cbuf_offset && + desc.secondary_shift_left == existing.secondary_shift_left && desc.count == existing.count && desc.size_shift == existing.size_shift; - }); + })}; + // TODO: Read this from TIC + texture_descriptors[index].is_multisample |= desc.is_multisample; + return index; } u32 Add(const ImageDescriptor& desc) { @@ -452,7 +459,8 @@ void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) { const IR::Value coord(inst.Arg(1)); const IR::Value handle(ir.Imm32(0)); const IR::U32 lod{ir.Imm32(0)}; - const IR::Value texture_size = ir.ImageQueryDimension(handle, lod, info); + const IR::U1 skip_mips{ir.Imm1(true)}; + const IR::Value texture_size = ir.ImageQueryDimension(handle, lod, skip_mips, info); inst.SetArg( 1, ir.CompositeConstruct( ir.FPMul(IR::F32(ir.CompositeExtract(coord, 0)), @@ -486,10 +494,10 @@ void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_ const IR::F32 w(ir.CompositeExtract(new_inst, 3)); const IR::F16F32F64 max_value(ir.Imm32(get_max_value())); const IR::Value converted = - ir.CompositeConstruct(ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(x)), max_value), - ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(y)), max_value), - ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(z)), max_value), - ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(w)), max_value)); + ir.CompositeConstruct(ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::U32>(x)), max_value), + ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::U32>(y)), max_value), + ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::U32>(z)), max_value), + ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::U32>(w)), max_value)); inst.ReplaceUsesWith(converted); } } // Anonymous namespace @@ -524,6 +532,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo const auto& cbuf{texture_inst.cbuf}; auto flags{inst->Flags<IR::TextureInstInfo>()}; + bool is_multisample{false}; switch (inst->GetOpcode()) { case IR::Opcode::ImageQueryDimensions: flags.type.Assign(ReadTextureType(env, cbuf)); @@ -538,6 +547,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } break; case IR::Opcode::ImageFetch: + if (flags.type == TextureType::Color2D || flags.type == TextureType::Color2DRect || + flags.type == TextureType::ColorArray2D) { + is_multisample = !inst->Arg(4).IsEmpty(); + } else { + inst->SetArg(4, IR::U32{}); + } if (flags.type != TextureType::Color1D) { break; } @@ -613,6 +628,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, + .is_multisample = is_multisample, .has_secondary = cbuf.has_secondary, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, diff --git a/src/shader_recompiler/object_pool.h b/src/shader_recompiler/object_pool.h index 2b42c4ba2..5d648b159 100644 --- a/src/shader_recompiler/object_pool.h +++ b/src/shader_recompiler/object_pool.h @@ -10,7 +10,7 @@ namespace Shader { template <typename T> -requires std::is_destructible_v<T> + requires std::is_destructible_v<T> class ObjectPool { public: explicit ObjectPool(size_t chunk_size = 8192) : new_chunk_size{chunk_size} { @@ -18,7 +18,7 @@ public: } template <typename... Args> - requires std::is_constructible_v<T, Args...> + requires std::is_constructible_v<T, Args...> [[nodiscard]] T* Create(Args&&... args) { return std::construct_at(Memory(), std::forward<Args>(args)...); } diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index f93181e1e..d308db942 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -109,6 +109,7 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip struct TextureDescriptor { TextureType type; bool is_depth; + bool is_multisample; bool has_secondary; u32 cbuf_index; u32 cbuf_offset; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 9b65e79cb..ae84408bc 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -14,7 +14,6 @@ add_executable(tests core/core_timing.cpp core/internal_network/network.cpp precompiled_headers.h - tests.cpp video_core/buffer_base.cpp input_common/calibration_configuration_job.cpp ) @@ -22,7 +21,7 @@ add_executable(tests create_target_directory_groups(tests) target_link_libraries(tests PRIVATE common core input_common) -target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads) +target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2WithMain Threads::Threads) add_test(NAME tests COMMAND tests) diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp index 0071ae52e..75e990ecd 100644 --- a/src/tests/common/bit_field.cpp +++ b/src/tests/common/bit_field.cpp @@ -4,7 +4,7 @@ #include <array> #include <cstring> #include <type_traits> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/bit_field.h" TEST_CASE("BitField", "[common]") { diff --git a/src/tests/common/cityhash.cpp b/src/tests/common/cityhash.cpp index 05942eadb..2a391dff1 100644 --- a/src/tests/common/cityhash.cpp +++ b/src/tests/common/cityhash.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/cityhash.h" diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp index 4e29f9199..ecad7583f 100644 --- a/src/tests/common/fibers.cpp +++ b/src/tests/common/fibers.cpp @@ -11,7 +11,7 @@ #include <unordered_map> #include <vector> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/common_types.h" #include "common/fiber.h" diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp index e49d0a09f..1b014b632 100644 --- a/src/tests/common/host_memory.cpp +++ b/src/tests/common/host_memory.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/host_memory.h" #include "common/literals.h" diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp index d036cc83a..41575def4 100644 --- a/src/tests/common/param_package.cpp +++ b/src/tests/common/param_package.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include <math.h> #include "common/logging/backend.h" #include "common/param_package.h" diff --git a/src/tests/common/range_map.cpp b/src/tests/common/range_map.cpp index 5a4630a38..d301ac5f6 100644 --- a/src/tests/common/range_map.cpp +++ b/src/tests/common/range_map.cpp @@ -3,7 +3,7 @@ #include <stdexcept> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/range_map.h" diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp index 4f81b6e5e..7dee988c8 100644 --- a/src/tests/common/ring_buffer.cpp +++ b/src/tests/common/ring_buffer.cpp @@ -7,7 +7,7 @@ #include <numeric> #include <thread> #include <vector> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/ring_buffer.h" namespace Common { diff --git a/src/tests/common/scratch_buffer.cpp b/src/tests/common/scratch_buffer.cpp index f6e50da4a..132f139fa 100644 --- a/src/tests/common/scratch_buffer.cpp +++ b/src/tests/common/scratch_buffer.cpp @@ -5,7 +5,7 @@ #include <array> #include <cstring> #include <span> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/common_types.h" #include "common/scratch_buffer.h" diff --git a/src/tests/common/unique_function.cpp b/src/tests/common/unique_function.cpp index 311272506..f7a23e876 100644 --- a/src/tests/common/unique_function.cpp +++ b/src/tests/common/unique_function.cpp @@ -3,7 +3,7 @@ #include <string> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/unique_function.h" diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index 284b2ae66..f08afbf9a 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include <array> #include <bitset> diff --git a/src/tests/core/internal_network/network.cpp b/src/tests/core/internal_network/network.cpp index 164b0ff24..10ddd8b42 100644 --- a/src/tests/core/internal_network/network.cpp +++ b/src/tests/core/internal_network/network.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "core/internal_network/network.h" #include "core/internal_network/sockets.h" diff --git a/src/tests/input_common/calibration_configuration_job.cpp b/src/tests/input_common/calibration_configuration_job.cpp index e5f698886..516ff1b30 100644 --- a/src/tests/input_common/calibration_configuration_job.cpp +++ b/src/tests/input_common/calibration_configuration_job.cpp @@ -6,7 +6,7 @@ #include <thread> #include <boost/asio.hpp> #include <boost/crc.hpp> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "input_common/drivers/udp_client.h" #include "input_common/helpers/udp_protocol.h" diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp deleted file mode 100644 index 3f905c05c..000000000 --- a/src/tests/tests.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#define CATCH_CONFIG_MAIN -#include <catch2/catch.hpp> - -// Catch provides the main function since we've given it the -// CATCH_CONFIG_MAIN preprocessor directive. diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp index 5cd0628f2..1275cca24 100644 --- a/src/tests/video_core/buffer_base.cpp +++ b/src/tests/video_core/buffer_base.cpp @@ -4,7 +4,7 @@ #include <stdexcept> #include <unordered_map> -#include <catch2/catch.hpp> +#include <catch2/catch_test_macros.hpp> #include "common/alignment.h" #include "common/common_types.h" diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index f617665de..4742bcbe9 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -52,6 +52,8 @@ add_library(video_core STATIC engines/puller.cpp engines/puller.h framebuffer_config.h + fsr.cpp + fsr.h host1x/codecs/codec.cpp host1x/codecs/codec.h host1x/codecs/h264.cpp @@ -100,6 +102,8 @@ add_library(video_core STATIC renderer_null/null_rasterizer.h renderer_null/renderer_null.cpp renderer_null/renderer_null.h + renderer_opengl/blit_image.cpp + renderer_opengl/blit_image.h renderer_opengl/gl_buffer_cache.cpp renderer_opengl/gl_buffer_cache.h renderer_opengl/gl_compute_pipeline.cpp @@ -108,6 +112,8 @@ add_library(video_core STATIC renderer_opengl/gl_device.h renderer_opengl/gl_fence_manager.cpp renderer_opengl/gl_fence_manager.h + renderer_opengl/gl_fsr.cpp + renderer_opengl/gl_fsr.h renderer_opengl/gl_graphics_pipeline.cpp renderer_opengl/gl_graphics_pipeline.h renderer_opengl/gl_rasterizer.cpp diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 2437121ce..1d22d25f1 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp @@ -51,6 +51,10 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) { LOG_WARNING(HW_GPU, "(STUBBED) called"); break; } + case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { + DrawTexture(); + break; + } default: break; } @@ -179,6 +183,33 @@ void DrawManager::DrawIndexSmall(u32 argument) { ProcessDraw(true, 1); } +void DrawManager::DrawTexture() { + const auto& regs{maxwell3d->regs}; + draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f; + draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f; + const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f; + const auto dst_height = static_cast<float>(regs.draw_texture.dst_height) / 4096.f; + const bool lower_left{regs.window_origin.mode != + Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft}; + if (lower_left) { + draw_texture_state.dst_y0 -= dst_height; + } + draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width; + draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height; + draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f; + draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f; + draw_texture_state.src_x1 = + (static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width + + draw_texture_state.src_x0; + draw_texture_state.src_y1 = + (static_cast<float>(regs.draw_texture.dy_dv) / 4294967296.f) * dst_height + + draw_texture_state.src_y0; + draw_texture_state.src_sampler = regs.draw_texture.src_sampler; + draw_texture_state.src_texture = regs.draw_texture.src_texture; + + maxwell3d->rasterizer->DrawTexture(); +} + void DrawManager::UpdateTopology() { const auto& regs{maxwell3d->regs}; switch (regs.primitive_topology_control) { diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h index 58d1b2d59..7c22c49f1 100644 --- a/src/video_core/engines/draw_manager.h +++ b/src/video_core/engines/draw_manager.h @@ -32,6 +32,19 @@ public: std::vector<u8> inline_index_draw_indexes; }; + struct DrawTextureState { + f32 dst_x0; + f32 dst_y0; + f32 dst_x1; + f32 dst_y1; + f32 src_x0; + f32 src_y0; + f32 src_x1; + f32 src_y1; + u32 src_sampler; + u32 src_texture; + }; + struct IndirectParams { bool is_indexed; bool include_count; @@ -64,6 +77,10 @@ public: return draw_state; } + const DrawTextureState& GetDrawTextureState() const { + return draw_texture_state; + } + IndirectParams& GetIndirectParams() { return indirect_state; } @@ -81,6 +98,8 @@ private: void DrawIndexSmall(u32 argument); + void DrawTexture(); + void UpdateTopology(); void ProcessDraw(bool draw_indexed, u32 instance_count); @@ -89,6 +108,7 @@ private: Maxwell3D* maxwell3d{}; State draw_state{}; + DrawTextureState draw_texture_state{}; IndirectParams indirect_state{}; }; } // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 68ceda519..ae9da6290 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -149,6 +149,7 @@ bool Maxwell3D::IsMethodExecutable(u32 method) { case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): case MAXWELL3D_REG_INDEX(vertex_array_instance_first): case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): + case MAXWELL3D_REG_INDEX(draw_texture.src_y0): case MAXWELL3D_REG_INDEX(wait_for_idle): case MAXWELL3D_REG_INDEX(shadow_ram_control): case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr): diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 0b2fd2928..c89969bb4 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1599,6 +1599,20 @@ public: }; static_assert(sizeof(TIRModulationCoeff) == 0x4); + struct DrawTexture { + s32 dst_x0; + s32 dst_y0; + s32 dst_width; + s32 dst_height; + s64 dx_du; + s64 dy_dv; + u32 src_sampler; + u32 src_texture; + s32 src_x0; + s32 src_y0; + }; + static_assert(sizeof(DrawTexture) == 0x30); + struct ReduceColorThreshold { union { BitField<0, 8, u32> all_hit_once; @@ -2751,7 +2765,7 @@ public: u32 reserved_sw_method2; ///< 0x102C std::array<TIRModulationCoeff, 5> tir_modulation_coeff; ///< 0x1030 std::array<u32, 15> spare_nop; ///< 0x1044 - INSERT_PADDING_BYTES_NOINIT(0x30); + DrawTexture draw_texture; ///< 0x1080 std::array<u32, 7> reserved_sw_method3_to_7; ///< 0x10B0 ReduceColorThreshold reduce_color_thresholds_unorm8; ///< 0x10CC std::array<u32, 4> reserved_sw_method10_to_13; ///< 0x10D0 diff --git a/src/video_core/fsr.cpp b/src/video_core/fsr.cpp new file mode 100644 index 000000000..5653c64fc --- /dev/null +++ b/src/video_core/fsr.cpp @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cmath> +#include "video_core/fsr.h" + +namespace FSR { +namespace { +// Reimplementations of the constant generating functions in ffx_fsr1.h +// GCC generated a lot of warnings when using the official header. +u32 AU1_AH1_AF1(f32 f) { + static constexpr u32 base[512]{ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, + 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, 0x2000, + 0x2400, 0x2800, 0x2c00, 0x3000, 0x3400, 0x3800, 0x3c00, 0x4000, 0x4400, 0x4800, 0x4c00, + 0x5000, 0x5400, 0x5800, 0x5c00, 0x6000, 0x6400, 0x6800, 0x6c00, 0x7000, 0x7400, 0x7800, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, + 0x7bff, 0x7bff, 0x7bff, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, + 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, 0x8200, 0x8400, 0x8800, 0x8c00, 0x9000, 0x9400, + 0x9800, 0x9c00, 0xa000, 0xa400, 0xa800, 0xac00, 0xb000, 0xb400, 0xb800, 0xbc00, 0xc000, + 0xc400, 0xc800, 0xcc00, 0xd000, 0xd400, 0xd800, 0xdc00, 0xe000, 0xe400, 0xe800, 0xec00, + 0xf000, 0xf400, 0xf800, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, + }; + static constexpr s8 shift[512]{ + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, 0x16, + 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, + 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, + }; + const u32 u = Common::BitCast<u32>(f); + const u32 i = u >> 23; + return base[i] + ((u & 0x7fffff) >> shift[i]); +} + +u32 AU1_AH2_AF2(f32 a[2]) { + return AU1_AH1_AF1(a[0]) + (AU1_AH1_AF1(a[1]) << 16); +} + +void FsrEasuCon(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], f32 inputViewportInPixelsX, + f32 inputViewportInPixelsY, f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, + f32 outputSizeInPixelsX, f32 outputSizeInPixelsY) { + con0[0] = Common::BitCast<u32>(inputViewportInPixelsX / outputSizeInPixelsX); + con0[1] = Common::BitCast<u32>(inputViewportInPixelsY / outputSizeInPixelsY); + con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f); + con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f); + con1[0] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); + con1[1] = Common::BitCast<u32>(1.0f / inputSizeInPixelsY); + con1[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); + con1[3] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsY); + con2[0] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsX); + con2[1] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); + con2[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); + con2[3] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); + con3[0] = Common::BitCast<u32>(0.0f / inputSizeInPixelsX); + con3[1] = Common::BitCast<u32>(4.0f / inputSizeInPixelsY); + con3[2] = con3[3] = 0; +} +} // Anonymous namespace + +void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], + f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, + f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, + f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, + inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f + + inputOffsetInPixelsX); + con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f + + inputOffsetInPixelsY); +} + +void FsrRcasCon(u32* con, f32 sharpness) { + sharpness = std::exp2f(-sharpness); + f32 hSharp[2]{sharpness, sharpness}; + con[0] = Common::BitCast<u32>(sharpness); + con[1] = AU1_AH2_AF2(hSharp); + con[2] = 0; + con[3] = 0; +} +} // namespace FSR diff --git a/src/video_core/fsr.h b/src/video_core/fsr.h new file mode 100644 index 000000000..db0d4ec6f --- /dev/null +++ b/src/video_core/fsr.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/bit_cast.h" +#include "common/common_types.h" + +namespace FSR { +// Reimplementations of the constant generating functions in ffx_fsr1.h +// GCC generated a lot of warnings when using the official header. +void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], + f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, + f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, + f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY); + +void FsrRcasCon(u32* con, f32 sharpness); + +} // namespace FSR diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index f275b2aa9..52cd5bb81 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -3,14 +3,19 @@ set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) -set(GLSL_INCLUDES - fidelityfx_fsr.comp +set(FIDELITYFX_FILES ${FIDELITYFX_INCLUDE_DIR}/ffx_a.h ${FIDELITYFX_INCLUDE_DIR}/ffx_fsr1.h ) +set(GLSL_INCLUDES + fidelityfx_fsr.comp + ${FIDELITYFX_FILES} +) + set(SHADER_FILES astc_decoder.comp + blit_color_float.frag block_linear_unswizzle_2d.comp block_linear_unswizzle_3d.comp convert_abgr8_to_d24s8.frag @@ -23,6 +28,9 @@ set(SHADER_FILES fxaa.vert opengl_convert_s8d24.comp opengl_copy_bc4.comp + opengl_fidelityfx_fsr.frag + opengl_fidelityfx_fsr_easu.frag + opengl_fidelityfx_fsr_rcas.frag opengl_present.frag opengl_present.vert opengl_present_scaleforce.frag @@ -36,8 +44,9 @@ set(SHADER_FILES smaa_blending_weight_calculation.frag smaa_neighborhood_blending.vert smaa_neighborhood_blending.frag - vulkan_blit_color_float.frag vulkan_blit_depth_stencil.frag + vulkan_color_clear.frag + vulkan_color_clear.vert vulkan_fidelityfx_fsr_easu_fp16.comp vulkan_fidelityfx_fsr_easu_fp32.comp vulkan_fidelityfx_fsr_rcas_fp16.comp @@ -118,6 +127,25 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES}) endif() endforeach() +foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES}) + get_filename_component(FILENAME ${FILEPATH} NAME) + string(REPLACE "." "_" HEADER_NAME ${FILENAME}) + set(SOURCE_FILE ${FILEPATH}) + set(SOURCE_HEADER_FILE ${SHADER_DIR}/${HEADER_NAME}.h) + add_custom_command( + OUTPUT + ${SOURCE_HEADER_FILE} + COMMAND + ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE} + MAIN_DEPENDENCY + ${SOURCE_FILE} + DEPENDS + ${INPUT_FILE} + # HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified + ) + set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE}) +endforeach() + set(SHADER_SOURCES ${SHADER_FILES}) list(APPEND SHADER_SOURCES ${GLSL_INCLUDES}) diff --git a/src/video_core/host_shaders/vulkan_blit_color_float.frag b/src/video_core/host_shaders/blit_color_float.frag index c0c832296..c0c832296 100644 --- a/src/video_core/host_shaders/vulkan_blit_color_float.frag +++ b/src/video_core/host_shaders/blit_color_float.frag diff --git a/src/video_core/host_shaders/full_screen_triangle.vert b/src/video_core/host_shaders/full_screen_triangle.vert index 2c976b19f..d16d98995 100644 --- a/src/video_core/host_shaders/full_screen_triangle.vert +++ b/src/video_core/host_shaders/full_screen_triangle.vert @@ -4,13 +4,20 @@ #version 450 #ifdef VULKAN +#define VERTEX_ID gl_VertexIndex #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) +#define FLIPY 1 #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv +#define VERTEX_ID gl_VertexID #define BEGIN_PUSH_CONSTANTS #define END_PUSH_CONSTANTS +#define FLIPY -1 #define UNIFORM(n) layout (location = n) uniform +out gl_PerVertex { + vec4 gl_Position; +}; #endif BEGIN_PUSH_CONSTANTS @@ -21,8 +28,8 @@ END_PUSH_CONSTANTS layout(location = 0) out vec2 texcoord; void main() { - float x = float((gl_VertexIndex & 1) << 2); - float y = float((gl_VertexIndex & 2) << 1); - gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0); + float x = float((VERTEX_ID & 1) << 2); + float y = float((VERTEX_ID & 2) << 1); + gl_Position = vec4(x - 1.0, FLIPY * (y - 1.0), 0.0, 1.0); texcoord = fma(vec2(x, y) / 2.0, tex_scale, tex_offset); } diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag new file mode 100644 index 000000000..16d22f58e --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +//!#version 460 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +#extension GL_AMD_gpu_shader_half_float : enable +#extension GL_NV_gpu_shader5 : enable + +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +layout (location = 0) uniform uvec4 constants[4]; + +#define A_GPU 1 +#define A_GLSL 1 + +#ifdef YUZU_USE_FP16 + #define A_HALF +#endif +#include "ffx_a.h" + +#ifndef YUZU_USE_FP16 + layout (binding=0) uniform sampler2D InputTexture; + #if USE_EASU + #define FSR_EASU_F 1 + AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(InputTexture, p, 0); return res; } + AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(InputTexture, p, 1); return res; } + AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(InputTexture, p, 2); return res; } + #endif + #if USE_RCAS + #define FSR_RCAS_F + AF4 FsrRcasLoadF(ASU2 p) { return texelFetch(InputTexture, ASU2(p), 0); } + void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} + #endif +#else + layout (binding=0) uniform sampler2D InputTexture; + #if USE_EASU + #define FSR_EASU_H 1 + AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 0)); return res; } + AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 1)); return res; } + AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 2)); return res; } + #endif + #if USE_RCAS + #define FSR_RCAS_H + AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(InputTexture, ASU2(p), 0)); } + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b){} + #endif +#endif + +#include "ffx_fsr1.h" + +#if USE_RCAS + layout(location = 0) in vec2 frag_texcoord; +#endif +layout (location = 0) out vec4 frag_color; + +void CurrFilter(AU2 pos) +{ +#if USE_EASU + #ifndef YUZU_USE_FP16 + AF3 c; + FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]); + frag_color = AF4(c, 1.0); + #else + AH3 c; + FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]); + frag_color = AH4(c, 1.0); + #endif +#endif +#if USE_RCAS + #ifndef YUZU_USE_FP16 + AF3 c; + FsrRcasF(c.r, c.g, c.b, pos, constants[0]); + frag_color = AF4(c, 1.0); + #else + AH3 c; + FsrRcasH(c.r, c.g, c.b, pos, constants[0]); + frag_color = AH4(c, 1.0); + #endif +#endif +} + +void main() +{ +#if USE_RCAS + CurrFilter(AU2(frag_texcoord * vec2(textureSize(InputTexture, 0)))); +#else + CurrFilter(AU2(gl_FragCoord.xy)); +#endif +} diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag new file mode 100644 index 000000000..d39f80ac1 --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 460 core +#extension GL_GOOGLE_include_directive : enable + +#define USE_EASU 1 + +#include "opengl_fidelityfx_fsr.frag" diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag new file mode 100644 index 000000000..cfa78ddc7 --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 460 core +#extension GL_GOOGLE_include_directive : enable + +#define USE_RCAS 1 + +#include "opengl_fidelityfx_fsr.frag" diff --git a/src/video_core/host_shaders/vulkan_color_clear.frag b/src/video_core/host_shaders/vulkan_color_clear.frag new file mode 100644 index 000000000..617bf01e1 --- /dev/null +++ b/src/video_core/host_shaders/vulkan_color_clear.frag @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 460 core + +layout (push_constant) uniform PushConstants { + vec4 clear_color; +}; + +layout(location = 0) out vec4 color; + +void main() { + color = clear_color; +} diff --git a/src/video_core/host_shaders/vulkan_color_clear.vert b/src/video_core/host_shaders/vulkan_color_clear.vert new file mode 100644 index 000000000..d85883141 --- /dev/null +++ b/src/video_core/host_shaders/vulkan_color_clear.vert @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 460 core + +void main() { + float x = float((gl_VertexIndex & 1) << 2); + float y = float((gl_VertexIndex & 2) << 1); + gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0); +} diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 3bcae3503..83924475b 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -6,7 +6,6 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" -#include "common/settings.h" #include "core/core.h" #include "core/device_memory.h" #include "core/hle/kernel/k_page_table.h" @@ -46,11 +45,6 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64 big_page_table_cpu.resize(big_page_table_size); big_page_continous.resize(big_page_table_size / continous_bits, 0); entries.resize(page_table_size / 32, 0); - if (!Settings::IsGPULevelExtreme() && Settings::IsFastmemEnabled()) { - fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer(); - } else { - fastmem_arena = nullptr; - } } MemoryManager::~MemoryManager() = default; @@ -360,7 +354,7 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si } } -template <bool is_safe, bool use_fastmem> +template <bool is_safe> void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, [[maybe_unused]] VideoCommon::CacheType which) const { auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, @@ -374,12 +368,8 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: if constexpr (is_safe) { rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); } - if constexpr (use_fastmem) { - std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount); - } else { - u8* physical = memory.GetPointer(cpu_addr_base); - std::memcpy(dest_buffer, physical, copy_amount); - } + u8* physical = memory.GetPointer(cpu_addr_base); + std::memcpy(dest_buffer, physical, copy_amount); dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; }; auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { @@ -388,15 +378,11 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: if constexpr (is_safe) { rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); } - if constexpr (use_fastmem) { - std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount); + if (!IsBigPageContinous(page_index)) [[unlikely]] { + memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount); } else { - if (!IsBigPageContinous(page_index)) [[unlikely]] { - memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount); - } else { - u8* physical = memory.GetPointer(cpu_addr_base); - std::memcpy(dest_buffer, physical, copy_amount); - } + u8* physical = memory.GetPointer(cpu_addr_base); + std::memcpy(dest_buffer, physical, copy_amount); } dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; }; @@ -410,20 +396,12 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which) const { - if (fastmem_arena) [[likely]] { - ReadBlockImpl<true, true>(gpu_src_addr, dest_buffer, size, which); - return; - } - ReadBlockImpl<true, false>(gpu_src_addr, dest_buffer, size, which); + ReadBlockImpl<true>(gpu_src_addr, dest_buffer, size, which); } void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, const std::size_t size) const { - if (fastmem_arena) [[likely]] { - ReadBlockImpl<false, true>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); - return; - } - ReadBlockImpl<false, false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); + ReadBlockImpl<false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); } template <bool is_safe> diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 2936364f0..9ebfb6179 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -141,7 +141,7 @@ private: inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; - template <bool is_safe, bool use_fastmem> + template <bool is_safe> void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which) const; @@ -215,7 +215,6 @@ private: std::vector<u64> big_page_continous; std::vector<std::pair<VAddr, std::size_t>> page_stash{}; - u8* fastmem_arena{}; constexpr static size_t continous_bits = 64; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 1735b6164..33e2610bc 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -47,6 +47,9 @@ public: /// Dispatches an indirect draw invocation virtual void DrawIndirect() {} + /// Dispatches an draw texture invocation + virtual void DrawTexture() = 0; + /// Clear the current framebuffer virtual void Clear(u32 layer_count) = 0; diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp index 2c11345d7..2b5c7defa 100644 --- a/src/video_core/renderer_null/null_rasterizer.cpp +++ b/src/video_core/renderer_null/null_rasterizer.cpp @@ -21,6 +21,7 @@ RasterizerNull::RasterizerNull(Core::Memory::Memory& cpu_memory_, Tegra::GPU& gp RasterizerNull::~RasterizerNull() = default; void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {} +void RasterizerNull::DrawTexture() {} void RasterizerNull::Clear(u32 layer_count) {} void RasterizerNull::DispatchCompute() {} void RasterizerNull::ResetCounter(VideoCore::QueryType type) {} diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h index 2112aa70e..51f896e43 100644 --- a/src/video_core/renderer_null/null_rasterizer.h +++ b/src/video_core/renderer_null/null_rasterizer.h @@ -31,6 +31,7 @@ public: ~RasterizerNull() override; void Draw(bool is_indexed, u32 instance_count) override; + void DrawTexture() override; void Clear(u32 layer_count) override; void DispatchCompute() override; void ResetCounter(VideoCore::QueryType type) override; diff --git a/src/video_core/renderer_opengl/blit_image.cpp b/src/video_core/renderer_opengl/blit_image.cpp new file mode 100644 index 000000000..9a560a73b --- /dev/null +++ b/src/video_core/renderer_opengl/blit_image.cpp @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> + +#include "video_core/host_shaders/blit_color_float_frag.h" +#include "video_core/host_shaders/full_screen_triangle_vert.h" +#include "video_core/renderer_opengl/blit_image.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" +#include "video_core/renderer_opengl/gl_shader_util.h" + +namespace OpenGL { + +BlitImageHelper::BlitImageHelper(ProgramManager& program_manager_) + : program_manager(program_manager_), + full_screen_vert(CreateProgram(HostShaders::FULL_SCREEN_TRIANGLE_VERT, GL_VERTEX_SHADER)), + blit_color_to_color_frag( + CreateProgram(HostShaders::BLIT_COLOR_FLOAT_FRAG, GL_FRAGMENT_SHADER)) {} + +BlitImageHelper::~BlitImageHelper() = default; + +void BlitImageHelper::BlitColor(GLuint dst_framebuffer, GLuint src_image_view, GLuint src_sampler, + const Region2D& dst_region, const Region2D& src_region, + const Extent3D& src_size) { + glEnable(GL_CULL_FACE); + glDisable(GL_COLOR_LOGIC_OP); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_RASTERIZER_DISCARD); + glDisable(GL_ALPHA_TEST); + glDisablei(GL_BLEND, 0); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glCullFace(GL_BACK); + glFrontFace(GL_CW); + glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthRangeIndexed(0, 0.0, 0.0); + + program_manager.BindPresentPrograms(full_screen_vert.handle, blit_color_to_color_frag.handle); + glProgramUniform2f(full_screen_vert.handle, 0, + static_cast<float>(src_region.end.x - src_region.start.x) / + static_cast<float>(src_size.width), + static_cast<float>(src_region.end.y - src_region.start.y) / + static_cast<float>(src_size.height)); + glProgramUniform2f(full_screen_vert.handle, 1, + static_cast<float>(src_region.start.x) / static_cast<float>(src_size.width), + static_cast<float>(src_region.start.y) / + static_cast<float>(src_size.height)); + glViewport(std::min(dst_region.start.x, dst_region.end.x), + std::min(dst_region.start.y, dst_region.end.y), + std::abs(dst_region.end.x - dst_region.start.x), + std::abs(dst_region.end.y - dst_region.start.y)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_framebuffer); + glBindSampler(0, src_sampler); + glBindTextureUnit(0, src_image_view); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 3); +} +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/blit_image.h b/src/video_core/renderer_opengl/blit_image.h new file mode 100644 index 000000000..5a2b12d16 --- /dev/null +++ b/src/video_core/renderer_opengl/blit_image.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <glad/glad.h> + +#include "video_core/engines/fermi_2d.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/texture_cache/types.h" + +namespace OpenGL { + +using VideoCommon::Extent3D; +using VideoCommon::Offset2D; +using VideoCommon::Region2D; + +class ProgramManager; +class Framebuffer; +class ImageView; + +class BlitImageHelper { +public: + explicit BlitImageHelper(ProgramManager& program_manager); + ~BlitImageHelper(); + + void BlitColor(GLuint dst_framebuffer, GLuint src_image_view, GLuint src_sampler, + const Region2D& dst_region, const Region2D& src_region, + const Extent3D& src_size); + +private: + ProgramManager& program_manager; + + OGLProgram full_screen_vert; + OGLProgram blit_color_to_color_frag; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 26d066004..1a0cea9b7 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp @@ -30,7 +30,7 @@ bool ComputePipelineKey::operator==(const ComputePipelineKey& rhs) const noexcep ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cache_, BufferCache& buffer_cache_, ProgramManager& program_manager_, const Shader::Info& info_, std::string code, - std::vector<u32> code_v) + std::vector<u32> code_v, bool force_context_flush) : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_}, info{info_} { switch (device.GetShaderBackend()) { @@ -63,6 +63,15 @@ ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cac writes_global_memory = !use_storage_buffers && std::ranges::any_of(info.storage_buffers_descriptors, [](const auto& desc) { return desc.is_written; }); + if (force_context_flush) { + std::scoped_lock lock{built_mutex}; + built_fence.Create(); + // Flush this context to ensure compilation commands and fence are in the GPU pipe. + glFlush(); + built_condvar.notify_one(); + } else { + is_built = true; + } } void ComputePipeline::Configure() { @@ -142,6 +151,9 @@ void ComputePipeline::Configure() { } texture_cache.FillComputeImageViews(std::span(views.data(), views.size())); + if (!is_built) { + WaitForBuild(); + } if (assembly_program.handle != 0) { program_manager.BindComputeAssemblyProgram(assembly_program.handle); } else { @@ -223,4 +235,13 @@ void ComputePipeline::Configure() { } } +void ComputePipeline::WaitForBuild() { + if (built_fence.handle == 0) { + std::unique_lock lock{built_mutex}; + built_condvar.wait(lock, [this] { return built_fence.handle != 0; }); + } + ASSERT(glClientWaitSync(built_fence.handle, 0, GL_TIMEOUT_IGNORED) != GL_WAIT_FAILED); + is_built = true; +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.h b/src/video_core/renderer_opengl/gl_compute_pipeline.h index 6534dec32..9bcc72b59 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.h +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.h @@ -50,7 +50,8 @@ class ComputePipeline { public: explicit ComputePipeline(const Device& device, TextureCache& texture_cache_, BufferCache& buffer_cache_, ProgramManager& program_manager_, - const Shader::Info& info_, std::string code, std::vector<u32> code_v); + const Shader::Info& info_, std::string code, std::vector<u32> code_v, + bool force_context_flush = false); void Configure(); @@ -65,6 +66,8 @@ public: } private: + void WaitForBuild(); + TextureCache& texture_cache; BufferCache& buffer_cache; Tegra::MemoryManager* gpu_memory; @@ -81,6 +84,11 @@ private: bool use_storage_buffers{}; bool writes_global_memory{}; + + std::mutex built_mutex; + std::condition_variable built_condvar; + OGLSync built_fence{}; + bool is_built{false}; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index cee5c3247..22ed16ebf 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -166,6 +166,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) { has_shader_int64 = HasExtension(extensions, "GL_ARB_gpu_shader_int64"); has_amd_shader_half_float = GLAD_GL_AMD_gpu_shader_half_float; has_sparse_texture_2 = GLAD_GL_ARB_sparse_texture2; + has_draw_texture = GLAD_GL_NV_draw_texture; warp_size_potentially_larger_than_guest = !is_nvidia && !is_intel; need_fastmath_off = is_nvidia; can_report_memory = GLAD_GL_NVX_gpu_memory_info; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index 2a72d84be..3ff8cad83 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -4,6 +4,8 @@ #pragma once #include <cstddef> +#include <string> + #include "common/common_types.h" #include "core/frontend/emu_window.h" #include "shader_recompiler/stage.h" @@ -146,6 +148,10 @@ public: return has_sparse_texture_2; } + bool HasDrawTexture() const { + return has_draw_texture; + } + bool IsWarpSizePotentiallyLargerThanGuest() const { return warp_size_potentially_larger_than_guest; } @@ -216,6 +222,7 @@ private: bool has_shader_int64{}; bool has_amd_shader_half_float{}; bool has_sparse_texture_2{}; + bool has_draw_texture{}; bool warp_size_potentially_larger_than_guest{}; bool need_fastmath_off{}; bool has_cbuf_ftou_bug{}; diff --git a/src/video_core/renderer_opengl/gl_fsr.cpp b/src/video_core/renderer_opengl/gl_fsr.cpp new file mode 100644 index 000000000..77262dcf1 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_fsr.cpp @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/settings.h" +#include "video_core/fsr.h" +#include "video_core/renderer_opengl/gl_fsr.h" +#include "video_core/renderer_opengl/gl_shader_manager.h" +#include "video_core/renderer_opengl/gl_shader_util.h" + +namespace OpenGL { +using namespace FSR; + +using FsrConstants = std::array<u32, 4 * 4>; + +FSR::FSR(std::string_view fsr_vertex_source, std::string_view fsr_easu_source, + std::string_view fsr_rcas_source) + : fsr_vertex{CreateProgram(fsr_vertex_source, GL_VERTEX_SHADER)}, + fsr_easu_frag{CreateProgram(fsr_easu_source, GL_FRAGMENT_SHADER)}, + fsr_rcas_frag{CreateProgram(fsr_rcas_source, GL_FRAGMENT_SHADER)} { + glProgramUniform2f(fsr_vertex.handle, 0, 1.0f, 1.0f); + glProgramUniform2f(fsr_vertex.handle, 1, 0.0f, 0.0f); +} + +FSR::~FSR() = default; + +void FSR::Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen, + u32 input_image_width, u32 input_image_height, + const Common::Rectangle<int>& crop_rect) { + + const auto output_image_width = screen.GetWidth(); + const auto output_image_height = screen.GetHeight(); + + if (fsr_intermediate_tex.handle) { + GLint fsr_tex_width, fsr_tex_height; + glGetTextureLevelParameteriv(fsr_intermediate_tex.handle, 0, GL_TEXTURE_WIDTH, + &fsr_tex_width); + glGetTextureLevelParameteriv(fsr_intermediate_tex.handle, 0, GL_TEXTURE_HEIGHT, + &fsr_tex_height); + if (static_cast<u32>(fsr_tex_width) != output_image_width || + static_cast<u32>(fsr_tex_height) != output_image_height) { + fsr_intermediate_tex.Release(); + } + } + if (!fsr_intermediate_tex.handle) { + fsr_intermediate_tex.Create(GL_TEXTURE_2D); + glTextureStorage2D(fsr_intermediate_tex.handle, 1, GL_RGB16F, output_image_width, + output_image_height); + glNamedFramebufferTexture(fsr_framebuffer.handle, GL_COLOR_ATTACHMENT0, + fsr_intermediate_tex.handle, 0); + } + + GLint old_draw_fb; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb); + + glFrontFace(GL_CW); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fsr_framebuffer.handle); + glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(output_image_width), + static_cast<GLfloat>(output_image_height)); + + FsrConstants constants; + FsrEasuConOffset( + constants.data() + 0, constants.data() + 4, constants.data() + 8, constants.data() + 12, + + static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()), + static_cast<f32>(input_image_width), static_cast<f32>(input_image_height), + static_cast<f32>(output_image_width), static_cast<f32>(output_image_height), + static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top)); + + glProgramUniform4uiv(fsr_easu_frag.handle, 0, sizeof(constants), std::data(constants)); + + program_manager.BindPresentPrograms(fsr_vertex.handle, fsr_easu_frag.handle); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); + glBindTextureUnit(0, fsr_intermediate_tex.handle); + + const float sharpening = + static_cast<float>(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f; + + FsrRcasCon(constants.data(), sharpening); + glProgramUniform4uiv(fsr_rcas_frag.handle, 0, sizeof(constants), std::data(constants)); +} + +void FSR::InitBuffers() { + fsr_framebuffer.Create(); +} + +void FSR::ReleaseBuffers() { + fsr_framebuffer.Release(); + fsr_intermediate_tex.Release(); +} + +const OGLProgram& FSR::GetPresentFragmentProgram() const noexcept { + return fsr_rcas_frag; +} + +bool FSR::AreBuffersInitialized() const noexcept { + return fsr_framebuffer.handle; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_fsr.h b/src/video_core/renderer_opengl/gl_fsr.h new file mode 100644 index 000000000..1f6ae3115 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_fsr.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <string_view> + +#include "common/common_types.h" +#include "common/math_util.h" +#include "video_core/fsr.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +namespace OpenGL { + +class ProgramManager; + +class FSR { +public: + explicit FSR(std::string_view fsr_vertex_source, std::string_view fsr_easu_source, + std::string_view fsr_rcas_source); + ~FSR(); + + void Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen, + u32 input_image_width, u32 input_image_height, + const Common::Rectangle<int>& crop_rect); + + void InitBuffers(); + + void ReleaseBuffers(); + + [[nodiscard]] const OGLProgram& GetPresentFragmentProgram() const noexcept; + + [[nodiscard]] bool AreBuffersInitialized() const noexcept; + +private: + OGLFramebuffer fsr_framebuffer; + OGLProgram fsr_vertex; + OGLProgram fsr_easu_frag; + OGLProgram fsr_rcas_frag; + OGLTexture fsr_intermediate_tex; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index c115dabe1..29491e762 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -176,7 +176,7 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c std::array<std::string, 5> sources, std::array<std::vector<u32>, 5> sources_spirv, const std::array<const Shader::Info*, 5>& infos, - const GraphicsPipelineKey& key_) + const GraphicsPipelineKey& key_, bool force_context_flush) : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_}, state_tracker{state_tracker_}, key{key_} { if (shader_notify) { @@ -231,7 +231,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c const bool in_parallel = thread_worker != nullptr; const auto backend = device.GetShaderBackend(); auto func{[this, sources = std::move(sources), sources_spirv = std::move(sources_spirv), - shader_notify, backend, in_parallel](ShaderContext::Context*) mutable { + shader_notify, backend, in_parallel, + force_context_flush](ShaderContext::Context*) mutable { for (size_t stage = 0; stage < 5; ++stage) { switch (backend) { case Settings::ShaderBackend::GLSL: @@ -251,7 +252,7 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c break; } } - if (in_parallel) { + if (force_context_flush || in_parallel) { std::scoped_lock lock{built_mutex}; built_fence.Create(); // Flush this context to ensure compilation commands and fence are in the GPU pipe. diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h index 1c06b3655..7bab3be0a 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h @@ -78,7 +78,7 @@ public: std::array<std::string, 5> sources, std::array<std::vector<u32>, 5> sources_spirv, const std::array<const Shader::Info*, 5>& infos, - const GraphicsPipelineKey& key_); + const GraphicsPipelineKey& key_, bool force_context_flush = false); void Configure(bool is_indexed) { configure_func(this, is_indexed); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 181857d9c..7bced675c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -64,7 +64,8 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager, state_tracker, gpu.ShaderNotify()), query_cache(*this), accelerate_dma(buffer_cache), - fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache) {} + fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache), + blit_image(program_manager_) {} RasterizerOpenGL::~RasterizerOpenGL() = default; @@ -320,6 +321,47 @@ void RasterizerOpenGL::DrawIndirect() { buffer_cache.SetDrawIndirect(nullptr); } +void RasterizerOpenGL::DrawTexture() { + MICROPROFILE_SCOPE(OpenGL_Drawing); + + SCOPE_EXIT({ gpu.TickWork(); }); + query_cache.UpdateCounters(); + + texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.UpdateRenderTargets(false); + + SyncState(); + + const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); + const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); + const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); + + if (device.HasDrawTexture()) { + state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle()); + + glDrawTextureNV(texture.DefaultHandle(), sampler->Handle(), draw_texture_state.dst_x0, + draw_texture_state.dst_y0, draw_texture_state.dst_x1, + draw_texture_state.dst_y1, 0, + draw_texture_state.src_x0 / static_cast<float>(texture.size.width), + draw_texture_state.src_y0 / static_cast<float>(texture.size.height), + draw_texture_state.src_x1 / static_cast<float>(texture.size.width), + draw_texture_state.src_y1 / static_cast<float>(texture.size.height)); + } else { + Region2D dst_region = {Offset2D{.x = static_cast<s32>(draw_texture_state.dst_x0), + .y = static_cast<s32>(draw_texture_state.dst_y0)}, + Offset2D{.x = static_cast<s32>(draw_texture_state.dst_x1), + .y = static_cast<s32>(draw_texture_state.dst_y1)}}; + Region2D src_region = {Offset2D{.x = static_cast<s32>(draw_texture_state.src_x0), + .y = static_cast<s32>(draw_texture_state.src_y0)}, + Offset2D{.x = static_cast<s32>(draw_texture_state.src_x1), + .y = static_cast<s32>(draw_texture_state.src_y1)}}; + blit_image.BlitColor(texture_cache.GetFramebuffer()->Handle(), texture.DefaultHandle(), + sampler->Handle(), dst_region, src_region, texture.size); + } + + ++num_queued_commands; +} + void RasterizerOpenGL::DispatchCompute() { gpu_memory->FlushCaching(); ComputePipeline* const pipeline{shader_cache.CurrentComputePipeline()}; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index be4f76c18..0c45832ae 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -16,6 +16,7 @@ #include "video_core/engines/maxwell_dma.h" #include "video_core/rasterizer_accelerated.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/blit_image.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_fence_manager.h" @@ -70,6 +71,7 @@ public: void Draw(bool is_indexed, u32 instance_count) override; void DrawIndirect() override; + void DrawTexture() override; void Clear(u32 layer_count) override; void DispatchCompute() override; void ResetCounter(VideoCore::QueryType type) override; @@ -224,6 +226,8 @@ private: AccelerateDMA accelerate_dma; FenceManagerOpenGL fence_manager; + BlitImageHelper blit_image; + boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices; std::array<ImageViewId, MAX_IMAGE_VIEWS> image_view_ids; boost::container::static_vector<GLuint, MAX_TEXTURES> sampler_handles; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 7dd854e0f..626ea7dcb 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -286,7 +286,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading, file.read(reinterpret_cast<char*>(&key), sizeof(key)); queue_work([this, key, env = std::move(env), &state, &callback](Context* ctx) mutable { ctx->pools.ReleaseContents(); - auto pipeline{CreateComputePipeline(ctx->pools, key, env)}; + auto pipeline{CreateComputePipeline(ctx->pools, key, env, true)}; std::scoped_lock lock{state.mutex}; if (pipeline) { compute_cache.emplace(key, std::move(pipeline)); @@ -307,7 +307,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading, env_ptrs.push_back(&env); } ctx->pools.ReleaseContents(); - auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false)}; + auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false, true)}; std::scoped_lock lock{state.mutex}; if (pipeline) { graphics_cache.emplace(key, std::move(pipeline)); @@ -439,7 +439,8 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() { std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline( ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key, - std::span<Shader::Environment* const> envs, bool build_in_parallel) try { + std::span<Shader::Environment* const> envs, bool use_shader_workers, + bool force_context_flush) try { LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash()); size_t env_index{}; u32 total_storage_buffers{}; @@ -531,10 +532,10 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline( } previous_program = &program; } - auto* const thread_worker{build_in_parallel ? workers.get() : nullptr}; + auto* const thread_worker{use_shader_workers ? workers.get() : nullptr}; return std::make_unique<GraphicsPipeline>(device, texture_cache, buffer_cache, program_manager, state_tracker, thread_worker, &shader_notify, sources, - sources_spirv, infos, key); + sources_spirv, infos, key, force_context_flush); } catch (Shader::Exception& exception) { LOG_ERROR(Render_OpenGL, "{}", exception.what()); @@ -559,8 +560,8 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline( } std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline( - ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, - Shader::Environment& env) try { + ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, + bool force_context_flush) try { LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash()); Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; @@ -589,7 +590,7 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline( } return std::make_unique<ComputePipeline>(device, texture_cache, buffer_cache, program_manager, - program.info, code, code_spirv); + program.info, code, code_spirv, force_context_flush); } catch (Shader::Exception& exception) { LOG_ERROR(Render_OpenGL, "{}", exception.what()); return nullptr; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index f82420592..6b9732fca 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -50,14 +50,16 @@ private: std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline( ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key, - std::span<Shader::Environment* const> envs, bool build_in_parallel); + std::span<Shader::Environment* const> envs, bool use_shader_workers, + bool force_context_flush = false); std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader); std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, - Shader::Environment& env); + Shader::Environment& env, + bool force_context_flush = false); std::unique_ptr<ShaderWorker> CreateWorkers() const; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index d9c29d8b7..98841ae65 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -1,2 +1,123 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later + +#include <glad/glad.h> + +#include "video_core/renderer_opengl/gl_shader_manager.h" + +namespace OpenGL { + +static constexpr std::array ASSEMBLY_PROGRAM_ENUMS{ + GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV, + GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV, +}; + +ProgramManager::ProgramManager(const Device& device) { + glCreateProgramPipelines(1, &pipeline.handle); + if (device.UseAssemblyShaders()) { + glEnable(GL_COMPUTE_PROGRAM_NV); + } +} + +void ProgramManager::BindComputeProgram(GLuint program) { + glUseProgram(program); + is_compute_bound = true; +} + +void ProgramManager::BindComputeAssemblyProgram(GLuint program) { + if (current_assembly_compute_program != program) { + current_assembly_compute_program = program; + glBindProgramARB(GL_COMPUTE_PROGRAM_NV, program); + } + UnbindPipeline(); +} + +void ProgramManager::BindSourcePrograms(std::span<const OGLProgram, NUM_STAGES> programs) { + static constexpr std::array<GLenum, 5> stage_enums{ + GL_VERTEX_SHADER_BIT, GL_TESS_CONTROL_SHADER_BIT, GL_TESS_EVALUATION_SHADER_BIT, + GL_GEOMETRY_SHADER_BIT, GL_FRAGMENT_SHADER_BIT, + }; + for (size_t stage = 0; stage < NUM_STAGES; ++stage) { + if (current_programs[stage] != programs[stage].handle) { + current_programs[stage] = programs[stage].handle; + glUseProgramStages(pipeline.handle, stage_enums[stage], programs[stage].handle); + } + } + BindPipeline(); +} + +void ProgramManager::BindPresentPrograms(GLuint vertex, GLuint fragment) { + if (current_programs[0] != vertex) { + current_programs[0] = vertex; + glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex); + } + if (current_programs[4] != fragment) { + current_programs[4] = fragment; + glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment); + } + glUseProgramStages( + pipeline.handle, + GL_TESS_CONTROL_SHADER_BIT | GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT, 0); + current_programs[1] = 0; + current_programs[2] = 0; + current_programs[3] = 0; + + if (current_stage_mask != 0) { + current_stage_mask = 0; + for (const GLenum program_type : ASSEMBLY_PROGRAM_ENUMS) { + glDisable(program_type); + } + } + BindPipeline(); +} + +void ProgramManager::BindAssemblyPrograms(std::span<const OGLAssemblyProgram, NUM_STAGES> programs, + u32 stage_mask) { + const u32 changed_mask = current_stage_mask ^ stage_mask; + current_stage_mask = stage_mask; + + if (changed_mask != 0) { + for (size_t stage = 0; stage < NUM_STAGES; ++stage) { + if (((changed_mask >> stage) & 1) != 0) { + if (((stage_mask >> stage) & 1) != 0) { + glEnable(ASSEMBLY_PROGRAM_ENUMS[stage]); + } else { + glDisable(ASSEMBLY_PROGRAM_ENUMS[stage]); + } + } + } + } + for (size_t stage = 0; stage < NUM_STAGES; ++stage) { + if (current_programs[stage] != programs[stage].handle) { + current_programs[stage] = programs[stage].handle; + glBindProgramARB(ASSEMBLY_PROGRAM_ENUMS[stage], programs[stage].handle); + } + } + UnbindPipeline(); +} + +void ProgramManager::RestoreGuestCompute() {} + +void ProgramManager::BindPipeline() { + if (!is_pipeline_bound) { + is_pipeline_bound = true; + glBindProgramPipeline(pipeline.handle); + } + UnbindCompute(); +} + +void ProgramManager::UnbindPipeline() { + if (is_pipeline_bound) { + is_pipeline_bound = false; + glBindProgramPipeline(0); + } + UnbindCompute(); +} + +void ProgramManager::UnbindCompute() { + if (is_compute_bound) { + is_compute_bound = false; + glUseProgram(0); + } +} +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index a84f5aeb3..07ffab77f 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -6,8 +6,6 @@ #include <array> #include <span> -#include <glad/glad.h> - #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -16,121 +14,28 @@ namespace OpenGL { class ProgramManager { static constexpr size_t NUM_STAGES = 5; - static constexpr std::array ASSEMBLY_PROGRAM_ENUMS{ - GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV, - GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV, - }; - public: - explicit ProgramManager(const Device& device) { - glCreateProgramPipelines(1, &pipeline.handle); - if (device.UseAssemblyShaders()) { - glEnable(GL_COMPUTE_PROGRAM_NV); - } - } - - void BindComputeProgram(GLuint program) { - glUseProgram(program); - is_compute_bound = true; - } - - void BindComputeAssemblyProgram(GLuint program) { - if (current_assembly_compute_program != program) { - current_assembly_compute_program = program; - glBindProgramARB(GL_COMPUTE_PROGRAM_NV, program); - } - UnbindPipeline(); - } - - void BindSourcePrograms(std::span<const OGLProgram, NUM_STAGES> programs) { - static constexpr std::array<GLenum, 5> stage_enums{ - GL_VERTEX_SHADER_BIT, GL_TESS_CONTROL_SHADER_BIT, GL_TESS_EVALUATION_SHADER_BIT, - GL_GEOMETRY_SHADER_BIT, GL_FRAGMENT_SHADER_BIT, - }; - for (size_t stage = 0; stage < NUM_STAGES; ++stage) { - if (current_programs[stage] != programs[stage].handle) { - current_programs[stage] = programs[stage].handle; - glUseProgramStages(pipeline.handle, stage_enums[stage], programs[stage].handle); - } - } - BindPipeline(); - } - - void BindPresentPrograms(GLuint vertex, GLuint fragment) { - if (current_programs[0] != vertex) { - current_programs[0] = vertex; - glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex); - } - if (current_programs[4] != fragment) { - current_programs[4] = fragment; - glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment); - } - glUseProgramStages( - pipeline.handle, - GL_TESS_CONTROL_SHADER_BIT | GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT, 0); - current_programs[1] = 0; - current_programs[2] = 0; - current_programs[3] = 0; - - if (current_stage_mask != 0) { - current_stage_mask = 0; - for (const GLenum program_type : ASSEMBLY_PROGRAM_ENUMS) { - glDisable(program_type); - } - } - BindPipeline(); - } + explicit ProgramManager(const Device& device); + + void BindComputeProgram(GLuint program); + + void BindComputeAssemblyProgram(GLuint program); + + void BindSourcePrograms(std::span<const OGLProgram, NUM_STAGES> programs); + + void BindPresentPrograms(GLuint vertex, GLuint fragment); void BindAssemblyPrograms(std::span<const OGLAssemblyProgram, NUM_STAGES> programs, - u32 stage_mask) { - const u32 changed_mask = current_stage_mask ^ stage_mask; - current_stage_mask = stage_mask; - - if (changed_mask != 0) { - for (size_t stage = 0; stage < NUM_STAGES; ++stage) { - if (((changed_mask >> stage) & 1) != 0) { - if (((stage_mask >> stage) & 1) != 0) { - glEnable(ASSEMBLY_PROGRAM_ENUMS[stage]); - } else { - glDisable(ASSEMBLY_PROGRAM_ENUMS[stage]); - } - } - } - } - for (size_t stage = 0; stage < NUM_STAGES; ++stage) { - if (current_programs[stage] != programs[stage].handle) { - current_programs[stage] = programs[stage].handle; - glBindProgramARB(ASSEMBLY_PROGRAM_ENUMS[stage], programs[stage].handle); - } - } - UnbindPipeline(); - } - - void RestoreGuestCompute() {} + u32 stage_mask); + + void RestoreGuestCompute(); private: - void BindPipeline() { - if (!is_pipeline_bound) { - is_pipeline_bound = true; - glBindProgramPipeline(pipeline.handle); - } - UnbindCompute(); - } - - void UnbindPipeline() { - if (is_pipeline_bound) { - is_pipeline_bound = false; - glBindProgramPipeline(0); - } - UnbindCompute(); - } - - void UnbindCompute() { - if (is_compute_bound) { - is_compute_bound = false; - glUseProgram(0); - } - } + void BindPipeline(); + + void UnbindPipeline(); + + void UnbindCompute(); OGLPipeline pipeline; bool is_pipeline_bound{}; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index de95f2634..2a74c1d05 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -17,8 +17,14 @@ #include "core/frontend/emu_window.h" #include "core/memory.h" #include "core/telemetry_session.h" +#include "video_core/host_shaders/ffx_a_h.h" +#include "video_core/host_shaders/ffx_fsr1_h.h" +#include "video_core/host_shaders/full_screen_triangle_vert.h" #include "video_core/host_shaders/fxaa_frag.h" #include "video_core/host_shaders/fxaa_vert.h" +#include "video_core/host_shaders/opengl_fidelityfx_fsr_easu_frag.h" +#include "video_core/host_shaders/opengl_fidelityfx_fsr_frag.h" +#include "video_core/host_shaders/opengl_fidelityfx_fsr_rcas_frag.h" #include "video_core/host_shaders/opengl_present_frag.h" #include "video_core/host_shaders/opengl_present_scaleforce_frag.h" #include "video_core/host_shaders/opengl_present_vert.h" @@ -31,6 +37,7 @@ #include "video_core/host_shaders/smaa_edge_detection_vert.h" #include "video_core/host_shaders/smaa_neighborhood_blending_frag.h" #include "video_core/host_shaders/smaa_neighborhood_blending_vert.h" +#include "video_core/renderer_opengl/gl_fsr.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -268,12 +275,17 @@ void RendererOpenGL::InitOpenGLObjects() { fxaa_vertex = CreateProgram(HostShaders::FXAA_VERT, GL_VERTEX_SHADER); fxaa_fragment = CreateProgram(HostShaders::FXAA_FRAG, GL_FRAGMENT_SHADER); - const auto SmaaShader = [](std::string_view specialized_source, GLenum stage) { - std::string shader_source{specialized_source}; - constexpr std::string_view include_string = "#include \"opengl_smaa.glsl\""; + const auto replace_include = [](std::string& shader_source, std::string_view include_name, + std::string_view include_content) { + const std::string include_string = fmt::format("#include \"{}\"", include_name); const std::size_t pos = shader_source.find(include_string); ASSERT(pos != std::string::npos); - shader_source.replace(pos, include_string.size(), HostShaders::OPENGL_SMAA_GLSL); + shader_source.replace(pos, include_string.size(), include_content); + }; + + const auto SmaaShader = [&](std::string_view specialized_source, GLenum stage) { + std::string shader_source{specialized_source}; + replace_include(shader_source, "opengl_smaa.glsl", HostShaders::OPENGL_SMAA_GLSL); return CreateProgram(shader_source, stage); }; @@ -298,14 +310,32 @@ void RendererOpenGL::InitOpenGLObjects() { CreateProgram(fmt::format("#version 460\n{}", HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG), GL_FRAGMENT_SHADER); + std::string fsr_source{HostShaders::OPENGL_FIDELITYFX_FSR_FRAG}; + replace_include(fsr_source, "ffx_a.h", HostShaders::FFX_A_H); + replace_include(fsr_source, "ffx_fsr1.h", HostShaders::FFX_FSR1_H); + + std::string fsr_easu_frag_source{HostShaders::OPENGL_FIDELITYFX_FSR_EASU_FRAG}; + std::string fsr_rcas_frag_source{HostShaders::OPENGL_FIDELITYFX_FSR_RCAS_FRAG}; + replace_include(fsr_easu_frag_source, "opengl_fidelityfx_fsr.frag", fsr_source); + replace_include(fsr_rcas_frag_source, "opengl_fidelityfx_fsr.frag", fsr_source); + + fsr = std::make_unique<FSR>(HostShaders::FULL_SCREEN_TRIANGLE_VERT, fsr_easu_frag_source, + fsr_rcas_frag_source); + // Generate presentation sampler present_sampler.Create(); glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); present_sampler_nn.Create(); glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Generate VBO handle for drawing vertex_buffer.Create(); @@ -525,6 +555,31 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { glBindTextureUnit(0, aa_texture.handle); } + glDisablei(GL_SCISSOR_TEST, 0); + + if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { + if (!fsr->AreBuffersInitialized()) { + fsr->InitBuffers(); + } + + auto crop_rect = framebuffer_crop_rect; + if (crop_rect.GetWidth() == 0) { + crop_rect.right = framebuffer_width; + } + if (crop_rect.GetHeight() == 0) { + crop_rect.bottom = framebuffer_height; + } + crop_rect = crop_rect.Scale(Settings::values.resolution_info.up_factor); + const auto fsr_input_width = Settings::values.resolution_info.ScaleUp(framebuffer_width); + const auto fsr_input_height = Settings::values.resolution_info.ScaleUp(framebuffer_height); + glBindSampler(0, present_sampler.handle); + fsr->Draw(program_manager, layout.screen, fsr_input_width, fsr_input_height, crop_rect); + } else { + if (fsr->AreBuffersInitialized()) { + fsr->ReleaseBuffers(); + } + } + const std::array ortho_matrix = MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height)); @@ -540,10 +595,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { case Settings::ScalingFilter::ScaleForce: return present_scaleforce_fragment.handle; case Settings::ScalingFilter::Fsr: - LOG_WARNING( - Render_OpenGL, - "FidelityFX Super Resolution is not supported in OpenGL, changing to ScaleForce"); - return present_scaleforce_fragment.handle; + return fsr->GetPresentFragmentProgram().handle; default: return present_bilinear_fragment.handle; } @@ -578,15 +630,18 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width); f32 scale_v = static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height); - // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering - // (e.g. handheld mode) on a 1920x1080 framebuffer. - if (framebuffer_crop_rect.GetWidth() > 0) { - scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / - static_cast<f32>(screen_info.texture.width); - } - if (framebuffer_crop_rect.GetHeight() > 0) { - scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / - static_cast<f32>(screen_info.texture.height); + + if (Settings::values.scaling_filter.GetValue() != Settings::ScalingFilter::Fsr) { + // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering + // (e.g. handheld mode) on a 1920x1080 framebuffer. + if (framebuffer_crop_rect.GetWidth() > 0) { + scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / + static_cast<f32>(screen_info.texture.width); + } + if (framebuffer_crop_rect.GetHeight() > 0) { + scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / + static_cast<f32>(screen_info.texture.height); + } } if (Settings::values.anti_aliasing.GetValue() == Settings::AntiAliasing::Fxaa && !screen_info.was_accelerated) { @@ -612,7 +667,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { } else { glDisable(GL_FRAMEBUFFER_SRGB); } - glDisablei(GL_SCISSOR_TEST, 0); glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width), static_cast<GLfloat>(layout.height)); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index cc97d7b26..f1d5fd954 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -10,6 +10,7 @@ #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_fsr.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_manager.h" @@ -141,6 +142,8 @@ private: OGLTexture smaa_edges_tex; OGLTexture smaa_blend_tex; + std::unique_ptr<FSR> fsr; + /// OpenGL framebuffer data std::vector<u8> gl_framebuffer_data; diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 3f2b139e0..cf2964a3f 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -4,14 +4,16 @@ #include <algorithm> #include "common/settings.h" +#include "video_core/host_shaders/blit_color_float_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" #include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" -#include "video_core/host_shaders/vulkan_blit_color_float_frag_spv.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" +#include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" +#include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -69,10 +71,11 @@ constexpr VkDescriptorSetLayoutCreateInfo TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CRE .bindingCount = static_cast<u32>(TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()), .pBindings = TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(), }; -constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{ - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, +template <VkShaderStageFlags stageFlags, size_t size> +inline constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{ + .stageFlags = stageFlags, .offset = 0, - .size = sizeof(PushConstants), + .size = static_cast<u32>(size), }; constexpr VkPipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, @@ -125,10 +128,8 @@ constexpr VkPipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; -constexpr std::array DYNAMIC_STATES{ - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR, -}; +constexpr std::array DYNAMIC_STATES{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_BLEND_CONSTANTS}; constexpr VkPipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pNext = nullptr, @@ -205,15 +206,15 @@ inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{ }; constexpr VkPipelineLayoutCreateInfo PipelineLayoutCreateInfo( - const VkDescriptorSetLayout* set_layout) { + const VkDescriptorSetLayout* set_layout, vk::Span<VkPushConstantRange> push_constants) { return VkPipelineLayoutCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, .flags = 0, - .setLayoutCount = 1, + .setLayoutCount = (set_layout != nullptr ? 1u : 0u), .pSetLayouts = set_layout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &PUSH_CONSTANT_RANGE, + .pushConstantRangeCount = push_constants.size(), + .pPushConstantRanges = push_constants.data(), }; } @@ -302,8 +303,7 @@ void UpdateTwoTexturesDescriptorSet(const Device& device, VkDescriptorSet descri device.GetLogical().UpdateDescriptorSets(write_descriptor_sets, nullptr); } -void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, const Region2D& dst_region, - const Region2D& src_region) { +void BindBlitState(vk::CommandBuffer cmdbuf, const Region2D& dst_region) { const VkOffset2D offset{ .x = std::min(dst_region.start.x, dst_region.end.x), .y = std::min(dst_region.start.y, dst_region.end.y), @@ -325,15 +325,23 @@ void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, const Regi .offset = offset, .extent = extent, }; - const float scale_x = static_cast<float>(src_region.end.x - src_region.start.x); - const float scale_y = static_cast<float>(src_region.end.y - src_region.start.y); + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); +} + +void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, const Region2D& dst_region, + const Region2D& src_region, const Extent3D& src_size = {1, 1, 1}) { + BindBlitState(cmdbuf, dst_region); + const float scale_x = static_cast<float>(src_region.end.x - src_region.start.x) / + static_cast<float>(src_size.width); + const float scale_y = static_cast<float>(src_region.end.y - src_region.start.y) / + static_cast<float>(src_size.height); const PushConstants push_constants{ .tex_scale = {scale_x, scale_y}, - .tex_offset = {static_cast<float>(src_region.start.x), - static_cast<float>(src_region.start.y)}, + .tex_offset = {static_cast<float>(src_region.start.x) / static_cast<float>(src_size.width), + static_cast<float>(src_region.start.y) / + static_cast<float>(src_size.height)}, }; - cmdbuf.SetViewport(0, viewport); - cmdbuf.SetScissor(0, scissor); cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants); } @@ -347,6 +355,51 @@ VkExtent2D GetConversionExtent(const ImageView& src_image_view) { .height = is_rescaled ? resolution.ScaleUp(height) : height, }; } + +void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout, + VkImageLayout source_layout = VK_IMAGE_LAYOUT_GENERAL) { + constexpr VkFlags flags{VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT}; + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = flags, + .dstAccessMask = flags, + .oldLayout = source_layout, + .newLayout = target_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, barrier); +} + +void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) { + const VkRenderPass render_pass = framebuffer->RenderPass(); + const VkFramebuffer framebuffer_handle = framebuffer->Handle(); + const VkExtent2D render_area = framebuffer->RenderArea(); + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = render_pass, + .framebuffer = framebuffer_handle, + .renderArea{ + .offset{}, + .extent = render_area, + }, + .clearValueCount = 0, + .pClearValues = nullptr, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); +} } // Anonymous namespace BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, @@ -360,13 +413,20 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, descriptor_pool.Allocator(*one_texture_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<1>)}, two_textures_descriptor_allocator{ descriptor_pool.Allocator(*two_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<2>)}, - one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout( - PipelineLayoutCreateInfo(one_texture_set_layout.address()))), - two_textures_pipeline_layout(device.GetLogical().CreatePipelineLayout( - PipelineLayoutCreateInfo(two_textures_set_layout.address()))), + one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + one_texture_set_layout.address(), + PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))), + two_textures_pipeline_layout( + device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + two_textures_set_layout.address(), + PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))), + clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + nullptr, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 4>))), full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), - blit_color_to_color_frag(BuildShader(device, VULKAN_BLIT_COLOR_FLOAT_FRAG_SPV)), + blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)), blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), + clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), + clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), @@ -404,6 +464,32 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView scheduler.InvalidateState(); } +void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view, + VkImage src_image, VkSampler src_sampler, + const Region2D& dst_region, const Region2D& src_region, + const Extent3D& src_size) { + const BlitImagePipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .operation = Tegra::Engines::Fermi2D::Operation::SrcCopy, + }; + const VkPipelineLayout layout = *one_texture_pipeline_layout; + const VkPipeline pipeline = FindOrEmplaceColorPipeline(key); + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, dst_framebuffer, src_image_view, src_image, src_sampler, dst_region, + src_region, src_size, pipeline, layout](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL); + BeginRenderPass(cmdbuf, dst_framebuffer); + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region, src_size); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + }); +} + void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, VkImageView src_depth_view, VkImageView src_stencil_view, const Region2D& dst_region, const Region2D& src_region, @@ -479,6 +565,30 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view); } +void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, + const std::array<f32, 4>& clear_color, + const Region2D& dst_region) { + const BlitImagePipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .operation = Tegra::Engines::Fermi2D::Operation::BlendPremult, + }; + const VkPipeline pipeline = FindOrEmplaceClearColorPipeline(key); + const VkPipelineLayout layout = *clear_color_pipeline_layout; + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record( + [pipeline, layout, color_mask, clear_color, dst_region](vk::CommandBuffer cmdbuf) { + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + const std::array blend_color = { + (color_mask & 0x1) ? 1.0f : 0.0f, (color_mask & 0x2) ? 1.0f : 0.0f, + (color_mask & 0x4) ? 1.0f : 0.0f, (color_mask & 0x8) ? 1.0f : 0.0f}; + cmdbuf.SetBlendConstants(blend_color.data()); + BindBlitState(cmdbuf, dst_region); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_color); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { const VkPipelineLayout layout = *one_texture_pipeline_layout; @@ -654,6 +764,58 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip return *blit_depth_stencil_pipelines.back(); } +VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key) { + const auto it = std::ranges::find(clear_color_keys, key); + if (it != clear_color_keys.end()) { + return *clear_color_pipelines[std::distance(clear_color_keys.begin(), it)]; + } + clear_color_keys.push_back(key); + const std::array stages = MakeStages(*clear_color_vert, *clear_color_frag); + const VkPipelineColorBlendAttachmentState color_blend_attachment_state{ + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_CONSTANT_COLOR, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_CONSTANT_ALPHA, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + const VkPipelineColorBlendStateCreateInfo color_blend_state_generic_create_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment_state, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + clear_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pColorBlendState = &color_blend_state_generic_create_info, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *clear_color_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *clear_color_pipelines.back(); +} + void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth) { if (pipeline) { diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 5df679fb4..2976a7d91 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -10,6 +10,8 @@ namespace Vulkan { +using VideoCommon::Extent3D; +using VideoCommon::Offset2D; using VideoCommon::Region2D; class Device; @@ -36,6 +38,10 @@ public: Tegra::Engines::Fermi2D::Filter filter, Tegra::Engines::Fermi2D::Operation operation); + void BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view, + VkImage src_image, VkSampler src_sampler, const Region2D& dst_region, + const Region2D& src_region, const Extent3D& src_size); + void BlitDepthStencil(const Framebuffer* dst_framebuffer, VkImageView src_depth_view, VkImageView src_stencil_view, const Region2D& dst_region, const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, @@ -55,6 +61,9 @@ public: void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); + void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, + const std::array<f32, 4>& clear_color, const Region2D& dst_region); + private: void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -66,6 +75,8 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); + void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); void ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); @@ -91,9 +102,12 @@ private: DescriptorAllocator two_textures_descriptor_allocator; vk::PipelineLayout one_texture_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; + vk::PipelineLayout clear_color_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule blit_color_to_color_frag; vk::ShaderModule blit_depth_stencil_frag; + vk::ShaderModule clear_color_vert; + vk::ShaderModule clear_color_frag; vk::ShaderModule convert_depth_to_float_frag; vk::ShaderModule convert_float_to_depth_frag; vk::ShaderModule convert_abgr8_to_d24s8_frag; @@ -106,6 +120,8 @@ private: std::vector<vk::Pipeline> blit_color_pipelines; std::vector<BlitImagePipelineKey> blit_depth_stencil_keys; std::vector<vk::Pipeline> blit_depth_stencil_pipelines; + std::vector<BlitImagePipelineKey> clear_color_keys; + std::vector<vk::Pipeline> clear_color_pipelines; vk::Pipeline convert_d32_to_r32_pipeline; vk::Pipeline convert_r32_to_d32_pipeline; vk::Pipeline convert_d16_to_r16_pipeline; diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp index 33daa8c1c..df972cd54 100644 --- a/src/video_core/renderer_vulkan/vk_fsr.cpp +++ b/src/video_core/renderer_vulkan/vk_fsr.cpp @@ -1,12 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cmath> -#include "common/bit_cast.h" #include "common/common_types.h" #include "common/div_ceil.h" #include "common/settings.h" +#include "video_core/fsr.h" #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16_comp_spv.h" #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32_comp_spv.h" #include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16_comp_spv.h" @@ -17,146 +16,7 @@ #include "video_core/vulkan_common/vulkan_device.h" namespace Vulkan { -namespace { -// Reimplementations of the constant generating functions in ffx_fsr1.h -// GCC generated a lot of warnings when using the official header. -u32 AU1_AH1_AF1(f32 f) { - static constexpr u32 base[512]{ - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, - 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, 0x2000, - 0x2400, 0x2800, 0x2c00, 0x3000, 0x3400, 0x3800, 0x3c00, 0x4000, 0x4400, 0x4800, 0x4c00, - 0x5000, 0x5400, 0x5800, 0x5c00, 0x6000, 0x6400, 0x6800, 0x6c00, 0x7000, 0x7400, 0x7800, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, - 0x7bff, 0x7bff, 0x7bff, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, - 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, - 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, 0x8200, 0x8400, 0x8800, 0x8c00, 0x9000, 0x9400, - 0x9800, 0x9c00, 0xa000, 0xa400, 0xa800, 0xac00, 0xb000, 0xb400, 0xb800, 0xbc00, 0xc000, - 0xc400, 0xc800, 0xcc00, 0xd000, 0xd400, 0xd800, 0xdc00, 0xe000, 0xe400, 0xe800, 0xec00, - 0xf000, 0xf400, 0xf800, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, - }; - static constexpr s8 shift[512]{ - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, 0x16, - 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, - 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, - 0x18, 0x18, - }; - const u32 u = Common::BitCast<u32>(f); - const u32 i = u >> 23; - return base[i] + ((u & 0x7fffff) >> shift[i]); -} - -u32 AU1_AH2_AF2(f32 a[2]) { - return AU1_AH1_AF1(a[0]) + (AU1_AH1_AF1(a[1]) << 16); -} - -void FsrEasuCon(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], f32 inputViewportInPixelsX, - f32 inputViewportInPixelsY, f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, - f32 outputSizeInPixelsX, f32 outputSizeInPixelsY) { - con0[0] = Common::BitCast<u32>(inputViewportInPixelsX / outputSizeInPixelsX); - con0[1] = Common::BitCast<u32>(inputViewportInPixelsY / outputSizeInPixelsY); - con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f); - con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f); - con1[0] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); - con1[1] = Common::BitCast<u32>(1.0f / inputSizeInPixelsY); - con1[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); - con1[3] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsY); - con2[0] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsX); - con2[1] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); - con2[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); - con2[3] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); - con3[0] = Common::BitCast<u32>(0.0f / inputSizeInPixelsX); - con3[1] = Common::BitCast<u32>(4.0f / inputSizeInPixelsY); - con3[2] = con3[3] = 0; -} - -void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], - f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, - f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, - f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY) { - FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, - inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); - con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f + - inputOffsetInPixelsX); - con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f + - inputOffsetInPixelsY); -} - -void FsrRcasCon(u32* con, f32 sharpness) { - sharpness = std::exp2f(-sharpness); - f32 hSharp[2]{sharpness, sharpness}; - con[0] = Common::BitCast<u32>(sharpness); - con[1] = AU1_AH2_AF2(hSharp); - con[2] = 0; - con[3] = 0; -} -} // Anonymous namespace +using namespace FSR; FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image_count_, VkExtent2D output_size_) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b75b8eec6..719edbcfb 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -266,6 +266,35 @@ void RasterizerVulkan::DrawIndirect() { buffer_cache.SetDrawIndirect(nullptr); } +void RasterizerVulkan::DrawTexture() { + MICROPROFILE_SCOPE(Vulkan_Drawing); + + SCOPE_EXIT({ gpu.TickWork(); }); + FlushWork(); + + query_cache.UpdateCounters(); + + texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.UpdateRenderTargets(false); + + UpdateDynamicStates(); + + const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState(); + const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); + const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); + Region2D dst_region = {Offset2D{.x = static_cast<s32>(draw_texture_state.dst_x0), + .y = static_cast<s32>(draw_texture_state.dst_y0)}, + Offset2D{.x = static_cast<s32>(draw_texture_state.dst_x1), + .y = static_cast<s32>(draw_texture_state.dst_y1)}}; + Region2D src_region = {Offset2D{.x = static_cast<s32>(draw_texture_state.src_x0), + .y = static_cast<s32>(draw_texture_state.src_y0)}, + Offset2D{.x = static_cast<s32>(draw_texture_state.src_x1), + .y = static_cast<s32>(draw_texture_state.src_y1)}}; + blit_image.BlitColor(texture_cache.GetFramebuffer(), texture.RenderTarget(), + texture.ImageHandle(), sampler->Handle(), dst_region, src_region, + texture.size); +} + void RasterizerVulkan::Clear(u32 layer_count) { MICROPROFILE_SCOPE(Vulkan_Clearing); @@ -365,7 +394,15 @@ void RasterizerVulkan::Clear(u32 layer_count) { cmdbuf.ClearAttachments(attachment, clear_rect); }); } else { - UNIMPLEMENTED_MSG("Unimplemented Clear only the specified channel"); + u8 color_mask = static_cast<u8>(regs.clear_surface.R | regs.clear_surface.G << 1 | + regs.clear_surface.B << 2 | regs.clear_surface.A << 3); + Region2D dst_region = { + Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, + Offset2D{.x = clear_rect.rect.offset.x + + static_cast<s32>(clear_rect.rect.extent.width), + .y = clear_rect.rect.offset.y + + static_cast<s32>(clear_rect.rect.extent.height)}}; + blit_image.ClearColor(framebuffer, color_mask, regs.clear_color, dst_region); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 472cc64d9..a0508b57c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -66,6 +66,7 @@ public: void Draw(bool is_indexed, u32 instance_count) override; void DrawIndirect() override; + void DrawTexture() override; void Clear(u32 layer_count) override; void DispatchCompute() override; void ResetCounter(VideoCore::QueryType type) override; diff --git a/src/video_core/texture_cache/descriptor_table.h b/src/video_core/texture_cache/descriptor_table.h index ee4240288..1bad83fb4 100644 --- a/src/video_core/texture_cache/descriptor_table.h +++ b/src/video_core/texture_cache/descriptor_table.h @@ -19,9 +19,7 @@ public: explicit DescriptorTable(Tegra::MemoryManager& gpu_memory_) : gpu_memory{gpu_memory_} {} [[nodiscard]] bool Synchronize(GPUVAddr gpu_addr, u32 limit) { - [[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) { - return false; - } + [[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) { return false; } Refresh(gpu_addr, limit); return true; } diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 852ec2519..e9100091e 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -100,6 +100,10 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept { ASSERT_MSG(false, "Invalid texture_type={}", static_cast<int>(config.texture_type.Value())); break; } + if (num_samples > 1) { + size.width *= NumSamplesX(config.msaa_mode); + size.height *= NumSamplesY(config.msaa_mode); + } if (type != ImageType::Linear) { // FIXME: Call this without passing *this layer_stride = CalculateLayerStride(*this); diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h index d552bccf0..203ac1b11 100644 --- a/src/video_core/texture_cache/samples_helper.h +++ b/src/video_core/texture_cache/samples_helper.h @@ -51,4 +51,48 @@ namespace VideoCommon { return 1; } +[[nodiscard]] inline int NumSamplesX(Tegra::Texture::MsaaMode msaa_mode) { + using Tegra::Texture::MsaaMode; + switch (msaa_mode) { + case MsaaMode::Msaa1x1: + return 1; + case MsaaMode::Msaa2x1: + case MsaaMode::Msaa2x1_D3D: + case MsaaMode::Msaa2x2: + case MsaaMode::Msaa2x2_VC4: + case MsaaMode::Msaa2x2_VC12: + return 2; + case MsaaMode::Msaa4x2: + case MsaaMode::Msaa4x2_D3D: + case MsaaMode::Msaa4x2_VC8: + case MsaaMode::Msaa4x2_VC24: + case MsaaMode::Msaa4x4: + return 4; + } + ASSERT_MSG(false, "Invalid MSAA mode={}", static_cast<int>(msaa_mode)); + return 1; +} + +[[nodiscard]] inline int NumSamplesY(Tegra::Texture::MsaaMode msaa_mode) { + using Tegra::Texture::MsaaMode; + switch (msaa_mode) { + case MsaaMode::Msaa1x1: + case MsaaMode::Msaa2x1: + case MsaaMode::Msaa2x1_D3D: + return 1; + case MsaaMode::Msaa2x2: + case MsaaMode::Msaa2x2_VC4: + case MsaaMode::Msaa2x2_VC12: + case MsaaMode::Msaa4x2: + case MsaaMode::Msaa4x2_D3D: + case MsaaMode::Msaa4x2_VC8: + case MsaaMode::Msaa4x2_VC24: + return 2; + case MsaaMode::Msaa4x4: + return 4; + } + ASSERT_MSG(false, "Invalid MSAA mode={}", static_cast<int>(msaa_mode)); + return 1; +} + } // namespace VideoCommon diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h index 1e2aad76a..9df6a2903 100644 --- a/src/video_core/texture_cache/slot_vector.h +++ b/src/video_core/texture_cache/slot_vector.h @@ -29,7 +29,7 @@ struct SlotId { }; template <class T> -requires std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T> + requires std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T> class SlotVector { public: class Iterator { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 87152c8e9..1b01990a4 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -149,6 +149,13 @@ typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) noexcept { } template <class P> +typename P::ImageView& TextureCache<P>::GetImageView(u32 index) noexcept { + const auto image_view_id = VisitImageView(channel_state->graphics_image_table, + channel_state->graphics_image_view_ids, index); + return slot_image_views[image_view_id]; +} + +template <class P> void TextureCache<P>::MarkModification(ImageId id) noexcept { MarkModification(slot_images[id]); } diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 4eea1f609..485eaabaa 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -129,6 +129,9 @@ public: /// Return a reference to the given image view id [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept; + /// Get the imageview from the graphics descriptor table in the specified index + [[nodiscard]] ImageView& GetImageView(u32 index) noexcept; + /// Mark an image as modified from the GPU void MarkModification(ImageId id) noexcept; diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp index 85f1d13e0..5fa0d9620 100644 --- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp @@ -57,7 +57,7 @@ NsightAftermathTracker::NsightAftermathTracker() { if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps( GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan, GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback, - ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) { + ShaderDebugInfoCallback, CrashDumpDescriptionCallback, nullptr, this))) { LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); return; } @@ -83,7 +83,7 @@ void NsightAftermathTracker::SaveShader(std::span<const u32> spirv) const { std::scoped_lock lock{mutex}; - GFSDK_Aftermath_ShaderHash hash; + GFSDK_Aftermath_ShaderBinaryHash hash; if (!GFSDK_Aftermath_SUCCEED( GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) { LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module"); @@ -121,8 +121,8 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump, u32 json_size = 0; if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON( decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO, - GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr, - this, &json_size))) { + GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, this, + &json_size))) { LOG_ERROR(Render_Vulkan, "Failed to generate JSON"); return; } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index dfc675cc8..06d982d9b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -353,7 +353,7 @@ if (USE_DISCORD_PRESENCE) discord_impl.cpp discord_impl.h ) - target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc) + target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) endif() diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0db62baa3..35fef506a 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -70,28 +70,28 @@ const std::array<int, 2> Config::default_ringcon_analogs{{ // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}}, }}; // clang-format on @@ -440,6 +440,7 @@ void Config::ReadControlValues() { ReadBasicSetting(Settings::values.emulate_analog_keyboard); Settings::values.mouse_panning = false; ReadBasicSetting(Settings::values.mouse_panning_sensitivity); + ReadBasicSetting(Settings::values.enable_joycon_driver); ReadBasicSetting(Settings::values.tas_enable); ReadBasicSetting(Settings::values.tas_loop); @@ -747,7 +748,7 @@ void Config::ReadShortcutValues() { for (const auto& [name, group, shortcut] : default_hotkeys) { qt_config->beginGroup(group); qt_config->beginGroup(name); - // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 + // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1 // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open // a file dialog in windowed mode UISettings::values.shortcuts.push_back( @@ -756,7 +757,7 @@ void Config::ReadShortcutValues() { {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(), ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq) .toString(), - shortcut.context}}); + shortcut.context, ReadSetting(QStringLiteral("Repeat"), shortcut.repeat).toBool()}}); qt_config->endGroup(); qt_config->endGroup(); } @@ -1139,6 +1140,7 @@ void Config::SaveControlValues() { WriteGlobalSetting(Settings::values.enable_accurate_vibrations); WriteGlobalSetting(Settings::values.motion_enabled); WriteBasicSetting(Settings::values.enable_raw_input); + WriteBasicSetting(Settings::values.enable_joycon_driver); WriteBasicSetting(Settings::values.keyboard_enabled); WriteBasicSetting(Settings::values.emulate_analog_keyboard); WriteBasicSetting(Settings::values.mouse_panning_sensitivity); @@ -1393,6 +1395,7 @@ void Config::SaveShortcutValues() { WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq, default_hotkey.controller_keyseq); WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context); + WriteSetting(QStringLiteral("Repeat"), shortcut.repeat, default_hotkey.repeat); qt_config->endGroup(); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp index 97fb664bf..ac42cc7fc 100644 --- a/src/yuzu/configuration/configuration_shared.cpp +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -92,3 +92,13 @@ void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text); combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX); } + +int ConfigurationShared::GetComboboxIndex(int global_setting_index, const QComboBox* combobox) { + if (Settings::IsConfiguringGlobal()) { + return combobox->currentIndex(); + } + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + return global_setting_index; + } + return combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET; +} diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index e597dcdb5..04c88758c 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -69,4 +69,7 @@ void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); // Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox void InsertGlobalItem(QComboBox* combobox, int global_index); +// Returns the correct index of a QComboBox taking into account global configuration +int GetComboboxIndex(int global_setting_index, const QComboBox* combobox); + } // namespace ConfigurationShared diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 4301313cf..2aaefcc05 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -66,7 +66,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, web_tab->SetWebServiceConfigEnabled(enable_web_config); hotkeys_tab->Populate(registry); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); input_tab->Initialize(input_subsystem); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index bb9910a53..a45ec69ec 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -460,7 +460,7 @@ </item> <item> <property name="text"> - <string>AMD FidelityFX™️ Super Resolution (Vulkan Only)</string> + <string>AMD FidelityFX™️ Super Resolution</string> </property> </item> </widget> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 235b813d9..77b976e74 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.controller_navigation = ui->controller_navigation->isChecked(); Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked(); + Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue()); + ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue()); UpdateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index fac8cf827..75d96d3ab 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2696,6 +2696,22 @@ </widget> </item> <item row="5" column="0"> + <widget class="QCheckBox" name="enable_joycon_driver"> + <property name="toolTip"> + <string>Requires restarting yuzu</string> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Enable direct JoyCon driver</string> + </property> + </widget> + </item> + <item row="6" column="0"> <widget class="QCheckBox" name="mouse_panning"> <property name="minimumSize"> <size> @@ -2708,7 +2724,7 @@ </property> </widget> </item> - <item row="5" column="2"> + <item row="6" column="2"> <widget class="QSpinBox" name="mouse_panning_sensitivity"> <property name="toolTip"> <string>Mouse sensitivity</string> @@ -2730,14 +2746,14 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="motion_touch"> <property name="text"> <string>Motion / Touch</string> </property> </widget> </item> - <item row="6" column="2"> + <item row="7" column="2"> <widget class="QPushButton" name="buttonMotionTouch"> <property name="text"> <string>Configure</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index c40d980c9..723690e71 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) { return QObject::tr("R"); case Common::Input::ButtonNames::TriggerL: return QObject::tr("L"); + case Common::Input::ButtonNames::TriggerZR: + return QObject::tr("ZR"); + case Common::Input::ButtonNames::TriggerZL: + return QObject::tr("ZL"); + case Common::Input::ButtonNames::TriggerSR: + return QObject::tr("SR"); + case Common::Input::ButtonNames::TriggerSL: + return QObject::tr("SL"); + case Common::Input::ButtonNames::ButtonStickL: + return QObject::tr("Stick L"); + case Common::Input::ButtonNames::ButtonStickR: + return QObject::tr("Stick R"); case Common::Input::ButtonNames::ButtonA: return QObject::tr("A"); case Common::Input::ButtonNames::ButtonB: @@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) { return QObject::tr("Y"); case Common::Input::ButtonNames::ButtonStart: return QObject::tr("Start"); + case Common::Input::ButtonNames::ButtonPlus: + return QObject::tr("Plus"); + case Common::Input::ButtonNames::ButtonMinus: + return QObject::tr("Minus"); + case Common::Input::ButtonNames::ButtonHome: + return QObject::tr("Home"); + case Common::Input::ButtonNames::ButtonCapture: + return QObject::tr("Capture"); case Common::Input::ButtonNames::L1: return QObject::tr("L1"); case Common::Input::ButtonNames::L2: @@ -162,12 +182,13 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); + const QString turbo = QString::fromStdString(param.Get("turbo", false) ? "$" : ""); const auto common_button_name = input_subsystem->GetButtonName(param); // Retrieve the names from Qt if (param.Get("engine", "") == "keyboard") { const QString button_str = GetKeyName(param.Get("code", 0)); - return QObject::tr("%1%2%3").arg(toggle, inverted, button_str); + return QObject::tr("%1%2%3%4").arg(turbo, toggle, inverted, button_str); } if (common_button_name == Common::Input::ButtonNames::Invalid) { @@ -181,7 +202,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { if (common_button_name == Common::Input::ButtonNames::Value) { if (param.Has("hat")) { const QString hat = GetDirectionName(param.Get("direction", "")); - return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); + return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, hat); } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); @@ -199,13 +220,13 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("button")) { const QString button = QString::fromStdString(param.Get("button", "")); - return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); + return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button); } } QString button_name = GetButtonName(common_button_name); if (param.Has("hat")) { - return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name); } if (param.Has("axis")) { return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); @@ -214,7 +235,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); } if (param.Has("button")) { - return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button_name); } return QObject::tr("[unknown]"); @@ -375,6 +396,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Turbo button"), [&] { + const bool turbo_value = !param.Get("turbo", false); + param.Set("turbo", turbo_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } if (param.Has("axis")) { context_menu.addAction(tr("Invert axis"), [&] { diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 11390fec0..c287220fc 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -81,7 +81,6 @@ void PlayerControlPreview::UpdateColors() { colors.outline = QColor(0, 0, 0); colors.primary = QColor(225, 225, 225); colors.button = QColor(109, 111, 114); - colors.button2 = QColor(109, 111, 114); colors.button2 = QColor(77, 80, 84); colors.slider_arrow = QColor(65, 68, 73); colors.font2 = QColor(0, 0, 0); @@ -100,12 +99,17 @@ void PlayerControlPreview::UpdateColors() { colors.led_off = QColor(170, 238, 255); colors.indicator2 = QColor(59, 165, 93); colors.charging = QColor(250, 168, 26); + colors.button_turbo = QColor(217, 158, 4); colors.left = colors.primary; colors.right = colors.primary; - // Possible alternative to set colors from settings - // colors.left = QColor(controller->GetColors().left.body); - // colors.right = QColor(controller->GetColors().right.body); + + const auto color_left = controller->GetColorsValues()[0].body; + const auto color_right = controller->GetColorsValues()[1].body; + if (color_left != 0 && color_right != 0) { + colors.left = QColor(color_left); + colors.right = QColor(color_right); + } } void PlayerControlPreview::ResetInputs() { @@ -2465,7 +2469,6 @@ void PlayerControlPreview::DrawJoystickDot(QPainter& p, const QPointF center, void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, const Common::Input::ButtonStatus& pressed, float width, float height, Direction direction, float radius) { - p.setBrush(button_color); if (pressed.value) { switch (direction) { case Direction::Left: @@ -2483,16 +2486,16 @@ void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, case Direction::None: break; } - p.setBrush(colors.highlight); } QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f}; + p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo)); p.drawRoundedRect(rect, radius, radius); } void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, const Common::Input::ButtonStatus& pressed, int button_size) { p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawRectangle(p, center, button_size, button_size / 3.0f); } void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, @@ -2500,7 +2503,7 @@ void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, int button_size) { // Draw outer line p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawRectangle(p, center, button_size, button_size / 3.0f); DrawRectangle(p, center, button_size / 3.0f, button_size); @@ -2522,7 +2525,7 @@ void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } @@ -2535,7 +2538,7 @@ void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } @@ -2549,17 +2552,15 @@ void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, } p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button2); + p.setBrush(GetButtonColor(colors.button2, pressed.value, pressed.turbo)); DrawPolygon(p, button_x); } void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, const Common::Input::ButtonStatus& pressed, float button_size) { - p.setBrush(button_color); - if (pressed.value) { - p.setBrush(colors.highlight); - } + + p.setBrush(GetButtonColor(button_color, pressed.value, pressed.turbo)); p.drawEllipse(center, button_size, button_size); } @@ -2616,7 +2617,7 @@ void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center, // Draw arrow button p.setPen(pressed.value ? colors.highlight : colors.button); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, arrow_button); switch (direction) { @@ -2668,10 +2669,20 @@ void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center, // Draw arrow button p.setPen(colors.outline); - p.setBrush(pressed.value ? colors.highlight : colors.button); + p.setBrush(GetButtonColor(colors.button, pressed.value, pressed.turbo)); DrawPolygon(p, qtrigger_button); } +QColor PlayerControlPreview::GetButtonColor(QColor default_color, bool is_pressed, bool turbo) { + if (is_pressed && turbo) { + return colors.button_turbo; + } + if (is_pressed) { + return colors.highlight; + } + return default_color; +} + void PlayerControlPreview::DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery) { if (battery == Common::Input::BatteryLevel::None) { diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index b258c6d77..0e9e95e85 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -81,6 +81,7 @@ private: QColor right{}; QColor button{}; QColor button2{}; + QColor button_turbo{}; QColor font{}; QColor font2{}; QColor highlight{}; @@ -183,6 +184,7 @@ private: const Common::Input::ButtonStatus& pressed, float size = 1.0f); void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, const Common::Input::ButtonStatus& pressed); + QColor GetButtonColor(QColor default_color, bool is_pressed, bool turbo); // Draw battery functions void DrawBattery(QPainter& p, QPointF center, Common::Input::BatteryLevel battery); diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index d1b870c72..fb1292f07 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -89,7 +89,6 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); SetConfiguration(); UpdateUiDisplay(); ConnectEvents(); diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 93db47cfd..7e757eafd 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -66,8 +66,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("Properties")); - // remove Help question mark button from the title bar - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); addons_tab->SetTitleId(title_id); diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index 688c2dd38..1275f10c8 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -4,9 +4,11 @@ #include <memory> #include <QKeyEvent> #include <QMenu> +#include <QMessageBox> #include <QTimer> +#include <fmt/format.h> -#include "core/hid/emulated_devices.h" +#include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" @@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, ui->buttonRingAnalogPush, }; - emulated_device = hid_core_.GetEmulatedDevices(); - emulated_device->SaveCurrentConfig(); - emulated_device->EnableConfiguration(); + emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); }, + .is_npad_service = false, + }; + callback_key = emulated_controller->SetCallback(engine_callback); + is_controller_set = true; LoadConfiguration(); @@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, HandleClick( analog_map_buttons[sub_button_id], [=, this](const Common::ParamPackage& params) { - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); }, InputCommon::Polling::InputType::Stick); }); @@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, connect(analog_button, &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) { QMenu context_menu; - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); context_menu.addAction(tr("Clear"), [&] { - emulated_device->SetRingParam({}); + emulated_controller->SetRingParam(param); analog_map_buttons[sub_button_id]->setText(tr("[not set]")); }); context_menu.addAction(tr("Invert axis"), [&] { const bool invert_value = param.Get("invert_x", "+") == "-"; const std::string invert_str = invert_value ? "+" : "-"; param.Set("invert_x", invert_str); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id2) { analog_map_buttons[sub_button_id2]->setText( @@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, } connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { - Common::ParamPackage param = emulated_device->GetRingParam(); + Common::ParamPackage param = emulated_controller->GetRingParam(); const auto slider_value = ui->sliderRingAnalogDeadzone->value(); ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value)); param.Set("deadzone", slider_value / 100.0f); - emulated_device->SetRingParam(param); + emulated_controller->SetRingParam(param); }); connect(ui->restore_defaults_button, &QPushButton::clicked, this, &ConfigureRingController::RestoreDefaults); + connect(ui->enable_ring_controller_button, &QPushButton::clicked, this, + &ConfigureRingController::EnableRingController); + timeout_timer->setSingleShot(true); connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); @@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent, } ConfigureRingController::~ConfigureRingController() { - emulated_device->DisableConfiguration(); + emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active); + emulated_controller->DisableConfiguration(); + + if (is_controller_set) { + emulated_controller->DeleteCallback(callback_key); + is_controller_set = false; + } }; void ConfigureRingController::changeEvent(QEvent* event) { @@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() { void ConfigureRingController::UpdateUI() { RetranslateUI(); - const Common::ParamPackage param = emulated_device->GetRingParam(); + const Common::ParamPackage param = emulated_controller->GetRingParam(); for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { auto* const analog_button = analog_map_buttons[sub_button_id]; @@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() { } void ConfigureRingController::ApplyConfiguration() { - emulated_device->DisableConfiguration(); - emulated_device->SaveCurrentConfig(); - emulated_device->EnableConfiguration(); + emulated_controller->DisableConfiguration(); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); } void ConfigureRingController::LoadConfiguration() { @@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() { void ConfigureRingController::RestoreDefaults() { const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys( 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); - emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); + emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string)); UpdateUI(); } +void ConfigureRingController::EnableRingController() { + const auto dialog_title = tr("Error enabling ring input"); + + is_ring_enabled = false; + ui->ring_controller_sensor_value->setText(tr("Not connected")); + + if (!Settings::values.enable_joycon_driver) { + QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled")); + return; + } + + ui->enable_ring_controller_button->setEnabled(false); + ui->enable_ring_controller_button->setText(tr("Configuring")); + // SetPollingMode is blocking. Allow to update the button status before calling the command + repaint(); + + const auto result = emulated_controller->SetPollingMode( + Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring); + switch (result) { + case Common::Input::DriverResult::Success: + is_ring_enabled = true; + break; + case Common::Input::DriverResult::NotSupported: + QMessageBox::warning(this, dialog_title, + tr("The current mapped device doesn't support the ring controller")); + break; + case Common::Input::DriverResult::NoDeviceDetected: + QMessageBox::warning(this, dialog_title, + tr("The current mapped device doesn't have a ring attached")); + break; + default: + QMessageBox::warning(this, dialog_title, + tr("Unexpected driver result %1").arg(static_cast<int>(result))); + break; + } + ui->enable_ring_controller_button->setEnabled(true); + ui->enable_ring_controller_button->setText(tr("Enable")); +} + +void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) { + if (!is_ring_enabled) { + return; + } + if (type != Core::HID::ControllerTriggerType::RingController) { + return; + } + + const auto value = emulated_controller->GetRingSensorValues(); + const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value)); + ui->ring_controller_sensor_value->setText(tex_value); +} + void ConfigureRingController::HandleClick( QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, InputCommon::Polling::InputType type) { diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h index 38a9cb716..b23c27906 100644 --- a/src/yuzu/configuration/configure_ringcon.h +++ b/src/yuzu/configuration/configure_ringcon.h @@ -13,7 +13,7 @@ class InputSubsystem; namespace Core::HID { class HIDCore; -class EmulatedDevices; +class EmulatedController; } // namespace Core::HID namespace Ui { @@ -42,6 +42,12 @@ private: /// Restore all buttons to their default values. void RestoreDefaults(); + /// Sets current polling mode to ring input + void EnableRingController(); + + // Handles emulated controller events + void ControllerUpdate(Core::HID::ControllerTriggerType type); + /// Called when the button was pressed. void HandleClick(QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, @@ -78,7 +84,11 @@ private: std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; InputCommon::InputSubsystem* input_subsystem; - Core::HID::EmulatedDevices* emulated_device; + Core::HID::EmulatedController* emulated_controller; + + bool is_ring_enabled{}; + bool is_controller_set{}; + int callback_key; std::unique_ptr<Ui::ConfigureRingController> ui; }; diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui index 9ec634dd4..514dff372 100644 --- a/src/yuzu/configuration/configure_ringcon.ui +++ b/src/yuzu/configuration/configure_ringcon.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>298</width> - <height>339</height> + <width>315</width> + <height>400</height> </rect> </property> <property name="windowTitle"> @@ -46,187 +46,283 @@ </property> </spacer> </item> - <item> - <widget class="QGroupBox" name="RingAnalog"> - <property name="title"> - <string>Ring Sensor Parameters</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="spacing"> - <number>0</number> - </property> - <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <item> + <widget class="QGroupBox" name="RingAnalog"> + <property name="title"> + <string>Virtual Ring Sensor Parameters</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_1"> <property name="spacing"> - <number>3</number> + <number>0</number> </property> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> - <property name="title"> - <string>Pull</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <property name="spacing"> + <number>3</number> </property> - <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> - <property name="spacing"> - <number>3</number> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> + <property name="title"> + <string>Pull</string> </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> + <property name="alignment"> + <set>Qt::AlignCenter</set> </property> - <property name="rightMargin"> - <number>3</number> + <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPull"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Pull</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> + <property name="title"> + <string>Push</string> </property> - <property name="bottomMargin"> - <number>3</number> + <property name="alignment"> + <set>Qt::AlignCenter</set> </property> - <item> - <widget class="QPushButton" name="buttonRingAnalogPull"> - <property name="minimumSize"> - <size> - <width>68</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> - <property name="text"> - <string>Pull</string> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPush"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Push</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> </item> - <item alignment="Qt::AlignHCenter"> - <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> - <property name="title"> - <string>Push</string> + <item> + <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + <property name="spacing"> + <number>3</number> </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> </property> - <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> - <property name="spacing"> - <number>3</number> - </property> - <property name="leftMargin"> - <number>3</number> - </property> - <property name="topMargin"> - <number>3</number> - </property> - <property name="rightMargin"> - <number>3</number> - </property> - <property name="bottomMargin"> - <number>3</number> - </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> <item> - <widget class="QPushButton" name="buttonRingAnalogPush"> - <property name="minimumSize"> - <size> - <width>68</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>68</width> - <height>16777215</height> - </size> - </property> - <property name="styleSheet"> - <string notr="true">min-width: 68px;</string> - </property> + <widget class="QLabel" name="labelRingAnalogDeadzone"> <property name="text"> - <string>Push</string> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> </property> - </widget> + </widget> </item> - </layout> - </widget> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRingAnalogDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="RingDriver"> + <property name="title"> + <string>Direct Joycon Driver</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> <property name="spacing"> - <number>3</number> + <number>0</number> </property> <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> + <enum>QLayout::SetDefaultConstraint</enum> </property> <property name="leftMargin"> - <number>0</number> + <number>3</number> </property> <property name="topMargin"> - <number>10</number> + <number>6</number> </property> <property name="rightMargin"> - <number>0</number> + <number>3</number> </property> <property name="bottomMargin"> - <number>3</number> + <number>10</number> </property> <item> - <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> - <item> - <widget class="QLabel" name="labelRingAnalogDeadzone"> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>10</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>76</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="enable_ring_controller_label"> + <property name="text"> + <string>Enable Ring Input</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="enable_ring_controller_button"> <property name="text"> - <string>Deadzone: 0%</string> + <string>Enable</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="ring_controller_sensor_label"> + <property name="text"> + <string>Ring Sensor Value</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="ring_controller_sensor_value"> + <property name="text"> + <string>Not connected</string> </property> <property name="alignment"> - <set>Qt::AlignHCenter</set> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> - </widget> + </widget> </item> - </layout> - </item> - <item> - <widget class="QSlider" name="sliderRingAnalogDeadzone"> - <property name="maximum"> - <number>100</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> + </layout> </item> - </layout> - </item> - </layout> - </widget> - </item> + </layout> + </widget> + </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> @@ -273,6 +369,6 @@ <signal>rejected()</signal> <receiver>ConfigureRingController</receiver> <slot>reject()</slot> - </connection> + </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 94049f2f4..9ea4c02da 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -31,6 +31,9 @@ constexpr std::array<u32, 7> LOCALE_BLOCKLIST{ }; static bool IsValidLocale(u32 region_index, u32 language_index) { + if (region_index >= LOCALE_BLOCKLIST.size()) { + return false; + } return ((LOCALE_BLOCKLIST.at(region_index) >> language_index) & 1) == 0; } @@ -55,8 +58,11 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) }); const auto locale_check = [this](int index) { - const bool valid_locale = - IsValidLocale(ui->combo_region->currentIndex(), ui->combo_language->currentIndex()); + const auto region_index = ConfigurationShared::GetComboboxIndex( + Settings::values.region_index.GetValue(true), ui->combo_region); + const auto language_index = ConfigurationShared::GetComboboxIndex( + Settings::values.language_index.GetValue(true), ui->combo_language); + const bool valid_locale = IsValidLocale(region_index, language_index); ui->label_warn_invalid_locale->setVisible(!valid_locale); if (!valid_locale) { ui->label_warn_invalid_locale->setText( diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 8f02880a7..a7f086258 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -42,13 +42,7 @@ private: std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; - int language_index = 0; - int region_index = 0; - int time_zone_index = 0; - int sound_index = 0; - ConfigurationShared::CheckState use_rng_seed; - ConfigurationShared::CheckState use_custom_rtc; Core::System& system; }; diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 1edc5f1f3..5a545aa70 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -17,7 +17,6 @@ ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) setFocusPolicy(Qt::ClickFocus); setWindowTitle(tr("TAS Configuration")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); connect(ui->tas_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 9bb69cab1..41ef4250a 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -58,13 +58,16 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() { std::vector<std::string> profile_names; profile_names.reserve(map_profiles.size()); - for (const auto& [profile_name, config] : map_profiles) { + auto it = map_profiles.cbegin(); + while (it != map_profiles.cend()) { + const auto& [profile_name, config] = *it; if (!ProfileExistsInFilesystem(profile_name)) { - DeleteProfile(profile_name); + it = map_profiles.erase(it); continue; } profile_names.push_back(profile_name); + ++it; } std::stable_sort(profile_names.begin(), profile_names.end()); diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 19f3775a3..e2f55ebae 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -20,9 +20,8 @@ ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_, setWindowTitle(tr("Controller P1")); resize(500, 350); setMinimumSize(500, 350); - // Remove the "?" button from the titlebar and enable the maximize button - setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | - Qt::WindowMaximizeButtonHint); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); widget = new PlayerControlPreview(this); refreshConfiguration(); diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index d3e2d3c12..493ee0b17 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -49,9 +49,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Di setObjectName(QStringLiteral("MicroProfile")); setWindowTitle(tr("&MicroProfile")); resize(1000, 600); - // Remove the "?" button from the titlebar and enable the maximize button - setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | - Qt::WindowMaximizeButtonHint); + // Enable the maximize button + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); #if MICROPROFILE_ENABLED diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp index c351e9b83..de0c307d4 100644 --- a/src/yuzu/discord_impl.cpp +++ b/src/yuzu/discord_impl.cpp @@ -4,7 +4,10 @@ #include <chrono> #include <string> #include <discord_rpc.h> +#include <fmt/format.h> +#include <httplib.h> #include "common/common_types.h" +#include "common/string_util.h" #include "core/core.h" #include "core/loader/loader.h" #include "yuzu/discord_impl.h" @@ -14,7 +17,6 @@ namespace DiscordRPC { DiscordImpl::DiscordImpl(Core::System& system_) : system{system_} { DiscordEventHandlers handlers{}; - // The number is the client ID for yuzu, it's used for images and the // application name Discord_Initialize("712465656758665259", &handlers, 1, nullptr); @@ -29,23 +31,74 @@ void DiscordImpl::Pause() { Discord_ClearPresence(); } +static std::string GetGameString(const std::string& title) { + // Convert to lowercase + std::string icon_name = Common::ToLower(title); + + // Replace spaces with dashes + std::replace(icon_name.begin(), icon_name.end(), ' ', '-'); + + // Remove non-alphanumeric characters but keep dashes + std::erase_if(icon_name, [](char c) { return !std::isalnum(c) && c != '-'; }); + + // Remove dashes from the start and end of the string + icon_name.erase(icon_name.begin(), std::find_if(icon_name.begin(), icon_name.end(), + [](int ch) { return ch != '-'; })); + icon_name.erase( + std::find_if(icon_name.rbegin(), icon_name.rend(), [](int ch) { return ch != '-'; }).base(), + icon_name.end()); + + // Remove double dashes + icon_name.erase(std::unique(icon_name.begin(), icon_name.end(), + [](char a, char b) { return a == '-' && b == '-'; }), + icon_name.end()); + + return icon_name; +} + void DiscordImpl::Update() { s64 start_time = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()) .count(); + const std::string default_text = "yuzu is an emulator for the Nintendo Switch"; + const std::string default_image = "yuzu_logo"; + std::string game_cover_url = "https://yuzu-emu.org"; std::string title; - if (system.IsPoweredOn()) { - system.GetAppLoader().ReadTitle(title); - } + DiscordRichPresence presence{}; - presence.largeImageKey = "yuzu_logo"; - presence.largeImageText = "yuzu is an emulator for the Nintendo Switch"; + if (system.IsPoweredOn()) { + system.GetAppLoader().ReadTitle(title); + + // Used to format Icon URL for yuzu website game compatibility page + std::string icon_name = GetGameString(title); + + // New Check for game cover + httplib::Client cli(game_cover_url); + + if (auto res = cli.Head(fmt::format("/images/game/boxart/{}.png", icon_name).c_str())) { + if (res->status == 200) { + game_cover_url += fmt::format("/images/game/boxart/{}.png", icon_name); + } else { + game_cover_url = "yuzu_logo"; + } + } else { + game_cover_url = "yuzu_logo"; + } + + presence.largeImageKey = game_cover_url.c_str(); + presence.largeImageText = title.c_str(); + + presence.smallImageKey = default_image.c_str(); + presence.smallImageText = default_text.c_str(); presence.state = title.c_str(); presence.details = "Currently in game"; } else { - presence.details = "Not in game"; + presence.largeImageKey = default_image.c_str(); + presence.largeImageText = default_text.c_str(); + presence.details = "Currently not in game"; } + presence.startTimestamp = start_time; Discord_UpdatePresence(&presence); } diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 13723f6e5..6530186c1 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -21,7 +21,7 @@ void HotkeyRegistry::SaveHotkeys() { {hotkey.first, group.first, UISettings::ContextualShortcut({hotkey.second.keyseq.toString(), hotkey.second.controller_keyseq, - hotkey.second.context})}); + hotkey.second.context, hotkey.second.repeat})}); } } } @@ -47,6 +47,7 @@ void HotkeyRegistry::LoadHotkeys() { hk.controller_shortcut->disconnect(); hk.controller_shortcut->SetKey(hk.controller_keyseq); } + hk.repeat = shortcut.shortcut.repeat; } } @@ -57,8 +58,7 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); } - hk.shortcut->setAutoRepeat(false); - + hk.shortcut->setAutoRepeat(hk.repeat); return hk.shortcut; } diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index dc5b7f628..848239c35 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h @@ -115,6 +115,7 @@ private: QShortcut* shortcut = nullptr; ControllerShortcut* controller_shortcut = nullptr; Qt::ShortcutContext context = Qt::WindowShortcut; + bool repeat; }; using HotkeyMap = std::map<QString, Hotkey>; diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 84ec4fe13..673bbaa83 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -46,7 +46,6 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo vbox_layout->addLayout(hbox_layout); setLayout(vbox_layout); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Install Files to NAND")); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index c278620ab..f5e6e0f58 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1015,11 +1015,6 @@ void GMainWindow::InitializeWidgets() { filter_status_button->setFocusPolicy(Qt::NoFocus); connect(filter_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleAdaptingFilter); - auto filter = Settings::values.scaling_filter.GetValue(); - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor); - } UpdateFilterText(); filter_status_button->setCheckable(true); filter_status_button->setChecked(true); @@ -2769,8 +2764,7 @@ void GMainWindow::OnMenuInstallToNAND() { ui->action_Install_File_NAND->setEnabled(false); install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); - install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & - ~Qt::WindowMaximizeButtonHint); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); install_progress->setAttribute(Qt::WA_DeleteOnClose, true); install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); install_progress->show(); @@ -3513,10 +3507,6 @@ void GMainWindow::OnToggleAdaptingFilter() { } else { filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1); } - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - filter = Settings::ScalingFilter::NearestNeighbor; - } Settings::values.scaling_filter.SetValue(filter); filter_status_button->setChecked(true); UpdateFilterText(); @@ -4513,6 +4503,11 @@ int main(int argc, char* argv[]) { } #endif +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // Disables the "?" button on all dialogs. Disabled by default on Qt6. + QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif + // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index cbd52da85..d71cc23a7 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -81,20 +81,13 @@ void DirectConnectWindow::Connect() { } } } - switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) { - case ConnectionType::TraversalServer: - break; - case ConnectionType::IP: - if (!ui->ip->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); - return; - } - if (!ui->port->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); - return; - } - break; + if (!ui->ip->hasAcceptableInput()) { + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); + return; + } + if (!ui->port->hasAcceptableInput()) { + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); + return; } // Store settings diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui index 57d6ec25a..0dd4e6829 100644 --- a/src/yuzu/multiplayer/direct_connect.ui +++ b/src/yuzu/multiplayer/direct_connect.ui @@ -27,19 +27,10 @@ <number>0</number> </property> <item> - <widget class="QComboBox" name="connection_type"> - <item> - <property name="text"> - <string>IP Address</string> - </property> - </item> - </widget> - </item> - <item> <widget class="QWidget" name="ip_container" native="true"> <layout class="QHBoxLayout" name="ip_layout"> <property name="leftMargin"> - <number>5</number> + <number>0</number> </property> <property name="topMargin"> <number>0</number> @@ -53,17 +44,17 @@ <item> <widget class="QLabel" name="label_2"> <property name="text"> - <string>IP</string> + <string>Server Address</string> </property> </widget> </item> <item> <widget class="QLineEdit" name="ip"> <property name="toolTip"> - <string><html><head/><body><p>IPv4 address of the host</p></body></html></string> + <string><html><head/><body><p>Server address of the host</p></body></html></string> </property> <property name="maxLength"> - <number>16</number> + <number>253</number> </property> </widget> </item> @@ -85,6 +76,12 @@ <property name="placeholderText"> <string notr="true" extracomment="placeholder string that tells user default port">24872</string> </property> + <property name="maximumSize"> + <size> + <width>65</width> + <height>50</height> + </size> + </property> </widget> </item> </layout> diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 08c275696..6c93e3511 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -77,6 +77,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, // UI Buttons connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); + connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); @@ -329,6 +330,16 @@ bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s return true; } + // filter by empty rooms + if (filter_empty) { + QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); + int player_count = + sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size(); + if (player_count == 0) { + return false; + } + } + // filter by filled rooms if (filter_full) { QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); @@ -399,6 +410,11 @@ void LobbyFilterProxyModel::SetFilterOwned(bool filter) { invalidate(); } +void LobbyFilterProxyModel::SetFilterEmpty(bool filter) { + filter_empty = filter; + invalidate(); +} + void LobbyFilterProxyModel::SetFilterFull(bool filter) { filter_full = filter; invalidate(); diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 300dad13e..2674ae7c3 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -130,12 +130,14 @@ public: public slots: void SetFilterOwned(bool); + void SetFilterEmpty(bool); void SetFilterFull(bool); void SetFilterSearch(const QString&); private: QStandardItemModel* game_list; bool filter_owned = false; + bool filter_empty = false; bool filter_full = false; QString filter_search; }; diff --git a/src/yuzu/multiplayer/lobby.ui b/src/yuzu/multiplayer/lobby.ui index 4c9901c9a..0ef0ef762 100644 --- a/src/yuzu/multiplayer/lobby.ui +++ b/src/yuzu/multiplayer/lobby.ui @@ -78,6 +78,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="hide_empty"> + <property name="text"> + <string>Hide Empty Rooms</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="hide_full"> <property name="text"> <string>Hide Full Rooms</string> diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h index dd25af280..cbbe6757b 100644 --- a/src/yuzu/multiplayer/validation.h +++ b/src/yuzu/multiplayer/validation.h @@ -38,11 +38,28 @@ private: QRegularExpression(QStringLiteral("^[a-zA-Z0-9._ -]{4,20}")); QRegularExpressionValidator nickname; - /// ipv4 address only - // TODO remove this when we support hostnames in direct connect + /// ipv4 / ipv6 / hostnames QRegularExpression ip_regex = QRegularExpression(QStringLiteral( - "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|" - "2[0-4][0-9]|25[0-5])")); + // IPv4 regex + "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|" + // IPv6 regex + "^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|" + "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-" + "5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)" + "(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]" + "\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[" + "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" + "(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?" + "\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|" + // Hostname regex + "^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$")); QRegularExpressionValidator ip; /// port must be between 0 and 65535 diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 2006b883e..db43b7033 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -22,6 +22,7 @@ struct ContextualShortcut { QString keyseq; QString controller_keyseq; int context; + bool repeat; }; struct Shortcut { diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp index bbb370595..5f6a9c193 100644 --- a/src/yuzu/util/limitable_input_dialog.cpp +++ b/src/yuzu/util/limitable_input_dialog.cpp @@ -16,8 +16,6 @@ LimitableInputDialog::LimitableInputDialog(QWidget* parent) : QDialog{parent} { LimitableInputDialog::~LimitableInputDialog() = default; void LimitableInputDialog::CreateUI() { - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - text_label = new QLabel(this); text_entry = new QLineEdit(this); text_label_invalid = new QLabel(this); diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp index 4b10fa517..1670aa596 100644 --- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp @@ -8,7 +8,6 @@ SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Enter a hotkey")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); key_sequence = new QKeySequenceEdit; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 527017282..9c34cdc6e 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -176,6 +176,9 @@ void Config::ReadValues() { Settings::values.debug_pad_analogs[i] = default_param; } + ReadSetting("ControlsGeneral", Settings::values.enable_raw_input); + ReadSetting("ControlsGeneral", Settings::values.enable_joycon_driver); + ReadSetting("ControlsGeneral", Settings::values.emulate_analog_keyboard); ReadSetting("ControlsGeneral", Settings::values.vibration_enabled); ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations); ReadSetting("ControlsGeneral", Settings::values.motion_enabled); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6fcf04e1b..cf3cc4c4e 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -5,8 +5,8 @@ namespace DefaultINI { -const char* sdl2_config_file = R"( - +const char* sdl2_config_file = + R"( [ControlsP0] # The input devices and parameters for each Switch native input # The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... @@ -14,6 +14,7 @@ const char* sdl2_config_file = R"( # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values # Indicates if this player should be connected at boot +# 0 (default): Disabled, 1: Enabled connected= # for button input, the following devices are available: @@ -94,6 +95,18 @@ motionright= # 0 (default): Disabled, 1: Enabled debug_pad_enabled = +# Enable sdl raw input. Allows to configure up to 8 xinput controllers. +# 0 (default): Disabled, 1: Enabled +enable_raw_input = + +# Enable yuzu joycon driver instead of SDL drive. +# 0: Disabled, 1 (default): Enabled +enable_joycon_driver = + +# Emulates an analog input from buttons. Allowing to dial any angle. +# 0 (default): Disabled, 1: Enabled +emulate_analog_keyboard = + # Whether to enable or disable vibration # 0: Disabled, 1 (default): Enabled vibration_enabled= @@ -143,6 +156,8 @@ mouse_enabled = # 0 (default): Disabled, 1: Enabled keyboard_enabled = +)" + R"( [Core] # Whether to use multi-core for CPU emulation # 0: Disabled, 1 (default): Enabled @@ -242,6 +257,8 @@ cpuopt_unsafe_fastmem_check = # 0: Disabled, 1 (default): Enabled cpuopt_unsafe_ignore_global_monitor = +)" + R"( [Renderer] # Which backend API to use. # 0: OpenGL, 1 (default): Vulkan @@ -269,11 +286,14 @@ vulkan_device = # 0: 0.5x (360p/540p) [EXPERIMENTAL] # 1: 0.75x (540p/810p) [EXPERIMENTAL] # 2 (default): 1x (720p/1080p) -# 3: 2x (1440p/2160p) -# 4: 3x (2160p/3240p) -# 5: 4x (2880p/4320p) -# 6: 5x (3600p/5400p) -# 7: 6x (4320p/6480p) +# 3: 1.5x (1080p/1620p) [EXPERIMENTAL] +# 4: 2x (1440p/2160p) +# 5: 3x (2160p/3240p) +# 6: 4x (2880p/4320p) +# 7: 5x (3600p/5400p) +# 8: 6x (4320p/6480p) +# 9: 7x (5040p/7560p) +# 10: 8x (5760/8640p) resolution_setup = # Pixel filter to use when up- or down-sampling rendered frames. @@ -282,11 +302,11 @@ resolution_setup = # 2: Bicubic # 3: Gaussian # 4: ScaleForce -# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only] +# 5: AMD FidelityFX™️ Super Resolution scaling_filter = # Anti-Aliasing (AA) -# 0 (default): None, 1: FXAA +# 0 (default): None, 1: FXAA, 2: SMAA anti_aliasing = # Whether to use fullscreen or borderless window mode @@ -360,6 +380,8 @@ bg_red = bg_blue = bg_green = +)" + R"( [Audio] # Which audio output engine to use. # auto (default): Auto-select diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 31f28a507..5450b8c38 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -18,11 +18,11 @@ EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : input_subsystem{input_subsystem_}, system{system_} { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { + input_subsystem->Initialize(); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); } - input_subsystem->Initialize(); SDL_SetMainReady(); } @@ -32,10 +32,6 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_Quit(); } -void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { - input_subsystem->GetMouse()->MouseMove(x, y, 0, 0, 0, 0); -} - InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { switch (button) { case SDL_BUTTON_LEFT: @@ -53,44 +49,36 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons } } +std::pair<float, float> EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const { + int w, h; + SDL_GetWindowSize(render_window, &w, &h); + const float fx = static_cast<float>(touch_x) / w; + const float fy = static_cast<float>(touch_y) / h; + + return {std::clamp<float>(fx, 0.0f, 1.0f), std::clamp<float>(fy, 0.0f, 1.0f)}; +} + void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { const auto mouse_button = SDLButtonToMouseButton(button); if (state == SDL_PRESSED) { - input_subsystem->GetMouse()->PressButton(x, y, 0, 0, mouse_button); + const auto [touch_x, touch_y] = MouseToTouchPos(x, y); + input_subsystem->GetMouse()->PressButton(x, y, touch_x, touch_y, mouse_button); } else { input_subsystem->GetMouse()->ReleaseButton(mouse_button); } } -std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const { - int w, h; - SDL_GetWindowSize(render_window, &w, &h); - - touch_x *= w; - touch_y *= h; - - return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)), - static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))}; +void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { + const auto [touch_x, touch_y] = MouseToTouchPos(x, y); + input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, 0, 0); } void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) { - int width, height; - SDL_GetWindowSize(render_window, &width, &height); - const auto [px, py] = TouchToPixelPos(x, y); - const float fx = px * 1.0f / width; - const float fy = py * 1.0f / height; - - input_subsystem->GetTouchScreen()->TouchPressed(fx, fy, id); + input_subsystem->GetTouchScreen()->TouchPressed(x, y, id); } void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { - int width, height; - SDL_GetWindowSize(render_window, &width, &height); - const auto [px, py] = TouchToPixelPos(x, y); - const float fx = px * 1.0f / width; - const float fy = py * 1.0f / height; - - input_subsystem->GetTouchScreen()->TouchMoved(fx, fy, id); + input_subsystem->GetTouchScreen()->TouchMoved(x, y, id); } void EmuWindow_SDL2::OnFingerUp() { diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index 25c23e2a5..d9b453dee 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -38,17 +38,17 @@ protected: /// Called by WaitEvent when a key is pressed or released. void OnKeyEvent(int key, u8 state); - /// Called by WaitEvent when the mouse moves. - void OnMouseMotion(s32 x, s32 y); - /// Converts a SDL mouse button into MouseInput mouse button InputCommon::MouseButton SDLButtonToMouseButton(u32 button) const; + /// Translates pixel position to float position + std::pair<float, float> MouseToTouchPos(s32 touch_x, s32 touch_y) const; + /// Called by WaitEvent when a mouse button is pressed or released void OnMouseButton(u32 button, u8 state, s32 x, s32 y); - /// Translates pixel position (0..1) to pixel positions - std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const; + /// Called by WaitEvent when the mouse moves. + void OnMouseMotion(s32 x, s32 y); /// Called by WaitEvent when a finger starts touching the touchscreen void OnFingerDown(float x, float y, std::size_t id); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 91133569d..d1f7b1d49 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -62,13 +62,15 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] <filename>\n" - "-m, --multiplayer=nick:password@address:port" - " Nickname, password, address and port for multiplayer\n" + "-c, --config Load the specified configuration file\n" "-f, --fullscreen Start in fullscreen mode\n" + "-g, --game File path of the game to load\n" "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n" + "-m, --multiplayer=nick:password@address:port" + " Nickname, password, address and port for multiplayer\n" "-p, --program Pass following string as arguments to executable\n" - "-c, --config Load the specified configuration file\n"; + "-u, --user Select a specific user profile from 0 to 7\n" + "-v, --version Output version information and exit\n"; } static void PrintVersion() { @@ -199,6 +201,7 @@ int main(int argc, char** argv) { std::string filepath; std::optional<std::string> config_path; std::string program_args; + std::optional<int> selected_user; bool use_multiplayer = false; bool fullscreen = false; @@ -209,12 +212,14 @@ int main(int argc, char** argv) { static struct option long_options[] = { // clang-format off - {"multiplayer", required_argument, 0, 'm'}, + {"config", required_argument, 0, 'c'}, {"fullscreen", no_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, + {"game", required_argument, 0, 'g'}, + {"multiplayer", required_argument, 0, 'm'}, {"program", optional_argument, 0, 'p'}, - {"config", required_argument, 0, 'c'}, + {"user", required_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, {0, 0, 0, 0}, // clang-format on }; @@ -223,6 +228,21 @@ int main(int argc, char** argv) { int arg = getopt_long(argc, argv, "g:fhvp::c:", long_options, &option_index); if (arg != -1) { switch (static_cast<char>(arg)) { + case 'c': + config_path = optarg; + break; + case 'f': + fullscreen = true; + LOG_INFO(Frontend, "Starting in fullscreen mode..."); + break; + case 'h': + PrintHelp(argv[0]); + return 0; + case 'g': { + const std::string str_arg(optarg); + filepath = str_arg; + break; + } case 'm': { use_multiplayer = true; const std::string str_arg(optarg); @@ -255,23 +275,16 @@ int main(int argc, char** argv) { } break; } - case 'f': - fullscreen = true; - LOG_INFO(Frontend, "Starting in fullscreen mode..."); + case 'p': + program_args = argv[optind]; + ++optind; break; - case 'h': - PrintHelp(argv[0]); + case 'u': + selected_user = atoi(optarg); return 0; case 'v': PrintVersion(); return 0; - case 'p': - program_args = argv[optind]; - ++optind; - break; - case 'c': - config_path = optarg; - break; } } else { #ifdef _WIN32 @@ -295,6 +308,10 @@ int main(int argc, char** argv) { Settings::values.program_args = program_args; } + if (selected_user.has_value()) { + Settings::values.current_user = std::clamp(*selected_user, 0, 7); + } + #ifdef _WIN32 LocalFree(argv_w); #endif |