diff options
Diffstat (limited to 'src')
263 files changed, 12601 insertions, 5655 deletions
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 3aea9b0f2..5005ba519 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -54,8 +54,9 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double l = 0.0; double r = 0.0; for (std::size_t j = 0; j < h.size(); j++) { - l += Lanczos(taps, pos + j - taps + 1) * h[j][0]; - r += Lanczos(taps, pos + j - taps + 1) * h[j][1]; + const double lanczos_calc = Lanczos(taps, pos + j - taps + 1); + l += lanczos_calc * h[j][0]; + r += lanczos_calc * h[j][1]; } output.emplace_back(static_cast<s16>(std::clamp(l, -32768.0, 32767.0))); output.emplace_back(static_cast<s16>(std::clamp(r, -32768.0, 32767.0))); diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 0c8f5b18e..cbba17632 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) { StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name, Stream::ReleaseCallback&& release_callback) { if (!sink) { - const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id); - sink = sink_details.factory(Settings::values.audio_device_id); + sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id); } return std::make_shared<Stream>( diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 23e5d3f10..2683f3a5f 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -8,7 +8,7 @@ #include "audio_core/codec.h" #include "common/assert.h" #include "common/logging/log.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/writable_event.h" #include "core/memory.h" namespace AudioCore { @@ -72,7 +72,7 @@ private: EffectInStatus info{}; }; AudioRenderer::AudioRenderer(AudioRendererParameter params, - Kernel::SharedPtr<Kernel::Event> buffer_event) + Kernel::SharedPtr<Kernel::WritableEvent> buffer_event) : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), effects(params.effect_count) { @@ -285,8 +285,11 @@ void AudioRenderer::VoiceState::RefreshBuffer() { break; } - samples = - Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE); + // Only interpolate when necessary, expensive. + if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { + samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, + STREAM_SAMPLE_RATE); + } is_refresh_pending = false; } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index 71ba4be40..7826881bf 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -15,7 +15,7 @@ #include "core/hle/kernel/object.h" namespace Kernel { -class Event; +class WritableEvent; } namespace AudioCore { @@ -208,7 +208,8 @@ static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size class AudioRenderer { public: - AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event); + AudioRenderer(AudioRendererParameter params, + Kernel::SharedPtr<Kernel::WritableEvent> buffer_event); ~AudioRenderer(); std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params); @@ -224,7 +225,7 @@ private: class VoiceState; AudioRendererParameter worker_params; - Kernel::SharedPtr<Kernel::Event> buffer_event; + Kernel::SharedPtr<Kernel::WritableEvent> buffer_event; std::vector<VoiceState> voices; std::vector<EffectState> effects; std::unique_ptr<AudioOut> audio_out; diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index d31a1c844..097328901 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -107,7 +107,7 @@ private: static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); }; -CubebSink::CubebSink(std::string target_device_name) { +CubebSink::CubebSink(std::string_view target_device_name) { if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); return; diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 59cbf05e9..efb9d1634 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -15,7 +15,7 @@ namespace AudioCore { class CubebSink final : public Sink { public: - explicit CubebSink(std::string device_id); + explicit CubebSink(std::string_view device_id); ~CubebSink() override; SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index a78d78893..61a28d542 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -10,7 +10,7 @@ namespace AudioCore { class NullSink final : public Sink { public: - explicit NullSink(std::string){}; + explicit NullSink(std::string_view) {} ~NullSink() override = default; SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 67cf1f3b2..a848eb1c9 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -14,31 +14,68 @@ #include "common/logging/log.h" namespace AudioCore { +namespace { +struct SinkDetails { + using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); + using ListDevicesFn = std::vector<std::string> (*)(); -// g_sink_details is ordered in terms of desirability, with the best choice at the top. -const std::vector<SinkDetails> g_sink_details = { + /// Name for this sink. + const char* id; + /// A method to call to construct an instance of this type of sink. + FactoryFn factory; + /// A method to call to list available devices. + ListDevicesFn list_devices; +}; + +// sink_details is ordered in terms of desirability, with the best choice at the top. +constexpr SinkDetails sink_details[] = { #ifdef HAVE_CUBEB - SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices}, + SinkDetails{"cubeb", + [](std::string_view device_id) -> std::unique_ptr<Sink> { + return std::make_unique<CubebSink>(device_id); + }, + &ListCubebSinkDevices}, #endif - SinkDetails{"null", &std::make_unique<NullSink, std::string>, + SinkDetails{"null", + [](std::string_view device_id) -> std::unique_ptr<Sink> { + return std::make_unique<NullSink>(device_id); + }, [] { return std::vector<std::string>{"null"}; }}, }; const SinkDetails& GetSinkDetails(std::string_view sink_id) { auto iter = - std::find_if(g_sink_details.begin(), g_sink_details.end(), + std::find_if(std::begin(sink_details), std::end(sink_details), [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); - if (sink_id == "auto" || iter == g_sink_details.end()) { + if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); } // Auto-select. - // g_sink_details is ordered in terms of desirability, with the best choice at the front. - iter = g_sink_details.begin(); + // sink_details is ordered in terms of desirability, with the best choice at the front. + iter = std::begin(sink_details); } return *iter; } +} // Anonymous namespace + +std::vector<const char*> GetSinkIDs() { + std::vector<const char*> sink_ids(std::size(sink_details)); + + std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), + [](const auto& sink) { return sink.id; }); + + return sink_ids; +} + +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { + return GetSinkDetails(sink_id).list_devices(); +} + +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { + return GetSinkDetails(sink_id).factory(device_id); +} } // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 03534b187..bc8786270 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -4,34 +4,21 @@ #pragma once -#include <functional> -#include <memory> #include <string> #include <string_view> -#include <utility> #include <vector> namespace AudioCore { class Sink; -struct SinkDetails { - using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>; - using ListDevicesFn = std::function<std::vector<std::string>()>; +/// Retrieves the IDs for all available audio sinks. +std::vector<const char*> GetSinkIDs(); - SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_) - : id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {} +/// Gets the list of devices for a particular sink identified by the given ID. +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); - /// Name for this sink. - const char* id; - /// A method to call to construct an instance of this type of sink. - FactoryFn factory; - /// A method to call to list available devices. - ListDevicesFn list_devices; -}; - -extern const std::vector<SinkDetails> g_sink_details; - -const SinkDetails& GetSinkDetails(std::string_view sink_id); +/// Creates an audio sink identified by the given device ID. +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); } // namespace AudioCore diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index eccd8f64a..a5e71d879 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -44,7 +44,6 @@ add_library(common STATIC detached_tasks.cpp detached_tasks.h bit_field.h - bit_set.h cityhash.cpp cityhash.h color.h @@ -95,14 +94,9 @@ if(ARCHITECTURE_x86_64) PRIVATE x64/cpu_detect.cpp x64/cpu_detect.h - x64/xbyak_abi.h - x64/xbyak_util.h ) endif() create_target_directory_groups(common) target_link_libraries(common PUBLIC Boost::boost fmt microprofile) -if (ARCHITECTURE_x86_64) - target_link_libraries(common PRIVATE xbyak) -endif() diff --git a/src/common/assert.h b/src/common/assert.h index 0d4eddc19..6002f7ab1 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -52,5 +52,8 @@ __declspec(noinline, noreturn) #define DEBUG_ASSERT_MSG(_a_, _desc_, ...) #endif -#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") +#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!") #define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__) + +#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") +#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) diff --git a/src/common/bit_set.h b/src/common/bit_set.h deleted file mode 100644 index 5cd1352b2..000000000 --- a/src/common/bit_set.h +++ /dev/null @@ -1,244 +0,0 @@ -// This file is under the public domain. - -#pragma once - -#include <cstddef> -#ifdef _WIN32 -#include <intrin.h> -#endif -#include <initializer_list> -#include <new> -#include <type_traits> -#include "common/common_types.h" - -// namespace avoids conflict with OS X Carbon; don't use BitSet<T> directly -namespace Common { - -// Helper functions: - -#ifdef _MSC_VER -template <typename T> -static inline int CountSetBits(T v) { - // from https://graphics.stanford.edu/~seander/bithacks.html - // GCC has this built in, but MSVC's intrinsic will only emit the actual - // POPCNT instruction, which we're not depending on - v = v - ((v >> 1) & (T) ~(T)0 / 3); - v = (v & (T) ~(T)0 / 15 * 3) + ((v >> 2) & (T) ~(T)0 / 15 * 3); - v = (v + (v >> 4)) & (T) ~(T)0 / 255 * 15; - return (T)(v * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8; -} -static inline int LeastSignificantSetBit(u8 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u16 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u32 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u64 val) { - unsigned long index; - _BitScanForward64(&index, val); - return (int)index; -} -#else -static inline int CountSetBits(u8 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u16 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u32 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u64 val) { - return __builtin_popcountll(val); -} -static inline int LeastSignificantSetBit(u8 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u16 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u32 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u64 val) { - return __builtin_ctzll(val); -} -#endif - -// Similar to std::bitset, this is a class which encapsulates a bitset, i.e. -// using the set bits of an integer to represent a set of integers. Like that -// class, it acts like an array of bools: -// BitSet32 bs; -// bs[1] = true; -// but also like the underlying integer ([0] = least significant bit): -// BitSet32 bs2 = ...; -// bs = (bs ^ bs2) & BitSet32(0xffff); -// The following additional functionality is provided: -// - Construction using an initializer list. -// BitSet bs { 1, 2, 4, 8 }; -// - Efficiently iterating through the set bits: -// for (int i : bs) -// [i is the *index* of a set bit] -// (This uses the appropriate CPU instruction to find the next set bit in one -// operation.) -// - Counting set bits using .Count() - see comment on that method. - -// TODO: use constexpr when MSVC gets out of the Dark Ages - -template <typename IntTy> -class BitSet { - static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types"); - -public: - // A reference to a particular bit, returned from operator[]. - class Ref { - public: - Ref(Ref&& other) : m_bs(other.m_bs), m_mask(other.m_mask) {} - Ref(BitSet* bs, IntTy mask) : m_bs(bs), m_mask(mask) {} - operator bool() const { - return (m_bs->m_val & m_mask) != 0; - } - bool operator=(bool set) { - m_bs->m_val = (m_bs->m_val & ~m_mask) | (set ? m_mask : 0); - return set; - } - - private: - BitSet* m_bs; - IntTy m_mask; - }; - - // A STL-like iterator is required to be able to use range-based for loops. - class Iterator { - public: - Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {} - Iterator(IntTy val) : m_val(val), m_bit(0) {} - Iterator& operator=(Iterator other) { - new (this) Iterator(other); - return *this; - } - int operator*() { - return m_bit + ComputeLsb(); - } - Iterator& operator++() { - int lsb = ComputeLsb(); - m_val >>= lsb + 1; - m_bit += lsb + 1; - m_has_lsb = false; - return *this; - } - Iterator operator++(int _) { - Iterator other(*this); - ++*this; - return other; - } - bool operator==(Iterator other) const { - return m_val == other.m_val; - } - bool operator!=(Iterator other) const { - return m_val != other.m_val; - } - - private: - int ComputeLsb() { - if (!m_has_lsb) { - m_lsb = LeastSignificantSetBit(m_val); - m_has_lsb = true; - } - return m_lsb; - } - IntTy m_val; - int m_bit; - int m_lsb = -1; - bool m_has_lsb = false; - }; - - BitSet() : m_val(0) {} - explicit BitSet(IntTy val) : m_val(val) {} - BitSet(std::initializer_list<int> init) { - m_val = 0; - for (int bit : init) - m_val |= (IntTy)1 << bit; - } - - static BitSet AllTrue(std::size_t count) { - return BitSet(count == sizeof(IntTy) * 8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1)); - } - - Ref operator[](std::size_t bit) { - return Ref(this, (IntTy)1 << bit); - } - const Ref operator[](std::size_t bit) const { - return (*const_cast<BitSet*>(this))[bit]; - } - bool operator==(BitSet other) const { - return m_val == other.m_val; - } - bool operator!=(BitSet other) const { - return m_val != other.m_val; - } - bool operator<(BitSet other) const { - return m_val < other.m_val; - } - bool operator>(BitSet other) const { - return m_val > other.m_val; - } - BitSet operator|(BitSet other) const { - return BitSet(m_val | other.m_val); - } - BitSet operator&(BitSet other) const { - return BitSet(m_val & other.m_val); - } - BitSet operator^(BitSet other) const { - return BitSet(m_val ^ other.m_val); - } - BitSet operator~() const { - return BitSet(~m_val); - } - BitSet& operator|=(BitSet other) { - return *this = *this | other; - } - BitSet& operator&=(BitSet other) { - return *this = *this & other; - } - BitSet& operator^=(BitSet other) { - return *this = *this ^ other; - } - operator u32() = delete; - operator bool() { - return m_val != 0; - } - - // Warning: Even though on modern CPUs this is a single fast instruction, - // Dolphin's official builds do not currently assume POPCNT support on x86, - // so slower explicit bit twiddling is generated. Still should generally - // be faster than a loop. - unsigned int Count() const { - return CountSetBits(m_val); - } - - Iterator begin() const { - return Iterator(m_val); - } - Iterator end() const { - return Iterator(0); - } - - IntTy m_val; -}; - -} // namespace Common - -typedef Common::BitSet<u8> BitSet8; -typedef Common::BitSet<u16> BitSet16; -typedef Common::BitSet<u32> BitSet32; -typedef Common::BitSet<u64> BitSet64; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 5753b871a..12f6d0114 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -13,7 +13,7 @@ #include <vector> #ifdef _WIN32 #include <share.h> // For _SH_DENYWR -#include <windows.h> // For OutputDebugStringA +#include <windows.h> // For OutputDebugStringW #else #define _SH_DENYWR 0 #endif @@ -148,7 +148,7 @@ void FileBackend::Write(const Entry& entry) { void DebuggerBackend::Write(const Entry& entry) { #ifdef _WIN32 - ::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str()); + ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); #endif } diff --git a/src/common/math_util.h b/src/common/math_util.h index 343cdd902..94b4394c5 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -4,18 +4,12 @@ #pragma once -#include <algorithm> #include <cstdlib> #include <type_traits> namespace MathUtil { -static constexpr float PI = 3.14159265f; - -inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, - unsigned length1) { - return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); -} +constexpr float PI = 3.14159265f; template <class T> struct Rectangle { @@ -24,16 +18,16 @@ struct Rectangle { T right{}; T bottom{}; - Rectangle() = default; + constexpr Rectangle() = default; - Rectangle(T left, T top, T right, T bottom) + constexpr Rectangle(T left, T top, T right, T bottom) : left(left), top(top), right(right), bottom(bottom) {} T GetWidth() const { - return std::abs(static_cast<typename std::make_signed<T>::type>(right - left)); + return std::abs(static_cast<std::make_signed_t<T>>(right - left)); } T GetHeight() const { - return std::abs(static_cast<typename std::make_signed<T>::type>(bottom - top)); + return std::abs(static_cast<std::make_signed_t<T>>(bottom - top)); } Rectangle<T> TranslateX(const T x) const { return Rectangle{left + x, top, right + x, bottom}; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 14f7037d8..959f278aa 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -214,6 +214,15 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t return std::string(buffer, len); } +std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, + std::size_t max_len) { + std::size_t len = 0; + while (len < max_len && buffer[len] != '\0') + ++len; + + return std::u16string(buffer.begin(), buffer.begin() + len); +} + const char* TrimSourcePath(const char* path, const char* root) { const char* p = path; diff --git a/src/common/string_util.h b/src/common/string_util.h index 08f96533b..583fd05e6 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -67,6 +67,14 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); /** + * Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't + * null-terminated, then the string ends at the greatest multiple of two less then or equal to + * max_len_bytes. + */ +std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, + std::size_t max_len); + +/** * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's * intended to be used to strip a system-specific build directory from the `__FILE__` macro, * leaving only the path relative to the sources root. diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 9e207118f..5144c0d9f 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -25,23 +25,6 @@ namespace Common { -int CurrentThreadId() { -#ifdef _MSC_VER - return GetCurrentThreadId(); -#elif defined __APPLE__ - return mach_thread_self(); -#else - return 0; -#endif -} - -#ifdef _WIN32 -// Supporting functions -void SleepCurrentThread(int ms) { - Sleep(ms); -} -#endif - #ifdef _MSC_VER void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) { @@ -62,7 +45,7 @@ void SwitchCurrentThread() { // This is implemented much nicer in upcoming msvc++, see: // http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.100).aspx -void SetCurrentThreadName(const char* szThreadName) { +void SetCurrentThreadName(const char* name) { static const DWORD MS_VC_EXCEPTION = 0x406D1388; #pragma pack(push, 8) @@ -75,7 +58,7 @@ void SetCurrentThreadName(const char* szThreadName) { #pragma pack(pop) info.dwType = 0x1000; - info.szName = szThreadName; + info.szName = name; info.dwThreadID = -1; // dwThreadID; info.dwFlags = 0; @@ -107,10 +90,6 @@ void SetCurrentThreadAffinity(u32 mask) { } #ifndef _WIN32 -void SleepCurrentThread(int ms) { - usleep(1000 * ms); -} - void SwitchCurrentThread() { usleep(1000 * 1); } @@ -118,15 +97,15 @@ void SwitchCurrentThread() { // MinGW with the POSIX threading model does not support pthread_setname_np #if !defined(_WIN32) || defined(_MSC_VER) -void SetCurrentThreadName(const char* szThreadName) { +void SetCurrentThreadName(const char* name) { #ifdef __APPLE__ - pthread_setname_np(szThreadName); + pthread_setname_np(name); #elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), szThreadName); + pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) - pthread_setname_np(pthread_self(), "%s", (void*)szThreadName); + pthread_setname_np(pthread_self(), "%s", (void*)name); #else - pthread_setname_np(pthread_self(), szThreadName); + pthread_setname_np(pthread_self(), name); #endif } #endif diff --git a/src/common/thread.h b/src/common/thread.h index 6cbdb96a3..2cf74452d 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -13,15 +13,8 @@ namespace Common { -int CurrentThreadId(); - -void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask); -void SetCurrentThreadAffinity(u32 mask); - class Event { public: - Event() : is_set(false) {} - void Set() { std::lock_guard<std::mutex> lk(mutex); if (!is_set) { @@ -53,14 +46,14 @@ public: } private: - bool is_set; + bool is_set = false; std::condition_variable condvar; std::mutex mutex; }; class Barrier { public: - explicit Barrier(std::size_t count_) : count(count_), waiting(0), generation(0) {} + explicit Barrier(std::size_t count_) : count(count_) {} /// Blocks until all "count" threads have called Sync() void Sync() { @@ -80,12 +73,13 @@ public: private: std::condition_variable condvar; std::mutex mutex; - const std::size_t count; - std::size_t waiting; - std::size_t generation; // Incremented once each time the barrier is used + std::size_t count; + std::size_t waiting = 0; + std::size_t generation = 0; // Incremented once each time the barrier is used }; -void SleepCurrentThread(int ms); +void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask); +void SetCurrentThreadAffinity(u32 mask); void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms void SetCurrentThreadName(const char* name); diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h deleted file mode 100644 index 636a5c0f9..000000000 --- a/src/common/x64/xbyak_abi.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <initializer_list> -#include <xbyak.h> -#include "common/assert.h" -#include "common/bit_set.h" - -namespace Common::X64 { - -inline int RegToIndex(const Xbyak::Reg& reg) { - using Kind = Xbyak::Reg::Kind; - ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, - "RegSet only support GPRs and XMM registers."); - ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); - return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); -} - -inline Xbyak::Reg64 IndexToReg64(int reg_index) { - ASSERT(reg_index < 16); - return Xbyak::Reg64(reg_index); -} - -inline Xbyak::Xmm IndexToXmm(int reg_index) { - ASSERT(reg_index >= 16 && reg_index < 32); - return Xbyak::Xmm(reg_index - 16); -} - -inline Xbyak::Reg IndexToReg(int reg_index) { - if (reg_index < 16) { - return IndexToReg64(reg_index); - } else { - return IndexToXmm(reg_index); - } -} - -inline BitSet32 BuildRegSet(std::initializer_list<Xbyak::Reg> regs) { - BitSet32 bits; - for (const Xbyak::Reg& reg : regs) { - bits[RegToIndex(reg)] = true; - } - return bits; -} - -const BitSet32 ABI_ALL_GPRS(0x0000FFFF); -const BitSet32 ABI_ALL_XMMS(0xFFFF0000); - -#ifdef _WIN32 - -// Microsoft x64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; - -const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rcx, - Xbyak::util::rdx, - Xbyak::util::r8, - Xbyak::util::r9, - Xbyak::util::r10, - Xbyak::util::r11, - // XMMs - Xbyak::util::xmm0, - Xbyak::util::xmm1, - Xbyak::util::xmm2, - Xbyak::util::xmm3, - Xbyak::util::xmm4, - Xbyak::util::xmm5, -}); - -const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rbx, - Xbyak::util::rsi, - Xbyak::util::rdi, - Xbyak::util::rbp, - Xbyak::util::r12, - Xbyak::util::r13, - Xbyak::util::r14, - Xbyak::util::r15, - // XMMs - Xbyak::util::xmm6, - Xbyak::util::xmm7, - Xbyak::util::xmm8, - Xbyak::util::xmm9, - Xbyak::util::xmm10, - Xbyak::util::xmm11, - Xbyak::util::xmm12, - Xbyak::util::xmm13, - Xbyak::util::xmm14, - Xbyak::util::xmm15, -}); - -constexpr std::size_t ABI_SHADOW_SPACE = 0x20; - -#else - -// System V x86-64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; - -const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rcx, - Xbyak::util::rdx, - Xbyak::util::rdi, - Xbyak::util::rsi, - Xbyak::util::r8, - Xbyak::util::r9, - Xbyak::util::r10, - Xbyak::util::r11, - // XMMs - Xbyak::util::xmm0, - Xbyak::util::xmm1, - Xbyak::util::xmm2, - Xbyak::util::xmm3, - Xbyak::util::xmm4, - Xbyak::util::xmm5, - Xbyak::util::xmm6, - Xbyak::util::xmm7, - Xbyak::util::xmm8, - Xbyak::util::xmm9, - Xbyak::util::xmm10, - Xbyak::util::xmm11, - Xbyak::util::xmm12, - Xbyak::util::xmm13, - Xbyak::util::xmm14, - Xbyak::util::xmm15, -}); - -const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rbx, - Xbyak::util::rbp, - Xbyak::util::r12, - Xbyak::util::r13, - Xbyak::util::r14, - Xbyak::util::r15, -}); - -constexpr std::size_t ABI_SHADOW_SPACE = 0; - -#endif - -inline void ABI_CalculateFrameSize(BitSet32 regs, std::size_t rsp_alignment, - std::size_t needed_frame_size, s32* out_subtraction, - s32* out_xmm_offset) { - int count = (regs & ABI_ALL_GPRS).Count(); - rsp_alignment -= count * 8; - std::size_t subtraction = 0; - int xmm_count = (regs & ABI_ALL_XMMS).Count(); - if (xmm_count) { - // If we have any XMMs to save, we must align the stack here. - subtraction = rsp_alignment & 0xF; - } - subtraction += 0x10 * xmm_count; - std::size_t xmm_base_subtraction = subtraction; - subtraction += needed_frame_size; - subtraction += ABI_SHADOW_SPACE; - // Final alignment. - rsp_alignment -= subtraction; - subtraction += rsp_alignment & 0xF; - - *out_subtraction = (s32)subtraction; - *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction); -} - -inline std::size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - std::size_t rsp_alignment, - std::size_t needed_frame_size = 0) { - s32 subtraction, xmm_offset; - ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); - - for (int reg_index : (regs & ABI_ALL_GPRS)) { - code.push(IndexToReg64(reg_index)); - } - - if (subtraction != 0) { - code.sub(code.rsp, subtraction); - } - - for (int reg_index : (regs & ABI_ALL_XMMS)) { - code.movaps(code.xword[code.rsp + xmm_offset], IndexToXmm(reg_index)); - xmm_offset += 0x10; - } - - return ABI_SHADOW_SPACE; -} - -inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - std::size_t rsp_alignment, - std::size_t needed_frame_size = 0) { - s32 subtraction, xmm_offset; - ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); - - for (int reg_index : (regs & ABI_ALL_XMMS)) { - code.movaps(IndexToXmm(reg_index), code.xword[code.rsp + xmm_offset]); - xmm_offset += 0x10; - } - - if (subtraction != 0) { - code.add(code.rsp, subtraction); - } - - // GPRs need to be popped in reverse order - for (int reg_index = 15; reg_index >= 0; reg_index--) { - if (regs[reg_index]) { - code.pop(IndexToReg64(reg_index)); - } - } -} - -} // namespace Common::X64 diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h deleted file mode 100644 index 5cc8a8c76..000000000 --- a/src/common/x64/xbyak_util.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <type_traits> -#include <xbyak.h> -#include "common/x64/xbyak_abi.h" - -namespace Common::X64 { - -// Constants for use with cmpps/cmpss -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -inline bool IsWithin2G(uintptr_t ref, uintptr_t target) { - u64 distance = target - (ref + 5); - return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); -} - -inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { - return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target); -} - -template <typename T> -inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { - static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer."); - std::size_t addr = reinterpret_cast<std::size_t>(f); - if (IsWithin2G(code, addr)) { - code.call(f); - } else { - // ABI_RETURN is a safe temp register to use before a call - code.mov(ABI_RETURN, addr); - code.call(ABI_RETURN); - } -} - -} // namespace Common::X64 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 64fdf38cd..882c9ab59 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,8 @@ add_library(core STATIC core_timing.h core_timing_util.cpp core_timing_util.h + cpu_core_manager.cpp + cpu_core_manager.h crypto/aes_util.cpp crypto/aes_util.h crypto/encryption_layer.cpp @@ -61,6 +63,10 @@ add_library(core STATIC file_sys/sdmc_factory.h file_sys/submission_package.cpp file_sys/submission_package.h + file_sys/system_archive/ng_word.cpp + file_sys/system_archive/ng_word.h + file_sys/system_archive/system_archive.cpp + file_sys/system_archive/system_archive.h file_sys/vfs.cpp file_sys/vfs.h file_sys/vfs_concat.cpp @@ -77,6 +83,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/software_keyboard.cpp + frontend/applets/software_keyboard.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp @@ -93,8 +101,6 @@ add_library(core STATIC hle/kernel/client_session.cpp hle/kernel/client_session.h hle/kernel/errors.h - hle/kernel/event.cpp - hle/kernel/event.h hle/kernel/handle_table.cpp hle/kernel/handle_table.h hle/kernel/hle_ipc.cpp @@ -107,6 +113,8 @@ add_library(core STATIC hle/kernel/object.h hle/kernel/process.cpp hle/kernel/process.h + hle/kernel/readable_event.cpp + hle/kernel/readable_event.h hle/kernel/resource_limit.cpp hle/kernel/resource_limit.h hle/kernel/scheduler.cpp @@ -129,6 +137,8 @@ add_library(core STATIC hle/kernel/vm_manager.h hle/kernel/wait_object.cpp hle/kernel/wait_object.h + hle/kernel/writable_event.cpp + hle/kernel/writable_event.h hle/lock.cpp hle/lock.h hle/result.h @@ -150,6 +160,12 @@ add_library(core STATIC hle/service/am/applet_ae.h hle/service/am/applet_oe.cpp hle/service/am/applet_oe.h + hle/service/am/applets/applets.cpp + hle/service/am/applets/applets.h + hle/service/am/applets/software_keyboard.cpp + hle/service/am/applets/software_keyboard.h + hle/service/am/applets/stub_applet.cpp + hle/service/am/applets/stub_applet.h hle/service/am/idle.cpp hle/service/am/idle.h hle/service/am/omm.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 6d5b5a2d0..ce7851538 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,12 +8,14 @@ #include <thread> #include <utility> +#include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" +#include "core/cpu_core_manager.h" #include "core/file_sys/mode.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" @@ -23,12 +25,13 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" +#include "core/hle/service/am/applets/software_keyboard.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" -#include "core/settings.h" #include "core/telemetry_session.h" +#include "frontend/applets/software_keyboard.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" @@ -38,7 +41,6 @@ namespace Core { /*static*/ System System::s_instance; -namespace { FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, const std::string& path) { // To account for split 00+01+etc files. @@ -67,66 +69,26 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); } - return vfs->OpenFile(path, FileSys::Mode::Read); -} + if (FileUtil::IsDirectory(path)) + return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); -/// Runs a CPU core while the system is powered on -void RunCpuCore(Cpu& cpu_state) { - while (Core::System::GetInstance().IsPoweredOn()) { - cpu_state.RunLoop(true); - } + return vfs->OpenFile(path, FileSys::Mode::Read); } -} // Anonymous namespace - struct System::Impl { - Cpu& CurrentCpuCore() { - if (Settings::values.use_multi_core) { - const auto& search = thread_to_cpu.find(std::this_thread::get_id()); - ASSERT(search != thread_to_cpu.end()); - ASSERT(search->second); - return *search->second; - } - // Otherwise, use single-threaded mode active_core variable - return *cpu_cores[active_core]; + Cpu& CurrentCpuCore() { + return cpu_core_manager.GetCurrentCore(); } ResultStatus RunLoop(bool tight_loop) { status = ResultStatus::Success; - // Update thread_to_cpu in case Core 0 is run from a different host thread - thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get(); - - if (GDBStub::IsServerEnabled()) { - GDBStub::HandlePacket(); - - // If the loop is halted and we want to step, use a tiny (1) number of instructions to - // execute. Otherwise, get out of the loop function. - if (GDBStub::GetCpuHaltFlag()) { - if (GDBStub::GetCpuStepFlag()) { - tight_loop = false; - } else { - return ResultStatus::Success; - } - } - } - - for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) { - cpu_cores[active_core]->RunLoop(tight_loop); - if (Settings::values.use_multi_core) { - // Cores 1-3 are run on other threads in this mode - break; - } - } - - if (GDBStub::IsServerEnabled()) { - GDBStub::SetCpuStepFlag(false); - } + cpu_core_manager.RunLoop(tight_loop); return status; } - ResultStatus Init(Frontend::EmuWindow& emu_window) { + ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { LOG_DEBUG(HW_Memory, "initialized OK"); CoreTiming::Init(); @@ -136,15 +98,13 @@ struct System::Impl { if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); + /// Create default implementations of applets if one is not provided. + if (software_keyboard == nullptr) + software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); + auto main_process = Kernel::Process::Create(kernel, "main"); kernel.MakeCurrentProcess(main_process.get()); - cpu_barrier = std::make_unique<CpuBarrier>(); - cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size()); - for (std::size_t index = 0; index < cpu_cores.size(); ++index) { - cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index); - } - telemetry_session = std::make_unique<Core::TelemetrySession>(); service_manager = std::make_shared<Service::SM::ServiceManager>(); @@ -158,17 +118,8 @@ struct System::Impl { gpu_core = std::make_unique<Tegra::GPU>(renderer->Rasterizer()); - // Create threads for CPU cores 1-3, and build thread_to_cpu map - // CPU core 0 is run on the main thread - thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get(); - if (Settings::values.use_multi_core) { - for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) { - cpu_core_threads[index] = - std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1])); - thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get(); - } - } - + cpu_core_manager.Initialize(system); + is_powered_on = true; LOG_DEBUG(Core, "Initialized OK"); // Reset counters and set time origin to current frame @@ -178,7 +129,8 @@ struct System::Impl { return ResultStatus::Success; } - ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { + ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, + const std::string& filepath) { app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); if (!app_loader) { @@ -195,7 +147,7 @@ struct System::Impl { return ResultStatus::ErrorSystemMode; } - ResultStatus init_result{Init(emu_window)}; + ResultStatus init_result{Init(system, emu_window)}; if (init_result != ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", static_cast<int>(init_result)); @@ -225,6 +177,8 @@ struct System::Impl { Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); + is_powered_on = false; + // Shutdown emulation session renderer.reset(); GDBStub::Shutdown(); @@ -234,19 +188,7 @@ struct System::Impl { gpu_core.reset(); // Close all CPU/threading state - cpu_barrier->NotifyEnd(); - if (Settings::values.use_multi_core) { - for (auto& thread : cpu_core_threads) { - thread->join(); - thread.reset(); - } - } - thread_to_cpu.clear(); - for (auto& cpu_core : cpu_cores) { - cpu_core.reset(); - } - cpu_exclusive_monitor.reset(); - cpu_barrier.reset(); + cpu_core_manager.Shutdown(); // Shutdown kernel and core timing kernel.Shutdown(); @@ -283,11 +225,11 @@ struct System::Impl { std::unique_ptr<VideoCore::RendererBase> renderer; std::unique_ptr<Tegra::GPU> gpu_core; std::shared_ptr<Tegra::DebugContext> debug_context; - std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor; - std::unique_ptr<CpuBarrier> cpu_barrier; - std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores; - std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; - std::size_t active_core{}; ///< Active core, only used in single thread mode + CpuCoreManager cpu_core_manager; + bool is_powered_on = false; + + /// Frontend applets + std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; @@ -298,9 +240,6 @@ struct System::Impl { ResultStatus status = ResultStatus::Success; std::string status_details = ""; - /// Map of guest threads to CPU cores - std::map<std::thread::id, Cpu*> thread_to_cpu; - Core::PerfStats perf_stats; Core::FrameLimiter frame_limiter; }; @@ -325,17 +264,15 @@ System::ResultStatus System::SingleStep() { } void System::InvalidateCpuInstructionCaches() { - for (auto& cpu : impl->cpu_cores) { - cpu->ArmInterface().ClearInstructionCache(); - } + impl->cpu_core_manager.InvalidateAllInstructionCaches(); } System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { - return impl->Load(emu_window, filepath); + return impl->Load(*this, emu_window, filepath); } bool System::IsPoweredOn() const { - return impl->cpu_barrier && impl->cpu_barrier->IsAlive(); + return impl->is_powered_on; } void System::PrepareReschedule() { @@ -399,21 +336,20 @@ const ARM_Interface& System::ArmInterface(std::size_t core_index) const { } Cpu& System::CpuCore(std::size_t core_index) { - ASSERT(core_index < NUM_CPU_CORES); - return *impl->cpu_cores[core_index]; + return impl->cpu_core_manager.GetCore(core_index); } const Cpu& System::CpuCore(std::size_t core_index) const { ASSERT(core_index < NUM_CPU_CORES); - return *impl->cpu_cores[core_index]; + return impl->cpu_core_manager.GetCore(core_index); } ExclusiveMonitor& System::Monitor() { - return *impl->cpu_exclusive_monitor; + return impl->cpu_core_manager.GetExclusiveMonitor(); } const ExclusiveMonitor& System::Monitor() const { - return *impl->cpu_exclusive_monitor; + return impl->cpu_core_manager.GetExclusiveMonitor(); } Tegra::GPU& System::GPU() { @@ -488,8 +424,16 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) { + impl->software_keyboard = std::move(applet); +} + +const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { + return *impl->software_keyboard; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { - return impl->Init(emu_window); + return impl->Init(*this, emu_window); } void System::Shutdown() { diff --git a/src/core/core.h b/src/core/core.h index cfacceb81..71031dfcf 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,10 +9,12 @@ #include <string> #include "common/common_types.h" +#include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" namespace Core::Frontend { class EmuWindow; +class SoftwareKeyboardApplet; } // namespace Core::Frontend namespace FileSys { @@ -54,6 +56,9 @@ class TelemetrySession; struct PerfStatsResults; +FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, + const std::string& path); + class System { public: System(const System&) = delete; @@ -236,6 +241,10 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet); + + const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; + private: System(); diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp new file mode 100644 index 000000000..769a6fefa --- /dev/null +++ b/src/core/cpu_core_manager.cpp @@ -0,0 +1,142 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/arm/exclusive_monitor.h" +#include "core/core.h" +#include "core/core_cpu.h" +#include "core/cpu_core_manager.h" +#include "core/gdbstub/gdbstub.h" +#include "core/settings.h" + +namespace Core { +namespace { +void RunCpuCore(const System& system, Cpu& cpu_state) { + while (system.IsPoweredOn()) { + cpu_state.RunLoop(true); + } +} +} // Anonymous namespace + +CpuCoreManager::CpuCoreManager() = default; +CpuCoreManager::~CpuCoreManager() = default; + +void CpuCoreManager::Initialize(System& system) { + barrier = std::make_unique<CpuBarrier>(); + exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size()); + + for (std::size_t index = 0; index < cores.size(); ++index) { + cores[index] = std::make_unique<Cpu>(*exclusive_monitor, *barrier, index); + } + + // Create threads for CPU cores 1-3, and build thread_to_cpu map + // CPU core 0 is run on the main thread + thread_to_cpu[std::this_thread::get_id()] = cores[0].get(); + if (!Settings::values.use_multi_core) { + return; + } + + for (std::size_t index = 0; index < core_threads.size(); ++index) { + core_threads[index] = std::make_unique<std::thread>(RunCpuCore, std::cref(system), + std::ref(*cores[index + 1])); + thread_to_cpu[core_threads[index]->get_id()] = cores[index + 1].get(); + } +} + +void CpuCoreManager::Shutdown() { + barrier->NotifyEnd(); + if (Settings::values.use_multi_core) { + for (auto& thread : core_threads) { + thread->join(); + thread.reset(); + } + } + + thread_to_cpu.clear(); + for (auto& cpu_core : cores) { + cpu_core.reset(); + } + + exclusive_monitor.reset(); + barrier.reset(); +} + +Cpu& CpuCoreManager::GetCore(std::size_t index) { + return *cores.at(index); +} + +const Cpu& CpuCoreManager::GetCore(std::size_t index) const { + return *cores.at(index); +} + +ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() { + return *exclusive_monitor; +} + +const ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() const { + return *exclusive_monitor; +} + +Cpu& CpuCoreManager::GetCurrentCore() { + if (Settings::values.use_multi_core) { + const auto& search = thread_to_cpu.find(std::this_thread::get_id()); + ASSERT(search != thread_to_cpu.end()); + ASSERT(search->second); + return *search->second; + } + + // Otherwise, use single-threaded mode active_core variable + return *cores[active_core]; +} + +const Cpu& CpuCoreManager::GetCurrentCore() const { + if (Settings::values.use_multi_core) { + const auto& search = thread_to_cpu.find(std::this_thread::get_id()); + ASSERT(search != thread_to_cpu.end()); + ASSERT(search->second); + return *search->second; + } + + // Otherwise, use single-threaded mode active_core variable + return *cores[active_core]; +} + +void CpuCoreManager::RunLoop(bool tight_loop) { + // Update thread_to_cpu in case Core 0 is run from a different host thread + thread_to_cpu[std::this_thread::get_id()] = cores[0].get(); + + if (GDBStub::IsServerEnabled()) { + GDBStub::HandlePacket(); + + // If the loop is halted and we want to step, use a tiny (1) number of instructions to + // execute. Otherwise, get out of the loop function. + if (GDBStub::GetCpuHaltFlag()) { + if (GDBStub::GetCpuStepFlag()) { + tight_loop = false; + } else { + return; + } + } + } + + for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) { + cores[active_core]->RunLoop(tight_loop); + if (Settings::values.use_multi_core) { + // Cores 1-3 are run on other threads in this mode + break; + } + } + + if (GDBStub::IsServerEnabled()) { + GDBStub::SetCpuStepFlag(false); + } +} + +void CpuCoreManager::InvalidateAllInstructionCaches() { + for (auto& cpu : cores) { + cpu->ArmInterface().ClearInstructionCache(); + } +} + +} // namespace Core diff --git a/src/core/cpu_core_manager.h b/src/core/cpu_core_manager.h new file mode 100644 index 000000000..a4d70ec56 --- /dev/null +++ b/src/core/cpu_core_manager.h @@ -0,0 +1,59 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <map> +#include <memory> +#include <thread> + +namespace Core { + +class Cpu; +class CpuBarrier; +class ExclusiveMonitor; +class System; + +class CpuCoreManager { +public: + CpuCoreManager(); + CpuCoreManager(const CpuCoreManager&) = delete; + CpuCoreManager(CpuCoreManager&&) = delete; + + ~CpuCoreManager(); + + CpuCoreManager& operator=(const CpuCoreManager&) = delete; + CpuCoreManager& operator=(CpuCoreManager&&) = delete; + + void Initialize(System& system); + void Shutdown(); + + Cpu& GetCore(std::size_t index); + const Cpu& GetCore(std::size_t index) const; + + Cpu& GetCurrentCore(); + const Cpu& GetCurrentCore() const; + + ExclusiveMonitor& GetExclusiveMonitor(); + const ExclusiveMonitor& GetExclusiveMonitor() const; + + void RunLoop(bool tight_loop); + + void InvalidateAllInstructionCaches(); + +private: + static constexpr std::size_t NUM_CPU_CORES = 4; + + std::unique_ptr<ExclusiveMonitor> exclusive_monitor; + std::unique_ptr<CpuBarrier> barrier; + std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cores; + std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> core_threads; + std::size_t active_core{}; ///< Active core, only used in single thread mode + + /// Map of guest threads to CPU cores + std::map<std::thread::id, Cpu*> thread_to_cpu; +}; + +} // namespace Core diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 904afa039..ca12fb4ab 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -246,7 +246,6 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { } std::vector<TicketRaw> out; - u32 magic{}; for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && buffer[offset + 3] == 0x0) { @@ -794,7 +793,7 @@ void KeyManager::DeriveBase() { void KeyManager::DeriveETicket(PartitionDataManager& data) { // ETicket keys - const auto es = Service::FileSystem::GetUnionContents()->GetEntry( + const auto es = Service::FileSystem::GetUnionContents().GetEntry( 0x0100000000000033, FileSys::ContentRecordType::Program); if (es == nullptr) diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 25f5914b6..a350496f7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -32,7 +32,18 @@ enum class GamecardSize : u8 { }; struct GamecardInfo { - std::array<u8, 0x70> data; + u64_le firmware_version; + u32_le access_control_flags; + u32_le read_wait_time1; + u32_le read_wait_time2; + u32_le write_wait_time1; + u32_le write_wait_time2; + u32_le firmware_mode; + u32_le cup_version; + std::array<u8, 4> reserved1; + u64_le update_partition_hash; + u64_le cup_id; + std::array<u8, 0x38> reserved2; }; static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index c8fa912bf..e065e592f 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -8,13 +8,23 @@ namespace FileSys { -const std::array<const char*, 15> LANGUAGE_NAMES = { - "AmericanEnglish", "BritishEnglish", "Japanese", - "French", "German", "LatinAmericanSpanish", - "Spanish", "Italian", "Dutch", - "CanadianFrench", "Portugese", "Russian", - "Korean", "Taiwanese", "Chinese", -}; +const std::array<const char*, 15> LANGUAGE_NAMES{{ + "AmericanEnglish", + "BritishEnglish", + "Japanese", + "French", + "German", + "LatinAmericanSpanish", + "Spanish", + "Italian", + "Dutch", + "CanadianFrench", + "Portuguese", + "Russian", + "Korean", + "Taiwanese", + "Chinese", +}}; std::string LanguageEntry::GetApplicationName() const { return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 12bb90ec8..6690aa575 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -29,8 +29,8 @@ struct Entry { filename[copy_size] = '\0'; } - char filename[0x300]; - INSERT_PADDING_BYTES(4); + char filename[0x301]; + INSERT_PADDING_BYTES(3); EntryType type; INSERT_PADDING_BYTES(3); u64 file_size; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 8d062eb3e..61706966e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -26,6 +26,11 @@ namespace FileSys { constexpr u64 SINGLE_BYTE_MODULUS = 0x100; constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; +constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ + "main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", + "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", +}; + struct NSOBuildHeader { u32_le magic; INSERT_PADDING_BYTES(0x3C); @@ -51,33 +56,82 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} PatchManager::~PatchManager() = default; +u64 PatchManager::GetTitleID() const { + return title_id; +} + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); if (exefs == nullptr) return exefs; + if (Settings::values.dump_exefs) { + LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); + const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + if (dump_dir != nullptr) { + const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); + VfsRawCopyD(exefs, exefs_dir); + } + } + const auto installed = Service::FileSystem::GetUnionContents(); + const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed->GetEntry(update_tid, ContentRecordType::Program); + const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); - if (update != nullptr && update->GetExeFS() != nullptr && + if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", - FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); } + // LayeredExeFS + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + if (load_dir != nullptr && load_dir->GetSize() > 0) { + auto patch_dirs = load_dir->GetSubdirectories(); + std::sort( + patch_dirs.begin(), patch_dirs.end(), + [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + + std::vector<VirtualDir> layers; + layers.reserve(patch_dirs.size() + 1); + for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + + auto exefs_dir = subdir->GetSubdirectory("exefs"); + if (exefs_dir != nullptr) + layers.push_back(std::move(exefs_dir)); + } + layers.push_back(exefs); + + auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); + if (layered != nullptr) { + LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully"); + exefs = std::move(layered); + } + } + return exefs; } -static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, - const std::string& build_id) { +std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) const { + const auto& disabled = Settings::values.disabled_addons[title_id]; + std::vector<VirtualFile> out; out.reserve(patch_dirs.size()); for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + auto exefs_dir = subdir->GetSubdirectory("exefs"); if (exefs_dir != nullptr) { for (const auto& file : exefs_dir->GetFiles()) { @@ -190,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } + const auto& disabled = Settings::values.disabled_addons[title_id]; auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -199,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t layers.reserve(patch_dirs.size() + 1); layers_ext.reserve(patch_dirs.size() + 1); for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + auto romfs_dir = subdir->GetSubdirectory("romfs"); if (romfs_dir != nullptr) layers.push_back(std::move(romfs_dir)); @@ -228,13 +286,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, VirtualFile update_raw) const { const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", - title_id, static_cast<u8>(type)) - .c_str(); + title_id, static_cast<u8>(type)); if (type == ContentRecordType::Program || type == ContentRecordType::Data) - LOG_INFO(Loader, log_string); + LOG_INFO(Loader, "{}", log_string); else - LOG_DEBUG(Loader, log_string); + LOG_DEBUG(Loader, "{}", log_string); if (romfs == nullptr) return romfs; @@ -243,16 +300,21 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed->GetEntryRaw(update_tid, type); - if (update != nullptr) { + const auto update = installed.GetEntryRaw(update_tid, type); + + const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + + if (!update_disabled && update != nullptr) { const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", - FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } - } else if (update_raw != nullptr) { + } else if (!update_disabled && update_raw != nullptr) { const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { @@ -282,25 +344,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam VirtualFile update_raw) const { std::map<std::string, std::string, std::less<>> out; const auto installed = Service::FileSystem::GetUnionContents(); + const auto& disabled = Settings::values.disabled_addons[title_id]; // Game Updates const auto update_tid = GetUpdateTitleID(title_id); PatchManager update{update_tid}; auto [nacp, discard_icon_file] = update.GetControlMetadata(); + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + const auto update_label = update_disabled ? "[D] Update" : "Update"; + if (nacp != nullptr) { - out.insert_or_assign("Update", nacp->GetVersionString()); + out.insert_or_assign(update_label, nacp->GetVersionString()); } else { - if (installed->HasEntry(update_tid, ContentRecordType::Program)) { - const auto meta_ver = installed->GetEntryVersion(update_tid); + if (installed.HasEntry(update_tid, ContentRecordType::Program)) { + const auto meta_ver = installed.GetEntryVersion(update_tid); if (meta_ver.value_or(0) == 0) { - out.insert_or_assign("Update", ""); + out.insert_or_assign(update_label, ""); } else { out.insert_or_assign( - "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); + update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); } } else if (update_raw != nullptr) { - out.insert_or_assign("Update", "PACKED"); + out.insert_or_assign(update_label, "PACKED"); } } @@ -314,18 +381,25 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (IsDirValidAndNonEmpty(exefs_dir)) { bool ips = false; bool ipswitch = false; + bool layeredfs = false; for (const auto& file : exefs_dir->GetFiles()) { - if (file->GetExtension() == "ips") + if (file->GetExtension() == "ips") { ips = true; - else if (file->GetExtension() == "pchtxt") + } else if (file->GetExtension() == "pchtxt") { ipswitch = true; + } else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(), + file->GetName()) != EXEFS_FILE_NAMES.end()) { + layeredfs = true; + } } if (ips) AppendCommaIfNotEmpty(types, "IPS"); if (ipswitch) AppendCommaIfNotEmpty(types, "IPSwitch"); + if (layeredfs) + AppendCommaIfNotEmpty(types, "LayeredExeFS"); } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); @@ -333,19 +407,20 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (types.empty()) continue; - out.insert_or_assign(mod->GetName(), types); + const auto mod_disabled = + std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); + out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types); } } // DLC - const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); + const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); std::vector<RegisteredCacheEntry> dlc_match; dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), [this, &installed](const RegisteredCacheEntry& entry) { return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id && - installed->GetEntry(entry)->GetStatus() == - Loader::ResultStatus::Success; + installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; }); if (!dlc_match.empty()) { // Ensure sorted so DLC IDs show in order. @@ -357,7 +432,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam list += fmt::format("{}", dlc_match.back().title_id & 0x7FF); - out.insert_or_assign("DLC", std::move(list)); + const auto dlc_disabled = + std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); + out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list)); } return out; @@ -366,7 +443,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto installed{Service::FileSystem::GetUnionContents()}; - const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control); + const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) return {}; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 7d168837f..b8a1652fd 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -30,6 +30,8 @@ public: explicit PatchManager(u64 title_id); ~PatchManager(); + u64 GetTitleID() const; + // Currently tracked ExeFS patches: // - Game Updates VirtualDir PatchExeFS(VirtualDir exefs) const; @@ -63,6 +65,9 @@ public: std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; private: + std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) const; + u64 title_id; }; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a3f8f2f73..128199063 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -107,42 +107,41 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const { const auto file = dir->GetFileRelative(path); - if (file != nullptr) + if (file != nullptr) { return file; + } const auto nca_dir = dir->GetDirectoryRelative(path); - if (nca_dir != nullptr) { - const auto nca_dir = dir->GetDirectoryRelative(path); - VirtualFile file = nullptr; + if (nca_dir == nullptr) { + return nullptr; + } - const auto files = nca_dir->GetFiles(); - if (files.size() == 1 && files[0]->GetName() == "00") { - file = files[0]; + const auto files = nca_dir->GetFiles(); + if (files.size() == 1 && files[0]->GetName() == "00") { + return files[0]; + } + + std::vector<VirtualFile> concat; + // Since the files are a two-digit hex number, max is FF. + for (std::size_t i = 0; i < 0x100; ++i) { + auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); + if (next != nullptr) { + concat.push_back(std::move(next)); } else { - std::vector<VirtualFile> concat; - // Since the files are a two-digit hex number, max is FF. - for (std::size_t i = 0; i < 0x100; ++i) { - auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); - if (next != nullptr) { - concat.push_back(std::move(next)); - } else { - next = nca_dir->GetFile(fmt::format("{:02x}", i)); - if (next != nullptr) - concat.push_back(std::move(next)); - else - break; - } + next = nca_dir->GetFile(fmt::format("{:02x}", i)); + if (next != nullptr) { + concat.push_back(std::move(next)); + } else { + break; } - - if (concat.empty()) - return nullptr; - - file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName()); } + } - return file; + if (concat.empty()) { + return nullptr; } - return nullptr; + + return ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName()); } VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { @@ -381,22 +380,22 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter( return out; } -static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) { - const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); +static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) { + const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); if (file == nullptr) return nullptr; return std::make_shared<NCA>(file); } -InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, +InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists, const VfsCopyFunction& copy) { - return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy); + return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); } -InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists, +InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, const VfsCopyFunction& copy) { - const auto& ncas = nsp->GetNCAsCollapsed(); - const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { + const auto ncas = nsp.GetNCAsCollapsed(); + const auto meta_iter = std::find_if(ncas.begin(), ncas.end(), [](const auto& nca) { return nca->GetType() == NCAContentType::Meta; }); @@ -410,7 +409,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); const auto meta_id = Common::HexStringToArray<16>(meta_id_raw); - const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id); + const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id); if (res != InstallResult::Success) return res; @@ -422,7 +421,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); if (nca == nullptr) return InstallResult::ErrorCopyFailed; - const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); + const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); if (res2 != InstallResult::Success) return res2; } @@ -431,21 +430,21 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw return InstallResult::Success; } -InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, +InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists, const VfsCopyFunction& copy) { CNMTHeader header{ - nca->GetTitleId(), ///< Title ID - 0, ///< Ignore/Default title version - type, ///< Type - {}, ///< Padding - 0x10, ///< Default table offset - 1, ///< 1 Content Entry - 0, ///< No Meta Entries - {}, ///< Padding + nca.GetTitleId(), ///< Title ID + 0, ///< Ignore/Default title version + type, ///< Type + {}, ///< Padding + 0x10, ///< Default table offset + 1, ///< 1 Content Entry + 0, ///< No Meta Entries + {}, ///< Padding }; OptionalHeader opt_header{0, 0}; - ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}}; - const auto& data = nca->GetBaseFile()->ReadBytes(0x100000); + ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}}; + const auto& data = nca.GetBaseFile()->ReadBytes(0x100000); mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); memcpy(&c_rec.nca_id, &c_rec.hash, 16); const CNMT new_cnmt(header, opt_header, {c_rec}, {}); @@ -454,10 +453,10 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } -InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, +InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional<NcaID> override_id) { - const auto in = nca->GetBaseFile(); + const auto in = nca.GetBaseFile(); Core::Crypto::SHA256Hash hash{}; // Calculate NcaID diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 6b89db8de..3b77af4e0 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -6,7 +6,6 @@ #include <array> #include <functional> -#include <map> #include <memory> #include <string> #include <vector> @@ -104,17 +103,16 @@ public: // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure // there is a meta NCA and all of them are accessible. - InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, + InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); - InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false, + InstallResult InstallEntry(const NSP& nsp, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there. // TODO(DarkLordZach): Author real meta-type NCAs and install those. - InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type, - bool overwrite_if_exists = false, + InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); private: @@ -128,7 +126,7 @@ private: std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; VirtualFile GetFileAtID(NcaID id) const; VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const; - InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, + InstallResult RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional<NcaID> override_id = {}); bool RawInstallYuzuMeta(const CNMT& cnmt); diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 0b645b106..6ad1e4f86 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte switch (storage) { case StorageId::None: - res = Service::FileSystem::GetUnionContents()->GetEntry(title_id, type); + res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type); break; case StorageId::NandSystem: res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 5434f2149..bd50fedc7 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -18,7 +18,11 @@ std::string SaveDataDescriptor::DebugInfo() const { static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); } -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} +SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { + // Delete all temporary storages + // On hardware, it is expected that temporary storage be empty at first use. + dir->DeleteSubdirectoryRecursive("temp"); +} SaveDataFactory::~SaveDataFactory() = default; @@ -120,6 +124,8 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ case SaveDataType::TemporaryStorage: return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); + case SaveDataType::CacheStorage: + return fmt::format("{}save/cache/{:016X}", out, title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); } diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 2a0088040..bd4919610 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -17,8 +17,10 @@ namespace FileSys { enum class SaveDataSpaceId : u8 { NandSystem = 0, NandUser = 1, - SdCard = 2, + SdCardSystem = 2, TemporaryStorage = 3, + SdCardUser = 4, + ProperSystem = 100, }; enum class SaveDataType : u8 { diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp new file mode 100644 index 000000000..f4443784d --- /dev/null +++ b/src/core/file_sys/system_archive/ng_word.cpp @@ -0,0 +1,81 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/format.h> +#include "common/common_types.h" +#include "core/file_sys/system_archive/ng_word.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys::SystemArchive { + +namespace NgWord1Data { + +constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10; + +// Should this archive replacement mysteriously not work on a future game, consider updating. +constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x19}; // 5.1.0 System Version + +constexpr std::array<u8, 30> WORD_TXT{ + 0xFE, 0xFF, 0x00, 0x5E, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x62, 0x00, + 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0A, +}; // "^verybadword$" in UTF-16 + +} // namespace NgWord1Data + +VirtualDir NgWord1() { + std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES); + + for (std::size_t i = 0; i < files.size(); ++i) { + files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>( + NgWord1Data::WORD_TXT, fmt::format("{}.txt", i)); + } + + files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>( + NgWord1Data::WORD_TXT, "common.txt")); + files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>( + NgWord1Data::VERSION_DAT, "version.dat")); + + return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data"); +} + +namespace NgWord2Data { + +constexpr std::size_t NUMBER_AC_NX_FILES = 0x10; + +// Should this archive replacement mysteriously not work on a future game, consider updating. +constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x15}; // 5.1.0 System Version + +constexpr std::array<u8, 0x2C> AC_NX_DATA{ + 0x1F, 0x8B, 0x08, 0x08, 0xD5, 0x2C, 0x09, 0x5C, 0x04, 0x00, 0x61, 0x63, 0x72, 0x61, 0x77, + 0x00, 0xED, 0xC1, 0x01, 0x0D, 0x00, 0x00, 0x00, 0xC2, 0x20, 0xFB, 0xA7, 0xB6, 0xC7, 0x07, + 0x0C, 0x00, 0x00, 0x00, 0xC8, 0x3B, 0x11, 0x00, 0x1C, 0xC7, 0x00, 0x10, 0x00, 0x00, +}; // Deserializes to no bad words + +} // namespace NgWord2Data + +VirtualDir NgWord2() { + std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3); + + for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) { + files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i)); + files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i)); + files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i)); + } + + files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, "ac_common_b1_nx")); + files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, "ac_common_b2_nx")); + files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>( + NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx")); + files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>( + NgWord2Data::VERSION_DAT, "version.dat")); + + return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data"); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h new file mode 100644 index 000000000..cd81e0abb --- /dev/null +++ b/src/core/file_sys/system_archive/ng_word.h @@ -0,0 +1,14 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualDir NgWord1(); +VirtualDir NgWord2(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp new file mode 100644 index 000000000..e3e79f40a --- /dev/null +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/ng_word.h" +#include "core/file_sys/system_archive/system_archive.h" + +namespace FileSys::SystemArchive { + +constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800; +constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28; + +using SystemArchiveSupplier = VirtualDir (*)(); + +struct SystemArchiveDescriptor { + u64 title_id; + const char* name; + SystemArchiveSupplier supplier; +}; + +constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ + {0x0100000000000800, "CertStore", nullptr}, + {0x0100000000000801, "ErrorMessage", nullptr}, + {0x0100000000000802, "MiiModel", nullptr}, + {0x0100000000000803, "BrowserDll", nullptr}, + {0x0100000000000804, "Help", nullptr}, + {0x0100000000000805, "SharedFont", nullptr}, + {0x0100000000000806, "NgWord", &NgWord1}, + {0x0100000000000807, "SsidList", nullptr}, + {0x0100000000000808, "Dictionary", nullptr}, + {0x0100000000000809, "SystemVersion", nullptr}, + {0x010000000000080A, "AvatarImage", nullptr}, + {0x010000000000080B, "LocalNews", nullptr}, + {0x010000000000080C, "Eula", nullptr}, + {0x010000000000080D, "UrlBlackList", nullptr}, + {0x010000000000080E, "TimeZoneBinary", nullptr}, + {0x010000000000080F, "CertStoreCruiser", nullptr}, + {0x0100000000000810, "FontNintendoExtension", nullptr}, + {0x0100000000000811, "FontStandard", nullptr}, + {0x0100000000000812, "FontKorean", nullptr}, + {0x0100000000000813, "FontChineseTraditional", nullptr}, + {0x0100000000000814, "FontChineseSimple", nullptr}, + {0x0100000000000815, "FontBfcpx", nullptr}, + {0x0100000000000816, "SystemUpdate", nullptr}, + {0x0100000000000817, "0100000000000817", nullptr}, + {0x0100000000000818, "FirmwareDebugSettings", nullptr}, + {0x0100000000000819, "BootImagePackage", nullptr}, + {0x010000000000081A, "BootImagePackageSafe", nullptr}, + {0x010000000000081B, "BootImagePackageExFat", nullptr}, + {0x010000000000081C, "BootImagePackageExFatSafe", nullptr}, + {0x010000000000081D, "FatalMessage", nullptr}, + {0x010000000000081E, "ControllerIcon", nullptr}, + {0x010000000000081F, "PlatformConfigIcosa", nullptr}, + {0x0100000000000820, "PlatformConfigCopper", nullptr}, + {0x0100000000000821, "PlatformConfigHoag", nullptr}, + {0x0100000000000822, "ControllerFirmware", nullptr}, + {0x0100000000000823, "NgWord2", &NgWord2}, + {0x0100000000000824, "PlatformConfigIcosaMariko", nullptr}, + {0x0100000000000825, "ApplicationBlackList", nullptr}, + {0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr}, + {0x0100000000000827, "ContentActionTable", nullptr}, +}}; + +VirtualFile SynthesizeSystemArchive(const u64 title_id) { + if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id) + return nullptr; + + const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID]; + + LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id); + + if (desc.supplier == nullptr) + return nullptr; + + const auto dir = desc.supplier(); + + if (dir == nullptr) + return nullptr; + + const auto romfs = CreateRomFS(dir); + + if (romfs == nullptr) + return nullptr; + + LOG_INFO(Service_FS, " - System archive generation successful!"); + return romfs; +} +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h new file mode 100644 index 000000000..724a8eb17 --- /dev/null +++ b/src/core/file_sys/system_archive/system_archive.h @@ -0,0 +1,14 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualFile SynthesizeSystemArchive(u64 title_id); + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 7b584de7f..e33327ef0 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -384,6 +384,28 @@ bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { return success; } +bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) { + auto dir = GetSubdirectory(name); + if (dir == nullptr) { + return false; + } + + bool success = true; + for (const auto& file : dir->GetFiles()) { + if (!dir->DeleteFile(file->GetName())) { + success = false; + } + } + + for (const auto& sdir : dir->GetSubdirectories()) { + if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) { + success = false; + } + } + + return success; +} + bool VfsDirectory::Copy(std::string_view src, std::string_view dest) { const auto f1 = GetFile(src); auto f2 = CreateFile(dest); @@ -431,10 +453,34 @@ std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name) return nullptr; } +std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { + return nullptr; +} + +std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { + return nullptr; +} + +std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { + return nullptr; +} + +std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { + return nullptr; +} + bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) { return false; } +bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { + return false; +} + +bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) { + return false; +} + bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) { return false; } diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 002f99d4e..e5641b255 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -245,12 +245,18 @@ public: // any failure. virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path); - // Deletes the subdirectory with name and returns true on success. + // Deletes the subdirectory with the given name and returns true on success. virtual bool DeleteSubdirectory(std::string_view name) = 0; - // Deletes all subdirectories and files of subdirectory with name recirsively and then deletes - // the subdirectory. Returns true on success. + + // Deletes all subdirectories and files within the provided directory and then deletes + // the directory itself. Returns true on success. virtual bool DeleteSubdirectoryRecursive(std::string_view name); - // Returnes whether or not the file with name name was deleted successfully. + + // Deletes all subdirectories and files within the provided directory. + // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory. + virtual bool CleanSubdirectoryRecursive(std::string_view name); + + // Returns whether or not the file with name name was deleted successfully. virtual bool DeleteFile(std::string_view name) = 0; // Returns whether or not this directory was renamed to name. @@ -276,7 +282,13 @@ public: bool IsReadable() const override; std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override; + std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override; + std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override; + std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override; bool DeleteSubdirectory(std::string_view name) override; + bool DeleteSubdirectoryRecursive(std::string_view name) override; + bool CleanSubdirectoryRecursive(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; }; diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 808f31e81..515626658 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include <cstring> #include <utility> #include "core/file_sys/vfs_vector.h" diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index 3e3f790c3..ac36cb2ee 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -4,10 +4,63 @@ #pragma once +#include <cstring> #include "core/file_sys/vfs.h" namespace FileSys { +// An implementation of VfsFile that is backed by a statically-sized array +template <std::size_t size> +class ArrayVfsFile : public VfsFile { +public: + ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr) + : data(data), name(std::move(name)), parent(std::move(parent)) {} + + std::string GetName() const override { + return name; + } + + std::size_t GetSize() const override { + return size; + } + + bool Resize(std::size_t new_size) override { + return false; + } + + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override { + return parent; + } + + bool IsWritable() const override { + return false; + } + + bool IsReadable() const override { + return true; + } + + std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + std::memcpy(data_, data.data() + offset, read); + return read; + } + + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + return 0; + } + + bool Rename(std::string_view name) override { + this->name = name; + return true; + } + +private: + std::array<u8, size> data; + std::string name; + VirtualDir parent; +}; + // An implementation of VfsFile that is backed by a vector optionally supplied upon construction class VectorVfsFile : public VfsFile { public: diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp new file mode 100644 index 000000000..856ed33da --- /dev/null +++ b/src/core/frontend/applets/software_keyboard.cpp @@ -0,0 +1,29 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/backend.h" +#include "common/string_util.h" +#include "core/frontend/applets/software_keyboard.h" + +namespace Core::Frontend { +SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; + +void DefaultSoftwareKeyboardApplet::RequestText( + std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const { + if (parameters.initial_text.empty()) + out(u"yuzu"); + + out(parameters.initial_text); +} + +void DefaultSoftwareKeyboardApplet::SendTextCheckDialog( + std::u16string error_message, std::function<void()> finished_check) const { + LOG_WARNING(Service_AM, + "(STUBBED) called - Default fallback software keyboard does not support text " + "check! (error_message={})", + Common::UTF16ToUTF8(error_message)); + finished_check(); +} +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h new file mode 100644 index 000000000..f9b202664 --- /dev/null +++ b/src/core/frontend/applets/software_keyboard.h @@ -0,0 +1,54 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include <string> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Core::Frontend { +struct SoftwareKeyboardParameters { + std::u16string submit_text; + std::u16string header_text; + std::u16string sub_text; + std::u16string guide_text; + std::u16string initial_text; + std::size_t max_length; + bool password; + bool cursor_at_beginning; + + union { + u8 value; + + BitField<1, 1, u8> disable_space; + BitField<2, 1, u8> disable_address; + BitField<3, 1, u8> disable_percent; + BitField<4, 1, u8> disable_slash; + BitField<6, 1, u8> disable_number; + BitField<7, 1, u8> disable_download_code; + }; +}; + +class SoftwareKeyboardApplet { +public: + virtual ~SoftwareKeyboardApplet(); + + virtual void RequestText(std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const = 0; + virtual void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const = 0; +}; + +class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { +public: + void RequestText(std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const override; + void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 39bdf4e21..16fdcd376 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -132,4 +132,11 @@ using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float> */ using TouchDevice = InputDevice<std::tuple<float, float, bool>>; +/** + * A mouse device is an input device that returns a tuple of two floats and four ints. + * The first two floats are X and Y device coordinates of the mouse (from 0-1). + * The s32s are the mouse wheel. + */ +using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>; + } // namespace Input diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index bdcc889e0..e6b5171ee 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -71,10 +71,6 @@ constexpr u32 PSTATE_REGISTER = 33; constexpr u32 UC_ARM64_REG_Q0 = 34; constexpr u32 FPCR_REGISTER = 66; -// TODO/WiP - Used while working on support for FPU -constexpr u32 TODO_DUMMY_REG_997 = 997; -constexpr u32 TODO_DUMMY_REG_998 = 998; - // For sample XML files see the GDB source /gdb/features // GDB also wants the l character at the start // This XML defines what the registers are for this specific ARM device @@ -260,6 +256,36 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) } } +static u128 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) { + if (!thread) { + return u128{0}; + } + + auto& thread_context = thread->GetContext(); + + if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { + return thread_context.vector_registers[id - UC_ARM64_REG_Q0]; + } else if (id == FPCR_REGISTER) { + return u128{thread_context.fpcr, 0}; + } else { + return u128{0}; + } +} + +static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr) { + if (!thread) { + return; + } + + auto& thread_context = thread->GetContext(); + + if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { + thread_context.vector_registers[id - UC_ARM64_REG_Q0] = val; + } else if (id == FPCR_REGISTER) { + thread_context.fpcr = static_cast<u32>(val[0]); + } +} + /** * Turns hex string character into the equivalent byte. * @@ -409,6 +435,27 @@ static u64 GdbHexToLong(const u8* src) { return output; } +/** + * Convert a gdb-formatted hex string into a u128. + * + * @param src Pointer to hex string. + */ +static u128 GdbHexToU128(const u8* src) { + u128 output; + + for (int i = 0; i < 16; i += 2) { + output[0] = (output[0] << 4) | HexCharToValue(src[15 - i - 1]); + output[0] = (output[0] << 4) | HexCharToValue(src[15 - i]); + } + + for (int i = 0; i < 16; i += 2) { + output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i - 1]); + output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i]); + } + + return output; +} + /// Read a byte from the gdb client. static u8 ReadByte() { u8 c; @@ -599,8 +646,7 @@ static void HandleQuery() { for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (const auto& thread : threads) { - val += fmt::format("{:x}", thread->GetThreadID()); - val += ","; + val += fmt::format("{:x},", thread->GetThreadID()); } } val.pop_back(); @@ -791,11 +837,15 @@ static void ReadRegister() { } else if (id == PSTATE_REGISTER) { IntToGdbHex(reply, static_cast<u32>(RegRead(id, current_thread))); } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); + u128 r = FpuRead(id, current_thread); + LongToGdbHex(reply, r[0]); + LongToGdbHex(reply + 16, r[1]); } else if (id == FPCR_REGISTER) { - LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_998, current_thread)); - } else { - LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_997, current_thread)); + u128 r = FpuRead(id, current_thread); + IntToGdbHex(reply, static_cast<u32>(r[0])); + } else if (id == FPCR_REGISTER + 1) { + u128 r = FpuRead(id, current_thread); + IntToGdbHex(reply, static_cast<u32>(r[0] >> 32)); } SendReply(reinterpret_cast<char*>(reply)); @@ -822,13 +872,18 @@ static void ReadRegisters() { bufptr += 8; - for (u32 reg = UC_ARM64_REG_Q0; reg <= UC_ARM64_REG_Q0 + 31; reg++) { - LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread)); + u128 r; + + for (u32 reg = UC_ARM64_REG_Q0; reg < FPCR_REGISTER; reg++) { + r = FpuRead(reg, current_thread); + LongToGdbHex(bufptr + reg * 32, r[0]); + LongToGdbHex(bufptr + reg * 32 + 16, r[1]); } bufptr += 32 * 32; - LongToGdbHex(bufptr, RegRead(TODO_DUMMY_REG_998, current_thread)); + r = FpuRead(FPCR_REGISTER, current_thread); + IntToGdbHex(bufptr, static_cast<u32>(r[0])); bufptr += 8; @@ -853,14 +908,12 @@ static void WriteRegister() { } else if (id == PSTATE_REGISTER) { RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); + FpuWrite(id, GdbHexToU128(buffer_ptr), current_thread); } else if (id == FPCR_REGISTER) { - RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr), current_thread); - } else { - RegWrite(TODO_DUMMY_REG_997, GdbHexToLong(buffer_ptr), current_thread); + } else if (id == FPCR_REGISTER + 1) { } - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -885,13 +938,13 @@ static void WriteRegisters() { } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) { RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else if (reg == FPCR_REGISTER) { - RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else { - UNIMPLEMENTED(); + RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); + } else if (reg == FPCR_REGISTER + 1) { + RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); } } - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -917,12 +970,6 @@ static void ReadMemory() { SendReply("E01"); } - const auto& vm_manager = Core::CurrentProcess()->VMManager(); - if (addr < vm_manager.GetCodeRegionBaseAddress() || - addr >= vm_manager.GetMapRegionEndAddress()) { - return SendReply("E00"); - } - if (!Memory::IsValidVirtualAddress(addr)) { return SendReply("E00"); } @@ -967,7 +1014,7 @@ void Break(bool is_memory_break) { static void Step() { if (command_length > 1) { RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -1010,7 +1057,7 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) { breakpoint.addr = addr; breakpoint.len = len; Memory::ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size()); - static constexpr std::array<u8, 4> btrap{{0x00, 0x7d, 0x20, 0xd4}}; + static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4}; Memory::WriteBlock(addr, btrap.data(), btrap.size()); Core::System::GetInstance().InvalidateCpuInstructionCaches(); p.insert({addr, breakpoint}); @@ -1321,13 +1368,15 @@ void SetCpuStepFlag(bool is_step) { } void SendTrap(Kernel::Thread* thread, int trap) { - if (send_trap) { - if (!halt_loop || current_thread == thread) { - current_thread = thread; - SendSignal(thread, trap); - } - halt_loop = true; - send_trap = false; + if (!send_trap) { + return; } + + if (!halt_loop || current_thread == thread) { + current_thread = thread; + SendSignal(thread, trap); + } + halt_loop = true; + send_trap = false; } }; // namespace GDBStub diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index 5ee5c05e3..c8acde5b1 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -12,12 +12,23 @@ #include "core/hle/kernel/thread.h" namespace Kernel { +namespace { +constexpr u16 GetSlot(Handle handle) { + return handle >> 15; +} + +constexpr u16 GetGeneration(Handle handle) { + return handle & 0x7FFF; +} +} // Anonymous namespace HandleTable::HandleTable() { next_generation = 1; Clear(); } +HandleTable::~HandleTable() = default; + ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { DEBUG_ASSERT(obj != nullptr); @@ -31,9 +42,10 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { u16 generation = next_generation++; // Overflow count so it fits in the 15 bits dedicated to the generation in the handle. - // CTR-OS doesn't use generation 0, so skip straight to 1. - if (next_generation >= (1 << 15)) + // Horizon OS uses zero to represent an invalid handle, so skip to 1. + if (next_generation >= (1 << 15)) { next_generation = 1; + } generations[slot] = generation; objects[slot] = std::move(obj); diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h index 9e2f33e8a..6b7927fd8 100644 --- a/src/core/hle/kernel/handle_table.h +++ b/src/core/hle/kernel/handle_table.h @@ -13,6 +13,7 @@ namespace Kernel { enum KernelHandle : Handle { + InvalidHandle = 0, CurrentThread = 0xFFFF8000, CurrentProcess = 0xFFFF8001, }; @@ -43,6 +44,7 @@ enum KernelHandle : Handle { class HandleTable final : NonCopyable { public: HandleTable(); + ~HandleTable(); /** * Allocates a handle for the given object. @@ -89,18 +91,8 @@ public: void Clear(); private: - /** - * This is the maximum limit of handles allowed per process in CTR-OS. It can be further - * reduced by ExHeader values, but this is not emulated here. - */ - static const std::size_t MAX_COUNT = 4096; - - static u16 GetSlot(Handle handle) { - return handle >> 15; - } - static u16 GetGeneration(Handle handle) { - return handle & 0x7FFF; - } + /// This is the maximum limit of handles allowed per process in Horizon + static constexpr std::size_t MAX_COUNT = 1024; /// Stores the Object referenced by the handle or null if the slot is empty. std::array<SharedPtr<Object>, MAX_COUNT> objects; diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 68d5376cb..61ce7d7e4 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -15,13 +15,14 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/writable_event.h" #include "core/memory.h" namespace Kernel { @@ -36,11 +37,9 @@ void SessionRequestHandler::ClientDisconnected(const SharedPtr<ServerSession>& s boost::range::remove_erase(connected_sessions, server_session); } -SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, - const std::string& reason, u64 timeout, - WakeupCallback&& callback, - Kernel::SharedPtr<Kernel::Event> event) { - +SharedPtr<WritableEvent> HLERequestContext::SleepClientThread( + SharedPtr<Thread> thread, const std::string& reason, u64 timeout, WakeupCallback&& callback, + SharedPtr<WritableEvent> writable_event) { // Put the client thread to sleep until the wait event is signaled or the timeout expires. thread->SetWakeupCallback([context = *this, callback]( ThreadWakeupReason reason, SharedPtr<Thread> thread, @@ -51,23 +50,25 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, return true; }); - if (!event) { + auto& kernel = Core::System::GetInstance().Kernel(); + if (!writable_event) { // Create event if not provided - auto& kernel = Core::System::GetInstance().Kernel(); - event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); + const auto pair = WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "HLE Pause Event: " + reason); + writable_event = pair.writable; } - event->Clear(); + const auto readable_event{writable_event->GetReadableEvent()}; + writable_event->Clear(); thread->SetStatus(ThreadStatus::WaitHLEEvent); - thread->SetWaitObjects({event}); - event->AddWaitingThread(thread); + thread->SetWaitObjects({readable_event}); + readable_event->AddWaitingThread(thread); if (timeout > 0) { thread->WakeAfterDelay(timeout); } - return event; + return writable_event; } HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session) diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index a38e34b74..e5c0610cd 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -24,10 +24,11 @@ class ServiceFrameworkBase; namespace Kernel { class Domain; -class Event; class HandleTable; class HLERequestContext; class Process; +class ReadableEvent; +class WritableEvent; /** * Interface implemented by HLE Session handlers. @@ -119,12 +120,13 @@ public: * @param callback Callback to be invoked when the thread is resumed. This callback must write * the entire command response once again, regardless of the state of it before this function * was called. - * @param event Event to use to wake up the thread. If unspecified, an event will be created. + * @param writable_event Event to use to wake up the thread. If unspecified, an event will be + * created. * @returns Event that when signaled will resume the thread and call the callback function. */ - SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason, - u64 timeout, WakeupCallback&& callback, - Kernel::SharedPtr<Kernel::Event> event = nullptr); + SharedPtr<WritableEvent> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason, + u64 timeout, WakeupCallback&& callback, + SharedPtr<WritableEvent> writable_event = nullptr); /// Populates this context with data from the requesting process/thread. ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1fd4ba5d2..e441c5bc6 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -105,7 +105,7 @@ struct KernelCore::Impl { void Initialize(KernelCore& kernel) { Shutdown(); - InitializeResourceLimits(kernel); + InitializeSystemResourceLimit(kernel); InitializeThreads(); InitializeTimers(); } @@ -118,7 +118,7 @@ struct KernelCore::Impl { process_list.clear(); current_process = nullptr; - resource_limits.fill(nullptr); + system_resource_limit = nullptr; thread_wakeup_callback_handle_table.Clear(); thread_wakeup_event_type = nullptr; @@ -129,63 +129,17 @@ struct KernelCore::Impl { named_ports.clear(); } - void InitializeResourceLimits(KernelCore& kernel) { - // Create the four resource limits that the system uses - // Create the APPLICATION resource limit - SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create(kernel, "Applications"); - resource_limit->max_priority = 0x18; - resource_limit->max_commit = 0x4000000; - resource_limit->max_threads = 0x20; - resource_limit->max_events = 0x20; - resource_limit->max_mutexes = 0x20; - resource_limit->max_semaphores = 0x8; - resource_limit->max_timers = 0x8; - resource_limit->max_shared_mems = 0x10; - resource_limit->max_address_arbiters = 0x2; - resource_limit->max_cpu_time = 0x1E; - resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit; - - // Create the SYS_APPLET resource limit - resource_limit = ResourceLimit::Create(kernel, "System Applets"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x5E00000; - resource_limit->max_threads = 0x1D; - resource_limit->max_events = 0xB; - resource_limit->max_mutexes = 0x8; - resource_limit->max_semaphores = 0x4; - resource_limit->max_timers = 0x4; - resource_limit->max_shared_mems = 0x8; - resource_limit->max_address_arbiters = 0x3; - resource_limit->max_cpu_time = 0x2710; - resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit; - - // Create the LIB_APPLET resource limit - resource_limit = ResourceLimit::Create(kernel, "Library Applets"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x600000; - resource_limit->max_threads = 0xE; - resource_limit->max_events = 0x8; - resource_limit->max_mutexes = 0x8; - resource_limit->max_semaphores = 0x4; - resource_limit->max_timers = 0x4; - resource_limit->max_shared_mems = 0x8; - resource_limit->max_address_arbiters = 0x1; - resource_limit->max_cpu_time = 0x2710; - resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit; - - // Create the OTHER resource limit - resource_limit = ResourceLimit::Create(kernel, "Others"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x2180000; - resource_limit->max_threads = 0xE1; - resource_limit->max_events = 0x108; - resource_limit->max_mutexes = 0x25; - resource_limit->max_semaphores = 0x43; - resource_limit->max_timers = 0x2C; - resource_limit->max_shared_mems = 0x1F; - resource_limit->max_address_arbiters = 0x2D; - resource_limit->max_cpu_time = 0x3E8; - resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit; + // Creates the default system resource limit + void InitializeSystemResourceLimit(KernelCore& kernel) { + system_resource_limit = ResourceLimit::Create(kernel, "System"); + + // If setting the default system values fails, then something seriously wrong has occurred. + ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000) + .IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess()); } void InitializeThreads() { @@ -208,7 +162,7 @@ struct KernelCore::Impl { std::vector<SharedPtr<Process>> process_list; Process* current_process = nullptr; - std::array<SharedPtr<ResourceLimit>, 4> resource_limits; + SharedPtr<ResourceLimit> system_resource_limit; /// The event type of the generic timer callback event CoreTiming::EventType* timer_callback_event_type = nullptr; @@ -239,9 +193,8 @@ void KernelCore::Shutdown() { impl->Shutdown(); } -SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory( - ResourceLimitCategory category) const { - return impl->resource_limits.at(static_cast<std::size_t>(category)); +SharedPtr<ResourceLimit> KernelCore::GetSystemResourceLimit() const { + return impl->system_resource_limit; } SharedPtr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 7f822d524..ea00c89f5 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -24,8 +24,6 @@ class ResourceLimit; class Thread; class Timer; -enum class ResourceLimitCategory : u8; - /// Represents a single instance of the kernel. class KernelCore { private: @@ -47,8 +45,8 @@ public: /// Clears all resources in use by the kernel instance. void Shutdown(); - /// Retrieves a shared pointer to a ResourceLimit identified by the given category. - SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const; + /// Retrieves a shared pointer to the system resource limit instance. + SharedPtr<ResourceLimit> GetSystemResourceLimit() const; /// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table. SharedPtr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const; diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index d87a62bb9..0ea851a74 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -13,16 +13,17 @@ Object::~Object() = default; bool Object::IsWaitable() const { switch (GetHandleType()) { - case HandleType::Event: + case HandleType::ReadableEvent: case HandleType::Thread: + case HandleType::Process: case HandleType::Timer: case HandleType::ServerPort: case HandleType::ServerSession: return true; case HandleType::Unknown: + case HandleType::WritableEvent: case HandleType::SharedMemory: - case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: case HandleType::ClientPort: diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h index c9f4d0bb3..f1606a204 100644 --- a/src/core/hle/kernel/object.h +++ b/src/core/hle/kernel/object.h @@ -19,7 +19,8 @@ using Handle = u32; enum class HandleType : u32 { Unknown, - Event, + WritableEvent, + ReadableEvent, SharedMemory, Thread, Process, @@ -33,9 +34,9 @@ enum class HandleType : u32 { }; enum class ResetType { - OneShot, - Sticky, - Pulse, + OneShot, ///< Reset automatically on object acquisition + Sticky, ///< Never reset automatically + Pulse, ///< Reset automatically on wakeup }; class Object : NonCopyable { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index a257c3726..5356a4a3f 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -4,10 +4,12 @@ #include <algorithm> #include <memory> +#include <random> #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" @@ -28,7 +30,7 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { process->name = std::move(name); process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); - process->resource_limit = kernel.ResourceLimitForCategory(ResourceLimitCategory::APPLICATION); + process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; process->process_id = kernel.CreateNewProcessID(); @@ -43,8 +45,28 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { return process; } +SharedPtr<ResourceLimit> Process::GetResourceLimit() const { + return resource_limit; +} + +ResultCode Process::ClearSignalState() { + if (status == ProcessStatus::Exited) { + LOG_ERROR(Kernel, "called on a terminated process instance."); + return ERR_INVALID_STATE; + } + + if (!is_signaled) { + LOG_ERROR(Kernel, "called on a process instance that isn't signaled."); + return ERR_INVALID_STATE; + } + + is_signaled = false; + return RESULT_SUCCESS; +} + void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { program_id = metadata.GetTitleID(); + ideal_processor = metadata.GetMainThreadCore(); is_64bit_process = metadata.Is64BitProgram(); vm_manager.Reset(metadata.GetAddressSpaceType()); } @@ -128,17 +150,17 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { vm_manager .MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size, std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, - MemoryState::Mapped) + MemoryState::Stack) .Unwrap(); vm_manager.LogLayout(); - status = ProcessStatus::Running; + ChangeStatus(ProcessStatus::Running); Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this); } void Process::PrepareForTermination() { - status = ProcessStatus::Exited; + ChangeStatus(ProcessStatus::Exiting); const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) { for (auto& thread : thread_list) { @@ -162,6 +184,8 @@ void Process::PrepareForTermination() { stop_threads(system.Scheduler(1).GetThreadList()); stop_threads(system.Scheduler(2).GetThreadList()); stop_threads(system.Scheduler(3).GetThreadList()); + + ChangeStatus(ProcessStatus::Exited); } /** @@ -260,7 +284,25 @@ ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) { return vm_manager.UnmapRange(dst_addr, size); } -Kernel::Process::Process(KernelCore& kernel) : Object{kernel} {} +Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {} Kernel::Process::~Process() {} +void Process::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "Object unavailable!"); +} + +bool Process::ShouldWait(Thread* thread) const { + return !is_signaled; +} + +void Process::ChangeStatus(ProcessStatus new_status) { + if (status == new_status) { + return; + } + + status = new_status; + is_signaled = true; + WakeupAllWaitingThreads(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 230e395ff..459eedfa6 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -8,16 +8,16 @@ #include <bitset> #include <cstddef> #include <memory> -#include <random> #include <string> #include <vector> #include <boost/container/static_vector.hpp> #include "common/bit_field.h" #include "common/common_types.h" #include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/object.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/vm_manager.h" +#include "core/hle/kernel/wait_object.h" +#include "core/hle/result.h" namespace FileSys { class ProgramMetadata; @@ -118,7 +118,7 @@ struct CodeSet final { VAddr entrypoint = 0; }; -class Process final : public Object { +class Process final : public WaitObject { public: static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; @@ -172,14 +172,7 @@ public: } /// Gets the resource limit descriptor for this process - ResourceLimit& GetResourceLimit() { - return *resource_limit; - } - - /// Gets the resource limit descriptor for this process - const ResourceLimit& GetResourceLimit() const { - return *resource_limit; - } + SharedPtr<ResourceLimit> GetResourceLimit() const; /// Gets the default CPU ID for this process u8 GetDefaultProcessorID() const { @@ -220,6 +213,16 @@ public: return random_entropy.at(index); } + /// Clears the signaled state of the process if and only if it's signaled. + /// + /// @pre The process must not be already terminated. If this is called on a + /// terminated process, then ERR_INVALID_STATE will be returned. + /// + /// @pre The process must be in a signaled state. If this is called on a + /// process instance that is not signaled, ERR_INVALID_STATE will be + /// returned. + ResultCode ClearSignalState(); + /** * Loads process-specifics configuration info with metadata provided * by an executable. @@ -259,8 +262,7 @@ public: ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u32 size); - ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, - MemoryState state = MemoryState::Mapped); + ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state); ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); @@ -268,6 +270,17 @@ private: explicit Process(KernelCore& kernel); ~Process() override; + /// Checks if the specified thread should wait until this process is available. + bool ShouldWait(Thread* thread) const override; + + /// Acquires/locks this process for the specified thread if it's available. + void Acquire(Thread* thread) override; + + /// Changes the process status. If the status is different + /// from the current process status, then this will trigger + /// a process signal. + void ChangeStatus(ProcessStatus new_status); + /// Memory manager for this process. Kernel::VMManager vm_manager; @@ -313,6 +326,10 @@ private: /// specified by metadata provided to the process during loading. bool is_64bit_process = true; + /// Whether or not this process is signaled. This occurs + /// upon the process changing to a different state. + bool is_signaled = false; + /// Total running time for the process in ticks. u64 total_process_running_time_ticks = 0; diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/readable_event.cpp index 8967e602e..ba01f495c 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/readable_event.cpp @@ -4,46 +4,47 @@ #include <algorithm> #include "common/assert.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/thread.h" namespace Kernel { -Event::Event(KernelCore& kernel) : WaitObject{kernel} {} -Event::~Event() = default; +ReadableEvent::ReadableEvent(KernelCore& kernel) : WaitObject{kernel} {} +ReadableEvent::~ReadableEvent() = default; -SharedPtr<Event> Event::Create(KernelCore& kernel, ResetType reset_type, std::string name) { - SharedPtr<Event> evt(new Event(kernel)); - - evt->signaled = false; - evt->reset_type = reset_type; - evt->name = std::move(name); - - return evt; -} - -bool Event::ShouldWait(Thread* thread) const { +bool ReadableEvent::ShouldWait(Thread* thread) const { return !signaled; } -void Event::Acquire(Thread* thread) { +void ReadableEvent::Acquire(Thread* thread) { ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); if (reset_type == ResetType::OneShot) signaled = false; } -void Event::Signal() { +void ReadableEvent::Signal() { signaled = true; WakeupAllWaitingThreads(); } -void Event::Clear() { +void ReadableEvent::Clear() { signaled = false; } -void Event::WakeupAllWaitingThreads() { +ResultCode ReadableEvent::Reset() { + if (!signaled) { + return ERR_INVALID_STATE; + } + + Clear(); + + return RESULT_SUCCESS; +} + +void ReadableEvent::WakeupAllWaitingThreads() { WaitObject::WakeupAllWaitingThreads(); if (reset_type == ResetType::Pulse) diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h new file mode 100644 index 000000000..80b3b0aba --- /dev/null +++ b/src/core/hle/kernel/readable_event.h @@ -0,0 +1,66 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/wait_object.h" + +union ResultCode; + +namespace Kernel { + +class KernelCore; +class WritableEvent; + +class ReadableEvent final : public WaitObject { + friend class WritableEvent; + +public: + ~ReadableEvent() override; + + std::string GetTypeName() const override { + return "ReadableEvent"; + } + std::string GetName() const override { + return name; + } + + ResetType GetResetType() const { + return reset_type; + } + + static const HandleType HANDLE_TYPE = HandleType::ReadableEvent; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; + + /// Unconditionally clears the readable event's state. + void Clear(); + + /// Clears the readable event's state if and only if it + /// has already been signaled. + /// + /// @pre The event must be in a signaled state. If this event + /// is in an unsignaled state and this function is called, + /// then ERR_INVALID_STATE will be returned. + ResultCode Reset(); + +private: + explicit ReadableEvent(KernelCore& kernel); + + void Signal(); + + ResetType reset_type; + bool signaled; + + std::string name; ///< Name of event (optional) +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index b253a680f..2f9695005 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -2,12 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> -#include "common/assert.h" -#include "common/logging/log.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/result.h" namespace Kernel { +namespace { +constexpr std::size_t ResourceTypeToIndex(ResourceType type) { + return static_cast<std::size_t>(type); +} +} // Anonymous namespace ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {} ResourceLimit::~ResourceLimit() = default; @@ -19,59 +23,22 @@ SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel, std::string n return resource_limit; } -s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { - switch (resource) { - case ResourceType::Commit: - return current_commit; - case ResourceType::Thread: - return current_threads; - case ResourceType::Event: - return current_events; - case ResourceType::Mutex: - return current_mutexes; - case ResourceType::Semaphore: - return current_semaphores; - case ResourceType::Timer: - return current_timers; - case ResourceType::SharedMemory: - return current_shared_mems; - case ResourceType::AddressArbiter: - return current_address_arbiters; - case ResourceType::CPUTime: - return current_cpu_time; - default: - LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); - UNIMPLEMENTED(); - return 0; - } +s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { + return values.at(ResourceTypeToIndex(resource)); +} + +s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { + return limits.at(ResourceTypeToIndex(resource)); } -u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { - switch (resource) { - case ResourceType::Priority: - return max_priority; - case ResourceType::Commit: - return max_commit; - case ResourceType::Thread: - return max_threads; - case ResourceType::Event: - return max_events; - case ResourceType::Mutex: - return max_mutexes; - case ResourceType::Semaphore: - return max_semaphores; - case ResourceType::Timer: - return max_timers; - case ResourceType::SharedMemory: - return max_shared_mems; - case ResourceType::AddressArbiter: - return max_address_arbiters; - case ResourceType::CPUTime: - return max_cpu_time; - default: - LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); - UNIMPLEMENTED(); - return 0; +ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) { + const auto index = ResourceTypeToIndex(resource); + + if (value < values[index]) { + return ERR_INVALID_STATE; } + + values[index] = value; + return RESULT_SUCCESS; } } // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 219e49562..59dc11c22 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h @@ -4,33 +4,31 @@ #pragma once +#include <array> #include "common/common_types.h" #include "core/hle/kernel/object.h" +union ResultCode; + namespace Kernel { class KernelCore; -enum class ResourceLimitCategory : u8 { - APPLICATION = 0, - SYS_APPLET = 1, - LIB_APPLET = 2, - OTHER = 3 -}; +enum class ResourceType : u32 { + PhysicalMemory, + Threads, + Events, + TransferMemory, + Sessions, -enum class ResourceType { - Priority = 0, - Commit = 1, - Thread = 2, - Event = 3, - Mutex = 4, - Semaphore = 5, - Timer = 6, - SharedMemory = 7, - AddressArbiter = 8, - CPUTime = 9, + // Used as a count, not an actual type. + ResourceTypeCount }; +constexpr bool IsValidResourceType(ResourceType type) { + return type < ResourceType::ResourceTypeCount; +} + class ResourceLimit final : public Object { public: /** @@ -55,61 +53,51 @@ public: * @param resource Requested resource type * @returns The current value of the resource type */ - s32 GetCurrentResourceValue(ResourceType resource) const; + s64 GetCurrentResourceValue(ResourceType resource) const; /** * Gets the max value for the specified resource. * @param resource Requested resource type * @returns The max value of the resource type */ - u32 GetMaxResourceValue(ResourceType resource) const; - - /// Name of resource limit object. - std::string name; - - /// Max thread priority that a process in this category can create - s32 max_priority = 0; - - /// Max memory that processes in this category can use - s32 max_commit = 0; + s64 GetMaxResourceValue(ResourceType resource) const; - ///< Max number of objects that can be collectively created by the processes in this category - s32 max_threads = 0; - s32 max_events = 0; - s32 max_mutexes = 0; - s32 max_semaphores = 0; - s32 max_timers = 0; - s32 max_shared_mems = 0; - s32 max_address_arbiters = 0; + /** + * Sets the limit value for a given resource type. + * + * @param resource The resource type to apply the limit to. + * @param value The limit to apply to the given resource type. + * + * @return A result code indicating if setting the limit value + * was successful or not. + * + * @note The supplied limit value *must* be greater than or equal to + * the current resource value for the given resource type, + * otherwise ERR_INVALID_STATE will be returned. + */ + ResultCode SetLimitValue(ResourceType resource, s64 value); - /// Max CPU time that the processes in this category can utilize - s32 max_cpu_time = 0; +private: + explicit ResourceLimit(KernelCore& kernel); + ~ResourceLimit() override; - // TODO(Subv): Increment these in their respective Kernel::T::Create functions, keeping in mind - // that APPLICATION resource limits should not be affected by the objects created by service - // modules. + // TODO(Subv): Increment resource limit current values in their respective Kernel::T::Create + // functions + // // Currently we have no way of distinguishing if a Create was called by the running application, // or by a service module. Approach this once we have separated the service modules into their // own processes - /// Current memory that the processes in this category are using - s32 current_commit = 0; + using ResourceArray = + std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>; - ///< Current number of objects among all processes in this category - s32 current_threads = 0; - s32 current_events = 0; - s32 current_mutexes = 0; - s32 current_semaphores = 0; - s32 current_timers = 0; - s32 current_shared_mems = 0; - s32 current_address_arbiters = 0; + /// Maximum values a resource type may reach. + ResourceArray limits{}; + /// Current resource limit values. + ResourceArray values{}; - /// Current CPU time that the processes in this category are utilizing - s32 current_cpu_time = 0; - -private: - explicit ResourceLimit(KernelCore& kernel); - ~ResourceLimit() override; + /// Name of resource limit object. + std::string name; }; } // namespace Kernel diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index a016a86b6..22d0c1dd5 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -17,13 +17,13 @@ namespace Kernel { SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {} SharedMemory::~SharedMemory() = default; -SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Process> owner_process, - u64 size, MemoryPermission permissions, +SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_process, u64 size, + MemoryPermission permissions, MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); - shared_memory->owner_process = std::move(owner_process); + shared_memory->owner_process = owner_process; shared_memory->name = std::move(name); shared_memory->size = size; shared_memory->permissions = permissions; @@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce shared_memory->backing_block.get()); } } else { - auto& vm_manager = shared_memory->owner_process->VMManager(); + const auto& vm_manager = shared_memory->owner_process->VMManager(); // The memory is already available and mapped in the owner process. - auto vma = vm_manager.FindVMA(address); - ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); + const auto vma = vm_manager.FindVMA(address); + ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address"); ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); // The returned VMA might be a bigger one encompassing the desired address. - auto vma_offset = address - vma->first; + const auto vma_offset = address - vma->first; ASSERT_MSG(vma_offset + size <= vma->second.size, "Shared memory exceeds bounds of mapped block"); @@ -61,7 +61,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce } SharedPtr<SharedMemory> SharedMemory::CreateForApplet( - KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size, + KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); @@ -78,10 +78,10 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet( return shared_memory; } -ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions, +ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions) { const MemoryPermission own_other_permissions = - target_process == owner_process ? this->permissions : this->other_permissions; + &target_process == owner_process ? this->permissions : this->other_permissions; // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare if (base_address == 0 && other_permissions != MemoryPermission::DontCare) { @@ -106,7 +106,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi VAddr target_address = address; // Map the memory block into the target process - auto result = target_process->VMManager().MapMemoryBlock( + auto result = target_process.VMManager().MapMemoryBlock( target_address, backing_block, backing_block_offset, size, MemoryState::Shared); if (result.Failed()) { LOG_ERROR( @@ -116,14 +116,14 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi return result.Code(); } - return target_process->VMManager().ReprotectRange(target_address, size, - ConvertPermissions(permissions)); + return target_process.VMManager().ReprotectRange(target_address, size, + ConvertPermissions(permissions)); } -ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) { +ResultCode SharedMemory::Unmap(Process& target_process, VAddr address) { // TODO(Subv): Verify what happens if the application tries to unmap an address that is not // mapped to a SharedMemory. - return target_process->VMManager().UnmapRange(address, size); + return target_process.VMManager().UnmapRange(address, size); } VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { @@ -132,7 +132,11 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { return static_cast<VMAPermission>(masked_permissions); } -u8* SharedMemory::GetPointer(u32 offset) { +u8* SharedMemory::GetPointer(std::size_t offset) { + return backing_block->data() + backing_block_offset + offset; +} + +const u8* SharedMemory::GetPointer(std::size_t offset) const { return backing_block->data() + backing_block_offset + offset; } diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 2c06bb7ce..dab2a6bea 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -45,8 +45,8 @@ public: * linear heap. * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> Create(KernelCore& kernel, SharedPtr<Process> owner_process, - u64 size, MemoryPermission permissions, + static SharedPtr<SharedMemory> Create(KernelCore& kernel, Process* owner_process, u64 size, + MemoryPermission permissions, MemoryPermission other_permissions, VAddr address = 0, MemoryRegion region = MemoryRegion::BASE, std::string name = "Unknown"); @@ -64,7 +64,7 @@ public: */ static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, - u32 offset, u32 size, + std::size_t offset, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name = "Unknown Applet"); @@ -81,6 +81,11 @@ public: return HANDLE_TYPE; } + /// Gets the size of the underlying memory block in bytes. + u64 GetSize() const { + return size; + } + /** * Converts the specified MemoryPermission into the equivalent VMAPermission. * @param permission The MemoryPermission to convert. @@ -94,44 +99,51 @@ public: * @param permissions Memory block map permissions (specified by SVC field) * @param other_permissions Memory block map other permissions (specified by SVC field) */ - ResultCode Map(Process* target_process, VAddr address, MemoryPermission permissions, + ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions); /** * Unmaps a shared memory block from the specified address in system memory - * @param target_process Process from which to umap the memory block. + * @param target_process Process from which to unmap the memory block. * @param address Address in system memory where the shared memory block is mapped * @return Result code of the unmap operation */ - ResultCode Unmap(Process* target_process, VAddr address); + ResultCode Unmap(Process& target_process, VAddr address); /** * Gets a pointer to the shared memory block * @param offset Offset from the start of the shared memory block to get pointer - * @return Pointer to the shared memory block from the specified offset + * @return A pointer to the shared memory block from the specified offset */ - u8* GetPointer(u32 offset = 0); + u8* GetPointer(std::size_t offset = 0); + + /** + * Gets a constant pointer to the shared memory block + * @param offset Offset from the start of the shared memory block to get pointer + * @return A constant pointer to the shared memory block from the specified offset + */ + const u8* GetPointer(std::size_t offset = 0) const; + +private: + explicit SharedMemory(KernelCore& kernel); + ~SharedMemory() override; - /// Process that created this shared memory block. - SharedPtr<Process> owner_process; - /// Address of shared memory block in the owner process if specified. - VAddr base_address; /// Backing memory for this shared memory block. std::shared_ptr<std::vector<u8>> backing_block; /// Offset into the backing block for this shared memory. - std::size_t backing_block_offset; + std::size_t backing_block_offset = 0; /// Size of the memory block. Page-aligned. - u64 size; + u64 size = 0; /// Permission restrictions applied to the process which created the block. - MemoryPermission permissions; + MemoryPermission permissions{}; /// Permission restrictions applied to other processes mapping the block. - MemoryPermission other_permissions; + MemoryPermission other_permissions{}; + /// Process that created this shared memory block. + Process* owner_process; + /// Address of shared memory block in the owner process if specified. + VAddr base_address = 0; /// Name of shared memory object. std::string name; - -private: - explicit SharedMemory(KernelCore& kernel); - ~SharedMemory() override; }; } // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index e3cf3f909..348a22904 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -20,21 +20,22 @@ #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_wrap.h" #include "core/hle/kernel/thread.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/lock.h" #include "core/hle/result.h" #include "core/hle/service/service.h" -#include "core/settings.h" +#include "core/memory.h" namespace Kernel { namespace { @@ -63,56 +64,129 @@ bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) { vm.GetNewMapRegionEndAddress()); } +// 8 GiB +constexpr u64 MAIN_MEMORY_SIZE = 0x200000000; + // 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. ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr, u64 size) { - if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) { + if (!Common::Is4KBAligned(dst_addr)) { + LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); return ERR_INVALID_ADDRESS; } - if (size == 0 || !Common::Is4KBAligned(size)) { + if (!Common::Is4KBAligned(src_addr)) { + LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); + return ERR_INVALID_SIZE; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ERR_INVALID_SIZE; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); return ERR_INVALID_SIZE; } 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 ERR_INVALID_ADDRESS_STATE; } 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 ERR_INVALID_ADDRESS_STATE; } if (!IsInsideAddressSpace(vm_manager, src_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", + src_addr, size); return ERR_INVALID_ADDRESS_STATE; } if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) { + LOG_ERROR(Kernel_SVC, + "Destination is not within the new map region, addr=0x{:016X}, size=0x{:016X}", + dst_addr, size); return ERR_INVALID_MEMORY_RANGE; } const VAddr dst_end_address = dst_addr + size; if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() && vm_manager.GetHeapRegionEndAddress() > dst_addr) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the heap region, addr=0x{:016X}, " + "size=0x{:016X}, end_addr=0x{:016X}", + dst_addr, size, dst_end_address); return ERR_INVALID_MEMORY_RANGE; } if (dst_end_address > vm_manager.GetMapRegionBaseAddress() && vm_manager.GetMapRegionEndAddress() > dst_addr) { + LOG_ERROR(Kernel_SVC, + "Destination does not fit within the map region, addr=0x{:016X}, " + "size=0x{:016X}, end_addr=0x{:016X}", + dst_addr, size, dst_end_address); return ERR_INVALID_MEMORY_RANGE; } return RESULT_SUCCESS; } + +enum class ResourceLimitValueType { + CurrentValue, + LimitValue, +}; + +ResultVal<s64> RetrieveResourceLimitValue(Handle resource_limit, u32 resource_type, + ResourceLimitValueType value_type) { + const auto type = static_cast<ResourceType>(resource_type); + if (!IsValidResourceType(type)) { + LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type); + return ERR_INVALID_ENUM_VALUE; + } + + const auto& kernel = Core::System::GetInstance().Kernel(); + const auto* const current_process = kernel.CurrentProcess(); + ASSERT(current_process != nullptr); + + const auto resource_limit_object = + current_process->GetHandleTable().Get<ResourceLimit>(resource_limit); + if (!resource_limit_object) { + LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}", + resource_limit); + return ERR_INVALID_HANDLE; + } + + if (value_type == ResourceLimitValueType::CurrentValue) { + return MakeResult(resource_limit_object->GetCurrentResourceValue(type)); + } + + return MakeResult(resource_limit_object->GetMaxResourceValue(type)); +} } // Anonymous namespace /// Set the process heap to a given Size. It can both extend and shrink the heap. static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) { LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size); - // Size must be a multiple of 0x200000 (2MB) and be equal to or less than 4GB. - if ((heap_size & 0xFFFFFFFE001FFFFF) != 0) { + // Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB. + if ((heap_size % 0x200000) != 0) { + LOG_ERROR(Kernel_SVC, "The heap size is not a multiple of 2MB, heap_size=0x{:016X}", + heap_size); + return ERR_INVALID_SIZE; + } + + if (heap_size >= 0x200000000) { + LOG_ERROR(Kernel_SVC, "The heap size is not less than 8GB, heap_size=0x{:016X}", heap_size); return ERR_INVALID_SIZE; } @@ -127,20 +201,31 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) { LOG_TRACE(Kernel_SVC, "called, addr=0x{:X}, size=0x{:X}, prot=0x{:X}", addr, size, prot); if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr); return ERR_INVALID_ADDRESS; } - if (size == 0 || !Common::Is4KBAligned(size)) { + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ERR_INVALID_SIZE; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size); return ERR_INVALID_SIZE; } if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + addr, size); return ERR_INVALID_ADDRESS_STATE; } const auto permission = static_cast<MemoryPermission>(prot); if (permission != MemoryPermission::None && permission != MemoryPermission::Read && permission != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, "Invalid memory permission specified, Got memory permission=0x{:08X}", + static_cast<u32>(permission)); return ERR_INVALID_MEMORY_PERMISSIONS; } @@ -148,11 +233,15 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) { auto& vm_manager = current_process->VMManager(); if (!IsInsideAddressSpace(vm_manager, addr, size)) { + LOG_ERROR(Kernel_SVC, + "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + size); return ERR_INVALID_ADDRESS_STATE; } const VMManager::VMAHandle iter = vm_manager.FindVMA(addr); - if (iter == vm_manager.vma_map.end()) { + if (!vm_manager.IsValidHandle(iter)) { + LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr); return ERR_INVALID_ADDRESS_STATE; } @@ -185,7 +274,7 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { return result; } - return current_process->MirrorMemory(dst_addr, src_addr, size); + return current_process->MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack); } /// Unmaps a region that was previously mapped with svcMapMemory @@ -207,6 +296,9 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { /// Connect to an OS service given the port name, returns the handle to the port to out static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) { 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 ERR_NOT_FOUND; } @@ -214,6 +306,8 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address // Read 1 char beyond the max allowed port name to detect names that are too long. 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 ERR_OUT_OF_RANGE; } @@ -262,6 +356,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", thread_handle); return ERR_INVALID_HANDLE; } @@ -276,6 +371,8 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) { const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Process> process = handle_table.Get<Process>(process_handle); if (!process) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); return ERR_INVALID_HANDLE; } @@ -305,12 +402,18 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}", handles_address, handle_count, nano_seconds); - if (!Memory::IsValidVirtualAddress(handles_address)) + if (!Memory::IsValidVirtualAddress(handles_address)) { + LOG_ERROR(Kernel_SVC, + "Handle address is not a valid virtual address, handle_address=0x{:016X}", + handles_address); return ERR_INVALID_POINTER; + } static constexpr u64 MaxHandles = 0x40; if (handle_count > MaxHandles) { + LOG_ERROR(Kernel_SVC, "Handle count specified is too large, expected {} but got {}", + MaxHandles, handle_count); return ERR_OUT_OF_RANGE; } @@ -325,6 +428,7 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 const auto object = handle_table.Get<WaitObject>(handle); if (object == nullptr) { + LOG_ERROR(Kernel_SVC, "Object is a nullptr"); return ERR_INVALID_HANDLE; } @@ -348,11 +452,13 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 // If a timeout value of 0 was provided, just return the Timeout error code instead of // suspending the thread. - if (nano_seconds == 0) + if (nano_seconds == 0) { return RESULT_TIMEOUT; + } - for (auto& object : objects) + for (auto& object : objects) { object->AddWaitingThread(thread); + } thread->SetWaitObjects(std::move(objects)); thread->SetStatus(ThreadStatus::WaitSynchAny); @@ -373,6 +479,8 @@ static ResultCode CancelSynchronization(Handle thread_handle) { const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", + thread_handle); return ERR_INVALID_HANDLE; } @@ -391,10 +499,13 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, holding_thread_handle, mutex_addr, requesting_thread_handle); if (Memory::IsKernelVirtualAddress(mutex_addr)) { + LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}", + mutex_addr); return ERR_INVALID_ADDRESS_STATE; } if (!Common::IsWordAligned(mutex_addr)) { + LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr); return ERR_INVALID_ADDRESS; } @@ -408,10 +519,13 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr); if (Memory::IsKernelVirtualAddress(mutex_addr)) { + LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}", + mutex_addr); return ERR_INVALID_ADDRESS_STATE; } if (!Common::IsWordAligned(mutex_addr)) { + LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr); return ERR_INVALID_ADDRESS; } @@ -549,7 +663,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) TotalMemoryUsage = 6, TotalHeapUsage = 7, IsCurrentProcessBeingDebugged = 8, - ResourceHandleLimit = 9, + RegisterResourceLimit = 9, IdleTickCount = 10, RandomEntropy = 11, PerformanceCounter = 0xF0000002, @@ -569,86 +683,172 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) ThreadTickCount = 0xF0000002, }; - const auto* current_process = Core::CurrentProcess(); - const auto& vm_manager = current_process->VMManager(); + const auto info_id_type = static_cast<GetInfoType>(info_id); - switch (static_cast<GetInfoType>(info_id)) { + switch (info_id_type) { case GetInfoType::AllowedCpuIdBitmask: - *result = current_process->GetAllowedProcessorMask(); - break; case GetInfoType::AllowedThreadPrioBitmask: - *result = current_process->GetAllowedThreadPriorityMask(); - break; case GetInfoType::MapRegionBaseAddr: - *result = vm_manager.GetMapRegionBaseAddress(); - break; case GetInfoType::MapRegionSize: - *result = vm_manager.GetMapRegionSize(); - break; case GetInfoType::HeapRegionBaseAddr: - *result = vm_manager.GetHeapRegionBaseAddress(); - break; case GetInfoType::HeapRegionSize: - *result = vm_manager.GetHeapRegionSize(); - break; + case GetInfoType::ASLRRegionBaseAddr: + case GetInfoType::ASLRRegionSize: + case GetInfoType::NewMapRegionBaseAddr: + case GetInfoType::NewMapRegionSize: case GetInfoType::TotalMemoryUsage: - *result = vm_manager.GetTotalMemoryUsage(); - break; case GetInfoType::TotalHeapUsage: - *result = vm_manager.GetTotalHeapUsage(); - break; + case GetInfoType::IsVirtualAddressMemoryEnabled: + case GetInfoType::PersonalMmHeapUsage: + case GetInfoType::TitleId: + case GetInfoType::UserExceptionContextAddr: { + if (info_sub_id != 0) { + return ERR_INVALID_ENUM_VALUE; + } + + const auto& current_process_handle_table = Core::CurrentProcess()->GetHandleTable(); + const auto process = current_process_handle_table.Get<Process>(static_cast<Handle>(handle)); + if (!process) { + return ERR_INVALID_HANDLE; + } + + switch (info_id_type) { + case GetInfoType::AllowedCpuIdBitmask: + *result = process->GetAllowedProcessorMask(); + return RESULT_SUCCESS; + + case GetInfoType::AllowedThreadPrioBitmask: + *result = process->GetAllowedThreadPriorityMask(); + return RESULT_SUCCESS; + + case GetInfoType::MapRegionBaseAddr: + *result = process->VMManager().GetMapRegionBaseAddress(); + return RESULT_SUCCESS; + + case GetInfoType::MapRegionSize: + *result = process->VMManager().GetMapRegionSize(); + return RESULT_SUCCESS; + + case GetInfoType::HeapRegionBaseAddr: + *result = process->VMManager().GetHeapRegionBaseAddress(); + return RESULT_SUCCESS; + + case GetInfoType::HeapRegionSize: + *result = process->VMManager().GetHeapRegionSize(); + return RESULT_SUCCESS; + + case GetInfoType::ASLRRegionBaseAddr: + *result = process->VMManager().GetASLRRegionBaseAddress(); + return RESULT_SUCCESS; + + case GetInfoType::ASLRRegionSize: + *result = process->VMManager().GetASLRRegionSize(); + return RESULT_SUCCESS; + + case GetInfoType::NewMapRegionBaseAddr: + *result = process->VMManager().GetNewMapRegionBaseAddress(); + return RESULT_SUCCESS; + + case GetInfoType::NewMapRegionSize: + *result = process->VMManager().GetNewMapRegionSize(); + return RESULT_SUCCESS; + + case GetInfoType::TotalMemoryUsage: + *result = process->VMManager().GetTotalMemoryUsage(); + return RESULT_SUCCESS; + + case GetInfoType::TotalHeapUsage: + *result = process->VMManager().GetTotalHeapUsage(); + return RESULT_SUCCESS; + + case GetInfoType::IsVirtualAddressMemoryEnabled: + *result = process->IsVirtualMemoryEnabled(); + return RESULT_SUCCESS; + + case GetInfoType::TitleId: + *result = process->GetTitleID(); + return RESULT_SUCCESS; + + case GetInfoType::UserExceptionContextAddr: + LOG_WARNING(Kernel_SVC, + "(STUBBED) Attempted to query user exception context address, returned 0"); + *result = 0; + return RESULT_SUCCESS; + + default: + break; + } + + LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ERR_INVALID_ENUM_VALUE; + } + case GetInfoType::IsCurrentProcessBeingDebugged: *result = 0; - break; + return RESULT_SUCCESS; + + case GetInfoType::RegisterResourceLimit: { + if (handle != 0) { + return ERR_INVALID_HANDLE; + } + + if (info_sub_id != 0) { + return ERR_INVALID_COMBINATION; + } + + Process* const current_process = Core::CurrentProcess(); + HandleTable& handle_table = current_process->GetHandleTable(); + const auto resource_limit = current_process->GetResourceLimit(); + if (!resource_limit) { + *result = KernelHandle::InvalidHandle; + // Yes, the kernel considers this a successful operation. + return RESULT_SUCCESS; + } + + const auto table_result = handle_table.Create(resource_limit); + if (table_result.Failed()) { + return table_result.Code(); + } + + *result = *table_result; + return RESULT_SUCCESS; + } + case GetInfoType::RandomEntropy: if (handle != 0) { + LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", + handle); return ERR_INVALID_HANDLE; } if (info_sub_id >= Process::RANDOM_ENTROPY_SIZE) { + LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}", + Process::RANDOM_ENTROPY_SIZE, info_sub_id); return ERR_INVALID_COMBINATION; } - *result = current_process->GetRandomEntropy(info_sub_id); + *result = Core::CurrentProcess()->GetRandomEntropy(info_sub_id); return RESULT_SUCCESS; - break; - case GetInfoType::ASLRRegionBaseAddr: - *result = vm_manager.GetASLRRegionBaseAddress(); - break; - case GetInfoType::ASLRRegionSize: - *result = vm_manager.GetASLRRegionSize(); - break; - case GetInfoType::NewMapRegionBaseAddr: - *result = vm_manager.GetNewMapRegionBaseAddress(); - break; - case GetInfoType::NewMapRegionSize: - *result = vm_manager.GetNewMapRegionSize(); - break; - case GetInfoType::IsVirtualAddressMemoryEnabled: - *result = current_process->IsVirtualMemoryEnabled(); - break; - case GetInfoType::TitleId: - *result = current_process->GetTitleID(); - break; + case GetInfoType::PrivilegedProcessId: LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query privileged process id bounds, returned 0"); *result = 0; - break; - case GetInfoType::UserExceptionContextAddr: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query user exception context address, returned 0"); - *result = 0; - break; + return RESULT_SUCCESS; + case GetInfoType::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 ERR_INVALID_COMBINATION; } const auto thread = - current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle)); + Core::CurrentProcess()->GetHandleTable().Get<Thread>(static_cast<Handle>(handle)); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", + static_cast<Handle>(handle)); return ERR_INVALID_HANDLE; } @@ -668,13 +868,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) } *result = out_ticks; - break; + return RESULT_SUCCESS; } + default: - UNIMPLEMENTED(); + LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id); + return ERR_INVALID_ENUM_VALUE; } - - return RESULT_SUCCESS; } /// Sets the thread activity @@ -690,14 +890,22 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { const auto* current_process = Core::CurrentProcess(); const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); return ERR_INVALID_HANDLE; } if (thread->GetOwnerProcess() != current_process) { + LOG_ERROR(Kernel_SVC, + "The current process does not own the current thread, thread_handle={:08X} " + "thread_pid={}, " + "current_process_pid={}", + handle, thread->GetOwnerProcess()->GetProcessID(), + current_process->GetProcessID()); return ERR_INVALID_HANDLE; } if (thread == GetCurrentThread()) { + LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); return ERR_ALREADY_REGISTERED; } @@ -718,9 +926,12 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { /// Gets the priority for the specified thread static ResultCode GetThreadPriority(u32* priority, Handle handle) { + LOG_TRACE(Kernel_SVC, "called"); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); return ERR_INVALID_HANDLE; } @@ -730,21 +941,21 @@ static ResultCode GetThreadPriority(u32* priority, Handle handle) { /// Sets the priority for the specified thread static ResultCode SetThreadPriority(Handle handle, u32 priority) { + LOG_TRACE(Kernel_SVC, "called"); + if (priority > THREADPRIO_LOWEST) { + LOG_ERROR( + Kernel_SVC, + "An invalid priority was specified, expected {} but got {} for thread_handle={:08X}", + THREADPRIO_LOWEST, priority, handle); return ERR_INVALID_THREAD_PRIORITY; } const auto* const current_process = Core::CurrentProcess(); - // Note: The kernel uses the current process's resource limit instead of - // the one from the thread owner's resource limit. - const ResourceLimit& resource_limit = current_process->GetResourceLimit(); - if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { - return ERR_INVALID_THREAD_PRIORITY; - } - SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); return ERR_INVALID_HANDLE; } @@ -767,36 +978,50 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s shared_memory_handle, addr, size, permissions); if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr); return ERR_INVALID_ADDRESS; } - if (size == 0 || !Common::Is4KBAligned(size)) { + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ERR_INVALID_SIZE; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size); return ERR_INVALID_SIZE; } if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + addr, size); return ERR_INVALID_ADDRESS_STATE; } const auto permissions_type = static_cast<MemoryPermission>(permissions); if (permissions_type != MemoryPermission::Read && permissions_type != MemoryPermission::ReadWrite) { - LOG_ERROR(Kernel_SVC, "Invalid permissions=0x{:08X}", permissions); + LOG_ERROR(Kernel_SVC, "Expected Read or ReadWrite permission but got permissions=0x{:08X}", + permissions); return ERR_INVALID_MEMORY_PERMISSIONS; } auto* const current_process = Core::CurrentProcess(); auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { + LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}", + shared_memory_handle); return ERR_INVALID_HANDLE; } const auto& vm_manager = current_process->VMManager(); if (!vm_manager.IsWithinASLRRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, "Region is not within the ASLR region. addr=0x{:016X}, size={:016X}", + addr, size); return ERR_INVALID_MEMORY_RANGE; } - return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare); + return shared_memory->Map(*current_process, addr, permissions_type, MemoryPermission::DontCare); } static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { @@ -804,61 +1029,82 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 shared_memory_handle, addr, size); if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr); return ERR_INVALID_ADDRESS; } - if (size == 0 || !Common::Is4KBAligned(size)) { + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ERR_INVALID_SIZE; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size); return ERR_INVALID_SIZE; } if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + addr, size); return ERR_INVALID_ADDRESS_STATE; } auto* const current_process = Core::CurrentProcess(); auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { + LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}", + shared_memory_handle); return ERR_INVALID_HANDLE; } const auto& vm_manager = current_process->VMManager(); if (!vm_manager.IsWithinASLRRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, "Region is not within the ASLR region. addr=0x{:016X}, size={:016X}", + addr, size); return ERR_INVALID_MEMORY_RANGE; } - return shared_memory->Unmap(current_process, addr); + return shared_memory->Unmap(*current_process, addr); } -/// Query process memory -static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/, - Handle process_handle, u64 addr) { +static ResultCode QueryProcessMemory(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 = Core::CurrentProcess()->GetHandleTable(); SharedPtr<Process> process = handle_table.Get<Process>(process_handle); if (!process) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); return ERR_INVALID_HANDLE; } - auto vma = process->VMManager().FindVMA(addr); - memory_info->attributes = 0; - if (vma == process->VMManager().vma_map.end()) { - memory_info->base_address = 0; - memory_info->permission = static_cast<u32>(VMAPermission::None); - memory_info->size = 0; - memory_info->type = static_cast<u32>(MemoryState::Unmapped); - } else { - memory_info->base_address = vma->second.base; - memory_info->permission = static_cast<u32>(vma->second.permissions); - memory_info->size = vma->second.size; - memory_info->type = static_cast<u32>(vma->second.meminfo_state); - } - LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr); + const auto& vm_manager = process->VMManager(); + const MemoryInfo memory_info = vm_manager.QueryMemory(address); + + Memory::Write64(memory_info_address, memory_info.base_address); + Memory::Write64(memory_info_address + 8, memory_info.size); + Memory::Write32(memory_info_address + 16, memory_info.state); + Memory::Write32(memory_info_address + 20, memory_info.attributes); + Memory::Write32(memory_info_address + 24, memory_info.permission); + Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count); + Memory::Write32(memory_info_address + 28, memory_info.device_ref_count); + Memory::Write32(memory_info_address + 36, 0); + + // Page info appears to be currently unused by the kernel and is always set to zero. + Memory::Write32(page_info_address, 0); + return RESULT_SUCCESS; } -/// Query memory -static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) { - LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr); - return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr); +static ResultCode QueryMemory(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(memory_info_address, page_info_address, CurrentProcess, + query_address); } /// Exits the current process @@ -880,15 +1126,18 @@ static void ExitProcess() { /// Creates a new thread static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top, u32 priority, s32 processor_id) { + LOG_TRACE(Kernel_SVC, + "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, " + "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", + entry_point, arg, stack_top, priority, processor_id, *out_handle); + if (priority > THREADPRIO_LOWEST) { + LOG_ERROR(Kernel_SVC, "An invalid priority was specified, expected {} but got {}", + THREADPRIO_LOWEST, priority); return ERR_INVALID_THREAD_PRIORITY; } auto* const current_process = Core::CurrentProcess(); - const ResourceLimit& resource_limit = current_process->GetResourceLimit(); - if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { - return ERR_INVALID_THREAD_PRIORITY; - } if (processor_id == THREADPROCESSORID_DEFAULT) { // Set the target CPU to the one specified in the process' exheader. @@ -915,6 +1164,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V const auto new_guest_handle = current_process->GetHandleTable().Create(thread); if (new_guest_handle.Failed()) { + LOG_ERROR(Kernel_SVC, "Failed to create handle with error=0x{:X}", + new_guest_handle.Code().raw); return new_guest_handle.Code(); } thread->SetGuestHandle(*new_guest_handle); @@ -922,11 +1173,6 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule(); - LOG_TRACE(Kernel_SVC, - "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, " - "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}", - entry_point, name, arg, stack_top, priority, processor_id, *out_handle); - return RESULT_SUCCESS; } @@ -937,6 +1183,8 @@ static ResultCode StartThread(Handle thread_handle) { const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", + thread_handle); return ERR_INVALID_HANDLE; } @@ -1136,10 +1384,12 @@ static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout address, type, value, timeout); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { + LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address); return ERR_INVALID_ADDRESS_STATE; } // If the address is not properly aligned to 4 bytes, return invalid address. - if (address % sizeof(u32) != 0) { + if (!Common::IsWordAligned(address)) { + LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address); return ERR_INVALID_ADDRESS; } @@ -1151,6 +1401,10 @@ static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout case AddressArbiter::ArbitrationType::WaitIfEqual: return AddressArbiter::WaitForAddressIfEqual(address, value, timeout); default: + LOG_ERROR(Kernel_SVC, + "Invalid arbitration type, expected WaitIfLessThan, DecrementAndWaitIfLessThan " + "or WaitIfEqual but got {}", + type); return ERR_INVALID_ENUM_VALUE; } } @@ -1161,10 +1415,12 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to address, type, value, num_to_wake); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { + LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address); return ERR_INVALID_ADDRESS_STATE; } // If the address is not properly aligned to 4 bytes, return invalid address. - if (address % sizeof(u32) != 0) { + if (!Common::IsWordAligned(address)) { + LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address); return ERR_INVALID_ADDRESS; } @@ -1177,12 +1433,18 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to return AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake); default: + LOG_ERROR(Kernel_SVC, + "Invalid signal type, expected Signal, IncrementAndSignalIfEqual " + "or ModifyByWaitingCountAndSignalIfEqual but got {}", + type); return ERR_INVALID_ENUM_VALUE; } } /// This returns the total CPU ticks elapsed since the CPU was powered-on static u64 GetSystemTick() { + LOG_TRACE(Kernel_SVC, "called"); + const u64 result{CoreTiming::GetTicks()}; // Advance time to defeat dumb games that busy-wait for the frame to end. @@ -1199,24 +1461,61 @@ static ResultCode CloseHandle(Handle handle) { return handle_table.Close(handle); } -/// Reset an event +/// Clears the signaled state of an event or process. static ResultCode ResetSignal(Handle handle) { LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - auto event = handle_table.Get<Event>(handle); - ASSERT(event != nullptr); + auto event = handle_table.Get<ReadableEvent>(handle); + if (event) { + return event->Reset(); + } - event->Clear(); - return RESULT_SUCCESS; + auto process = handle_table.Get<Process>(handle); + if (process) { + return process->ClearSignalState(); + } + + LOG_ERROR(Kernel_SVC, "Invalid handle (0x{:08X})", handle); + return ERR_INVALID_HANDLE; } /// Creates a TransferMemory object static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, - permissions); - *handle = 0; + LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, + permissions); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr); + return ERR_INVALID_ADDRESS; + } + + if (!Common::Is4KBAligned(size) || size == 0) { + LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size); + return ERR_INVALID_ADDRESS; + } + + if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})", + addr, size); + return ERR_INVALID_ADDRESS_STATE; + } + + const auto perms = static_cast<MemoryPermission>(permissions); + if (perms != MemoryPermission::None && perms != MemoryPermission::Read && + perms != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})", + permissions); + return ERR_INVALID_MEMORY_PERMISSIONS; + } + + auto& kernel = Core::System::GetInstance().Kernel(); + auto process = kernel.CurrentProcess(); + auto& handle_table = process->GetHandleTable(); + const auto shared_mem_handle = SharedMemory::Create(kernel, process, size, perms, perms, addr); + + CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle)); return RESULT_SUCCESS; } @@ -1226,6 +1525,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", + thread_handle); return ERR_INVALID_HANDLE; } @@ -1236,12 +1537,14 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) } static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle, + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:016X}, core=0x{:X}", thread_handle, mask, core); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", + thread_handle); return ERR_INVALID_HANDLE; } @@ -1256,6 +1559,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { } if (mask == 0) { + LOG_ERROR(Kernel_SVC, "Mask is 0"); return ERR_INVALID_COMBINATION; } @@ -1265,11 +1569,14 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { if (core == OnlyChangeMask) { core = thread->GetIdealCore(); } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) { + LOG_ERROR(Kernel_SVC, "Invalid core specified, got {}", core); return ERR_INVALID_PROCESSOR_ID; } // Error out if the input core isn't enabled in the input mask. if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) { + LOG_ERROR(Kernel_SVC, "Core is not enabled for the current mask, core={}, mask={:016X}", + core, mask); return ERR_INVALID_COMBINATION; } @@ -1282,44 +1589,109 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss u32 remote_permissions) { LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size, local_permissions, remote_permissions); + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is 0"); + return ERR_INVALID_SIZE; + } + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); + return ERR_INVALID_SIZE; + } - // Size must be a multiple of 4KB and be less than or equal to - // approx. 8 GB (actually (1GB - 512B) * 8) - if (size == 0 || (size & 0xFFFFFFFE00000FFF) != 0) { + if (size >= MAIN_MEMORY_SIZE) { + LOG_ERROR(Kernel_SVC, "Size is not less than 8GB, 0x{:016X}", size); return ERR_INVALID_SIZE; } const auto local_perms = static_cast<MemoryPermission>(local_permissions); if (local_perms != MemoryPermission::Read && local_perms != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, + "Invalid local memory permissions, expected Read or ReadWrite but got " + "local_permissions={}", + static_cast<u32>(local_permissions)); return ERR_INVALID_MEMORY_PERMISSIONS; } const auto remote_perms = static_cast<MemoryPermission>(remote_permissions); if (remote_perms != MemoryPermission::Read && remote_perms != MemoryPermission::ReadWrite && remote_perms != MemoryPermission::DontCare) { + LOG_ERROR(Kernel_SVC, + "Invalid remote memory permissions, expected Read, ReadWrite or DontCare but got " + "remote_permissions={}", + static_cast<u32>(remote_permissions)); return ERR_INVALID_MEMORY_PERMISSIONS; } auto& kernel = Core::System::GetInstance().Kernel(); - auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - auto shared_mem_handle = - SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size, - local_perms, remote_perms); + auto process = kernel.CurrentProcess(); + auto& handle_table = process->GetHandleTable(); + auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms); CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle)); return RESULT_SUCCESS; } +static ResultCode CreateEvent(Handle* write_handle, Handle* read_handle) { + LOG_DEBUG(Kernel_SVC, "called"); + + auto& kernel = Core::System::GetInstance().Kernel(); + const auto [readable_event, writable_event] = + WritableEvent::CreateEventPair(kernel, ResetType::Sticky, "CreateEvent"); + + HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable(); + + const auto write_create_result = handle_table.Create(writable_event); + if (write_create_result.Failed()) { + return write_create_result.Code(); + } + *write_handle = *write_create_result; + + const auto read_create_result = handle_table.Create(readable_event); + if (read_create_result.Failed()) { + handle_table.Close(*write_create_result); + return read_create_result.Code(); + } + *read_handle = *read_create_result; + + LOG_DEBUG(Kernel_SVC, + "successful. Writable event handle=0x{:08X}, Readable event handle=0x{:08X}", + *write_create_result, *read_create_result); + return RESULT_SUCCESS; +} + static ResultCode ClearEvent(Handle handle) { LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - SharedPtr<Event> evt = handle_table.Get<Event>(handle); - if (evt == nullptr) { + + auto writable_event = handle_table.Get<WritableEvent>(handle); + if (writable_event) { + writable_event->Clear(); + return RESULT_SUCCESS; + } + + auto readable_event = handle_table.Get<ReadableEvent>(handle); + if (readable_event) { + readable_event->Clear(); + return RESULT_SUCCESS; + } + + LOG_ERROR(Kernel_SVC, "Event handle does not exist, handle=0x{:08X}", handle); + return ERR_INVALID_HANDLE; +} + +static ResultCode SignalEvent(Handle handle) { + LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle); + + HandleTable& handle_table = Core::CurrentProcess()->GetHandleTable(); + auto writable_event = handle_table.Get<WritableEvent>(handle); + + if (!writable_event) { + LOG_ERROR(Kernel_SVC, "Non-existent writable event handle used (0x{:08X})", handle); return ERR_INVALID_HANDLE; } - evt->Clear(); + writable_event->Signal(); return RESULT_SUCCESS; } @@ -1334,11 +1706,14 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) { const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); const auto process = handle_table.Get<Process>(process_handle); if (!process) { + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + process_handle); return ERR_INVALID_HANDLE; } const auto info_type = static_cast<InfoType>(type); if (info_type != InfoType::Status) { + LOG_ERROR(Kernel_SVC, "Expected info_type to be Status but got {} instead", type); return ERR_INVALID_ENUM_VALUE; } @@ -1346,6 +1721,87 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) { return RESULT_SUCCESS; } +static ResultCode CreateResourceLimit(Handle* out_handle) { + LOG_DEBUG(Kernel_SVC, "called"); + + auto& kernel = Core::System::GetInstance().Kernel(); + auto resource_limit = ResourceLimit::Create(kernel); + + auto* const current_process = kernel.CurrentProcess(); + ASSERT(current_process != nullptr); + + const auto handle = current_process->GetHandleTable().Create(std::move(resource_limit)); + if (handle.Failed()) { + return handle.Code(); + } + + *out_handle = *handle; + return RESULT_SUCCESS; +} + +static ResultCode GetResourceLimitLimitValue(u64* out_value, Handle resource_limit, + u32 resource_type) { + LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}", resource_limit, resource_type); + + const auto limit_value = RetrieveResourceLimitValue(resource_limit, resource_type, + ResourceLimitValueType::LimitValue); + if (limit_value.Failed()) { + return limit_value.Code(); + } + + *out_value = static_cast<u64>(*limit_value); + return RESULT_SUCCESS; +} + +static ResultCode GetResourceLimitCurrentValue(u64* out_value, Handle resource_limit, + u32 resource_type) { + LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}", resource_limit, resource_type); + + const auto current_value = RetrieveResourceLimitValue(resource_limit, resource_type, + ResourceLimitValueType::CurrentValue); + if (current_value.Failed()) { + return current_value.Code(); + } + + *out_value = static_cast<u64>(*current_value); + return RESULT_SUCCESS; +} + +static ResultCode SetResourceLimitLimitValue(Handle resource_limit, u32 resource_type, u64 value) { + LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}, Value={}", resource_limit, + resource_type, value); + + const auto type = static_cast<ResourceType>(resource_type); + if (!IsValidResourceType(type)) { + LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type); + return ERR_INVALID_ENUM_VALUE; + } + + auto& kernel = Core::System::GetInstance().Kernel(); + auto* const current_process = kernel.CurrentProcess(); + ASSERT(current_process != nullptr); + + auto resource_limit_object = + current_process->GetHandleTable().Get<ResourceLimit>(resource_limit); + if (!resource_limit_object) { + LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}", + resource_limit); + return ERR_INVALID_HANDLE; + } + + const auto set_result = resource_limit_object->SetLimitValue(type, static_cast<s64>(value)); + if (set_result.IsError()) { + LOG_ERROR( + Kernel_SVC, + "Attempted to lower resource limit ({}) for category '{}' below its current value ({})", + resource_limit_object->GetMaxResourceValue(type), resource_type, + resource_limit_object->GetCurrentResourceValue(type)); + return set_result; + } + + return RESULT_SUCCESS; +} + namespace { struct FunctionDef { using Func = void(); @@ -1374,7 +1830,7 @@ static const FunctionDef SVC_Table[] = { {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"}, {0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"}, {0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"}, - {0x11, nullptr, "SignalEvent"}, + {0x11, SvcWrap<SignalEvent>, "SignalEvent"}, {0x12, SvcWrap<ClearEvent>, "ClearEvent"}, {0x13, SvcWrap<MapSharedMemory>, "MapSharedMemory"}, {0x14, SvcWrap<UnmapSharedMemory>, "UnmapSharedMemory"}, @@ -1405,8 +1861,8 @@ static const FunctionDef SVC_Table[] = { {0x2D, nullptr, "UnmapPhysicalMemory"}, {0x2E, nullptr, "GetFutureThreadInfo"}, {0x2F, nullptr, "GetLastThreadInfo"}, - {0x30, nullptr, "GetResourceLimitLimitValue"}, - {0x31, nullptr, "GetResourceLimitCurrentValue"}, + {0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"}, + {0x31, SvcWrap<GetResourceLimitCurrentValue>, "GetResourceLimitCurrentValue"}, {0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"}, {0x33, SvcWrap<GetThreadContext>, "GetThreadContext"}, {0x34, SvcWrap<WaitForAddress>, "WaitForAddress"}, @@ -1426,7 +1882,7 @@ static const FunctionDef SVC_Table[] = { {0x42, nullptr, "ReplyAndReceiveLight"}, {0x43, nullptr, "ReplyAndReceive"}, {0x44, nullptr, "ReplyAndReceiveWithUserBuffer"}, - {0x45, nullptr, "CreateEvent"}, + {0x45, SvcWrap<CreateEvent>, "CreateEvent"}, {0x46, nullptr, "Unknown"}, {0x47, nullptr, "Unknown"}, {0x48, nullptr, "MapPhysicalMemoryUnsafe"}, @@ -1475,15 +1931,15 @@ static const FunctionDef SVC_Table[] = { {0x73, nullptr, "SetProcessMemoryPermission"}, {0x74, nullptr, "MapProcessMemory"}, {0x75, nullptr, "UnmapProcessMemory"}, - {0x76, nullptr, "QueryProcessMemory"}, + {0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"}, {0x77, nullptr, "MapProcessCodeMemory"}, {0x78, nullptr, "UnmapProcessCodeMemory"}, {0x79, nullptr, "CreateProcess"}, {0x7A, nullptr, "StartProcess"}, {0x7B, nullptr, "TerminateProcess"}, {0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"}, - {0x7D, nullptr, "CreateResourceLimit"}, - {0x7E, nullptr, "SetResourceLimitLimitValue"}, + {0x7D, SvcWrap<CreateResourceLimit>, "CreateResourceLimit"}, + {0x7E, SvcWrap<SetResourceLimitLimitValue>, "SetResourceLimitLimitValue"}, {0x7F, nullptr, "CallSecureMonitor"}, }; diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index b06aac4ec..c37ae0f98 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -8,22 +8,6 @@ namespace Kernel { -struct MemoryInfo { - u64 base_address; - u64 size; - u32 type; - u32 attributes; - u32 permission; - u32 device_refcount; - u32 ipc_refcount; - INSERT_PADDING_WORDS(1); -}; -static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size."); - -struct PageInfo { - u64 flags; -}; - void CallSVC(u32 immediate); } // namespace Kernel diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 233a99fb0..2f758b959 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -7,9 +7,7 @@ #include "common/common_types.h" #include "core/arm/arm_interface.h" #include "core/core.h" -#include "core/hle/kernel/svc.h" #include "core/hle/result.h" -#include "core/memory.h" namespace Kernel { @@ -43,6 +41,14 @@ void SvcWrap() { FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw); } +template <ResultCode func(u32*)> +void SvcWrap() { + u32 param = 0; + const u32 retval = func(¶m).raw; + Core::CurrentArmInterface().SetReg(1, param); + FuncReturn(retval); +} + template <ResultCode func(u32*, u32)> void SvcWrap() { u32 param_1 = 0; @@ -51,6 +57,19 @@ void SvcWrap() { FuncReturn(retval); } +template <ResultCode func(u32*, u32*)> +void SvcWrap() { + u32 param_1 = 0; + u32 param_2 = 0; + const u32 retval = func(¶m_1, ¶m_2).raw; + + auto& arm_interface = Core::CurrentArmInterface(); + arm_interface.SetReg(1, param_1); + arm_interface.SetReg(2, param_2); + + FuncReturn(retval); +} + template <ResultCode func(u32*, u64)> void SvcWrap() { u32 param_1 = 0; @@ -108,7 +127,12 @@ void SvcWrap() { template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( - func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw); + func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw); +} + +template <ResultCode func(u64, u64, u32, u64)> +void SvcWrap() { + FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw); } template <ResultCode func(u32, u64, u32)> @@ -170,21 +194,6 @@ void SvcWrap() { FuncReturn(retval); } -template <ResultCode func(MemoryInfo*, PageInfo*, u64)> -void SvcWrap() { - MemoryInfo memory_info = {}; - PageInfo page_info = {}; - u32 retval = func(&memory_info, &page_info, Param(2)).raw; - - Memory::Write64(Param(0), memory_info.base_address); - Memory::Write64(Param(0) + 8, memory_info.size); - Memory::Write32(Param(0) + 16, memory_info.type); - Memory::Write32(Param(0) + 20, memory_info.attributes); - Memory::Write32(Param(0) + 24, memory_info.permission); - - FuncReturn(retval); -} - template <ResultCode func(u32*, u64, u64, u32)> void SvcWrap() { u32 param_1 = 0; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 100f8f6bf..d3b55a51e 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -25,14 +25,14 @@ static const char* GetMemoryStateName(MemoryState state) { "CodeMutable", "Heap", "Shared", "Unknown1", "ModuleCodeStatic", "ModuleCodeMutable", - "IpcBuffer0", "Mapped", + "IpcBuffer0", "Stack", "ThreadLocal", "TransferMemoryIsolated", "TransferMemory", "ProcessMemory", - "Unknown2", "IpcBuffer1", + "Inaccessible", "IpcBuffer1", "IpcBuffer3", "KernelStack", }; - return names[static_cast<int>(state)]; + return names[ToSvcMemoryState(state)]; } bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { @@ -87,6 +87,10 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const { } } +bool VMManager::IsValidHandle(VMAHandle handle) const { + return handle != vma_map.cend(); +} + ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, std::size_t offset, u64 size, @@ -298,6 +302,25 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) { return RESULT_SUCCESS; } +MemoryInfo VMManager::QueryMemory(VAddr address) const { + const auto vma = FindVMA(address); + MemoryInfo memory_info{}; + + if (IsValidHandle(vma)) { + memory_info.base_address = vma->second.base; + memory_info.permission = static_cast<u32>(vma->second.permissions); + memory_info.size = vma->second.size; + memory_info.state = ToSvcMemoryState(vma->second.meminfo_state); + } else { + memory_info.base_address = address_space_end; + memory_info.permission = static_cast<u32>(VMAPermission::None); + memory_info.size = 0 - address_space_end; + memory_info.state = static_cast<u32>(MemoryState::Inaccessible); + } + + return memory_info; +} + ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) { const auto vma = FindVMA(src_addr); diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index d522404fe..10bacac3e 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -43,26 +43,129 @@ enum class VMAPermission : u8 { ReadWriteExecute = Read | Write | Execute, }; -/// Set of values returned in MemoryInfo.state by svcQueryMemory. +// clang-format off +/// Represents memory states and any relevant flags, as used by the kernel. +/// svcQueryMemory interprets these by masking away all but the first eight +/// bits when storing memory state into a MemoryInfo instance. enum class MemoryState : u32 { - Unmapped = 0x0, - Io = 0x1, - Normal = 0x2, - CodeStatic = 0x3, - CodeMutable = 0x4, - Heap = 0x5, - Shared = 0x6, - ModuleCodeStatic = 0x8, - ModuleCodeMutable = 0x9, - IpcBuffer0 = 0xA, - Mapped = 0xB, - ThreadLocal = 0xC, - TransferMemoryIsolated = 0xD, - TransferMemory = 0xE, - ProcessMemory = 0xF, - IpcBuffer1 = 0x11, - IpcBuffer3 = 0x12, - KernelStack = 0x13, + Mask = 0xFF, + FlagProtect = 1U << 8, + FlagDebug = 1U << 9, + FlagIPC0 = 1U << 10, + FlagIPC3 = 1U << 11, + FlagIPC1 = 1U << 12, + FlagMapped = 1U << 13, + FlagCode = 1U << 14, + FlagAlias = 1U << 15, + FlagModule = 1U << 16, + FlagTransfer = 1U << 17, + FlagQueryPhysicalAddressAllowed = 1U << 18, + FlagSharedDevice = 1U << 19, + FlagSharedDeviceAligned = 1U << 20, + FlagIPCBuffer = 1U << 21, + FlagMemoryPoolAllocated = 1U << 22, + FlagMapProcess = 1U << 23, + FlagUncached = 1U << 24, + FlagCodeMemory = 1U << 25, + + // Convenience flag sets to reduce repetition + IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1, + + CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed | + FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + + DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer | + FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned | + FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached, + + Unmapped = 0x00, + Io = 0x01 | FlagMapped, + Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed, + CodeStatic = 0x03 | CodeFlags | FlagMapProcess, + CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory, + Heap = 0x05 | DataFlags | FlagCodeMemory, + Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated, + ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess, + ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory, + + IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated | + IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned, + + Stack = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed | + FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + + ThreadLocal = 0x0C | FlagMapped | FlagMemoryPoolAllocated, + + TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed | + FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated | + FlagUncached, + + TransferMemory = 0x0E | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed | + FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + + ProcessMemory = 0x0F | FlagIPC3 | FlagIPC1 | FlagMapped | FlagMemoryPoolAllocated, + + // Used to signify an inaccessible or invalid memory region with memory queries + Inaccessible = 0x10, + + IpcBuffer1 = 0x11 | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed | + FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + + IpcBuffer3 = 0x12 | FlagIPC3 | FlagMapped | FlagQueryPhysicalAddressAllowed | + FlagSharedDeviceAligned | FlagMemoryPoolAllocated, + + KernelStack = 0x13 | FlagMapped, +}; +// clang-format on + +constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) { + return static_cast<MemoryState>(u32(lhs) | u32(rhs)); +} + +constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) { + return static_cast<MemoryState>(u32(lhs) & u32(rhs)); +} + +constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) { + return static_cast<MemoryState>(u32(lhs) ^ u32(rhs)); +} + +constexpr MemoryState operator~(MemoryState lhs) { + return static_cast<MemoryState>(~u32(lhs)); +} + +constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) { + lhs = lhs | rhs; + return lhs; +} + +constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) { + lhs = lhs & rhs; + return lhs; +} + +constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) { + lhs = lhs ^ rhs; + return lhs; +} + +constexpr u32 ToSvcMemoryState(MemoryState state) { + return static_cast<u32>(state & MemoryState::Mask); +} + +struct MemoryInfo { + u64 base_address; + u64 size; + u32 state; + u32 attributes; + u32 permission; + u32 ipc_ref_count; + u32 device_ref_count; +}; +static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size."); + +struct PageInfo { + u32 flags; }; /** @@ -113,16 +216,10 @@ struct VirtualMemoryArea { * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ */ class VMManager final { + using VMAMap = std::map<VAddr, VirtualMemoryArea>; + public: - /** - * A map covering the entirety of the managed address space, keyed by the `base` field of each - * VMA. It must always be modified by splitting or merging VMAs, so that the invariant - * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be - * merged when possible so that no two similar and adjacent regions exist that have not been - * merged. - */ - std::map<VAddr, VirtualMemoryArea> vma_map; - using VMAHandle = decltype(vma_map)::const_iterator; + using VMAHandle = VMAMap::const_iterator; VMManager(); ~VMManager(); @@ -133,6 +230,9 @@ public: /// Finds the VMA in which the given address is included in, or `vma_map.end()`. VMAHandle FindVMA(VAddr target) const; + /// Indicates whether or not the given handle is within the VMA map. + bool IsValidHandle(VMAHandle handle) const; + // TODO(yuriks): Should these functions actually return the handle? /** @@ -189,8 +289,15 @@ public: ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u64 size); - ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, - MemoryState state = MemoryState::Mapped); + ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state); + + /// Queries the memory manager for information about the given address. + /// + /// @param address The address to query the memory manager about for information. + /// + /// @return A MemoryInfo instance containing information about the given address. + /// + MemoryInfo QueryMemory(VAddr address) const; /** * Scans all VMAs and updates the page table range of any that use the given vector as backing @@ -281,7 +388,7 @@ public: Memory::PageTable page_table; private: - using VMAIter = decltype(vma_map)::iterator; + using VMAIter = VMAMap::iterator; /// Converts a VMAHandle to a mutable VMAIter. VMAIter StripIterConstness(const VMAHandle& iter); @@ -328,6 +435,15 @@ private: /// Clears out the page table void ClearPageTable(); + /** + * A map covering the entirety of the managed address space, keyed by the `base` field of each + * VMA. It must always be modified by splitting or merging VMAs, so that the invariant + * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be + * merged when possible so that no two similar and adjacent regions exist that have not been + * merged. + */ + VMAMap vma_map; + u32 address_space_width = 0; VAddr address_space_base = 0; VAddr address_space_end = 0; diff --git a/src/core/hle/kernel/writable_event.cpp b/src/core/hle/kernel/writable_event.cpp new file mode 100644 index 000000000..a58ea6ec8 --- /dev/null +++ b/src/core/hle/kernel/writable_event.cpp @@ -0,0 +1,52 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/assert.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/writable_event.h" + +namespace Kernel { + +WritableEvent::WritableEvent(KernelCore& kernel) : Object{kernel} {} +WritableEvent::~WritableEvent() = default; + +EventPair WritableEvent::CreateEventPair(KernelCore& kernel, ResetType reset_type, + std::string name) { + SharedPtr<WritableEvent> writable_event(new WritableEvent(kernel)); + SharedPtr<ReadableEvent> readable_event(new ReadableEvent(kernel)); + + writable_event->name = name + ":Writable"; + writable_event->readable = readable_event; + readable_event->name = name + ":Readable"; + readable_event->signaled = false; + readable_event->reset_type = reset_type; + + return {std::move(readable_event), std::move(writable_event)}; +} + +SharedPtr<ReadableEvent> WritableEvent::GetReadableEvent() const { + return readable; +} + +ResetType WritableEvent::GetResetType() const { + return readable->reset_type; +} + +void WritableEvent::Signal() { + readable->Signal(); +} + +void WritableEvent::Clear() { + readable->Clear(); +} + +bool WritableEvent::IsSignaled() const { + return readable->signaled; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/writable_event.h index 27d6126b0..8fa8d68ee 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/writable_event.h @@ -11,49 +11,52 @@ namespace Kernel { class KernelCore; +class ReadableEvent; +class WritableEvent; -class Event final : public WaitObject { +struct EventPair { + SharedPtr<ReadableEvent> readable; + SharedPtr<WritableEvent> writable; +}; + +class WritableEvent final : public Object { public: + ~WritableEvent() override; + /** * Creates an event * @param kernel The kernel instance to create this event under. * @param reset_type ResetType describing how to create event * @param name Optional name of event */ - static SharedPtr<Event> Create(KernelCore& kernel, ResetType reset_type, - std::string name = "Unknown"); + static EventPair CreateEventPair(KernelCore& kernel, ResetType reset_type, + std::string name = "Unknown"); std::string GetTypeName() const override { - return "Event"; + return "WritableEvent"; } std::string GetName() const override { return name; } - static const HandleType HANDLE_TYPE = HandleType::Event; + static const HandleType HANDLE_TYPE = HandleType::WritableEvent; HandleType GetHandleType() const override { return HANDLE_TYPE; } - ResetType GetResetType() const { - return reset_type; - } - - bool ShouldWait(Thread* thread) const override; - void Acquire(Thread* thread) override; + SharedPtr<ReadableEvent> GetReadableEvent() const; - void WakeupAllWaitingThreads() override; + ResetType GetResetType() const; void Signal(); void Clear(); + bool IsSignaled() const; private: - explicit Event(KernelCore& kernel); - ~Event() override; + explicit WritableEvent(KernelCore& kernel); - ResetType reset_type; ///< Current ResetType + SharedPtr<ReadableEvent> readable; - bool signaled; ///< Whether the event has already been signaled std::string name; ///< Name of event (optional) }; diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index c629f9357..1f8ed265e 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -21,17 +21,6 @@ namespace Service::Account { -// TODO: RE this structure -struct UserData { - INSERT_PADDING_WORDS(1); - u32 icon_id; - u8 bg_color_id; - INSERT_PADDING_BYTES(0x7); - INSERT_PADDING_BYTES(0x10); - INSERT_PADDING_BYTES(0x60); -}; -static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); - // Smallest JPEG https://github.com/mathiasbynens/small/blob/master/jpeg.jpg // used as a backup should the one on disk not exist constexpr u32 backup_jpeg_size = 107; @@ -72,9 +61,11 @@ private: void Get(Kernel::HLERequestContext& ctx) { LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - std::array<u8, MAX_DATA> data{}; + ProfileData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { - ctx.WriteBuffer(data); + std::array<u8, sizeof(ProfileData)> raw_data; + std::memcpy(raw_data.data(), &data, sizeof(ProfileData)); + ctx.WriteBuffer(raw_data); IPC::ResponseBuilder rb{ctx, 16}; rb.Push(RESULT_SUCCESS); rb.PushRaw(profile_base); @@ -216,10 +207,11 @@ void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; UUID user_id = rp.PopRaw<UUID>(); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IProfile>(user_id, *profile_manager); - LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); } void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { @@ -236,10 +228,10 @@ void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx } void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IManagerForApplication>(); - LOG_DEBUG(Service_ACC, "called"); } void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 968263846..1316d0b07 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -18,7 +18,7 @@ struct UserRaw { UUID uuid2; u64 timestamp; ProfileUsername username; - INSERT_PADDING_BYTES(0x80); + ProfileData extra_data; }; static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size."); @@ -346,7 +346,7 @@ void ProfileManager::ParseUserSaveFile() { continue; } - AddUser({user.uuid, user.username, user.timestamp, {}, false}); + AddUser({user.uuid, user.username, user.timestamp, user.extra_data, false}); } std::stable_partition(profiles.begin(), profiles.end(), @@ -361,6 +361,7 @@ void ProfileManager::WriteUserSaveFile() { raw.users[i].uuid2 = profiles[i].user_uuid; raw.users[i].uuid = profiles[i].user_uuid; raw.users[i].timestamp = profiles[i].creation_time; + raw.users[i].extra_data = profiles[i].data; } const auto raw_path = diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index d2d8e6c6b..c4ce2e0b3 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -13,7 +13,6 @@ namespace Service::Account { constexpr std::size_t MAX_USERS = 8; -constexpr std::size_t MAX_DATA = 128; constexpr u128 INVALID_UUID{{0, 0}}; struct UUID { @@ -50,9 +49,20 @@ static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); constexpr std::size_t profile_username_size = 32; using ProfileUsername = std::array<u8, profile_username_size>; -using ProfileData = std::array<u8, MAX_DATA>; using UserIDArray = std::array<UUID, MAX_USERS>; +/// Contains extra data related to a user. +/// TODO: RE this structure +struct ProfileData { + INSERT_PADDING_WORDS(1); + u32 icon_id; + u8 bg_color_id; + INSERT_PADDING_BYTES(0x7); + INSERT_PADDING_BYTES(0x10); + INSERT_PADDING_BYTES(0x60); +}; +static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size"); + /// This holds general information about a users profile. This is where we store all the information /// based on a specific user struct ProfileInfo { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3758ecae1..3a7b6da84 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -6,14 +6,21 @@ #include <cinttypes> #include <cstring> #include <stack> +#include "audio_core/audio_renderer.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/software_keyboard.h" +#include "core/hle/service/am/applets/stub_applet.h" #include "core/hle/service/am/idle.h" #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" @@ -28,6 +35,13 @@ namespace Service::AM { +constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2}; +constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; + +enum class AppletId : u32 { + SoftwareKeyboard = 0x11, +}; + constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; struct LaunchParameters { @@ -196,8 +210,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - launchable_event = - Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent"); + launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "ISelfController:LaunchableEvent"); } ISelfController::~ISelfController() = default; @@ -205,6 +219,7 @@ ISelfController::~ISelfController() = default; void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { // Takes 3 input u8s with each field located immediately after the previous // u8, these are bool flags. No output. + LOG_WARNING(Service_AM, "(STUBBED) called"); IPC::RequestParser rp{ctx}; @@ -217,44 +232,40 @@ void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; bool flag = rp.Pop<bool>(); + LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); } void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; bool flag = rp.Pop<bool>(); + LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag); } void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) { @@ -263,45 +274,45 @@ void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& IPC::RequestParser rp{ctx}; bool enabled = rp.Pop<bool>(); + LOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled); } void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) { - launchable_event->Signal(); + LOG_WARNING(Service_AM, "(STUBBED) called"); + + launchable_event.writable->Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(launchable_event); - - LOG_WARNING(Service_AM, "(STUBBED) called"); + rb.PushCopyObjects(launchable_event.readable); } void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); // TODO(Subv): Find out how AM determines the display to use, for now just // create the layer in the Default display. u64 display_id = nvflinger->OpenDisplay("Default"); @@ -310,66 +321,67 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push(layer_id); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; idle_time_detection_extension = rp.Pop<u32>(); + LOG_WARNING(Service_AM, "(STUBBED) called idle_time_detection_extension={}", + idle_time_detection_extension); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(idle_time_detection_extension); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } AppletMessageQueue::AppletMessageQueue() { auto& kernel = Core::System::GetInstance().Kernel(); - on_new_message = Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, - "AMMessageQueue:OnMessageRecieved"); - on_operation_mode_changed = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "AMMessageQueue:OperationModeChanged"); + on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "AMMessageQueue:OnMessageRecieved"); + on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "AMMessageQueue:OperationModeChanged"); } AppletMessageQueue::~AppletMessageQueue() = default; -const Kernel::SharedPtr<Kernel::Event>& AppletMessageQueue::GetMesssageRecieveEvent() const { - return on_new_message; +const Kernel::SharedPtr<Kernel::ReadableEvent>& AppletMessageQueue::GetMesssageRecieveEvent() + const { + return on_new_message.readable; } -const Kernel::SharedPtr<Kernel::Event>& AppletMessageQueue::GetOperationModeChangedEvent() const { - return on_operation_mode_changed; +const Kernel::SharedPtr<Kernel::ReadableEvent>& AppletMessageQueue::GetOperationModeChangedEvent() + const { + return on_operation_mode_changed.readable; } void AppletMessageQueue::PushMessage(AppletMessage msg) { messages.push(msg); - on_new_message->Signal(); + on_new_message.writable->Signal(); } AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() { if (messages.empty()) { - on_new_message->Clear(); + on_new_message.writable->Clear(); return AppletMessage::NoMessage; } auto msg = messages.front(); messages.pop(); if (messages.empty()) { - on_new_message->Clear(); + on_new_message.writable->Clear(); } return msg; } @@ -381,7 +393,7 @@ std::size_t AppletMessageQueue::GetMessageCount() const { void AppletMessageQueue::OperationModeChanged() { PushMessage(AppletMessage::OperationModeChanged); PushMessage(AppletMessage::PerformanceModeChanged); - on_operation_mode_changed->Signal(); + on_operation_mode_changed.writable->Signal(); } ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_queue) @@ -418,97 +430,131 @@ ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_q // clang-format on RegisterHandlers(functions); - - auto& kernel = Core::System::GetInstance().Kernel(); - event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "ICommonStateGetter:Event"); } ICommonStateGetter::~ICommonStateGetter() = default; void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u8>(static_cast<u8>(Service::PM::SystemBootMode::Normal)); // Normal boot mode - - LOG_DEBUG(Service_AM, "called"); } void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(msg_queue->GetMesssageRecieveEvent()); - - LOG_DEBUG(Service_AM, "called"); } void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushEnum<AppletMessageQueue::AppletMessage>(msg_queue->PopMessage()); - - LOG_DEBUG(Service_AM, "called"); } void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u8>(FocusState::InFocus)); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(msg_queue->GetOperationModeChangedEvent()); - - LOG_DEBUG(Service_AM, "called"); } void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } +} - LOG_DEBUG(Service_AM, "called"); +IStorage::IStorage(std::vector<u8> buffer) + : ServiceFramework("IStorage"), buffer(std::move(buffer)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IStorage::Open, "Open"}, + {1, nullptr, "OpenTransferStorage"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +IStorage::~IStorage() = default; + +const std::vector<u8>& IStorage::GetData() const { + return buffer; } void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; + LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld)); - - LOG_DEBUG(Service_AM, "called"); } void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; + LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked : APM::PerformanceMode::Handheld)); - - LOG_DEBUG(Service_AM, "called"); } -class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: - explicit IStorageAccessor(std::vector<u8> buffer) - : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet) + : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) { // clang-format off static const FunctionInfo functions[] = { - {0, &IStorageAccessor::GetSize, "GetSize"}, - {10, &IStorageAccessor::Write, "Write"}, - {11, &IStorageAccessor::Read, "Read"}, + {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, + {1, &ILibraryAppletAccessor::IsCompleted, "IsCompleted"}, + {10, &ILibraryAppletAccessor::Start, "Start"}, + {20, nullptr, "RequestExit"}, + {25, nullptr, "Terminate"}, + {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, + {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, + {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, + {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, + {102, nullptr, "PushExtraStorage"}, + {103, &ILibraryAppletAccessor::PushInteractiveInData, "PushInteractiveInData"}, + {104, &ILibraryAppletAccessor::PopInteractiveOutData, "PopInteractiveOutData"}, + {105, &ILibraryAppletAccessor::GetPopOutDataEvent, "GetPopOutDataEvent"}, + {106, &ILibraryAppletAccessor::GetPopInteractiveOutDataEvent, "GetPopInteractiveOutDataEvent"}, + {110, nullptr, "NeedsToExitProcess"}, + {120, nullptr, "GetLibraryAppletInfo"}, + {150, nullptr, "RequestForAppletToGetForeground"}, + {160, nullptr, "GetIndirectLayerConsumerHandle"}, }; // clang-format on @@ -516,158 +562,200 @@ public: } private: - std::vector<u8> buffer; + void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - void GetSize(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; + applet->GetBroker().SignalStateChanged(); + const auto event = applet->GetBroker().GetStateChangedEvent(); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u64>(buffer.size())); + rb.PushCopyObjects(event); + } + void IsCompleted(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(applet->TransactionComplete()); } - void Write(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; + void GetResult(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - const u64 offset{rp.Pop<u64>()}; - const std::vector<u8> data{ctx.ReadBuffer()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(applet->GetStatus()); + } + + void Start(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - ASSERT(offset + data.size() <= buffer.size()); + ASSERT(applet != nullptr); - std::memcpy(&buffer[offset], data.data(), data.size()); + applet->Initialize(); + applet->Execute(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_AM, "called, offset={}", offset); } - void Read(Kernel::HLERequestContext& ctx) { + void PushInData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::RequestParser rp{ctx}; + applet->GetBroker().PushNormalDataFromGame(*rp.PopIpcInterface<IStorage>()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void PopOutData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - const u64 offset{rp.Pop<u64>()}; - const std::size_t size{ctx.GetWriteBufferSize()}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - ASSERT(offset + size <= buffer.size()); + const auto storage = applet->GetBroker().PopNormalDataToGame(); + if (storage == nullptr) { + LOG_ERROR(Service_AM, + "storage is a nullptr. There is no data in the current normal channel"); - ctx.WriteBuffer(buffer.data() + offset, size); + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_AM, "called, offset={}", offset); + rb.PushIpcInterface<IStorage>(std::move(*storage)); } -}; -class IStorage final : public ServiceFramework<IStorage> { -public: - explicit IStorage(std::vector<u8> buffer) - : ServiceFramework("IStorage"), buffer(std::move(buffer)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IStorage::Open, "Open"}, - {1, nullptr, "OpenTransferStorage"}, - }; - // clang-format on + void PushInteractiveInData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - RegisterHandlers(functions); + IPC::RequestParser rp{ctx}; + applet->GetBroker().PushInteractiveDataFromGame(*rp.PopIpcInterface<IStorage>()); + + ASSERT(applet->IsInitialized()); + applet->ExecuteInteractive(); + applet->Execute(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } -private: - std::vector<u8> buffer; + void PopInteractiveOutData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - void Open(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + const auto storage = applet->GetBroker().PopInteractiveDataToGame(); + if (storage == nullptr) { + LOG_ERROR(Service_AM, + "storage is a nullptr. There is no data in the current interactive channel"); + + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } + rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + rb.PushIpcInterface<IStorage>(std::move(*storage)); + } + void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(applet->GetBroker().GetNormalDataEvent()); } + + void GetPopInteractiveOutDataEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent()); + } + + std::shared_ptr<Applets::Applet> applet; }; -class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { -public: - explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { - // clang-format off +void IStorage::Open(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorageAccessor>(*this); +} + +IStorageAccessor::IStorageAccessor(IStorage& storage) + : ServiceFramework("IStorageAccessor"), backing(storage) { + // clang-format off static const FunctionInfo functions[] = { - {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, - {1, nullptr, "IsCompleted"}, - {10, &ILibraryAppletAccessor::Start, "Start"}, - {20, nullptr, "RequestExit"}, - {25, nullptr, "Terminate"}, - {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, - {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, - {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, - {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, - {102, nullptr, "PushExtraStorage"}, - {103, nullptr, "PushInteractiveInData"}, - {104, nullptr, "PopInteractiveOutData"}, - {105, nullptr, "GetPopOutDataEvent"}, - {106, nullptr, "GetPopInteractiveOutDataEvent"}, - {110, nullptr, "NeedsToExitProcess"}, - {120, nullptr, "GetLibraryAppletInfo"}, - {150, nullptr, "RequestForAppletToGetForeground"}, - {160, nullptr, "GetIndirectLayerConsumerHandle"}, + {0, &IStorageAccessor::GetSize, "GetSize"}, + {10, &IStorageAccessor::Write, "Write"}, + {11, &IStorageAccessor::Read, "Read"}, }; - // clang-format on + // clang-format on - RegisterHandlers(functions); + RegisterHandlers(functions); +} - auto& kernel = Core::System::GetInstance().Kernel(); - state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "ILibraryAppletAccessor:StateChangedEvent"); - } +IStorageAccessor::~IStorageAccessor() = default; -private: - void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { - state_changed_event->Signal(); +void IStorageAccessor::GetSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(state_changed_event); + IPC::ResponseBuilder rb{ctx, 4}; - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(backing.buffer.size())); +} - void GetResult(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); +void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + const u64 offset{rp.Pop<u64>()}; + LOG_DEBUG(Service_AM, "called, offset={}", offset); - void Start(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + const std::vector<u8> data{ctx.ReadBuffer()}; - LOG_WARNING(Service_AM, "(STUBBED) called"); + if (data.size() > backing.buffer.size() - offset) { + LOG_ERROR(Service_AM, + "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", + backing.buffer.size(), data.size(), offset); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - void PushInData(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - storage_stack.push(rp.PopIpcInterface<AM::IStorage>()); + std::memcpy(backing.buffer.data() + offset, data.data(), data.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_AM, "called"); - } +void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - void PopOutData(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top())); + const u64 offset{rp.Pop<u64>()}; + LOG_DEBUG(Service_AM, "called, offset={}", offset); - storage_stack.pop(); + const std::size_t size{ctx.GetWriteBufferSize()}; - LOG_DEBUG(Service_AM, "called"); + if (size > backing.buffer.size() - offset) { + LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", + backing.buffer.size(), size, offset); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - std::stack<std::shared_ptr<AM::IStorage>> storage_stack; - Kernel::SharedPtr<Kernel::Event> state_changed_event; -}; + ctx.WriteBuffer(backing.buffer.data() + offset, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { static const FunctionInfo functions[] = { @@ -675,7 +763,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, - {11, nullptr, "CreateTransferMemoryStorage"}, + {11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"}, {12, nullptr, "CreateHandleStorage"}, }; RegisterHandlers(functions); @@ -683,25 +771,79 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple ILibraryAppletCreator::~ILibraryAppletCreator() = default; +static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { + switch (id) { + case AppletId::SoftwareKeyboard: + return std::make_shared<Applets::SoftwareKeyboard>(); + default: + LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!", + static_cast<u32>(id)); + return std::make_shared<Applets::StubApplet>(); + } +} + void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_id = rp.PopRaw<AppletId>(); + const auto applet_mode = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", + static_cast<u32>(applet_id), applet_mode); + + const auto applet = GetAppletFromId(applet_id); + + if (applet == nullptr) { + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::ILibraryAppletAccessor>(); - - LOG_DEBUG(Service_AM, "called"); + rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet); } void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 size{rp.Pop<u64>()}; + LOG_DEBUG(Service_AM, "called, size={}", size); + std::vector<u8> buffer(size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<AM::IStorage>(std::move(buffer)); +} - LOG_DEBUG(Service_AM, "called, size={}", size); +void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::RequestParser rp{ctx}; + + rp.SetCurrentOffset(3); + const auto handle{rp.Pop<Kernel::Handle>()}; + + const auto shared_mem = + Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::SharedMemory>( + handle); + + if (shared_mem == nullptr) { + LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + + const u8* mem_begin = shared_mem->GetPointer(); + const u8* mem_end = mem_begin + shared_mem->GetSize(); + std::vector<u8> memory{mem_begin, mem_end}; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); } IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { @@ -753,38 +895,45 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF IApplicationFunctions::~IApplicationFunctions() = default; void IApplicationFunctions::EnableApplicationCrashReport(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed( Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed( Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::BeginBlockingHomeButton(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + LaunchParameters params{}; params.magic = POP_LAUNCH_PARAMETER_MAGIC; @@ -803,21 +952,19 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { std::memcpy(buffer.data(), ¶ms, buffer.size()); rb.PushIpcInterface<AM::IStorage>(buffer); - - LOG_DEBUG(Service_AM, "called"); } void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u128 uid = rp.PopRaw<u128>(); // What does this do? - LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]); IPC::ResponseBuilder rb{ctx, 4}; @@ -832,60 +979,62 @@ void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u32 result = rp.Pop<u32>(); + LOG_WARNING(Service_AM, "(STUBBED) called, result=0x{:08X}", result); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called, result=0x{:08X}", result); } void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(1); rb.Push<u64>(0); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { // TODO(bunnei): This should be configurable + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push( static_cast<u64>(Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index))); - LOG_DEBUG(Service_AM, "called"); } void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); // Unknown, seems to be ignored by official processes - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); // Returns a 128-bit UUID rb.Push<u64>(0); rb.Push<u64>(0); - - LOG_WARNING(Service_AM, "(STUBBED) called"); } void InstallInterfaces(SM::ServiceManager& service_manager, @@ -922,9 +1071,10 @@ IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions" IHomeMenuFunctions::~IHomeMenuFunctions() = default; void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_AM, "(STUBBED) called"); } IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") { diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 5a3fcba8f..34c45fadf 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -6,12 +6,9 @@ #include <memory> #include <queue> +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/service.h" -namespace Kernel { -class Event; -} - namespace Service { namespace NVFlinger { class NVFlinger; @@ -52,8 +49,8 @@ public: AppletMessageQueue(); ~AppletMessageQueue(); - const Kernel::SharedPtr<Kernel::Event>& GetMesssageRecieveEvent() const; - const Kernel::SharedPtr<Kernel::Event>& GetOperationModeChangedEvent() const; + const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; + const Kernel::SharedPtr<Kernel::ReadableEvent>& GetOperationModeChangedEvent() const; void PushMessage(AppletMessage msg); AppletMessage PopMessage(); std::size_t GetMessageCount() const; @@ -61,8 +58,8 @@ public: private: std::queue<AppletMessage> messages; - Kernel::SharedPtr<Kernel::Event> on_new_message; - Kernel::SharedPtr<Kernel::Event> on_operation_mode_changed; + Kernel::EventPair on_new_message; + Kernel::EventPair on_operation_mode_changed; }; class IWindowController final : public ServiceFramework<IWindowController> { @@ -122,7 +119,7 @@ private: void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; - Kernel::SharedPtr<Kernel::Event> launchable_event; + Kernel::EventPair launchable_event; u32 idle_time_detection_extension = 0; }; @@ -151,10 +148,37 @@ private: void GetBootMode(Kernel::HLERequestContext& ctx); void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); - Kernel::SharedPtr<Kernel::Event> event; std::shared_ptr<AppletMessageQueue> msg_queue; }; +class IStorage final : public ServiceFramework<IStorage> { +public: + explicit IStorage(std::vector<u8> buffer); + ~IStorage() override; + + const std::vector<u8>& GetData() const; + +private: + void Open(Kernel::HLERequestContext& ctx); + + std::vector<u8> buffer; + + friend class IStorageAccessor; +}; + +class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +public: + explicit IStorageAccessor(IStorage& backing); + ~IStorageAccessor() override; + +private: + void GetSize(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); + void Read(Kernel::HLERequestContext& ctx); + + IStorage& backing; +}; + class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: ILibraryAppletCreator(); @@ -163,6 +187,7 @@ public: private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); + void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index ec93e3529..41a573a91 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -32,66 +32,75 @@ public: private: void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(msg_queue); - LOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISelfController>(nvflinger); - LOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service_AM, "called"); } void GetAudioController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service_AM, "called"); } void GetProcessWindingController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IProcessWindingController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service_AM, "called"); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationFunctions>(); - LOG_DEBUG(Service_AM, "called"); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; @@ -122,97 +131,110 @@ public: private: void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(msg_queue); - LOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISelfController>(nvflinger); - LOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service_AM, "called"); } void GetAudioController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service_AM, "called"); } void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IHomeMenuFunctions>(); - LOG_DEBUG(Service_AM, "called"); } void GetGlobalStateController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IGlobalStateController>(); - LOG_DEBUG(Service_AM, "called"); } void GetApplicationCreator(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationCreator>(); - LOG_DEBUG(Service_AM, "called"); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; }; void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue); - LOG_DEBUG(Service_AM, "called"); } void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue); - LOG_DEBUG(Service_AM, "called"); } void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue); - LOG_DEBUG(Service_AM, "called"); } AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index 20c8d5fff..d3a0a1568 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -35,59 +35,67 @@ public: private: void GetAudioController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAudioController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDisplayController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDisplayController>(); - LOG_DEBUG(Service_AM, "called"); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IDebugFunctions>(); - LOG_DEBUG(Service_AM, "called"); } void GetWindowController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IWindowController>(); - LOG_DEBUG(Service_AM, "called"); } void GetSelfController(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISelfController>(nvflinger); - LOG_DEBUG(Service_AM, "called"); } void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ICommonStateGetter>(msg_queue); - LOG_DEBUG(Service_AM, "called"); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILibraryAppletCreator>(); - LOG_DEBUG(Service_AM, "called"); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationFunctions>(); - LOG_DEBUG(Service_AM, "called"); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; @@ -95,10 +103,11 @@ private: }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue); - LOG_DEBUG(Service_AM, "called"); } AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp new file mode 100644 index 000000000..47da35537 --- /dev/null +++ b/src/core/hle/service/am/applets/applets.cpp @@ -0,0 +1,114 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +AppletDataBroker::AppletDataBroker() { + auto& kernel = Core::System::GetInstance().Kernel(); + state_changed_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:StateChangedEvent"); + pop_out_data_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopDataOutEvent"); + pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); +} + +AppletDataBroker::~AppletDataBroker() = default; + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { + if (out_channel.empty()) + return nullptr; + + auto out = std::move(out_channel.front()); + out_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { + if (in_channel.empty()) + return nullptr; + + auto out = std::move(in_channel.front()); + in_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { + if (out_interactive_channel.empty()) + return nullptr; + + auto out = std::move(out_interactive_channel.front()); + out_interactive_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { + if (in_interactive_channel.empty()) + return nullptr; + + auto out = std::move(in_interactive_channel.front()); + in_interactive_channel.pop(); + return out; +} + +void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { + in_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { + out_channel.push(std::make_unique<IStorage>(storage)); + pop_out_data_event.writable->Signal(); +} + +void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { + in_interactive_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { + out_interactive_channel.push(std::make_unique<IStorage>(storage)); + pop_interactive_out_data_event.writable->Signal(); +} + +void AppletDataBroker::SignalStateChanged() const { + state_changed_event.writable->Signal(); +} + +Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetNormalDataEvent() const { + return pop_out_data_event.readable; +} + +Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const { + return pop_interactive_out_data_event.readable; +} + +Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent() const { + return state_changed_event.readable; +} + +Applet::Applet() = default; + +Applet::~Applet() = default; + +void Applet::Initialize() { + const auto common = broker.PopNormalDataToApplet(); + ASSERT(common != nullptr); + + const auto common_data = common->GetData(); + + ASSERT(common_data.size() >= sizeof(CommonArguments)); + std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments)); + + initialized = true; +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h new file mode 100644 index 000000000..b0a8913c3 --- /dev/null +++ b/src/core/hle/service/am/applets/applets.h @@ -0,0 +1,109 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <queue> +#include "common/swap.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/writable_event.h" + +union ResultCode; + +namespace Service::AM { + +class IStorage; + +namespace Applets { + +class AppletDataBroker final { +public: + AppletDataBroker(); + ~AppletDataBroker(); + + std::unique_ptr<IStorage> PopNormalDataToGame(); + std::unique_ptr<IStorage> PopNormalDataToApplet(); + + std::unique_ptr<IStorage> PopInteractiveDataToGame(); + std::unique_ptr<IStorage> PopInteractiveDataToApplet(); + + void PushNormalDataFromGame(IStorage storage); + void PushNormalDataFromApplet(IStorage storage); + + void PushInteractiveDataFromGame(IStorage storage); + void PushInteractiveDataFromApplet(IStorage storage); + + void SignalStateChanged() const; + + Kernel::SharedPtr<Kernel::ReadableEvent> GetNormalDataEvent() const; + Kernel::SharedPtr<Kernel::ReadableEvent> GetInteractiveDataEvent() const; + Kernel::SharedPtr<Kernel::ReadableEvent> GetStateChangedEvent() const; + +private: + // Queues are named from applet's perspective + + // PopNormalDataToApplet and PushNormalDataFromGame + std::queue<std::unique_ptr<IStorage>> in_channel; + + // PopNormalDataToGame and PushNormalDataFromApplet + std::queue<std::unique_ptr<IStorage>> out_channel; + + // PopInteractiveDataToApplet and PushInteractiveDataFromGame + std::queue<std::unique_ptr<IStorage>> in_interactive_channel; + + // PopInteractiveDataToGame and PushInteractiveDataFromApplet + std::queue<std::unique_ptr<IStorage>> out_interactive_channel; + + Kernel::EventPair state_changed_event; + + // Signaled on PushNormalDataFromApplet + Kernel::EventPair pop_out_data_event; + + // Signaled on PushInteractiveDataFromApplet + Kernel::EventPair pop_interactive_out_data_event; +}; + +class Applet { +public: + Applet(); + virtual ~Applet(); + + virtual void Initialize(); + + virtual bool TransactionComplete() const = 0; + virtual ResultCode GetStatus() const = 0; + virtual void ExecuteInteractive() = 0; + virtual void Execute() = 0; + + bool IsInitialized() const { + return initialized; + } + + AppletDataBroker& GetBroker() { + return broker; + } + + const AppletDataBroker& GetBroker() const { + return broker; + } + +protected: + struct CommonArguments { + u32_le arguments_version; + u32_le size; + u32_le library_version; + u32_le theme_color; + u8 play_startup_sound; + u64_le system_tick; + }; + static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); + + CommonArguments common_args{}; + AppletDataBroker broker; + bool initialized = false; +}; + +} // namespace Applets +} // namespace Service::AM diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp new file mode 100644 index 000000000..981bdec51 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -0,0 +1,161 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/software_keyboard.h" + +namespace Service::AM::Applets { + +constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; +constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; +constexpr std::size_t DEFAULT_MAX_LENGTH = 500; +constexpr bool INTERACTIVE_STATUS_OK = false; + +static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( + KeyboardConfig config, std::u16string initial_text) { + Core::Frontend::SoftwareKeyboardParameters params{}; + + params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.submit_text.data(), config.submit_text.size()); + params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.header_text.data(), config.header_text.size()); + params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), + config.sub_text.size()); + params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), + config.guide_text.size()); + params.initial_text = initial_text; + params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; + params.password = static_cast<bool>(config.is_password); + params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position); + params.value = static_cast<u8>(config.keyset_disable_bitmask); + + return params; +} + +SoftwareKeyboard::SoftwareKeyboard() = default; + +SoftwareKeyboard::~SoftwareKeyboard() = default; + +void SoftwareKeyboard::Initialize() { + complete = false; + initial_text.clear(); + final_data.clear(); + + Applet::Initialize(); + + const auto keyboard_config_storage = broker.PopNormalDataToApplet(); + ASSERT(keyboard_config_storage != nullptr); + const auto& keyboard_config = keyboard_config_storage->GetData(); + + ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); + std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); + + const auto work_buffer_storage = broker.PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + const auto& work_buffer = work_buffer_storage->GetData(); + + if (config.initial_string_size == 0) + return; + + std::vector<char16_t> string(config.initial_string_size); + std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, + string.size() * 2); + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); +} + +bool SoftwareKeyboard::TransactionComplete() const { + return complete; +} + +ResultCode SoftwareKeyboard::GetStatus() const { + return RESULT_SUCCESS; +} + +void SoftwareKeyboard::ExecuteInteractive() { + if (complete) + return; + + const auto storage = broker.PopInteractiveDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + const auto status = static_cast<bool>(data[0]); + + if (status == INTERACTIVE_STATUS_OK) { + complete = true; + } else { + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; + std::memcpy(string.data(), data.data() + 4, string.size() * 2); + frontend.SendTextCheckDialog( + Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), + [this] { broker.SignalStateChanged(); }); + } +} + +void SoftwareKeyboard::Execute() { + if (complete) { + broker.PushNormalDataFromApplet(IStorage{final_data}); + return; + } + + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + const auto parameters = ConvertToFrontendParameters(config, initial_text); + + frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); }, + parameters); +} + +void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { + std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE); + + if (text.has_value()) { + std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE); + + if (config.utf_8) { + const u64 size = text->size() + 8; + const auto new_text = Common::UTF16ToUTF8(*text); + + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } else { + const u64 size = text->size() * 2 + 8; + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } + + complete = !config.text_check; + final_data = output_main; + + if (complete) { + broker.PushNormalDataFromApplet(IStorage{output_main}); + } else { + broker.PushInteractiveDataFromApplet(IStorage{output_sub}); + } + + broker.SignalStateChanged(); + } else { + output_main[0] = 1; + complete = true; + broker.PushNormalDataFromApplet(IStorage{output_main}); + broker.SignalStateChanged(); + } +} +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h new file mode 100644 index 000000000..efd5753a1 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -0,0 +1,74 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +enum class KeysetDisable : u32 { + Space = 0x02, + Address = 0x04, + Percent = 0x08, + Slashes = 0x10, + Numbers = 0x40, + DownloadCode = 0x80, +}; + +struct KeyboardConfig { + INSERT_PADDING_BYTES(4); + std::array<char16_t, 9> submit_text; + u16_le left_symbol_key; + u16_le right_symbol_key; + INSERT_PADDING_BYTES(1); + KeysetDisable keyset_disable_bitmask; + u32_le initial_cursor_position; + std::array<char16_t, 65> header_text; + std::array<char16_t, 129> sub_text; + std::array<char16_t, 257> guide_text; + u32_le length_limit; + INSERT_PADDING_BYTES(4); + u32_le is_password; + INSERT_PADDING_BYTES(5); + bool utf_8; + bool draw_background; + u32_le initial_string_offset; + u32_le initial_string_size; + u32_le user_dictionary_offset; + u32_le user_dictionary_size; + bool text_check; + u64_le text_check_callback; +}; +static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); + +class SoftwareKeyboard final : public Applet { +public: + SoftwareKeyboard(); + ~SoftwareKeyboard() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void WriteText(std::optional<std::u16string> text); + +private: + KeyboardConfig config; + std::u16string initial_text; + bool complete = false; + std::vector<u8> final_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/stub_applet.cpp b/src/core/hle/service/am/applets/stub_applet.cpp new file mode 100644 index 000000000..ed166b87d --- /dev/null +++ b/src/core/hle/service/am/applets/stub_applet.cpp @@ -0,0 +1,70 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <string> + +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/stub_applet.h" + +namespace Service::AM::Applets { + +static void LogCurrentStorage(AppletDataBroker& broker, std::string prefix) { + std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet(); + for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) { + const auto data = storage->GetData(); + LOG_INFO(Service_AM, + "called (STUBBED), during {} recieved normal data with size={:08X}, data={}", + prefix, data.size(), Common::HexVectorToString(data)); + } + + storage = broker.PopInteractiveDataToApplet(); + for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) { + const auto data = storage->GetData(); + LOG_INFO(Service_AM, + "called (STUBBED), during {} recieved interactive data with size={:08X}, data={}", + prefix, data.size(), Common::HexVectorToString(data)); + } +} + +StubApplet::StubApplet() = default; + +StubApplet::~StubApplet() = default; + +void StubApplet::Initialize() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + Applet::Initialize(); + LogCurrentStorage(broker, "Initialize"); +} + +bool StubApplet::TransactionComplete() const { + LOG_WARNING(Service_AM, "called (STUBBED)"); + return true; +} + +ResultCode StubApplet::GetStatus() const { + LOG_WARNING(Service_AM, "called (STUBBED)"); + return RESULT_SUCCESS; +} + +void StubApplet::ExecuteInteractive() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + LogCurrentStorage(broker, "ExecuteInteractive"); + + broker.PushNormalDataFromApplet(IStorage{std::vector<u8>(0x1000)}); + broker.PushInteractiveDataFromApplet(IStorage{std::vector<u8>(0x1000)}); + broker.SignalStateChanged(); +} + +void StubApplet::Execute() { + LOG_WARNING(Service_AM, "called (STUBBED)"); + LogCurrentStorage(broker, "Execute"); + + broker.PushNormalDataFromApplet(IStorage{std::vector<u8>(0x1000)}); + broker.PushInteractiveDataFromApplet(IStorage{std::vector<u8>(0x1000)}); + broker.SignalStateChanged(); +} +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/stub_applet.h b/src/core/hle/service/am/applets/stub_applet.h new file mode 100644 index 000000000..7d8dc968d --- /dev/null +++ b/src/core/hle/service/am/applets/stub_applet.h @@ -0,0 +1,24 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +class StubApplet final : public Applet { +public: + StubApplet(); + ~StubApplet() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 54305cf05..b506bc3dd 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -13,11 +13,14 @@ #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#include "core/settings.h" namespace Service::AOC { @@ -32,14 +35,14 @@ static std::vector<u64> AccumulateAOCTitleIDs() { std::vector<u64> add_on_content; const auto rcu = FileSystem::GetUnionContents(); const auto list = - rcu->ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); std::transform(list.begin(), list.end(), std::back_inserter(add_on_content), [](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; }); add_on_content.erase( std::remove_if( add_on_content.begin(), add_on_content.end(), [&rcu](u64 tid) { - return rcu->GetEntry(tid, FileSys::ContentRecordType::Data)->GetStatus() != + return rcu.GetEntry(tid, FileSys::ContentRecordType::Data)->GetStatus() != Loader::ResultStatus::Success; }), add_on_content.end()); @@ -61,17 +64,26 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - aoc_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, - "GetAddOnContentListChanged:Event"); + aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "GetAddOnContentListChanged:Event"); } AOC_U::~AOC_U() = default; void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { + rb.Push<u32>(0); + return; + } + rb.Push<u32>(static_cast<u32>( std::count_if(add_on_content.begin(), add_on_content.end(), [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }))); @@ -82,6 +94,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { const auto offset = rp.PopRaw<u32>(); auto count = rp.PopRaw<u32>(); + LOG_DEBUG(Service_AOC, "called with offset={}, count={}", offset, count); const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); @@ -91,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF)); } + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) + out = {}; + if (out.size() < offset) { IPC::ResponseBuilder rb{ctx, 2}; // TODO(DarkLordZach): Find the correct error code. @@ -110,6 +127,8 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { } void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); @@ -128,7 +147,6 @@ void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto aoc_id = rp.PopRaw<u32>(); - LOG_WARNING(Service_AOC, "(STUBBED) called with aoc_id={:08X}", aoc_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -140,7 +158,7 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(aoc_change_event); + rb.PushCopyObjects(aoc_change_event.readable); } void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 68d94fdaa..5effea730 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Kernel { +class WritableEvent; +} + namespace Service::AOC { class AOC_U final : public ServiceFramework<AOC_U> { @@ -21,7 +25,7 @@ private: void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx); std::vector<u64> add_on_content; - Kernel::SharedPtr<Kernel::Event> aoc_change_event; + Kernel::EventPair aoc_change_event; }; /// Registers all AOC services with the specified service manager. diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index c22bd3859..fcacbab72 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -40,24 +40,22 @@ private: auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); u32 config = rp.Pop<u32>(); + LOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode), + config); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode), - config); } void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); + LOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode)); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(PerformanceConfiguration::Config1)); - - LOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode)); } }; @@ -73,11 +71,11 @@ APM::APM(std::shared_ptr<Module> apm, const char* name) APM::~APM() = default; void APM::OpenSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISession>(); - - LOG_DEBUG(Service_APM, "called"); } APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} { @@ -98,11 +96,11 @@ APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} { APM_Sys::~APM_Sys() = default; void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISession>(); - - LOG_DEBUG(Service_APM, "called"); } } // namespace Service::APM diff --git a/src/core/hle/service/arp/arp.cpp b/src/core/hle/service/arp/arp.cpp index 358ef2576..e675b0188 100644 --- a/src/core/hle/service/arp/arp.cpp +++ b/src/core/hle/service/arp/arp.cpp @@ -59,11 +59,11 @@ public: private: void AcquireRegistrar(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IRegistrar>(); - - LOG_DEBUG(Service_ARP, "called"); } }; diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index ff1edefbb..dc6a6b188 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -13,8 +13,10 @@ #include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/audio/audout_u.h" #include "core/memory.h" @@ -44,8 +46,10 @@ enum class AudioState : u32 { class IAudioOut final : public ServiceFramework<IAudioOut> { public: - IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) - : ServiceFramework("IAudioOut"), audio_core(audio_core), audio_params(audio_params) { + IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core, std::string&& device_name, + std::string&& unique_name) + : ServiceFramework("IAudioOut"), audio_core(audio_core), + device_name(std::move(device_name)), audio_params(audio_params) { static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, @@ -65,11 +69,12 @@ public: // This is the event handle used to check if the audio buffer was released auto& kernel = Core::System::GetInstance().Kernel(); - buffer_event = - Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "IAudioOutBufferReleased"); + buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "IAudioOutBufferReleased"); stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count, - "IAudioOut", [=]() { buffer_event->Signal(); }); + std::move(unique_name), + [=]() { buffer_event.writable->Signal(); }); } private: @@ -84,6 +89,7 @@ private: void GetAudioOutState(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); @@ -118,7 +124,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(buffer_event); + rb.PushCopyObjects(buffer_event.readable); } void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { @@ -146,6 +152,7 @@ private: void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); + IPC::RequestParser rp{ctx}; const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)}; @@ -161,6 +168,7 @@ private: void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; const u64 tag{rp.Pop<u64>()}; IPC::ResponseBuilder rb{ctx, 3}; @@ -170,6 +178,7 @@ private: void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(static_cast<u32>(stream->GetQueueSize())); @@ -177,15 +186,17 @@ private: AudioCore::AudioOut& audio_core; AudioCore::StreamPtr stream; + std::string device_name; AudoutParams audio_params{}; - /// This is the evend handle used to check if the audio buffer was released - Kernel::SharedPtr<Kernel::Event> buffer_event; + /// This is the event handle used to check if the audio buffer was released + Kernel::EventPair buffer_event; }; void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; ctx.WriteBuffer(DefaultDevice); @@ -199,7 +210,15 @@ void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - ctx.WriteBuffer(DefaultDevice); + const auto device_name_data{ctx.ReadBuffer()}; + std::string device_name; + if (device_name_data[0] != '\0') { + device_name.assign(device_name_data.begin(), device_name_data.end()); + } else { + device_name.assign(DefaultDevice.begin(), DefaultDevice.end()); + } + ctx.WriteBuffer(device_name); + IPC::RequestParser rp{ctx}; auto params{rp.PopRaw<AudoutParams>()}; if (params.channel_count <= 2) { @@ -212,10 +231,9 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { params.sample_rate = DefaultSampleRate; } - // TODO(bunnei): Support more than one IAudioOut interface. When we add this, ListAudioOutsImpl - // will likely need to be updated as well. - ASSERT_MSG(!audio_out_interface, "Unimplemented"); - audio_out_interface = std::make_shared<IAudioOut>(params, *audio_core); + std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; + auto audio_out_interface = std::make_shared<IAudioOut>( + params, *audio_core, std::move(device_name), std::move(unique_name)); IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -224,6 +242,8 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16)); rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface); + + audio_out_interfaces.push_back(std::move(audio_out_interface)); } AudOutU::AudOutU() : ServiceFramework("audout:u") { diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index dcaf64708..aed4c43b2 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -4,6 +4,7 @@ #pragma once +#include <vector> #include "core/hle/service/service.h" namespace AudioCore { @@ -24,7 +25,7 @@ public: ~AudOutU() override; private: - std::shared_ptr<IAudioOut> audio_out_interface; + std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; std::unique_ptr<AudioCore::AudioOut> audio_core; void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index d3ea57ea7..945259c7d 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -12,8 +12,10 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/audio/audren_u.h" namespace Service::Audio { @@ -41,85 +43,90 @@ public: RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - system_event = - Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent"); - renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event); + system_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "IAudioRenderer:SystemEvent"); + renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event.writable); } private: void UpdateAudioCallback() { - system_event->Signal(); + system_event.writable->Signal(); } void GetSampleRate(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetSampleRate()); - LOG_DEBUG(Service_Audio, "called"); } void GetSampleCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetSampleCount()); - LOG_DEBUG(Service_Audio, "called"); } void GetState(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(renderer->GetStreamState())); - LOG_DEBUG(Service_Audio, "called"); } void GetMixBufferCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetMixBufferCount()); - LOG_DEBUG(Service_Audio, "called"); } void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer())); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_Audio, "(STUBBED) called"); } void Start(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_Audio, "(STUBBED) called"); } void Stop(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_Audio, "(STUBBED) called"); } void QuerySystemEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(system_event); - - LOG_WARNING(Service_Audio, "(STUBBED) called"); + rb.PushCopyObjects(system_event.readable); } void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; rendering_time_limit_percent = rp.Pop<u32>(); + LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}", + rendering_time_limit_percent); + ASSERT(rendering_time_limit_percent >= 0 && rendering_time_limit_percent <= 100); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}", - rendering_time_limit_percent); } void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { @@ -130,7 +137,7 @@ private: rb.Push(rendering_time_limit_percent); } - Kernel::SharedPtr<Kernel::Event> system_event; + Kernel::EventPair system_event; std::unique_ptr<AudioCore::AudioRenderer> renderer; u32 rendering_time_limit_percent = 100; }; @@ -157,8 +164,8 @@ public: RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - buffer_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "IAudioOutBufferReleasedEvent"); + buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IAudioOutBufferReleasedEvent"); } private: @@ -202,21 +209,22 @@ private: void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); - buffer_event->Signal(); + buffer_event.writable->Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(buffer_event); + rb.PushCopyObjects(buffer_event.readable); } void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); } - Kernel::SharedPtr<Kernel::Event> buffer_event; + Kernel::EventPair buffer_event; }; // namespace Audio @@ -235,19 +243,20 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") { AudRenU::~AudRenU() = default; void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<Audio::IAudioRenderer>(std::move(params)); - - LOG_DEBUG(Service_Audio, "called"); } void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); + LOG_DEBUG(Service_Audio, "called"); u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40); buffer_sz += params.unknown_c * 1024; @@ -301,26 +310,26 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); rb.Push<u64>(output_sz); - LOG_DEBUG(Service_Audio, "called, buffer_size=0x{:X}", output_sz); + LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", output_sz); } void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<Audio::IAudioDevice>(); - - LOG_DEBUG(Service_Audio, "called"); } void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<Audio::IAudioDevice>(); - - LOG_WARNING(Service_Audio, "(STUBBED) called"); // TODO(ogniK): Figure out what is different - // based on the current revision + rb.PushIpcInterface<Audio::IAudioDevice>(); // TODO(ogniK): Figure out what is different + // based on the current revision } bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 763e619a4..a850cadc8 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -46,10 +46,13 @@ public: private: void DecodeInterleaved(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Audio, "called"); + u32 consumed = 0; u32 sample_count = 0; std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16)); if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) { + LOG_ERROR(Audio, "Failed to decode opus data"); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code rb.Push(ResultCode(-1)); @@ -63,12 +66,15 @@ private: } void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Audio, "called"); + u32 consumed = 0; u32 sample_count = 0; u64 performance = 0; std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16)); if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { + LOG_ERROR(Audio, "Failed to decode opus data"); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code rb.Push(ResultCode(-1)); @@ -88,24 +94,39 @@ private: std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) { const auto start_time = std::chrono::high_resolution_clock::now(); std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusHeader) > input.size()) + if (sizeof(OpusHeader) > input.size()) { + LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", + sizeof(OpusHeader), input.size()); return false; + } OpusHeader hdr{}; std::memcpy(&hdr, input.data(), sizeof(OpusHeader)); if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) { + LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", + sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size()); return false; } auto frame = input.data() + sizeof(OpusHeader); auto decoded_sample_count = opus_packet_get_nb_samples( frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)), static_cast<opus_int32>(sample_rate)); - if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) + if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { + LOG_ERROR( + Audio, + "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", + decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); return false; + } + const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); auto out_sample_count = - opus_decode(decoder.get(), frame, hdr.sz, output.data(), - (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0); - if (out_sample_count < 0) + opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0); + if (out_sample_count < 0) { + LOG_ERROR(Audio, + "Incorrect sample count received from opus_decode, " + "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", + out_sample_count, frame_size, static_cast<u32>(hdr.sz)); return false; + } const auto end_time = std::chrono::high_resolution_clock::now() - start_time; sample_count = out_sample_count; consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); @@ -134,14 +155,17 @@ static std::size_t WorkerBufferSize(u32 channel_count) { void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto sample_rate = rp.Pop<u32>(); - auto channel_count = rp.Pop<u32>(); + const auto sample_rate = rp.Pop<u32>(); + const auto channel_count = rp.Pop<u32>(); + LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); + ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || sample_rate == 12000 || sample_rate == 8000, "Invalid sample rate"); ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); - LOG_DEBUG(Audio, "called worker_buffer_sz={}", worker_buffer_sz); + + const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); + LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -155,6 +179,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { auto buffer_sz = rp.Pop<u32>(); LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, channel_count, buffer_sz); + ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || sample_rate == 12000 || sample_rate == 8000, "Invalid sample rate"); @@ -164,7 +189,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ static_cast<OpusDecoder*>(operator new(worker_sz))}; - if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) { + if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) { + LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err); IPC::ResponseBuilder rb{ctx, 2}; // TODO(ogniK): Use correct error code rb.Push(ResultCode(-1)); diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index 6e7b795fb..b7bd738fc 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -33,10 +33,11 @@ public: }; void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IBcatService>(); - LOG_DEBUG(Service_BCAT, "called"); } Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index f3bde6d0d..5704ca0ab 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -4,8 +4,10 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -30,19 +32,22 @@ public: }; // clang-format on RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + register_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "BT:RegisterEvent"); } private: void RegisterEvent(Kernel::HLERequestContext& ctx) { - auto& kernel = Core::System::GetInstance().Kernel(); - register_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "BT:RegisterEvent"); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(register_event); - LOG_WARNING(Service_BTM, "(STUBBED) called"); + rb.PushCopyObjects(register_event.readable); } - Kernel::SharedPtr<Kernel::Event> register_event; + + Kernel::EventPair register_event; }; class BtDrv final : public ServiceFramework<BtDrv> { diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index a02f6b53a..ef7398a23 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -6,8 +6,10 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/btm/btm.h" #include "core/hle/service/service.h" @@ -53,49 +55,55 @@ public: }; // clang-format on RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IBtmUserCore:ScanEvent"); + connection_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ConnectionEvent"); + service_discovery = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "IBtmUserCore:Discovery"); + config_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IBtmUserCore:ConfigEvent"); } private: void GetScanEvent(Kernel::HLERequestContext& ctx) { - auto& kernel = Core::System::GetInstance().Kernel(); - scan_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ScanEvent"); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(scan_event); - LOG_WARNING(Service_BTM, "(STUBBED) called"); + rb.PushCopyObjects(scan_event.readable); } + void GetConnectionEvent(Kernel::HLERequestContext& ctx) { - auto& kernel = Core::System::GetInstance().Kernel(); - connection_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "IBtmUserCore:ConnectionEvent"); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(connection_event); - LOG_WARNING(Service_BTM, "(STUBBED) called"); + rb.PushCopyObjects(connection_event.readable); } + void GetDiscoveryEvent(Kernel::HLERequestContext& ctx) { - auto& kernel = Core::System::GetInstance().Kernel(); - service_discovery = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:Discovery"); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(service_discovery); - LOG_WARNING(Service_BTM, "(STUBBED) called"); + rb.PushCopyObjects(service_discovery.readable); } + void GetConfigEvent(Kernel::HLERequestContext& ctx) { - auto& kernel = Core::System::GetInstance().Kernel(); - config_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ConfigEvent"); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(config_event); - LOG_WARNING(Service_BTM, "(STUBBED) called"); + rb.PushCopyObjects(config_event.readable); } - Kernel::SharedPtr<Kernel::Event> scan_event; - Kernel::SharedPtr<Kernel::Event> connection_event; - Kernel::SharedPtr<Kernel::Event> service_discovery; - Kernel::SharedPtr<Kernel::Event> config_event; + + Kernel::EventPair scan_event; + Kernel::EventPair connection_event; + Kernel::EventPair service_discovery; + Kernel::EventPair config_event; }; class BTM_USR final : public ServiceFramework<BTM_USR> { @@ -111,10 +119,11 @@ public: private: void GetCoreImpl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BTM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IBtmUserCore>(); - LOG_DEBUG(Service_BTM, "called"); } }; @@ -209,11 +218,11 @@ public: private: void GetCoreImpl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BTM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IBtmSystemCore>(); - - LOG_DEBUG(Service_BTM, "called"); } }; diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp index ee11cd78e..d9b32954e 100644 --- a/src/core/hle/service/erpt/erpt.cpp +++ b/src/core/hle/service/erpt/erpt.cpp @@ -17,11 +17,13 @@ public: static const FunctionInfo functions[] = { {0, nullptr, "SubmitContext"}, {1, nullptr, "CreateReport"}, - {2, nullptr, "Unknown1"}, - {3, nullptr, "Unknown2"}, - {4, nullptr, "Unknown3"}, - {5, nullptr, "Unknown4"}, - {6, nullptr, "Unknown5"}, + {2, nullptr, "SetInitialLaunchSettingsCompletionTime"}, + {3, nullptr, "ClearInitialLaunchSettingsCompletionTime"}, + {4, nullptr, "UpdatePowerOnTime"}, + {5, nullptr, "UpdateAwakeTime"}, + {6, nullptr, "SubmitMultipleCategoryContext"}, + {7, nullptr, "UpdateApplicationLaunchTime"}, + {8, nullptr, "ClearApplicationLaunchTime"}, }; // clang-format on diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp index 566fbf924..e461274c1 100644 --- a/src/core/hle/service/fgm/fgm.cpp +++ b/src/core/hle/service/fgm/fgm.cpp @@ -42,11 +42,11 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FGM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IRequest>(); - - LOG_DEBUG(Service_FGM, "called"); } }; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 5d6294016..b1490e6fa 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -113,6 +113,18 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str return RESULT_SUCCESS; } +ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const { + const std::string sanitized_path(FileUtil::SanitizePath(path)); + auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(sanitized_path)); + + if (!dir->CleanSubdirectoryRecursive(FileUtil::GetFilename(sanitized_path))) { + // TODO(DarkLordZach): Find a better error code for this + return ResultCode(-1); + } + + return RESULT_SUCCESS; +} + ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, const std::string& dest_path_) const { std::string src_path(FileUtil::SanitizePath(src_path_)); @@ -329,16 +341,9 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } -std::shared_ptr<FileSys::RegisteredCacheUnion> registered_cache_union; - -std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() { - if (registered_cache_union == nullptr) { - registered_cache_union = - std::make_shared<FileSys::RegisteredCacheUnion>(std::vector<FileSys::RegisteredCache*>{ - GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}); - } - - return registered_cache_union; +FileSys::RegisteredCacheUnion GetUnionContents() { + return FileSys::RegisteredCacheUnion{ + {GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}}; } FileSys::RegisteredCache* GetSystemNANDContents() { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index ff9182e84..965414be0 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -48,7 +48,7 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space); ResultVal<FileSys::VirtualDir> OpenSDMC(); -std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); +FileSys::RegisteredCacheUnion GetUnionContents(); FileSys::RegisteredCache* GetSystemNANDContents(); FileSys::RegisteredCache* GetUserNANDContents(); @@ -113,6 +113,18 @@ public: ResultCode DeleteDirectoryRecursively(const std::string& path) const; /** + * Cleans the specified directory. This is similar to DeleteDirectoryRecursively, + * in that it deletes all the contents of the specified directory, however, this + * function does *not* delete the directory itself. It only deletes everything + * within it. + * + * @param path Path relative to the archive. + * + * @return Result of the operation. + */ + ResultCode CleanDirectoryRecursively(const std::string& path) const; + + /** * Rename a File specified by its path * @param src_path Source path relative to the archive * @param dest_path Destination path relative to the archive diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 038dc80b1..74c4e583b 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -20,6 +20,7 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/savedata_factory.h" +#include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" @@ -44,8 +45,12 @@ public: explicit IStorage(FileSys::VirtualFile backend_) : ServiceFramework("IStorage"), backend(std::move(backend_)) { static const FunctionInfo functions[] = { - {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"}, - {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, + {0, &IStorage::Read, "Read"}, + {1, nullptr, "Write"}, + {2, nullptr, "Flush"}, + {3, nullptr, "SetSize"}, + {4, &IStorage::GetSize, "GetSize"}, + {5, nullptr, "OperateRange"}, }; RegisterHandlers(functions); } @@ -62,11 +67,13 @@ private: // Error checking if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_SIZE); return; } if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_OFFSET); return; @@ -80,6 +87,15 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + void GetSize(Kernel::HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + LOG_DEBUG(Service_FS, "called, size={}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(size); + } }; class IFile final : public ServiceFramework<IFile> { @@ -107,11 +123,13 @@ private: // Error checking if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_SIZE); return; } if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_OFFSET); return; @@ -138,11 +156,13 @@ private: // Error checking if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_SIZE); return; } if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_INVALID_OFFSET); return; @@ -180,9 +200,10 @@ private: void SetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 size = rp.Pop<u64>(); - backend->Resize(size); LOG_DEBUG(Service_FS, "called, size={}", size); + backend->Resize(size); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -284,7 +305,7 @@ public: {10, &IFileSystem::Commit, "Commit"}, {11, nullptr, "GetFreeSpaceSize"}, {12, nullptr, "GetTotalSpaceSize"}, - {13, nullptr, "CleanDirectoryRecursively"}, + {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, {14, nullptr, "GetFileTimeStampRaw"}, {15, nullptr, "QueryEntry"}, }; @@ -354,6 +375,16 @@ public: rb.Push(backend.DeleteDirectoryRecursively(name)); } + void CleanDirectoryRecursively(Kernel::HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. Directory: {}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.CleanDirectoryRecursively(name)); + } + void RenameFile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -465,6 +496,8 @@ public: } void ReadSaveDataInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + // Calculate how many entries we can fit in the output buffer const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(SaveDataInfo); @@ -703,6 +736,8 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { const auto type = rp.PopRaw<FileSystemType>(); const auto title_id = rp.PopRaw<u64>(); + LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", + static_cast<u8>(type), title_id); IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(ResultCode(-1)); @@ -738,6 +773,7 @@ void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) { auto space_id = rp.PopRaw<FileSys::SaveDataSpaceId>(); auto unk = rp.Pop<u32>(); LOG_INFO(Service_FS, "called with unknown={:08X}", unk); + auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); auto dir = OpenSaveData(space_id, save_struct); @@ -763,6 +799,7 @@ void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto space = rp.PopRaw<FileSys::SaveDataSpaceId>(); + LOG_INFO(Service_FS, "called, space={}", static_cast<u8>(space)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -772,9 +809,18 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FS, "(STUBBED) called"); + enum class LogMode : u32 { + Off, + Log, + RedirectToSdCard, + LogToSdCard = Log | RedirectToSdCard, + }; + + // Given we always want to receive logging information, + // we always specify logging as enabled. IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(5); + rb.PushEnum(LogMode::Log); } void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { @@ -808,6 +854,15 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); if (data.Failed()) { + const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + + if (archive != nullptr) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(std::make_shared<IStorage>(archive)); + return; + } + // TODO(DarkLordZach): Find the right error code to use here LOG_ERROR(Service_FS, "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 3d100763f..c22357d8c 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -6,9 +6,14 @@ #include "common/common_types.h" #include "core/core_timing.h" #include "core/hle/service/hid/controllers/debug_pad.h" +#include "core/settings.h" namespace Service::HID { +constexpr s32 HID_JOYSTICK_MAX = 0x7fff; +constexpr s32 HID_JOYSTICK_MIN = -0x7fff; +enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; + Controller_DebugPad::Controller_DebugPad() = default; Controller_DebugPad::~Controller_DebugPad() = default; @@ -33,10 +38,44 @@ void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update debug pad states + cur_entry.attribute.connected.Assign(1); + auto& pad = cur_entry.pad_state; + + using namespace Settings::NativeButton; + pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); std::memcpy(data, &shared_memory, sizeof(SharedMemory)); } -void Controller_DebugPad::OnLoadInputDevices() {} +void Controller_DebugPad::OnLoadInputDevices() { + std::transform(Settings::values.debug_pad_buttons.begin(), + Settings::values.debug_pad_buttons.begin() + + Settings::NativeButton::NUM_BUTTONS_HID, + buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(Settings::values.debug_pad_analogs.begin(), + Settings::values.debug_pad_analogs.end(), analogs.begin(), + Input::CreateDevice<Input::AnalogDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 62b4f2682..68b734248 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -5,10 +5,13 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_DebugPad final : public ControllerBase { @@ -35,11 +38,40 @@ private: }; static_assert(sizeof(AnalogStick) == 0x8); + struct PadState { + union { + u32_le raw{}; + BitField<0, 1, u32_le> a; + BitField<1, 1, u32_le> b; + BitField<2, 1, u32_le> x; + BitField<3, 1, u32_le> y; + BitField<4, 1, u32_le> l; + BitField<5, 1, u32_le> r; + BitField<6, 1, u32_le> zl; + BitField<7, 1, u32_le> zr; + BitField<8, 1, u32_le> plus; + BitField<9, 1, u32_le> minus; + BitField<10, 1, u32_le> d_left; + BitField<11, 1, u32_le> d_up; + BitField<12, 1, u32_le> d_right; + BitField<13, 1, u32_le> d_down; + }; + }; + static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size"); + + struct Attributes { + union { + u32_le raw{}; + BitField<0, 1, u32_le> connected; + }; + }; + static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + struct PadStates { s64_le sampling_number; s64_le sampling_number2; - u32_le attribute; - u32_le button_state; + Attributes attribute; + PadState pad_state; AnalogStick r_stick; AnalogStick l_stick; }; @@ -52,5 +84,10 @@ private: }; static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; + + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> + analogs; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index ccfbce9ac..ca75adc2b 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -6,9 +6,11 @@ #include "common/common_types.h" #include "core/core_timing.h" #include "core/hle/service/hid/controllers/keyboard.h" +#include "core/settings.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; +constexpr u8 KEYS_PER_BYTE = 8; Controller_Keyboard::Controller_Keyboard() = default; Controller_Keyboard::~Controller_Keyboard() = default; @@ -34,10 +36,24 @@ void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update keyboard states + + for (std::size_t i = 0; i < keyboard_keys.size(); ++i) { + for (std::size_t k = 0; k < KEYS_PER_BYTE; ++k) { + cur_entry.key[i / KEYS_PER_BYTE] |= (keyboard_keys[i]->GetStatus() << k); + } + } + + for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { + cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i); + } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } -void Controller_Keyboard::OnLoadInputDevices() {} +void Controller_Keyboard::OnLoadInputDevices() { + std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(), + keyboard_keys.begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(), + keyboard_mods.begin(), Input::CreateDevice<Input::ButtonDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index 493e68fce..f52775456 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -8,7 +8,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_Keyboard final : public ControllerBase { @@ -46,5 +48,10 @@ private: }; static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; + + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardKeys> + keyboard_keys; + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods> + keyboard_mods; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 4e246a57d..63391dbe9 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -5,6 +5,7 @@ #include <cstring> #include "common/common_types.h" #include "core/core_timing.h" +#include "core/frontend/emu_window.h" #include "core/hle/service/hid/controllers/mouse.h" namespace Service::HID { @@ -14,7 +15,6 @@ Controller_Mouse::Controller_Mouse() = default; Controller_Mouse::~Controller_Mouse() = default; void Controller_Mouse::OnInit() {} - void Controller_Mouse::OnRelease() {} void Controller_Mouse::OnUpdate(u8* data, std::size_t size) { @@ -34,10 +34,29 @@ void Controller_Mouse::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update mouse states + + if (Settings::values.mouse_enabled) { + const auto [px, py, sx, sy] = mouse_device->GetStatus(); + const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width); + const auto y = static_cast<s32>(py * Layout::ScreenUndocked::Height); + cur_entry.x = x; + cur_entry.y = y; + cur_entry.delta_x = x - last_entry.x; + cur_entry.delta_y = y - last_entry.y; + cur_entry.mouse_wheel_x = sx; + cur_entry.mouse_wheel_y = sy; + + for (std::size_t i = 0; i < mouse_button_devices.size(); ++i) { + cur_entry.button |= (mouse_button_devices[i]->GetStatus() << i); + } + } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } -void Controller_Mouse::OnLoadInputDevices() {} +void Controller_Mouse::OnLoadInputDevices() { + mouse_device = Input::CreateDevice<Input::MouseDevice>(Settings::values.mouse_device); + std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), + mouse_button_devices.begin(), Input::CreateDevice<Input::ButtonDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index 543b0b71f..70b654d07 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -7,7 +7,9 @@ #include <array> #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_Mouse final : public ControllerBase { @@ -35,7 +37,8 @@ private: s32_le y; s32_le delta_x; s32_le delta_y; - s32_le mouse_wheel; + s32_le mouse_wheel_x; + s32_le mouse_wheel_y; s32_le button; s32_le attribute; }; @@ -46,5 +49,9 @@ private: std::array<MouseState, 17> mouse_states; }; SharedMemory shared_memory{}; + + std::unique_ptr<Input::MouseDevice> mouse_device; + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons> + mouse_button_devices; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 205e4fd14..d6829d0b8 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -12,27 +12,20 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/input.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/settings.h" namespace Service::HID { - -constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; -constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; -constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; -constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; -constexpr u32 NPAD_HANDHELD = 32; -constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? constexpr u32 MAX_NPAD_ID = 7; -constexpr Controller_NPad::NPadControllerType PREFERRED_CONTROLLER = - Controller_NPad::NPadControllerType::JoyDual; constexpr std::array<u32, 10> npad_id_list{ - 0, 1, 2, 3, 4, 5, 6, 7, 32, 16, + 0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN, }; enum class JoystickId : std::size_t { @@ -40,7 +33,23 @@ enum class JoystickId : std::size_t { Joystick_Right, }; -static std::size_t NPadIdToIndex(u32 npad_id) { +static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) { + switch (type) { + case Settings::ControllerType::ProController: + return Controller_NPad::NPadControllerType::ProController; + case Settings::ControllerType::DualJoycon: + return Controller_NPad::NPadControllerType::JoyDual; + case Settings::ControllerType::LeftJoycon: + return Controller_NPad::NPadControllerType::JoyLeft; + case Settings::ControllerType::RightJoycon: + return Controller_NPad::NPadControllerType::JoyRight; + default: + UNREACHABLE(); + return Controller_NPad::NPadControllerType::JoyDual; + } +} + +std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) { switch (npad_id) { case 0: case 1: @@ -63,6 +72,27 @@ static std::size_t NPadIdToIndex(u32 npad_id) { } } +u32 Controller_NPad::IndexToNPad(std::size_t index) { + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return static_cast<u32>(index); + case 8: + return NPAD_HANDHELD; + case 9: + return NPAD_UNKNOWN; + default: + UNIMPLEMENTED_MSG("Unknown npad index {}", index); + return 0; + }; +} + Controller_NPad::Controller_NPad() = default; Controller_NPad::~Controller_NPad() = default; @@ -79,22 +109,32 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.joy_styles.handheld.Assign(1); controller.device_type.handheld.Assign(1); controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); break; case NPadControllerType::JoyDual: controller.joy_styles.joycon_dual.Assign(1); controller.device_type.joycon_left.Assign(1); controller.device_type.joycon_right.Assign(1); + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); controller.pad_assignment = NPadAssignments::Dual; break; case NPadControllerType::JoyLeft: controller.joy_styles.joycon_left.Assign(1); controller.device_type.joycon_left.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_horizontal.Assign(1); + controller.properties.use_minus.Assign(1); + controller.pad_assignment = NPadAssignments::Single; break; case NPadControllerType::JoyRight: controller.joy_styles.joycon_right.Assign(1); controller.device_type.joycon_right.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_horizontal.Assign(1); + controller.properties.use_plus.Assign(1); + controller.pad_assignment = NPadAssignments::Single; break; case NPadControllerType::Pokeball: controller.joy_styles.pokeball.Assign(1); @@ -104,6 +144,9 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { case NPadControllerType::ProController: controller.joy_styles.pro_controller.Assign(1); controller.device_type.pro_controller.Assign(1); + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); controller.pad_assignment = NPadAssignments::Single; break; } @@ -113,14 +156,12 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.single_color.button_color = 0; controller.dual_color_error = ColorReadError::ReadOk; - controller.left_color.body_color = JOYCON_BODY_NEON_BLUE; - controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE; - controller.right_color.body_color = JOYCON_BODY_NEON_RED; - controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED; - - controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations - controller.properties.use_plus.Assign(1); - controller.properties.use_minus.Assign(1); + controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left; + controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left; + controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right; + controller.right_color.button_color = + Settings::values.players[controller_idx].button_color_right; + controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; controller.battery_level[2] = BATTERY_FULL; @@ -128,8 +169,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { void Controller_NPad::OnInit() { auto& kernel = Core::System::GetInstance().Kernel(); - styleset_changed_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged"); + styleset_changed_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged"); if (!IsControllerActivated()) { return; @@ -144,26 +185,109 @@ void Controller_NPad::OnInit() { style.pro_controller.Assign(1); style.pokeball.Assign(1); } + + std::transform( + Settings::values.players.begin(), Settings::values.players.end(), + connected_controllers.begin(), [](const Settings::PlayerInput& player) { + return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected}; + }); + + std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8, + [](const ControllerHolder& holder) { return holder.is_connected; }); + + // Account for handheld + if (connected_controllers[8].is_connected) + connected_controllers[8].type = NPadControllerType::Handheld; + + supported_npad_id_types.resize(npad_id_list.size()); + std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), + npad_id_list.size() * sizeof(u32)); + + // Add a default dual joycon controller if none are present. if (std::none_of(connected_controllers.begin(), connected_controllers.end(), [](const ControllerHolder& controller) { return controller.is_connected; })) { supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), npad_id_list.size() * sizeof(u32)); - AddNewController(PREFERRED_CONTROLLER); + AddNewController(NPadControllerType::JoyDual); + } + + for (std::size_t i = 0; i < connected_controllers.size(); ++i) { + const auto& controller = connected_controllers[i]; + if (controller.is_connected) { + AddNewControllerAt(controller.type, IndexToNPad(i)); + } } } void Controller_NPad::OnLoadInputDevices() { - std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, - buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); - std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END, - sticks.begin(), Input::CreateDevice<Input::AnalogDevice>); + const auto& players = Settings::values.players; + for (std::size_t i = 0; i < players.size(); ++i) { + std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, + buttons[i].begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, + sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); + } } void Controller_NPad::OnRelease() {} +void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { + const auto controller_idx = NPadIdToIndex(npad_id); + const auto controller_type = connected_controllers[controller_idx].type; + if (!connected_controllers[controller_idx].is_connected) { + return; + } + auto& pad_state = npad_pad_states[controller_idx].pad_states; + auto& lstick_entry = npad_pad_states[controller_idx].l_stick; + auto& rstick_entry = npad_pad_states[controller_idx].r_stick; + const auto& button_state = buttons[controller_idx]; + const auto& analog_state = sticks[controller_idx]; + + using namespace Settings::NativeButton; + pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.l_stick_left.Assign(button_state[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_up.Assign(button_state[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_right.Assign(button_state[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_down.Assign(button_state[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.r_stick_left.Assign(button_state[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_up.Assign(button_state[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_right.Assign(button_state[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_down.Assign(button_state[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); +} + void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { if (!IsControllerActivated()) return; @@ -199,97 +323,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { continue; } - - // Pad states - ControllerPadState pad_state{}; - using namespace Settings::NativeButton; - pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); - - AnalogPosition lstick_entry{}; - AnalogPosition rstick_entry{}; - - const auto [stick_l_x_f, stick_l_y_f] = - sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = - sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); - lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); - lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); - rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); - rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); - - if (controller_type == NPadControllerType::JoyLeft || - controller_type == NPadControllerType::JoyRight) { - if (npad.properties.is_horizontal) { - ControllerPadState state{}; - AnalogPosition temp_lstick_entry{}; - AnalogPosition temp_rstick_entry{}; - if (controller_type == NPadControllerType::JoyLeft) { - state.d_down.Assign(pad_state.d_left.Value()); - state.d_left.Assign(pad_state.d_up.Value()); - state.d_right.Assign(pad_state.d_down.Value()); - state.d_up.Assign(pad_state.d_right.Value()); - state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); - state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); - - state.zl.Assign(pad_state.zl.Value()); - state.plus.Assign(pad_state.minus.Value()); - - temp_lstick_entry = lstick_entry; - temp_rstick_entry = rstick_entry; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_lstick_entry.y *= -1; - } else if (controller_type == NPadControllerType::JoyRight) { - state.x.Assign(pad_state.a.Value()); - state.a.Assign(pad_state.b.Value()); - state.b.Assign(pad_state.y.Value()); - state.y.Assign(pad_state.b.Value()); - - state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); - state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); - state.zr.Assign(pad_state.zr.Value()); - state.plus.Assign(pad_state.plus.Value()); - - temp_lstick_entry = lstick_entry; - temp_rstick_entry = rstick_entry; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_rstick_entry.x *= -1; - } - pad_state.raw = state.raw; - lstick_entry = temp_lstick_entry; - rstick_entry = temp_rstick_entry; - } - } + const u32 npad_index = static_cast<u32>(i); + RequestPadStateUpdate(npad_index); + auto& pad_state = npad_pad_states[npad_index]; auto& main_controller = npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; @@ -304,8 +340,51 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; if (hold_type == NpadHoldType::Horizontal) { - // TODO(ogniK): Remap buttons for different orientations + ControllerPadState state{}; + AnalogPosition temp_lstick_entry{}; + AnalogPosition temp_rstick_entry{}; + if (controller_type == NPadControllerType::JoyLeft) { + state.d_down.Assign(pad_state.pad_states.d_left.Value()); + state.d_left.Assign(pad_state.pad_states.d_up.Value()); + state.d_right.Assign(pad_state.pad_states.d_down.Value()); + state.d_up.Assign(pad_state.pad_states.d_right.Value()); + state.l.Assign(pad_state.pad_states.l.Value() | + pad_state.pad_states.left_sl.Value()); + state.r.Assign(pad_state.pad_states.r.Value() | + pad_state.pad_states.left_sr.Value()); + + state.zl.Assign(pad_state.pad_states.zl.Value()); + state.plus.Assign(pad_state.pad_states.minus.Value()); + + temp_lstick_entry = pad_state.l_stick; + temp_rstick_entry = pad_state.r_stick; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_lstick_entry.y *= -1; + } else if (controller_type == NPadControllerType::JoyRight) { + state.x.Assign(pad_state.pad_states.a.Value()); + state.a.Assign(pad_state.pad_states.b.Value()); + state.b.Assign(pad_state.pad_states.y.Value()); + state.y.Assign(pad_state.pad_states.b.Value()); + + state.l.Assign(pad_state.pad_states.l.Value() | + pad_state.pad_states.right_sl.Value()); + state.r.Assign(pad_state.pad_states.r.Value() | + pad_state.pad_states.right_sr.Value()); + state.zr.Assign(pad_state.pad_states.zr.Value()); + state.plus.Assign(pad_state.pad_states.plus.Value()); + + temp_lstick_entry = pad_state.l_stick; + temp_rstick_entry = pad_state.r_stick; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_rstick_entry.x *= -1; + } + pad_state.pad_states.raw = state.raw; + pad_state.l_stick = temp_lstick_entry; + pad_state.r_stick = temp_rstick_entry; } + libnx_entry.connection_status.raw = 0; switch (controller_type) { @@ -316,9 +395,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { handheld_entry.connection_status.IsRightJoyConnected.Assign(1); handheld_entry.connection_status.IsLeftJoyWired.Assign(1); handheld_entry.connection_status.IsRightJoyWired.Assign(1); - handheld_entry.pad_states.raw = pad_state.raw; - handheld_entry.l_stick = lstick_entry; - handheld_entry.r_stick = rstick_entry; + handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; + handheld_entry.pad.l_stick = pad_state.l_stick; + handheld_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyDual: dual_entry.connection_status.raw = 0; @@ -331,25 +410,25 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { libnx_entry.connection_status.IsRightJoyConnected.Assign(1); libnx_entry.connection_status.IsConnected.Assign(1); - dual_entry.pad_states.raw = pad_state.raw; - dual_entry.l_stick = lstick_entry; - dual_entry.r_stick = rstick_entry; + dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; + dual_entry.pad.l_stick = pad_state.l_stick; + dual_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyLeft: left_entry.connection_status.raw = 0; left_entry.connection_status.IsConnected.Assign(1); - left_entry.pad_states.raw = pad_state.raw; - left_entry.l_stick = lstick_entry; - left_entry.r_stick = rstick_entry; + left_entry.pad.pad_states.raw = pad_state.pad_states.raw; + left_entry.pad.l_stick = pad_state.l_stick; + left_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyRight: right_entry.connection_status.raw = 0; right_entry.connection_status.IsConnected.Assign(1); - right_entry.pad_states.raw = pad_state.raw; - right_entry.l_stick = lstick_entry; - right_entry.r_stick = rstick_entry; + right_entry.pad.pad_states.raw = pad_state.pad_states.raw; + right_entry.pad.l_stick = pad_state.l_stick; + right_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::Pokeball: pokeball_entry.connection_status.raw = 0; @@ -357,30 +436,30 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { pokeball_entry.connection_status.IsConnected.Assign(1); pokeball_entry.connection_status.IsWired.Assign(1); - pokeball_entry.pad_states.raw = pad_state.raw; - pokeball_entry.l_stick = lstick_entry; - pokeball_entry.r_stick = rstick_entry; + pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; + pokeball_entry.pad.l_stick = pad_state.l_stick; + pokeball_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::ProController: main_controller.connection_status.raw = 0; main_controller.connection_status.IsConnected.Assign(1); main_controller.connection_status.IsWired.Assign(1); - main_controller.pad_states.raw = pad_state.raw; - main_controller.l_stick = lstick_entry; - main_controller.r_stick = rstick_entry; + main_controller.pad.pad_states.raw = pad_state.pad_states.raw; + main_controller.pad.l_stick = pad_state.l_stick; + main_controller.pad.r_stick = pad_state.r_stick; break; } // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate // any controllers. - libnx_entry.pad_states.raw = pad_state.raw; - libnx_entry.l_stick = lstick_entry; - libnx_entry.r_stick = rstick_entry; + libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; + libnx_entry.pad.l_stick = pad_state.l_stick; + libnx_entry.pad.r_stick = pad_state.r_stick; } std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), shared_memory_entries.size() * sizeof(NPadEntry)); -} // namespace Service::HID +} void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { style.raw = style_set.raw; @@ -401,23 +480,24 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { if (!controller.is_connected) { continue; } - if (!IsControllerSupported(PREFERRED_CONTROLLER)) { - const auto best_type = DecideBestController(PREFERRED_CONTROLLER); - const bool is_handheld = (best_type == NPadControllerType::Handheld || - PREFERRED_CONTROLLER == NPadControllerType::Handheld); + const auto requested_controller = + i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type) + : NPadControllerType::Handheld; + if (!IsControllerSupported(requested_controller)) { + const auto is_handheld = requested_controller == NPadControllerType::Handheld; if (is_handheld) { controller.type = NPadControllerType::None; controller.is_connected = false; - AddNewController(best_type); + AddNewController(requested_controller); } else { - controller.type = best_type; + controller.type = requested_controller; InitNewlyAddedControler(i); } had_controller_update = true; } - } - if (had_controller_update) { - styleset_changed_event->Signal(); + if (had_controller_update) { + styleset_changed_event.writable->Signal(); + } } } @@ -431,7 +511,7 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { } void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { - styleset_changed_event->Signal(); + styleset_changed_event.writable->Signal(); hold_type = joy_hold_type; } @@ -440,44 +520,40 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { } void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { - ASSERT(npad_id < shared_memory_entries.size()); - shared_memory_entries[npad_id].pad_assignment = assignment_mode; + const std::size_t npad_index = NPadIdToIndex(npad_id); + ASSERT(npad_index < shared_memory_entries.size()); + shared_memory_entries[npad_index].pad_assignment = assignment_mode; } void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, const std::vector<Vibration>& vibrations) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + if (!can_controllers_vibrate) { return; } for (std::size_t i = 0; i < controller_ids.size(); i++) { - std::size_t controller_pos = i; - // Handheld controller conversion - if (controller_pos == NPAD_HANDHELD) { - controller_pos = 8; - } - // Unknown controller conversion - if (controller_pos == NPAD_UNKNOWN) { - controller_pos = 9; - } + std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i)); if (connected_controllers[controller_pos].is_connected) { // TODO(ogniK): Vibrate the physical controller } } - LOG_WARNING(Service_HID, "(STUBBED) called"); last_processed_vibration = vibrations.back(); } -Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const { +Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent() const { // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should // be signalled at least once, and signaled after a new controller is connected? - styleset_changed_event->Signal(); - return styleset_changed_event; + styleset_changed_event.writable->Signal(); + return styleset_changed_event.readable; } Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { return last_processed_vibration; } + void Controller_NPad::AddNewController(NPadControllerType controller) { + controller = DecideBestController(controller); if (controller == NPadControllerType::Handheld) { connected_controllers[8] = {controller, true}; InitNewlyAddedControler(8); @@ -495,6 +571,18 @@ void Controller_NPad::AddNewController(NPadControllerType controller) { InitNewlyAddedControler(controller_id); } +void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) { + controller = DecideBestController(controller); + if (controller == NPadControllerType::Handheld) { + connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true}; + InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD)); + return; + } + + connected_controllers[NPadIdToIndex(npad_id)] = {controller, true}; + InitNewlyAddedControler(NPadIdToIndex(npad_id)); +} + void Controller_NPad::ConnectNPad(u32 npad_id) { connected_controllers[NPadIdToIndex(npad_id)].is_connected = true; } @@ -503,6 +591,36 @@ void Controller_NPad::DisconnectNPad(u32 npad_id) { connected_controllers[NPadIdToIndex(npad_id)].is_connected = false; } +bool Controller_NPad::IsControllerSupported(NPadControllerType controller) { + if (controller == NPadControllerType::Handheld) { + // Handheld is not even a supported type, lets stop here + if (std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), + NPAD_HANDHELD) == supported_npad_id_types.end()) { + return false; + } + // Handheld should not be supported in docked mode + if (Settings::values.use_docked_mode) { + return false; + } + } + switch (controller) { + case NPadControllerType::ProController: + return style.pro_controller; + case NPadControllerType::Handheld: + return style.handheld; + case NPadControllerType::JoyDual: + return style.joycon_dual; + case NPadControllerType::JoyLeft: + return style.joycon_left; + case NPadControllerType::JoyRight: + return style.joycon_right; + case NPadControllerType::Pokeball: + return style.pokeball; + default: + return false; + } +} + Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) { // These are controllers without led patterns @@ -534,6 +652,36 @@ void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { can_controllers_vibrate = can_vibrate; } +void Controller_NPad::ClearAllConnectedControllers() { + for (auto& controller : connected_controllers) { + if (controller.is_connected && controller.type != NPadControllerType::None) { + controller.type = NPadControllerType::None; + controller.is_connected = false; + } + } +} +void Controller_NPad::DisconnectAllConnectedControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { controller.is_connected = false; }); +} + +void Controller_NPad::ConnectAllDisconnectedControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { + if (controller.type != NPadControllerType::None && !controller.is_connected) { + controller.is_connected = false; + } + }); +} + +void Controller_NPad::ClearAllControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { + controller.type = NPadControllerType::None; + controller.is_connected = false; + }); +} + bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { const bool support_handheld = std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) != diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index ac86985ff..29851f16a 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -5,13 +5,19 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_types.h" #include "core/frontend/input.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/controllers/controller_base.h" #include "core/settings.h" namespace Service::HID { +constexpr u32 NPAD_HANDHELD = 32; +constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? + class Controller_NPad final : public ControllerBase { public: Controller_NPad(); @@ -75,9 +81,9 @@ public: struct LedPattern { explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) { position1.Assign(light1); - position1.Assign(light2); - position1.Assign(light3); - position1.Assign(light4); + position2.Assign(light2); + position3.Assign(light3); + position4.Assign(light4); } union { u64 raw{}; @@ -103,15 +109,23 @@ public: void VibrateController(const std::vector<u32>& controller_ids, const std::vector<Vibration>& vibrations); - Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const; + Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent() const; Vibration GetLastVibration() const; void AddNewController(NPadControllerType controller); + void AddNewControllerAt(NPadControllerType controller, u32 npad_id); void ConnectNPad(u32 npad_id); void DisconnectNPad(u32 npad_id); LedPattern GetLedPattern(u32 npad_id); void SetVibrationEnabled(bool can_vibrate); + void ClearAllConnectedControllers(); + void DisconnectAllConnectedControllers(); + void ConnectAllDisconnectedControllers(); + void ClearAllControllers(); + + static std::size_t NPadIdToIndex(u32 npad_id); + static u32 IndexToNPad(std::size_t index); private: struct CommonHeader { @@ -164,8 +178,11 @@ private: BitField<23, 1, u64_le> r_stick_down; // Not always active? - BitField<24, 1, u64_le> sl; - BitField<25, 1, u64_le> sr; + BitField<24, 1, u64_le> left_sl; + BitField<25, 1, u64_le> left_sr; + + BitField<26, 1, u64_le> right_sl; + BitField<27, 1, u64_le> right_sr; }; }; static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); @@ -189,12 +206,17 @@ private: }; static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); - struct GenericStates { - s64_le timestamp; - s64_le timestamp2; + struct ControllerPad { ControllerPadState pad_states; AnalogPosition l_stick; AnalogPosition r_stick; + }; + static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad is an invalid size"); + + struct GenericStates { + s64_le timestamp; + s64_le timestamp2; + ControllerPad pad; ConnectionState connection_status; }; static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size"); @@ -266,18 +288,23 @@ private: static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); struct ControllerHolder { - Controller_NPad::NPadControllerType type; + NPadControllerType type; bool is_connected; }; NPadType style{}; std::array<NPadEntry, 10> shared_memory_entries{}; - std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + std::array< + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, + 10> buttons; - std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; + std::array< + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, + 10> + sticks; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; - Kernel::SharedPtr<Kernel::Event> styleset_changed_event; + Kernel::EventPair styleset_changed_event; Vibration last_processed_vibration{}; std::array<ControllerHolder, 10> connected_controllers{}; bool can_controllers_vibrate{true}; @@ -285,5 +312,8 @@ private: void InitNewlyAddedControler(std::size_t controller_idx); bool IsControllerSupported(NPadControllerType controller) const; NPadControllerType DecideBestController(NPadControllerType priority) const; + void RequestPadStateUpdate(u32 npad_id); + std::array<ControllerPad, 10> npad_pad_states{}; + bool IsControllerSupported(NPadControllerType controller); }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 43efef803..f666b1bd8 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -41,16 +41,17 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) { const auto [x, y, pressed] = touch_device->GetStatus(); auto& touch_entry = cur_entry.states[0]; - if (pressed) { + touch_entry.attribute.raw = 0; + if (pressed && Settings::values.touchscreen.enabled) { touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); - touch_entry.diameter_x = 15; - touch_entry.diameter_y = 15; - touch_entry.rotation_angle = 0; + touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; + touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; + touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; const u64 tick = CoreTiming::GetTicks(); touch_entry.delta_time = tick - last_touch; last_touch = tick; - touch_entry.finger = 0; + touch_entry.finger = Settings::values.touchscreen.finger; cur_entry.entry_count = 1; } else { cur_entry.entry_count = 0; @@ -60,6 +61,6 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) { } void Controller_Touchscreen::OnLoadInputDevices() { - touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); + touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e5db6e6ba..94cd0eba9 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -4,6 +4,7 @@ #pragma once +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -29,9 +30,18 @@ public: void OnLoadInputDevices() override; private: + struct Attributes { + union { + u32 raw{}; + BitField<0, 1, u32_le> start_touch; + BitField<1, 1, u32_le> end_touch; + }; + }; + static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + struct TouchState { u64_le delta_time; - u32_le attribute; + Attributes attribute; u32_le finger; u32_le x; u32_le y; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 39631b14f..2ec38c726 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -13,8 +13,9 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/irs.h" #include "core/hle/service/hid/xcd.h" @@ -34,8 +35,8 @@ namespace Service::HID { // Updating period for each HID device. -// TODO(shinyquagsire23): These need better values. -constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; +// TODO(ogniK): Find actual polling rate of hid +constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 66; constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; @@ -124,10 +125,11 @@ public: private: void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(shared_mem); - LOG_DEBUG(Service_HID, "called"); } void UpdateControllers(u64 userdata, int cycles_late) { @@ -163,9 +165,10 @@ public: private: void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } }; @@ -303,6 +306,8 @@ private: std::shared_ptr<IAppletResource> applet_resource; void CreateAppletResource(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + if (applet_resource == nullptr) { applet_resource = std::make_shared<IAppletResource>(); } @@ -310,206 +315,228 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAppletResource>(applet_resource); - LOG_DEBUG(Service_HID, "called"); } void ActivateXpad(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::XPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateDebugPad(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::DebugPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateTouchScreen(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::Touchscreen); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateMouse(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::Mouse); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateKeyboard(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::Keyboard); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateGesture(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::Gesture); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { // Should have no effect with how our npad sets up the data + LOG_DEBUG(Service_HID, "called"); + applet_resource->ActivateController(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto handle = rp.PopRaw<u32>(); + LOG_WARNING(Service_HID, "(STUBBED) called with handle={}", handle); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. rb.Push(true); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto supported_styleset = rp.PopRaw<u32>(); + LOG_DEBUG(Service_HID, "called with supported_styleset={}", supported_styleset); + applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetSupportedStyleSet({supported_styleset}); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_HID, "called"); } void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(controller.GetSupportedStyleSet().raw); - LOG_DEBUG(Service_HID, "called"); } void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void ActivateNpad(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); applet_resource->ActivateController(HidController::NPad); - LOG_DEBUG(Service_HID, "called"); } void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto npad_id = rp.PopRaw<u32>(); + LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) .GetStyleSetChangedEvent()); - LOG_DEBUG(Service_HID, "called"); } void DisconnectNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto npad_id = rp.PopRaw<u32>(); + LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id); + applet_resource->GetController<Controller_NPad>(HidController::NPad) .DisconnectNPad(npad_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto npad_id = rp.PopRaw<u32>(); + LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) .GetLedPattern(npad_id) .raw); - LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::RequestParser rp{ctx}; const auto hold_type = rp.PopRaw<u64>(); + LOG_DEBUG(Service_HID, "called with hold_type={}", hold_type); + + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); - LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto npad_id = rp.PopRaw<u32>(); + LOG_WARNING(Service_HID, "(STUBBED) called with npad_id={}", npad_id); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetVibrationEnabled(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetVibrationEnabled(false); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void SendVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto controller_id = rp.PopRaw<u32>(); const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>(); + LOG_DEBUG(Service_HID, "called with controller_id={}", controller_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); applet_resource->GetController<Controller_NPad>(HidController::NPad) .VibrateController({controller_id}, {vibration_values}); - LOG_DEBUG(Service_HID, "called"); } void SendVibrationValues(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + const auto controllers = ctx.ReadBuffer(0); const auto vibrations = ctx.ReadBuffer(1); @@ -527,86 +554,96 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.PushRaw<Controller_NPad::Vibration>( applet_resource->GetController<Controller_NPad>(HidController::NPad) .GetLastVibration()); - LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto npad_id = rp.PopRaw<u32>(); + LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id); + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_HID, "called"); } void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto mode = rp.PopRaw<u32>(); + LOG_WARNING(Service_HID, "(STUBBED) called with mode={}", mode); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); rb.Push<u32>(0); - LOG_DEBUG(Service_HID, "called"); } void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IActiveVibrationDeviceList>(); - LOG_DEBUG(Service_HID, "called"); } void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void StopSixAxisSensor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } void SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); } }; diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 872e3c344..3c7f8b1ee 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -44,115 +44,133 @@ IRS::IRS() : ServiceFramework{"irs"} { } void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_IRS, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(shared_mem); - LOG_DEBUG(Service_IRS, "called"); } void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u64>(CoreTiming::GetTicks()); rb.PushRaw<u32>(0); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u32>(device_handle); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_IRS, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IRS, "(STUBBED) called"); } IRS::~IRS() = default; diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp index 164c57e18..e8f9f2d29 100644 --- a/src/core/hle/service/lbl/lbl.cpp +++ b/src/core/hle/service/lbl/lbl.cpp @@ -55,29 +55,29 @@ public: private: void EnableVrMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); vr_mode_enabled = true; - - LOG_DEBUG(Service_LBL, "called"); } void DisableVrMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); vr_mode_enabled = false; - - LOG_DEBUG(Service_LBL, "called"); } void IsVrModeEnabled(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LBL, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(vr_mode_enabled); - - LOG_DEBUG(Service_LBL, "called"); } bool vr_mode_enabled = false; diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index 167f2c66a..e250595e3 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -44,11 +44,11 @@ public: } void CreateMonitorService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LDN, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IMonitorService>(); - - LOG_DEBUG(Service_LDN, "called"); } }; @@ -104,11 +104,11 @@ public: } void CreateSystemLocalCommunicationService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LDN, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILocalCommunicationService>("ISystemLocalCommunicationService"); - - LOG_DEBUG(Service_LDN, "called"); } }; @@ -125,11 +125,11 @@ public: } void CreateUserLocalCommunicationService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LDN, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILocalCommunicationService>("IUserLocalCommunicationService"); - - LOG_DEBUG(Service_LDN, "called"); } }; diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index b43f1f054..13bcefe07 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -16,35 +16,18 @@ namespace Service::LDR { -namespace ErrCodes { -enum { - InvalidMemoryState = 51, - InvalidNRO = 52, - InvalidNRR = 53, - MissingNRRHash = 54, - MaximumNRO = 55, - MaximumNRR = 56, - AlreadyLoaded = 57, - InvalidAlignment = 81, - InvalidSize = 82, - InvalidNROAddress = 84, - InvalidNRRAddress = 85, - NotInitialized = 87, -}; -} - -constexpr ResultCode ERROR_INVALID_MEMORY_STATE(ErrorModule::Loader, ErrCodes::InvalidMemoryState); -constexpr ResultCode ERROR_INVALID_NRO(ErrorModule::Loader, ErrCodes::InvalidNRO); -constexpr ResultCode ERROR_INVALID_NRR(ErrorModule::Loader, ErrCodes::InvalidNRR); -constexpr ResultCode ERROR_MISSING_NRR_HASH(ErrorModule::Loader, ErrCodes::MissingNRRHash); -constexpr ResultCode ERROR_MAXIMUM_NRO(ErrorModule::Loader, ErrCodes::MaximumNRO); -constexpr ResultCode ERROR_MAXIMUM_NRR(ErrorModule::Loader, ErrCodes::MaximumNRR); -constexpr ResultCode ERROR_ALREADY_LOADED(ErrorModule::Loader, ErrCodes::AlreadyLoaded); -constexpr ResultCode ERROR_INVALID_ALIGNMENT(ErrorModule::Loader, ErrCodes::InvalidAlignment); -constexpr ResultCode ERROR_INVALID_SIZE(ErrorModule::Loader, ErrCodes::InvalidSize); -constexpr ResultCode ERROR_INVALID_NRO_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNROAddress); -constexpr ResultCode ERROR_INVALID_NRR_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNRRAddress); -constexpr ResultCode ERROR_NOT_INITIALIZED(ErrorModule::Loader, ErrCodes::NotInitialized); +constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; +constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52}; +constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53}; +constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54}; +constexpr ResultCode ERROR_MAXIMUM_NRO{ErrorModule::Loader, 55}; +constexpr ResultCode ERROR_MAXIMUM_NRR{ErrorModule::Loader, 56}; +constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57}; +constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81}; +constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82}; +constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84}; +constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; +constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87}; constexpr u64 MAXIMUM_LOADED_RO = 0x40; @@ -114,6 +97,8 @@ public: rp.Skip(2, false); const VAddr nrr_addr{rp.Pop<VAddr>()}; const u64 nrr_size{rp.Pop<u64>()}; + LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}, nrr_size={:016X}", nrr_addr, + nrr_size); if (!initialized) { LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); @@ -206,6 +191,7 @@ public: IPC::RequestParser rp{ctx}; rp.Skip(2, false); const auto nrr_addr{rp.Pop<VAddr>()}; + LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}", nrr_addr); if (!Common::Is4KBAligned(nrr_addr)) { LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); @@ -236,6 +222,10 @@ public: const u64 nro_size{rp.Pop<u64>()}; const VAddr bss_addr{rp.Pop<VAddr>()}; const u64 bss_size{rp.Pop<u64>()}; + LOG_DEBUG( + Service_LDR, + "called with nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, bss_size={:016X}", + nro_addr, nro_size, bss_addr, bss_size); if (!initialized) { LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); @@ -345,10 +335,7 @@ public: vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size, Kernel::VMAPermission::ReadWrite); - Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + Core::System::GetInstance().InvalidateCpuInstructionCaches(); nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size}); @@ -362,6 +349,8 @@ public: rp.Skip(2, false); const VAddr mapped_addr{rp.PopRaw<VAddr>()}; const VAddr heap_addr{rp.PopRaw<VAddr>()}; + LOG_DEBUG(Service_LDR, "called with mapped_addr={:016X}, heap_addr={:016X}", mapped_addr, + heap_addr); if (!initialized) { LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); @@ -399,10 +388,7 @@ public: Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); ASSERT(process->UnmapMemory(mapped_addr, 0, nro_size) == RESULT_SUCCESS); - Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); - Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + Core::System::GetInstance().InvalidateCpuInstructionCaches(); nro.erase(iter); IPC::ResponseBuilder rb{ctx, 2}; @@ -410,24 +396,25 @@ public: } void Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDR, "(STUBBED) called"); + initialized = true; IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_LDR, "(STUBBED) called"); } private: using SHA256Hash = std::array<u8, 0x20>; struct NROHeader { - u32_le entrypoint_insn; + INSERT_PADDING_WORDS(1); u32_le mod_offset; INSERT_PADDING_WORDS(2); u32_le magic; - INSERT_PADDING_WORDS(1); + u32_le version; u32_le nro_size; - INSERT_PADDING_WORDS(1); + u32_le flags; u32_le text_offset; u32_le text_size; u32_le ro_offset; @@ -443,9 +430,10 @@ private: struct NRRHeader { u32_le magic; - INSERT_PADDING_BYTES(0x1C); + INSERT_PADDING_BYTES(12); u64_le title_id_mask; u64_le title_id_pattern; + INSERT_PADDING_BYTES(16); std::array<u8, 0x100> modulus; std::array<u8, 0x100> signature_1; std::array<u8, 0x100> signature_2; diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index c89157a4d..1f462e087 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -18,7 +18,7 @@ public: ILogger() : ServiceFramework("ILogger") { static const FunctionInfo functions[] = { {0x00000000, &ILogger::Initialize, "Initialize"}, - {0x00000001, nullptr, "SetDestination"}, + {0x00000001, &ILogger::SetDestination, "SetDestination"}, }; RegisterHandlers(functions); } @@ -178,6 +178,17 @@ private: } } + // This service function is intended to be used as a way to + // redirect logging output to different destinations, however, + // given we always want to see the logging output, it's sufficient + // to do nothing and return success here. + void SetDestination(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LM, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + std::ostringstream log_stream; }; @@ -198,11 +209,11 @@ public: * 0: ResultCode */ void OpenLogger(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ILogger>(); - - LOG_DEBUG(Service_LM, "called"); } }; diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index e1f17a926..def63dc8a 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -31,12 +31,14 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Finalize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -45,15 +47,16 @@ private: IPC::RequestParser rp{ctx}; min = rp.Pop<u32>(); max = rp.Pop<u32>(); - current = min; - LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); + + current = min; IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Get(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(current); @@ -61,6 +64,7 @@ private: void InitializeWithId(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(id); // Any non zero value @@ -68,6 +72,7 @@ private: void FinalizeWithId(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -77,16 +82,17 @@ private: u32 input_id = rp.Pop<u32>(); min = rp.Pop<u32>(); max = rp.Pop<u32>(); - current = min; - LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", input_id, min, max); + + current = min; IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void GetWithId(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(current); diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 30e542542..5c62d42ba 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -43,11 +43,11 @@ public: private: void CreateAmInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IAm>(); - - LOG_DEBUG(Service_NFC, "called"); } }; @@ -91,11 +91,11 @@ public: private: void CreateUserInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<MFIUser>(); - - LOG_DEBUG(Service_NFC, "called"); } }; @@ -138,19 +138,19 @@ private: }; void InitializeOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0}; rb.Push(RESULT_SUCCESS); - // We don't deal with hardware initialization so we can just stub this. - LOG_DEBUG(Service_NFC, "called"); } void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "IsNfcEnabledOld"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u8>(Settings::values.enable_nfc); - - LOG_DEBUG(Service_NFC, "IsNfcEnabledOld"); } void GetStateOld(Kernel::HLERequestContext& ctx) { @@ -183,11 +183,11 @@ public: private: void CreateUserInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IUser>(); - - LOG_DEBUG(Service_NFC, "called"); } }; @@ -241,11 +241,11 @@ public: private: void CreateSystemInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystem>(); - - LOG_DEBUG(Service_NFC, "called"); } }; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 1d6e7756f..d5df112a0 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -7,7 +7,9 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/lock.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/nfp/nfp.h" @@ -23,8 +25,8 @@ constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) { auto& kernel = Core::System::GetInstance().Kernel(); - nfc_tag_load = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:NFCTagDetected"); + nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IUser:NFCTagDetected"); } Module::Interface::~Interface() = default; @@ -63,10 +65,10 @@ public: RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - deactivate_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent"); - availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "IUser:AvailabilityChangeEvent"); + deactivate_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent"); + availability_change_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent"); } private: @@ -108,30 +110,29 @@ private: static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size"); void Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0}; rb.Push(RESULT_SUCCESS); state = State::Initialized; - - LOG_DEBUG(Service_NFC, "called"); } void GetState(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + IPC::ResponseBuilder rb{ctx, 3, 0}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u32>(static_cast<u32>(state)); - - LOG_DEBUG(Service_NFC, "called"); } void ListDevices(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 array_size = rp.Pop<u32>(); + LOG_DEBUG(Service_NFP, "called, array_size={}", array_size); ctx.WriteBuffer(&device_handle, sizeof(device_handle)); - LOG_DEBUG(Service_NFP, "called, array_size={}", array_size); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); @@ -141,6 +142,7 @@ private: IPC::RequestParser rp{ctx}; const u64 dev_handle = rp.Pop<u64>(); LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(npad_id); @@ -150,6 +152,7 @@ private: IPC::RequestParser rp{ctx}; const u64 dev_handle = rp.Pop<u64>(); LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(nfp_interface.GetNFCEvent()); @@ -163,15 +166,16 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(deactivate_event); + rb.PushCopyObjects(deactivate_event.readable); } void StopDetection(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); + switch (device_state) { case DeviceState::TagFound: case DeviceState::TagNearby: - deactivate_event->Signal(); + deactivate_event.writable->Signal(); device_state = DeviceState::Initialized; break; case DeviceState::SearchingForTag: @@ -185,6 +189,7 @@ private: void GetDeviceState(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); + auto nfc_event = nfp_interface.GetNFCEvent(); if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) { device_state = DeviceState::TagFound; @@ -261,7 +266,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(availability_change_event); + rb.PushCopyObjects(availability_change_event.readable); } void GetRegisterInfo(Kernel::HLERequestContext& ctx) { @@ -316,13 +321,14 @@ private: const u32 npad_id{0}; // Player 1 controller State state{State::NonInitialized}; DeviceState device_state{DeviceState::Initialized}; - Kernel::SharedPtr<Kernel::Event> deactivate_event; - Kernel::SharedPtr<Kernel::Event> availability_change_event; + Kernel::EventPair deactivate_event; + Kernel::EventPair availability_change_event; const Module::Interface& nfp_interface; }; void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IUser>(*this); @@ -335,12 +341,14 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { } std::memcpy(&amiibo, buffer.data(), sizeof(amiibo)); - nfc_tag_load->Signal(); + nfc_tag_load.writable->Signal(); return true; } -const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const { - return nfc_tag_load; + +const Kernel::SharedPtr<Kernel::ReadableEvent>& Module::Interface::GetNFCEvent() const { + return nfc_tag_load.readable; } + const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const { return amiibo; } diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 5c0ae8a54..a1817e991 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -6,7 +6,8 @@ #include <array> #include <vector> -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/service.h" namespace Service::NFP { @@ -33,11 +34,11 @@ public: void CreateUserInterface(Kernel::HLERequestContext& ctx); bool LoadAmiibo(const std::vector<u8>& buffer); - const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const; + const Kernel::SharedPtr<Kernel::ReadableEvent>& GetNFCEvent() const; const AmiiboFile& GetAmiiboBuffer() const; private: - Kernel::SharedPtr<Kernel::Event> nfc_tag_load{}; + Kernel::EventPair nfc_tag_load{}; AmiiboFile amiibo{}; protected: diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 75dcd94a3..60479bb45 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -4,7 +4,9 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/service.h" @@ -56,19 +58,23 @@ public: RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - event1 = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IRequest:Event1"); - event2 = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IRequest:Event2"); + event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IRequest:Event1"); + event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "IRequest:Event2"); } private: void Submit(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void GetRequestState(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); @@ -76,30 +82,34 @@ private: void GetResult(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void GetSystemEventReadableHandles(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 2}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(event1, event2); + rb.PushCopyObjects(event1.readable, event2.readable); } void Cancel(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void SetConnectionConfirmationOption(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - Kernel::SharedPtr<Kernel::Event> event1, event2; + Kernel::EventPair event1, event2; }; class INetworkProfile final : public ServiceFramework<INetworkProfile> { @@ -122,32 +132,36 @@ private: void GetClientId(Kernel::HLERequestContext& ctx) { static constexpr u32 client_id = 1; LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid } void CreateScanRequest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IScanRequest>(); - - LOG_DEBUG(Service_NIFM, "called"); } void CreateRequest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IRequest>(); - - LOG_DEBUG(Service_NIFM, "called"); } void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); + ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "NetworkProfileData is not the correct size"); u128 uuid{}; auto buffer = ctx.ReadBuffer(); @@ -158,23 +172,24 @@ private: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<INetworkProfile>(); rb.PushRaw<u128>(uuid); - - LOG_DEBUG(Service_NIFM, "called"); } void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); } void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); } void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); @@ -235,17 +250,19 @@ public: } void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); } void CreateGeneralService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IGeneralService>(); - LOG_DEBUG(Service_NIFM, "called"); } }; diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 18091c9bb..0dabcd23b 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -6,7 +6,9 @@ #include <ctime> #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -138,57 +140,61 @@ public: RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - finished_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "IEnsureNetworkClockAvailabilityService:FinishEvent"); + finished_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, + "IEnsureNetworkClockAvailabilityService:FinishEvent"); } private: - Kernel::SharedPtr<Kernel::Event> finished_event; + Kernel::EventPair finished_event; void StartTask(Kernel::HLERequestContext& ctx) { // No need to connect to the internet, just finish the task straight away. - finished_event->Signal(); + LOG_DEBUG(Service_NIM, "called"); + finished_event.writable->Signal(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_NIM, "called"); } void GetFinishNotificationEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(finished_event); - LOG_DEBUG(Service_NIM, "called"); + rb.PushCopyObjects(finished_event.readable); } void GetResult(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIM, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_NIM, "called"); } void Cancel(Kernel::HLERequestContext& ctx) { - finished_event->Clear(); + LOG_DEBUG(Service_NIM, "called"); + finished_event.writable->Clear(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_NIM, "called"); } void IsProcessing(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIM, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushRaw<u32>(0); // We instantly process the request - LOG_DEBUG(Service_NIM, "called"); } void GetServerTime(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIM, "called"); + const s64 server_time{std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()) .count()}; IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.PushRaw<s64>(server_time); - LOG_DEBUG(Service_NIM, "called"); } }; @@ -208,23 +214,26 @@ public: private: void OpenEnsureNetworkClockAvailabilityService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(); - LOG_DEBUG(Service_NIM, "called"); } // TODO(ogniK): Do we need these? void SuspendAutonomicTimeCorrection(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_NIM, "(STUBBED) called"); } void ResumeAutonomicTimeCorrection(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_NIM, "(STUBBED) called"); } }; diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 1d2978f24..2663f56b1 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -433,11 +433,11 @@ public: private: template <typename T> void PushInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<T>(); - - LOG_DEBUG(Service_NS, "called"); } }; @@ -526,11 +526,11 @@ public: private: void OpenSystemUpdateControl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemUpdateControl>(); - - LOG_DEBUG(Service_NS, "called"); } }; diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 1066bf505..ad176f89d 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -281,6 +281,7 @@ void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { const u32 shared_font_type{rp.Pop<u32>()}; // Games don't call this so all fonts should be loaded LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -288,8 +289,8 @@ void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(LoadState::Done)); @@ -298,8 +299,8 @@ void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { void PL_U::GetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(impl->GetSharedFontRegion(font_id).size); @@ -308,8 +309,8 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) { void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 font_id{rp.Pop<u32>()}; - LOG_DEBUG(Service_NS, "called, font_id={}", font_id); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset); @@ -317,6 +318,7 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { // Map backing memory for the font data + LOG_DEBUG(Service_NS, "called"); Core::CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared); @@ -328,7 +330,6 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, "PL_U:shared_font_mem"); - LOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(impl->shared_font_mem); @@ -338,6 +339,7 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); + IPC::ResponseBuilder rb{ctx, 4}; std::vector<u32> font_codes; std::vector<u32> font_offsets; 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 c41ef7058..466db7ccd 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -54,6 +54,7 @@ u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& ou IoctlInitalizeEx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size); + return 0; } @@ -191,6 +192,7 @@ u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& ou IoctlBindChannel params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); + channel = params.fd; return 0; } 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 7a88ae029..d57a54ee8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -5,6 +5,8 @@ #include <cstring> #include "common/assert.h" #include "common/logging/log.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" namespace Service::Nvidia::Devices { @@ -33,6 +35,8 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec return ZBCQueryTable(input, output); case IoctlCommand::IocFlushL2: return FlushL2(input, output); + case IoctlCommand::IocGetGpuTime: + return GetGpuTime(input, output); } UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; @@ -99,6 +103,7 @@ u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); + IoctlActiveSlotMask params{}; if (input.size() > 0) { std::memcpy(¶ms, input.data(), input.size()); @@ -111,6 +116,7 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); + IoctlZcullGetCtxSize params{}; if (input.size() > 0) { std::memcpy(¶ms, input.data(), input.size()); @@ -122,6 +128,7 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); + IoctlNvgpuGpuZcullGetInfoArgs params{}; if (input.size() > 0) { @@ -144,6 +151,7 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlZbcSetTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO(ogniK): What does this even actually do? @@ -153,6 +161,7 @@ u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlZbcQueryTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly @@ -162,6 +171,7 @@ u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8> u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IoctlFlushL2 params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly @@ -169,4 +179,14 @@ u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& outp return 0; } +u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) { + LOG_DEBUG(Service_NVDRV, "called"); + + IoctlGetGpuTime params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.gpu_time = CoreTiming::cyclesToNs(CoreTiming::GetTicks()); + std::memcpy(output.data(), ¶ms, output.size()); + return 0; +} + } // namespace Service::Nvidia::Devices 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 3bbf028ad..240435eea 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -156,6 +156,11 @@ private: }; static_assert(sizeof(IoctlFlushL2) == 8, "IoctlFlushL2 is incorrect size"); + struct IoctlGetGpuTime { + u64_le gpu_time; + }; + static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size"); + u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); @@ -164,6 +169,7 @@ private: u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output); u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output); u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 874d5e1c3..3bfce0110 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -8,7 +8,6 @@ #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" #include "core/memory.h" -#include "video_core/command_processor.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" @@ -61,12 +60,14 @@ u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; return 0; } u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); + IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); user_data = params.data; @@ -75,6 +76,7 @@ u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& out u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called"); + IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); params.data = user_data; @@ -86,6 +88,7 @@ u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) std::memcpy(&zcull_params, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va, zcull_params.mode); + std::memcpy(output.data(), &zcull_params, output.size()); return 0; } @@ -95,6 +98,7 @@ u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset, params.size, params.mem); + std::memcpy(output.data(), ¶ms, output.size()); return 0; } @@ -102,6 +106,7 @@ u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) { std::memcpy(&channel_priority, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority); + return 0; } @@ -113,6 +118,7 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou "unk1={:X}, unk2={:X}, unk3={:X}", params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, params.unk3); + params.fence_out.id = 0; params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, output.size()); @@ -124,11 +130,18 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector< std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num, params.flags); + params.obj_id = 0x0; std::memcpy(output.data(), ¶ms, output.size()); return 0; } +static void PushGPUEntries(Tegra::CommandList&& entries) { + auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()}; + dma_pusher.Push(std::move(entries)); + dma_pusher.DispatchCalls(); +} + u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); @@ -142,11 +155,11 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp params.num_entries * sizeof(Tegra::CommandListHeader), "Incorrect input size"); - std::vector<Tegra::CommandListHeader> entries(params.num_entries); + Tegra::CommandList entries(params.num_entries); std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)], params.num_entries * sizeof(Tegra::CommandListHeader)); - Core::System::GetInstance().GPU().ProcessCommandLists(entries); + PushGPUEntries(std::move(entries)); params.fence_out.id = 0; params.fence_out.value = 0; @@ -163,11 +176,11 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, params.num_entries, params.flags); - std::vector<Tegra::CommandListHeader> entries(params.num_entries); + Tegra::CommandList entries(params.num_entries); Memory::ReadBlock(params.address, entries.data(), params.num_entries * sizeof(Tegra::CommandListHeader)); - Core::System::GetInstance().GPU().ProcessCommandLists(entries); + PushGPUEntries(std::move(entries)); params.fence_out.id = 0; params.fence_out.value = 0; @@ -179,6 +192,7 @@ u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& outpu IoctlGetWaitbase params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown); + params.value = 0; // Seems to be hard coded at 0 std::memcpy(output.data(), ¶ms, output.size()); return 0; @@ -188,6 +202,7 @@ u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& IoctlChannelSetTimeout params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlChannelSetTimeout)); LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout); + return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 46dbbc37c..f5e8ea7c3 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -30,6 +30,7 @@ u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& outp IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index c67f934f6..3e0951ab0 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -30,6 +30,7 @@ u32 nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& outp IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index 727b9fee4..d544f0f31 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -30,6 +30,7 @@ u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + nvmap_fd = params.nvmap_fd; return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 43651d8a6..1ec796fc6 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -54,6 +54,7 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); if (!params.size) { + LOG_ERROR(Service_NVDRV, "Size is 0"); return static_cast<u32>(NvErrCodes::InvalidValue); } // Create a new nvmap object and obtain a handle to it. @@ -78,10 +79,12 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); if (!params.handle) { + LOG_ERROR(Service_NVDRV, "Handle is 0"); return static_cast<u32>(NvErrCodes::InvalidValue); } if ((params.align - 1) & params.align) { + LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align); return static_cast<u32>(NvErrCodes::InvalidValue); } @@ -92,10 +95,12 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { auto object = GetObject(params.handle); if (!object) { + LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } if (object->status == Object::Status::Allocated) { + LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::OperationNotPermitted); } @@ -116,11 +121,13 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "called"); if (!params.handle) { + LOG_ERROR(Service_NVDRV, "Handle is zero"); return static_cast<u32>(NvErrCodes::InvalidValue); } auto object = GetObject(params.handle); if (!object) { + LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::OperationNotPermitted); } @@ -139,11 +146,13 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { auto itr = std::find_if(handles.begin(), handles.end(), [&](const auto& entry) { return entry.second->id == params.id; }); if (itr == handles.end()) { + LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } auto& object = itr->second; if (object->status != Object::Status::Allocated) { + LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } @@ -166,10 +175,12 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { auto object = GetObject(params.handle); if (!object) { + LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } if (object->status != Object::Status::Allocated) { + LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::OperationNotPermitted); } @@ -209,9 +220,14 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { auto itr = handles.find(params.handle); if (itr == handles.end()) { + LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } if (!itr->second->refcount) { + LOG_ERROR( + Service_NVDRV, + "There is no references to this object. The object is already freed. handle={:08X}", + params.handle); return static_cast<u32>(NvErrCodes::InvalidValue); } diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index ac3859353..3b9ab4b14 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -6,7 +6,9 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" @@ -55,6 +57,7 @@ void NVDRV::Close(Kernel::HLERequestContext& ctx) { void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); @@ -68,15 +71,15 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(query_event); + rb.PushCopyObjects(query_event.readable); rb.Push<u32>(0); } void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; pid = rp.Pop<u64>(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); @@ -84,6 +87,23 @@ void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) { void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { + // According to SwitchBrew, this has no inputs and no outputs, so effectively does nothing on + // retail hardware. + LOG_DEBUG(Service_NVDRV, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -97,10 +117,10 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) {3, &NVDRV::Initialize, "Initialize"}, {4, &NVDRV::QueryEvent, "QueryEvent"}, {5, nullptr, "MapSharedMem"}, - {6, nullptr, "GetStatus"}, + {6, &NVDRV::GetStatus, "GetStatus"}, {7, nullptr, "ForceSetClientPID"}, {8, &NVDRV::SetClientPID, "SetClientPID"}, - {9, nullptr, "DumpGraphicsMemoryInfo"}, + {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, {10, nullptr, "InitializeDevtools"}, {11, &NVDRV::Ioctl, "Ioctl2"}, {12, nullptr, "Ioctl3"}, @@ -109,7 +129,8 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - query_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "NVDRV::query_event"); + query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::OneShot, + "NVDRV::query_event"); } NVDRV::~NVDRV() = default; diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index d340893c2..fe311b069 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -5,10 +5,13 @@ #pragma once #include <memory> -#include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/service.h" +namespace Kernel { +class WritableEvent; +} + namespace Service::Nvidia { class NVDRV final : public ServiceFramework<NVDRV> { @@ -24,12 +27,14 @@ private: void QueryEvent(Kernel::HLERequestContext& ctx); void SetClientPID(Kernel::HLERequestContext& ctx); void FinishInitialize(Kernel::HLERequestContext& ctx); + void GetStatus(Kernel::HLERequestContext& ctx); + void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); std::shared_ptr<Module> nvdrv; u64 pid{}; - Kernel::SharedPtr<Kernel::Event> query_event; + Kernel::EventPair query_event; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 630ebbfc7..fc07d9bb8 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -7,28 +7,31 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvflinger/buffer_queue.h" namespace Service::NVFlinger { BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { auto& kernel = Core::System::GetInstance().Kernel(); - buffer_wait_event = - Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "BufferQueue NativeHandle"); + buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "BufferQueue NativeHandle"); } BufferQueue::~BufferQueue() = default; void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { + LOG_WARNING(Service, "Adding graphics buffer {}", slot); + Buffer buffer{}; buffer.slot = slot; buffer.igbp_buffer = igbp_buffer; buffer.status = Buffer::Status::Free; - LOG_WARNING(Service, "Adding graphics buffer {}", slot); - queue.emplace_back(buffer); - buffer_wait_event->Signal(); + buffer_wait_event.writable->Signal(); } std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) { @@ -87,11 +90,12 @@ void BufferQueue::ReleaseBuffer(u32 slot) { ASSERT(itr->status == Buffer::Status::Acquired); itr->status = Buffer::Status::Free; - buffer_wait_event->Signal(); + buffer_wait_event.writable->Signal(); } u32 BufferQueue::Query(QueryType type) { LOG_WARNING(Service, "(STUBBED) called type={}", static_cast<u32>(type)); + switch (type) { case QueryType::NativeWindowFormat: // TODO(Subv): Use an enum for this @@ -103,4 +107,12 @@ u32 BufferQueue::Query(QueryType type) { return 0; } +Kernel::SharedPtr<Kernel::WritableEvent> BufferQueue::GetWritableBufferWaitEvent() const { + return buffer_wait_event.writable; +} + +Kernel::SharedPtr<Kernel::ReadableEvent> BufferQueue::GetBufferWaitEvent() const { + return buffer_wait_event.readable; +} + } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 8cff5eb71..b171f256c 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -10,7 +10,8 @@ #include "common/common_funcs.h" #include "common/math_util.h" #include "common/swap.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/object.h" +#include "core/hle/kernel/writable_event.h" namespace CoreTiming { struct EventType; @@ -86,16 +87,16 @@ public: return id; } - Kernel::SharedPtr<Kernel::Event> GetBufferWaitEvent() const { - return buffer_wait_event; - } + Kernel::SharedPtr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const; + + Kernel::SharedPtr<Kernel::ReadableEvent> GetBufferWaitEvent() const; private: u32 id; u64 layer_id; std::vector<Buffer> queue; - Kernel::SharedPtr<Kernel::Event> buffer_wait_event; + Kernel::EventPair buffer_wait_event; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 214e6d1b3..05af2d593 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -13,6 +13,9 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/core_timing_util.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" @@ -83,9 +86,8 @@ u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) { return layer.buffer_queue->GetId(); } -Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) { - const auto& display = GetDisplay(display_id); - return display.vsync_event; +Kernel::SharedPtr<Kernel::ReadableEvent> NVFlinger::GetVsyncEvent(u64 display_id) { + return GetDisplay(display_id).vsync_event.readable; } std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const { @@ -117,7 +119,7 @@ Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) { void NVFlinger::Compose() { for (auto& display : displays) { // Trigger vsync for this display at the end of drawing - SCOPE_EXIT({ display.vsync_event->Signal(); }); + SCOPE_EXIT({ display.vsync_event.writable->Signal(); }); // Don't do anything for displays without layers. if (display.layers.empty()) @@ -164,7 +166,8 @@ Layer::~Layer() = default; Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { auto& kernel = Core::System::GetInstance().Kernel(); - vsync_event = Kernel::Event::Create(kernel, Kernel::ResetType::Pulse, "Display VSync Event"); + vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Pulse, + fmt::format("Display VSync Event {}", id)); } Display::~Display() = default; diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 3dc69e69b..9abba555b 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -10,12 +10,17 @@ #include <vector> #include "common/common_types.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/object.h" namespace CoreTiming { struct EventType; } +namespace Kernel { +class ReadableEvent; +class WritableEvent; +} // namespace Kernel + namespace Service::Nvidia { class Module; } @@ -40,7 +45,7 @@ struct Display { std::string name; std::vector<Layer> layers; - Kernel::SharedPtr<Kernel::Event> vsync_event; + Kernel::EventPair vsync_event; }; class NVFlinger final { @@ -61,7 +66,7 @@ public: u32 GetBufferQueueId(u64 display_id, u64 layer_id); /// Gets the vsync event for the specified display. - Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id); + Kernel::SharedPtr<Kernel::ReadableEvent> GetVsyncEvent(u64 display_id); /// Obtains a buffer queue identified by the id. std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const; diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index 4fd185f69..6081f41e1 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -114,29 +114,33 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_SUCCESS); } void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } }; void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IParentalControlService>(); - LOG_DEBUG(Service_PCTL, "called"); } void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCTL, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IParentalControlService>(); - LOG_DEBUG(Service_PCTL, "called"); } Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index 6ec35ca60..53e7da9c3 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -20,11 +20,11 @@ public: private: void GetBootMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PM, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(SystemBootMode::Normal)); // Normal boot mode - - LOG_DEBUG(Service_PM, "called"); } }; diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp index bbad870a2..0ba0a4076 100644 --- a/src/core/hle/service/psc/psc.cpp +++ b/src/core/hle/service/psc/psc.cpp @@ -61,11 +61,11 @@ public: private: void GetPmModule(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PSC, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IPmModule>(); - - LOG_DEBUG(Service_PSC, "called"); } }; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 1ec340466..d41df3732 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -70,10 +70,6 @@ #include "core/hle/service/vi/vi.h" #include "core/hle/service/wlan/wlan.h" -using Kernel::ClientPort; -using Kernel::ServerPort; -using Kernel::SharedPtr; - namespace Service { /** @@ -110,10 +106,8 @@ void ServiceFrameworkBase::InstallAsNamedPort() { ASSERT(port == nullptr); auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<ServerPort> server_port; - SharedPtr<ClientPort> client_port; - std::tie(server_port, client_port) = - ServerPort::CreatePortPair(kernel, max_sessions, service_name); + auto [server_port, client_port] = + Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); server_port->SetHleHandler(shared_from_this()); kernel.AddNamedPort(service_name, std::move(client_port)); } @@ -122,11 +116,9 @@ Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { ASSERT(port == nullptr); auto& kernel = Core::System::GetInstance().Kernel(); - Kernel::SharedPtr<Kernel::ServerPort> server_port; - Kernel::SharedPtr<Kernel::ClientPort> client_port; - std::tie(server_port, client_port) = + auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); - port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap(); + port = MakeResult(std::move(server_port)).Unwrap(); port->SetHleHandler(shared_from_this()); return client_port; } @@ -152,8 +144,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext } buf.push_back('}'); - LOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf)); - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); } void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 9e5af7839..1afc43f75 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -35,6 +35,8 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{ constexpr std::size_t pre4_0_0_max_entries = 0xF; constexpr std::size_t post4_0_0_max_entries = 0x40; +constexpr ResultCode ERR_INVALID_LANGUAGE{ErrorModule::Settings, 625}; + LanguageCode GetLanguageCodeFromIndex(std::size_t index) { return available_language_codes.at(index); } @@ -49,38 +51,54 @@ static std::array<LanguageCode, size> MakeLanguageCodeSubset() { static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t max_size) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - if (available_language_codes.size() > max_size) + if (available_language_codes.size() > max_size) { rb.Push(static_cast<u32>(max_size)); - else + } else { rb.Push(static_cast<u32>(available_language_codes.size())); + } } void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { - if (available_language_codes.size() > pre4_0_0_max_entries) + LOG_DEBUG(Service_SET, "called"); + + if (available_language_codes.size() > pre4_0_0_max_entries) { ctx.WriteBuffer(MakeLanguageCodeSubset<pre4_0_0_max_entries>()); - else + } else { ctx.WriteBuffer(available_language_codes); - + } PushResponseLanguageCode(ctx, pre4_0_0_max_entries); +} - LOG_DEBUG(Service_SET, "called"); +void SET::MakeLanguageCode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto index = rp.Pop<u32>(); + + if (index >= available_language_codes.size()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_LANGUAGE); + return; + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(available_language_codes[index]); } void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) { - if (available_language_codes.size() > post4_0_0_max_entries) + LOG_DEBUG(Service_SET, "called"); + + if (available_language_codes.size() > post4_0_0_max_entries) { ctx.WriteBuffer(MakeLanguageCodeSubset<post4_0_0_max_entries>()); - else + } else { ctx.WriteBuffer(available_language_codes); - + } PushResponseLanguageCode(ctx, post4_0_0_max_entries); - - LOG_DEBUG(Service_SET, "called"); } void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) { - PushResponseLanguageCode(ctx, pre4_0_0_max_entries); - LOG_DEBUG(Service_SET, "called"); + + PushResponseLanguageCode(ctx, pre4_0_0_max_entries); } void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) { @@ -90,18 +108,18 @@ void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) { } void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u64>(available_language_codes[Settings::values.language_index])); - - LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index); + rb.PushEnum(available_language_codes[Settings::values.language_index]); } SET::SET() : ServiceFramework("set") { static const FunctionInfo functions[] = { {0, &SET::GetLanguageCode, "GetLanguageCode"}, {1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"}, - {2, nullptr, "MakeLanguageCode"}, + {2, &SET::MakeLanguageCode, "MakeLanguageCode"}, {3, &SET::GetAvailableLanguageCodeCount, "GetAvailableLanguageCodeCount"}, {4, nullptr, "GetRegionCode"}, {5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"}, diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 266f13e46..31f9cb296 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -38,6 +38,7 @@ public: private: void GetLanguageCode(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx); + void MakeLanguageCode(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index 41efca31c..c9b4da5b0 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -10,22 +10,22 @@ namespace Service::Set { void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.PushEnum(color_set); - - LOG_DEBUG(Service_SET, "called"); } void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called"); + IPC::RequestParser rp{ctx}; color_set = rp.PopEnum<ColorSet>(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - - LOG_DEBUG(Service_SET, "called"); } SET_SYS::SET_SYS() : ServiceFramework("set:sys") { diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 98f6e4111..74da4d5e6 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -14,25 +14,26 @@ namespace Service::SM { void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain"); + LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetObjectId()); ctx.Session()->ConvertToDomain(); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); // Converted sessions start with 1 request handler - - LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetObjectId()); } void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) { // TODO(bunnei): This is just creating a new handle to the same Session. I assume this is wrong // and that we probably want to actually make an entirely new Session, but we still need to // verify this on hardware. + LOG_DEBUG(Service, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(RESULT_SUCCESS); Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client}; rb.PushMoveObjects(session); - LOG_DEBUG(Service, "called, session={}", session->GetObjectId()); + LOG_DEBUG(Service, "session={}", session->GetObjectId()); } void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) { @@ -42,11 +43,11 @@ void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) { } void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u16>(0x500); - - LOG_WARNING(Service, "(STUBBED) called"); } Controller::Controller() : ServiceFramework("IpcController") { diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 464e79d01..d73530086 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -54,13 +54,22 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService return ERR_ALREADY_REGISTERED; auto& kernel = Core::System::GetInstance().Kernel(); - Kernel::SharedPtr<Kernel::ServerPort> server_port; - Kernel::SharedPtr<Kernel::ClientPort> client_port; - std::tie(server_port, client_port) = + auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name); registered_services.emplace(std::move(name), std::move(client_port)); - return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)); + return MakeResult(std::move(server_port)); +} + +ResultCode ServiceManager::UnregisterService(const std::string& name) { + CASCADE_CODE(ValidateServiceName(name)); + + const auto iter = registered_services.find(name); + if (iter == registered_services.end()) + return ERR_SERVICE_NOT_REGISTERED; + + registered_services.erase(iter); + return RESULT_SUCCESS; } ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort( @@ -72,7 +81,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort( return ERR_SERVICE_NOT_REGISTERED; } - return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second); + return MakeResult(it->second); } ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService( @@ -92,9 +101,10 @@ SM::~SM() = default; * 0: ResultCode */ void SM::Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SM, "called"); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_SM, "called"); } void SM::GetService(Kernel::HLERequestContext& ctx) { @@ -127,13 +137,53 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { } } +void SM::RegisterService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto name_buf = rp.PopRaw<std::array<char, 8>>(); + const auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + + const std::string name(name_buf.begin(), end); + + const auto unk_bool = static_cast<bool>(rp.PopRaw<u32>()); + const auto session_count = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_SM, "called with unk_bool={}", unk_bool); + + auto handle = service_manager->RegisterService(name, session_count); + if (handle.Failed()) { + LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}", + handle.Code().raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(handle.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; + rb.Push(handle.Code()); + rb.PushMoveObjects(std::move(handle).Unwrap()); +} + +void SM::UnregisterService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto name_buf = rp.PopRaw<std::array<char, 8>>(); + const auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + + const std::string name(name_buf.begin(), end); + LOG_DEBUG(Service_SM, "called with name={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(service_manager->UnregisterService(name)); +} + SM::SM(std::shared_ptr<ServiceManager> service_manager) : ServiceFramework("sm:", 4), service_manager(std::move(service_manager)) { static const FunctionInfo functions[] = { {0x00000000, &SM::Initialize, "Initialize"}, {0x00000001, &SM::GetService, "GetService"}, - {0x00000002, nullptr, "RegisterService"}, - {0x00000003, nullptr, "UnregisterService"}, + {0x00000002, &SM::RegisterService, "RegisterService"}, + {0x00000003, &SM::UnregisterService, "UnregisterService"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 4f8145dda..bef25433e 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -35,6 +35,8 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx); void GetService(Kernel::HLERequestContext& ctx); + void RegisterService(Kernel::HLERequestContext& ctx); + void UnregisterService(Kernel::HLERequestContext& ctx); std::shared_ptr<ServiceManager> service_manager; }; @@ -48,6 +50,7 @@ public: ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name, unsigned int max_sessions); + ResultCode UnregisterService(const std::string& name); ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index b2de2a818..8db0c2f13 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -24,6 +24,8 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) Module::Interface::~Interface() = default; void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SPL, "called"); + IPC::RequestParser rp{ctx}; std::size_t size = ctx.GetWriteBufferSize(); @@ -36,7 +38,6 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_SPL, "called"); } void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index bc4f7a437..af40a1815 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -69,6 +69,7 @@ public: private: void SetOption(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_SSL, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; IPC::ResponseBuilder rb{ctx, 2}; @@ -114,6 +115,7 @@ private: void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_SSL, "called"); + IPC::RequestParser rp{ctx}; ssl_version = rp.Pop<u32>(); diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index e561a0c52..60b201d06 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -72,6 +72,7 @@ private: std::chrono::system_clock::now().time_since_epoch()) .count()}; LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(time_since_epoch); @@ -79,6 +80,7 @@ private: void GetSystemClockContext(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Time, "(STUBBED) called"); + SystemClockContext system_clock_ontext{}; IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; rb.Push(RESULT_SUCCESS); @@ -98,6 +100,7 @@ public: private: void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); + SteadyClockTimePoint steady_clock_time_point{ CoreTiming::cyclesToMs(CoreTiming::GetTicks()) / 1000}; IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; @@ -130,6 +133,7 @@ private: void GetDeviceLocationName(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(location_name); @@ -137,6 +141,7 @@ private: void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Time, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(0); @@ -154,7 +159,6 @@ private: void ToCalendarTime(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); TimeZoneRule time_zone_rule{}; @@ -175,7 +179,6 @@ private: void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; @@ -192,6 +195,7 @@ private: void ToPosixTime(Kernel::HLERequestContext& ctx) { // TODO(ogniK): Figure out how to handle multiple times LOG_WARNING(Service_Time, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; auto calendar_time = rp.PopRaw<CalendarTime>(); auto posix_time = CalendarToPosix(calendar_time, {}); @@ -204,6 +208,7 @@ private: void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Time, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; auto calendar_time = rp.PopRaw<CalendarTime>(); auto posix_time = CalendarToPosix(calendar_time, {}); @@ -216,38 +221,43 @@ private: }; void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISteadyClock>(); - LOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ITimeZoneService>(); - LOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<ISystemClock>(); - LOG_DEBUG(Service_Time, "called"); } void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { @@ -265,6 +275,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { const std::time_t time(time_since_epoch); const std::tm* tm = std::localtime(&time); if (tm == nullptr) { + LOG_ERROR(Service_Time, "tm is a nullptr"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultCode(-1)); // TODO(ogniK): Find appropriate error code return; diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp index f0a831d45..58a9845fc 100644 --- a/src/core/hle/service/usb/usb.cpp +++ b/src/core/hle/service/usb/usb.cpp @@ -73,7 +73,7 @@ public: {3, nullptr, "Populate"}, {4, nullptr, "PostBufferAsync"}, {5, nullptr, "GetXferReport"}, - {6, nullptr, "Unknown2"}, + {6, nullptr, "PostBufferMultiAsync"}, {7, nullptr, "Unknown3"}, {8, nullptr, "Unknown4"}, }; @@ -159,11 +159,11 @@ public: private: void GetPdSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_USB, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<IPdSession>(); - - LOG_DEBUG(Service_USB, "called"); } }; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index d25fdb1fe..311b0c765 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -18,7 +18,8 @@ #include "common/swap.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -504,13 +505,17 @@ private: u32 id = rp.Pop<u32>(); auto transaction = static_cast<TransactionId>(rp.Pop<u32>()); u32 flags = rp.Pop<u32>(); - auto buffer_queue = nv_flinger->GetBufferQueue(id); - LOG_DEBUG(Service_VI, "called, transaction={:X}", static_cast<u32>(transaction)); + auto buffer_queue = nv_flinger->GetBufferQueue(id); + if (transaction == TransactionId::Connect) { IGBPConnectRequestParcel request{ctx.ReadBuffer()}; - IGBPConnectResponseParcel response{1280, 720}; + IGBPConnectResponseParcel response{ + static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) * + Settings::values.resolution_factor), + static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * + Settings::values.resolution_factor)}; ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::SetPreallocatedBuffer) { IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()}; @@ -538,12 +543,14 @@ private: // Repeat TransactParcel DequeueBuffer when a buffer is available auto buffer_queue = nv_flinger->GetBufferQueue(id); std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); + ASSERT_MSG(slot != std::nullopt, "Could not dequeue buffer."); + IGBPDequeueBufferResponseParcel response{*slot}; ctx.WriteBuffer(response.Serialize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); }, - buffer_queue->GetBufferWaitEvent()); + buffer_queue->GetWritableBufferWaitEvent()); } } else if (transaction == TransactionId::RequestBuffer) { IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; @@ -589,9 +596,9 @@ private: u32 id = rp.Pop<u32>(); s32 addval = rp.PopRaw<s32>(); u32 type = rp.Pop<u32>(); - LOG_WARNING(Service_VI, "(STUBBED) called id={}, addval={:08X}, type={:08X}", id, addval, type); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -600,12 +607,11 @@ private: IPC::RequestParser rp{ctx}; u32 id = rp.Pop<u32>(); u32 unknown = rp.Pop<u32>(); + LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); auto buffer_queue = nv_flinger->GetBufferQueue(id); // TODO(Subv): Find out what this actually is. - - LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(buffer_queue->GetBufferWaitEvent()); @@ -669,6 +675,7 @@ public: private: void SetLayerZ(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); u64 z_value = rp.Pop<u64>(); @@ -681,28 +688,33 @@ private: IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); bool visibility = rp.Pop<bool>(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:08X}, visibility={}", layer_id, visibility); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void GetDisplayMode(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } - rb.PushRaw<float>(60.0f); + rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. rb.Push<u32>(0); - - LOG_DEBUG(Service_VI, "called"); } }; @@ -785,6 +797,7 @@ public: private: void CloseDisplay(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u64 display = rp.Pop<u64>(); @@ -794,6 +807,7 @@ private: void CreateManagedLayer(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u32 unknown = rp.Pop<u32>(); rp.Skip(1, false); @@ -809,6 +823,7 @@ private: void AddToLayerStack(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u32 stack = rp.Pop<u32>(); u64 layer_id = rp.Pop<u64>(); @@ -821,10 +836,11 @@ private: IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); bool visibility = rp.Pop<bool>(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:X}, visibility={}", layer_id, visibility); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; @@ -870,6 +886,7 @@ private: void OpenDisplay(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); @@ -885,6 +902,7 @@ private: void CloseDisplay(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u64 display_id = rp.Pop<u64>(); @@ -894,6 +912,7 @@ private: void GetDisplayResolution(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u64 display_id = rp.Pop<u64>(); @@ -901,16 +920,21 @@ private: rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u64>(DisplayResolution::DockedWidth)); - rb.Push(static_cast<u64>(DisplayResolution::DockedHeight)); + rb.Push(static_cast<u64>(DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u64>(DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } } void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u32 scaling_mode = rp.Pop<u32>(); u64 unknown = rp.Pop<u64>(); @@ -920,17 +944,21 @@ private: } void ListDisplays(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; DisplayInfo display_info; + display_info.width *= static_cast<u64>(Settings::values.resolution_factor); + display_info.height *= static_cast<u64>(Settings::values.resolution_factor); ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(1); - LOG_WARNING(Service_VI, "(STUBBED) called"); } void OpenLayer(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_VI, "called"); + IPC::RequestParser rp{ctx}; auto name_buf = rp.PopRaw<std::array<u8, 0x40>>(); auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); @@ -981,6 +1009,7 @@ private: void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; u64 display_id = rp.Pop<u64>(); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 8518dddcb..ac04d72d7 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -7,7 +7,6 @@ #include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -146,7 +145,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) const VAddr load_addr = next_load_addr; const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; const auto tentative_next_load_addr = - AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm); + AppLoader_NSO::LoadModule(process, *module_file, load_addr, should_pass_arguments, pm); if (!tentative_next_load_addr) { return ResultStatus::ErrorLoadingNSO; } diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index d109ed2b5..1615cb5a8 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -33,7 +33,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 6af76441c..a2d33021c 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -22,7 +22,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7686634bf..0838e303b 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -12,6 +12,7 @@ #include <vector> #include "common/common_types.h" +#include "core/file_sys/control_metadata.h" #include "core/file_sys/vfs.h" namespace Kernel { @@ -131,7 +132,7 @@ public: * Returns the type of this file * @return FileType corresponding to the loaded file */ - virtual FileType GetFileType() = 0; + virtual FileType GetFileType() const = 0; /** * Load the application and return the created Process instance @@ -243,6 +244,15 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the developer of the application + * @param developer Reference to store the application developer into + * @return ResultStatus result of function + */ + virtual ResultStatus ReadDeveloper(std::string& developer) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileSys::VirtualFile file; bool is_loaded = false; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 42f4a777b..a093e3d36 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -37,7 +37,7 @@ FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) { return IdentifyTypeImpl(nax); } -FileType AppLoader_NAX::GetFileType() { +FileType AppLoader_NAX::GetFileType() const { return IdentifyTypeImpl(*nax); } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index b4d93bd01..0a97511b8 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -31,7 +31,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override; + FileType GetFileType() const override; ResultStatus Load(Kernel::Process& process) override; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 95d9b73a1..cbbe701d2 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -29,7 +29,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index fbbd6b0de..4fad0c0dd 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -10,7 +10,6 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" -#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_offset.h" @@ -129,9 +128,8 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name, - VAddr load_base) { - +static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data, + const std::string& name, VAddr load_base) { if (data.size() < sizeof(NroHeader)) { return {}; } @@ -189,7 +187,7 @@ static constexpr u32 PageAlignSize(u32 size) { // Load codeset for current process codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); + process.LoadModule(std::move(codeset), load_base); // Register module with GDBStub GDBStub::RegisterModule(name, load_base, load_base); @@ -197,8 +195,9 @@ static constexpr u32 PageAlignSize(u32 size) { return true; } -bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) { - return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base); +bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file, + VAddr load_base) { + return LoadNroImpl(process, file.ReadAllBytes(), file.GetName(), load_base); } ResultStatus AppLoader_NRO::Load(Kernel::Process& process) { @@ -209,7 +208,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) { // Load NRO const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - if (!LoadNro(*file, base_address)) { + if (!LoadNro(process, *file, base_address)) { return ResultStatus::ErrorLoadingNRO; } diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 3e6959302..013d629c0 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -14,6 +14,10 @@ namespace FileSys { class NACP; } +namespace Kernel { +class Process; +} + namespace Loader { /// Loads an NRO file @@ -29,7 +33,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } @@ -41,10 +45,8 @@ public: ResultStatus ReadTitle(std::string& title) override; bool IsRomFSUpdatable() const override; - static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base); - private: - bool LoadNro(const FileSys::VfsFile& file, VAddr load_base); + bool LoadNro(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base); std::vector<u8> icon_data; std::unique_ptr<FileSys::NACP> nacp; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index aaf006309..6ded0b707 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -9,7 +9,6 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" -#include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" @@ -93,7 +92,8 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base, +std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, + const FileSys::VfsFile& file, VAddr load_base, bool should_pass_arguments, std::optional<FileSys::PatchManager> pm) { if (file.GetSize() < sizeof(NsoHeader)) @@ -166,7 +166,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAd // Load codeset for current process codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); + process.LoadModule(std::move(codeset), load_base); // Register module with GDBStub GDBStub::RegisterModule(file.GetName(), load_base, load_base); @@ -181,7 +181,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) { // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - if (!LoadModule(*file, base_address, true)) { + if (!LoadModule(process, *file, base_address, true)) { return ResultStatus::ErrorLoadingNSO; } LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 433306139..135b6ea5a 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -10,6 +10,10 @@ #include "core/loader/linker.h" #include "core/loader/loader.h" +namespace Kernel { +class Process; +} + namespace Loader { constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; @@ -33,12 +37,12 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } - static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base, - bool should_pass_arguments, + static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, + VAddr load_base, bool should_pass_arguments, std::optional<FileSys::PatchManager> pm = {}); ResultStatus Load(Kernel::Process& process) override; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 080d89904..b4ab88ae8 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) { title = nacp_file->GetApplicationName(); return ResultStatus::Success; } + +ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) { + if (nacp_file == nullptr) + return ResultStatus::ErrorNoControl; + developer = nacp_file->GetDeveloperName(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index db91cd01e..2b1e0719b 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -31,7 +31,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr<FileSys::NSP> nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 461607c95..bd5a83b49 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) { title = nacp_file->GetApplicationName(); return ResultStatus::Success; } + +ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) { + if (nacp_file == nullptr) + return ResultStatus::ErrorNoControl; + developer = nacp_file->GetDeveloperName(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 46f8dfc9e..15d1b1a23 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -31,7 +31,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { + FileType GetFileType() const override { return IdentifyType(file); } @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr<FileSys::XCI> xci; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 70abd856a..643afdee8 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -53,6 +53,14 @@ void PageTable::Resize(std::size_t address_space_width_in_bits) { pointers.resize(num_page_table_entries); attributes.resize(num_page_table_entries); + + // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the + // vector size is subsequently decreased (via resize), the vector might not automatically + // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for + // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use. + + pointers.shrink_to_fit(); + attributes.shrink_to_fit(); } static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) { @@ -117,14 +125,13 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin * using a VMA from the current process */ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { - u8* direct_pointer = nullptr; + const auto& vm_manager = process.VMManager(); - auto& vm_manager = process.VMManager(); + const auto it = vm_manager.FindVMA(vaddr); + DEBUG_ASSERT(vm_manager.IsValidHandle(it)); - auto it = vm_manager.FindVMA(vaddr); - ASSERT(it != vm_manager.vma_map.end()); - - auto& vma = it->second; + u8* direct_pointer = nullptr; + const auto& vma = it->second; switch (vma.type) { case Kernel::VMAType::AllocatedMemoryBlock: direct_pointer = vma.backing_block->data() + vma.offset; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 0da159559..26fcd3405 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -10,6 +10,56 @@ namespace Settings { +namespace NativeButton { +const std::array<const char*, NumButtons> mapping = {{ + "button_a", + "button_b", + "button_x", + "button_y", + "button_lstick", + "button_rstick", + "button_l", + "button_r", + "button_zl", + "button_zr", + "button_plus", + "button_minus", + "button_dleft", + "button_dup", + "button_dright", + "button_ddown", + "button_lstick_left", + "button_lstick_up", + "button_lstick_right", + "button_lstick_down", + "button_rstick_left", + "button_rstick_up", + "button_rstick_right", + "button_rstick_down", + "button_sl", + "button_sr", + "button_home", + "button_screenshot", +}}; +} + +namespace NativeAnalog { +const std::array<const char*, NumAnalogs> mapping = {{ + "lstick", + "rstick", +}}; +} + +namespace NativeMouseButton { +const std::array<const char*, NumMouseButtons> mapping = {{ + "left", + "right", + "middle", + "forward", + "back", +}}; +} + Values values = {}; void Apply() { diff --git a/src/core/settings.h b/src/core/settings.h index e424479f2..de01b05c0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,8 +6,10 @@ #include <array> #include <atomic> +#include <map> #include <optional> #include <string> +#include <vector> #include "common/common_types.h" namespace Settings { @@ -60,36 +62,7 @@ constexpr int BUTTON_NS_END = NumButtons; constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; -static const std::array<const char*, NumButtons> mapping = {{ - "button_a", - "button_b", - "button_x", - "button_y", - "button_lstick", - "button_rstick", - "button_l", - "button_r", - "button_zl", - "button_zr", - "button_plus", - "button_minus", - "button_dleft", - "button_dup", - "button_dright", - "button_ddown", - "button_lstick_left", - "button_lstick_up", - "button_lstick_right", - "button_lstick_down", - "button_rstick_left", - "button_rstick_up", - "button_rstick_right", - "button_rstick_down", - "button_sl", - "button_sr", - "button_home", - "button_screenshot", -}}; +extern const std::array<const char*, NumButtons> mapping; } // namespace NativeButton @@ -105,12 +78,273 @@ constexpr int STICK_HID_BEGIN = LStick; constexpr int STICK_HID_END = NumAnalogs; constexpr int NUM_STICKS_HID = NumAnalogs; -static const std::array<const char*, NumAnalogs> mapping = {{ - "lstick", - "rstick", -}}; +extern const std::array<const char*, NumAnalogs> mapping; } // namespace NativeAnalog +namespace NativeMouseButton { +enum Values { + Left, + Right, + Middle, + Forward, + Back, + + NumMouseButtons, +}; + +constexpr int MOUSE_HID_BEGIN = Left; +constexpr int MOUSE_HID_END = NumMouseButtons; +constexpr int NUM_MOUSE_HID = NumMouseButtons; + +extern const std::array<const char*, NumMouseButtons> mapping; +} // namespace NativeMouseButton + +namespace NativeKeyboard { +enum Keys { + None, + Error, + + A = 4, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + N0, + Enter, + Escape, + Backspace, + Tab, + Space, + Minus, + Equal, + LeftBrace, + RightBrace, + Backslash, + Tilde, + Semicolon, + Apostrophe, + Grave, + Comma, + Dot, + Slash, + CapsLockKey, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + SystemRequest, + ScrollLockKey, + Pause, + Insert, + Home, + PageUp, + Delete, + End, + PageDown, + Right, + Left, + Down, + Up, + + NumLockKey, + KPSlash, + KPAsterisk, + KPMinus, + KPPlus, + KPEnter, + KP1, + KP2, + KP3, + KP4, + KP5, + KP6, + KP7, + KP8, + KP9, + KP0, + KPDot, + + Key102, + Compose, + Power, + KPEqual, + + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + Open, + Help, + Properties, + Front, + Stop, + Repeat, + Undo, + Cut, + Copy, + Paste, + Find, + Mute, + VolumeUp, + VolumeDown, + CapsLockActive, + NumLockActive, + ScrollLockActive, + KPComma, + + KPLeftParenthesis, + KPRightParenthesis, + + LeftControlKey = 0xE0, + LeftShiftKey, + LeftAltKey, + LeftMetaKey, + RightControlKey, + RightShiftKey, + RightAltKey, + RightMetaKey, + + MediaPlayPause, + MediaStopCD, + MediaPrevious, + MediaNext, + MediaEject, + MediaVolumeUp, + MediaVolumeDown, + MediaMute, + MediaWebsite, + MediaBack, + MediaForward, + MediaStop, + MediaFind, + MediaScrollUp, + MediaScrollDown, + MediaEdit, + MediaSleep, + MediaCoffee, + MediaRefresh, + MediaCalculator, + + NumKeyboardKeys, +}; + +static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); + +enum Modifiers { + LeftControl, + LeftShift, + LeftAlt, + LeftMeta, + RightControl, + RightShift, + RightAlt, + RightMeta, + CapsLock, + ScrollLock, + NumLock, + + NumKeyboardMods, +}; + +constexpr int KEYBOARD_KEYS_HID_BEGIN = None; +constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; +constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; + +constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; +constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; +constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; + +} // namespace NativeKeyboard + +using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; +using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; +using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; +using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; +using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; + +enum class ControllerType { + ProController, + DualJoycon, + RightJoycon, + LeftJoycon, +}; + +struct PlayerInput { + bool connected; + ControllerType type; + ButtonsRaw buttons; + AnalogsRaw analogs; + + u32 body_color_right; + u32 button_color_right; + u32 body_color_left; + u32 button_color_left; +}; + +struct TouchscreenInput { + bool enabled; + std::string device; + + u32 finger; + u32 diameter_x; + u32 diameter_y; + u32 rotation_angle; +}; + struct Values { // System bool use_docked_mode; @@ -120,10 +354,22 @@ struct Values { s32 language_index; // Controls - std::array<std::string, NativeButton::NumButtons> buttons; - std::array<std::string, NativeAnalog::NumAnalogs> analogs; + std::array<PlayerInput, 10> players; + + bool mouse_enabled; + std::string mouse_device; + MouseButtonsRaw mouse_buttons; + + bool keyboard_enabled; + KeyboardKeysRaw keyboard_keys; + KeyboardModsRaw keyboard_mods; + + bool debug_pad_enabled; + ButtonsRaw debug_pad_buttons; + AnalogsRaw debug_pad_analogs; + std::string motion_device; - std::string touch_device; + TouchscreenInput touchscreen; std::atomic_bool is_device_reload_pending{true}; // Core @@ -159,6 +405,7 @@ struct Values { bool use_gdbstub; u16 gdbstub_port; std::string program_args; + bool dump_exefs; bool dump_nso; // WebService @@ -166,6 +413,9 @@ struct Values { std::string web_api_url; std::string yuzu_username; std::string yuzu_token; + + // Add-Ons + std::map<u64, std::vector<std::string>> disabled_addons; } extern values; void Apply(); diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index a3b08c740..09ed74d78 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -103,13 +103,8 @@ bool VerifyLogin(const std::string& username, const std::string& token) { TelemetrySession::TelemetrySession() { #ifdef ENABLE_WEB_SERVICE - if (Settings::values.enable_telemetry) { - backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url, - Settings::values.yuzu_username, - Settings::values.yuzu_token); - } else { - backend = std::make_unique<Telemetry::NullVisitor>(); - } + backend = std::make_unique<WebService::TelemetryJson>( + Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token); #else backend = std::make_unique<Telemetry::NullVisitor>(); #endif @@ -180,7 +175,8 @@ TelemetrySession::~TelemetrySession() { // This is just a placeholder to wrap up the session once the core completes and this is // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service. field_collection.Accept(*backend); - backend->Complete(); + if (Settings::values.enable_telemetry) + backend->Complete(); backend = nullptr; } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index a780215c1..0406fbcd9 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(video_core STATIC - command_processor.cpp - command_processor.h + dma_pusher.cpp + dma_pusher.h debug_utils/debug_utils.cpp debug_utils/debug_utils.h engines/fermi_2d.cpp @@ -21,6 +21,8 @@ add_library(video_core STATIC macro_interpreter.h memory_manager.cpp memory_manager.h + morton.cpp + morton.h rasterizer_cache.cpp rasterizer_cache.h rasterizer_interface.h @@ -62,7 +64,6 @@ add_library(video_core STATIC textures/decoders.cpp textures/decoders.h textures/texture.h - utils.h video_core.cpp video_core.h ) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp deleted file mode 100644 index 28e8c13aa..000000000 --- a/src/video_core/command_processor.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <cstddef> -#include <memory> -#include <utility> -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/microprofile.h" -#include "common/vector_math.h" -#include "core/memory.h" -#include "core/tracer/recorder.h" -#include "video_core/command_processor.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/kepler_memory.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/engines/maxwell_compute.h" -#include "video_core/engines/maxwell_dma.h" -#include "video_core/gpu.h" -#include "video_core/renderer_base.h" -#include "video_core/video_core.h" - -namespace Tegra { - -enum class BufferMethods { - BindObject = 0, - CountBufferMethods = 0x40, -}; - -MICROPROFILE_DEFINE(ProcessCommandLists, "GPU", "Execute command buffer", MP_RGB(128, 128, 192)); - -void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) { - MICROPROFILE_SCOPE(ProcessCommandLists); - - auto WriteReg = [this](u32 method, u32 subchannel, u32 value, u32 remaining_params) { - LOG_TRACE(HW_GPU, - "Processing method {:08X} on subchannel {} value " - "{:08X} remaining params {}", - method, subchannel, value, remaining_params); - - ASSERT(subchannel < bound_engines.size()); - - if (method == static_cast<u32>(BufferMethods::BindObject)) { - // Bind the current subchannel to the desired engine id. - LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value); - bound_engines[subchannel] = static_cast<EngineID>(value); - return; - } - - if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) { - // TODO(Subv): Research and implement these methods. - LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); - return; - } - - const EngineID engine = bound_engines[subchannel]; - - switch (engine) { - case EngineID::FERMI_TWOD_A: - fermi_2d->WriteReg(method, value); - break; - case EngineID::MAXWELL_B: - maxwell_3d->WriteReg(method, value, remaining_params); - break; - case EngineID::MAXWELL_COMPUTE_B: - maxwell_compute->WriteReg(method, value); - break; - case EngineID::MAXWELL_DMA_COPY_A: - maxwell_dma->WriteReg(method, value); - break; - case EngineID::KEPLER_INLINE_TO_MEMORY_B: - kepler_memory->WriteReg(method, value); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented engine"); - } - }; - - for (auto entry : commands) { - Tegra::GPUVAddr address = entry.Address(); - u32 size = entry.sz; - const std::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address); - VAddr current_addr = *head_address; - while (current_addr < *head_address + size * sizeof(CommandHeader)) { - const CommandHeader header = {Memory::Read32(current_addr)}; - current_addr += sizeof(u32); - - switch (header.mode.Value()) { - case SubmissionMode::IncreasingOld: - case SubmissionMode::Increasing: { - // Increase the method value with each argument. - for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); - current_addr += sizeof(u32); - } - break; - } - case SubmissionMode::NonIncreasingOld: - case SubmissionMode::NonIncreasing: { - // Use the same method value for all arguments. - for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); - current_addr += sizeof(u32); - } - break; - } - case SubmissionMode::IncreaseOnce: { - ASSERT(header.arg_count.Value() >= 1); - - // Use the original method for the first argument and then the next method for all - // other arguments. - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), - header.arg_count - 1); - current_addr += sizeof(u32); - - for (unsigned i = 1; i < header.arg_count; ++i) { - WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); - current_addr += sizeof(u32); - } - break; - } - case SubmissionMode::Inline: { - // The register value is stored in the bits 16-28 as an immediate - WriteReg(header.method, header.subchannel, header.inline_data, 0); - break; - } - default: - UNIMPLEMENTED(); - } - } - } -} - -} // namespace Tegra diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h deleted file mode 100644 index bd766e77a..000000000 --- a/src/video_core/command_processor.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <type_traits> -#include "common/bit_field.h" -#include "common/common_types.h" -#include "video_core/memory_manager.h" - -namespace Tegra { - -enum class SubmissionMode : u32 { - IncreasingOld = 0, - Increasing = 1, - NonIncreasingOld = 2, - NonIncreasing = 3, - Inline = 4, - IncreaseOnce = 5 -}; - -struct CommandListHeader { - u32 entry0; // gpu_va_lo - union { - u32 entry1; // gpu_va_hi | (unk_0x02 << 0x08) | (size << 0x0A) | (unk_0x01 << 0x1F) - BitField<0, 8, u32> gpu_va_hi; - BitField<8, 2, u32> unk1; - BitField<10, 21, u32> sz; - BitField<31, 1, u32> unk2; - }; - - GPUVAddr Address() const { - return (static_cast<GPUVAddr>(gpu_va_hi) << 32) | entry0; - } -}; -static_assert(sizeof(CommandListHeader) == 8, "CommandListHeader is incorrect size"); - -union CommandHeader { - u32 hex; - - BitField<0, 13, u32> method; - BitField<13, 3, u32> subchannel; - - BitField<16, 13, u32> arg_count; - BitField<16, 13, u32> inline_data; - - BitField<29, 3, SubmissionMode> mode; -}; -static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout"); -static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!"); - -} // namespace Tegra diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp new file mode 100644 index 000000000..63a958f11 --- /dev/null +++ b/src/video_core/dma_pusher.cpp @@ -0,0 +1,123 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/microprofile.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/dma_pusher.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" + +namespace Tegra { + +DmaPusher::DmaPusher(GPU& gpu) : gpu(gpu) {} + +DmaPusher::~DmaPusher() = default; + +MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128, 128, 192)); + +void DmaPusher::DispatchCalls() { + MICROPROFILE_SCOPE(DispatchCalls); + + // On entering GPU code, assume all memory may be touched by the ARM core. + gpu.Maxwell3D().dirty_flags.OnMemoryWrite(); + + dma_pushbuffer_subindex = 0; + + while (Core::System::GetInstance().IsPoweredOn()) { + if (!Step()) { + break; + } + } +} + +bool DmaPusher::Step() { + if (dma_get != dma_put) { + // Push buffer non-empty, read a word + const CommandHeader command_header{ + Memory::Read32(*gpu.MemoryManager().GpuToCpuAddress(dma_get))}; + + dma_get += sizeof(u32); + + if (!non_main) { + dma_mget = dma_get; + } + + // now, see if we're in the middle of a command + if (dma_state.length_pending) { + // Second word of long non-inc methods command - method count + dma_state.length_pending = 0; + dma_state.method_count = command_header.method_count_; + } else if (dma_state.method_count) { + // Data word of methods command + CallMethod(command_header.argument); + + if (!dma_state.non_incrementing) { + dma_state.method++; + } + + if (dma_increment_once) { + dma_state.non_incrementing = true; + } + + dma_state.method_count--; + } else { + // No command active - this is the first word of a new one + switch (command_header.mode) { + case SubmissionMode::Increasing: + SetState(command_header); + dma_state.non_incrementing = false; + dma_increment_once = false; + break; + case SubmissionMode::NonIncreasing: + SetState(command_header); + dma_state.non_incrementing = true; + dma_increment_once = false; + break; + case SubmissionMode::Inline: + dma_state.method = command_header.method; + dma_state.subchannel = command_header.subchannel; + CallMethod(command_header.arg_count); + dma_state.non_incrementing = true; + dma_increment_once = false; + break; + case SubmissionMode::IncreaseOnce: + SetState(command_header); + dma_state.non_incrementing = false; + dma_increment_once = true; + break; + } + } + } else if (ib_enable && !dma_pushbuffer.empty()) { + // Current pushbuffer empty, but we have more IB entries to read + const CommandList& command_list{dma_pushbuffer.front()}; + const CommandListHeader& command_list_header{command_list[dma_pushbuffer_subindex++]}; + dma_get = command_list_header.addr; + dma_put = dma_get + command_list_header.size * sizeof(u32); + non_main = command_list_header.is_non_main; + + if (dma_pushbuffer_subindex >= command_list.size()) { + // We've gone through the current list, remove it from the queue + dma_pushbuffer.pop(); + dma_pushbuffer_subindex = 0; + } + } else { + // Otherwise, pushbuffer empty and IB empty or nonexistent - nothing to do + return {}; + } + + return true; +} + +void DmaPusher::SetState(const CommandHeader& command_header) { + dma_state.method = command_header.method; + dma_state.subchannel = command_header.subchannel; + dma_state.method_count = command_header.method_count; +} + +void DmaPusher::CallMethod(u32 argument) const { + gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count}); +} + +} // namespace Tegra diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h new file mode 100644 index 000000000..16e0697c4 --- /dev/null +++ b/src/video_core/dma_pusher.h @@ -0,0 +1,99 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <queue> + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra { + +enum class SubmissionMode : u32 { + IncreasingOld = 0, + Increasing = 1, + NonIncreasingOld = 2, + NonIncreasing = 3, + Inline = 4, + IncreaseOnce = 5 +}; + +struct CommandListHeader { + union { + u64 raw; + BitField<0, 40, GPUVAddr> addr; + BitField<41, 1, u64> is_non_main; + BitField<42, 21, u64> size; + }; +}; +static_assert(sizeof(CommandListHeader) == sizeof(u64), "CommandListHeader is incorrect size"); + +union CommandHeader { + u32 argument; + BitField<0, 13, u32> method; + BitField<0, 24, u32> method_count_; + BitField<13, 3, u32> subchannel; + BitField<16, 13, u32> arg_count; + BitField<16, 13, u32> method_count; + BitField<29, 3, SubmissionMode> mode; +}; +static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout"); +static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!"); + +class GPU; + +using CommandList = std::vector<Tegra::CommandListHeader>; + +/** + * The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the + * emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled + * into a "command stream" consisting of 32-bit words that make up "commands". + * See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for + * details on this implementation. + */ +class DmaPusher { +public: + explicit DmaPusher(GPU& gpu); + ~DmaPusher(); + + void Push(CommandList&& entries) { + dma_pushbuffer.push(std::move(entries)); + } + + void DispatchCalls(); + +private: + bool Step(); + + void SetState(const CommandHeader& command_header); + + void CallMethod(u32 argument) const; + + GPU& gpu; + + std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed + std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer + + struct DmaState { + u32 method; ///< Current method + u32 subchannel; ///< Current subchannel + u32 method_count; ///< Current method count + u32 length_pending; ///< Large NI command length pending + bool non_incrementing; ///< Current command’s NI flag + }; + + DmaState dma_state{}; + bool dma_increment_once{}; + + GPUVAddr dma_put{}; ///< pushbuffer current end address + GPUVAddr dma_get{}; ///< pushbuffer current read address + GPUVAddr dma_mget{}; ///< main pushbuffer last read address + bool ib_enable{true}; ///< IB mode enabled + bool non_main{}; ///< non-main pushbuffer active +}; + +} // namespace Tegra diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 74e44c7fe..80f70e332 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/memory.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" @@ -12,13 +14,13 @@ namespace Tegra::Engines { Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) : memory_manager(memory_manager), rasterizer{rasterizer} {} -void Fermi2D::WriteReg(u32 method, u32 value) { - ASSERT_MSG(method < Regs::NUM_REGS, +void Fermi2D::CallMethod(const GPU::MethodCall& method_call) { + ASSERT_MSG(method_call.method < Regs::NUM_REGS, "Invalid Fermi2D register, increase the size of the Regs structure"); - regs.reg_array[method] = value; + regs.reg_array[method_call.method] = method_call.argument; - switch (method) { + switch (method_call.method) { case FERMI2D_REG_INDEX(trigger): { HandleSurfaceCopy(); break; @@ -47,6 +49,9 @@ void Fermi2D::HandleSurfaceCopy() { u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) { + // All copies here update the main memory, so mark all rasterizer states as invalid. + Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); + rasterizer.FlushRegion(source_cpu, src_bytes_per_pixel * regs.src.width * regs.src.height); // We have to invalidate the destination region to evict any outdated surfaces from the // cache. We do this before actually writing the new data because the destination address @@ -68,13 +73,13 @@ void Fermi2D::HandleSurfaceCopy() { Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth, src_bytes_per_pixel, dst_bytes_per_pixel, src_buffer, dst_buffer, true, regs.src.BlockHeight(), - regs.src.BlockDepth()); + regs.src.BlockDepth(), 0); } else { // If the input is linear and the output is tiled, swizzle the input and copy it over. Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth, src_bytes_per_pixel, dst_bytes_per_pixel, dst_buffer, src_buffer, false, regs.dst.BlockHeight(), - regs.dst.BlockDepth()); + regs.dst.BlockDepth(), 0); } } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 2a6e8bbbb..50009bf75 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -27,7 +27,7 @@ public: ~Fermi2D() = default; /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value); + void CallMethod(const GPU::MethodCall& method_call); struct Regs { static constexpr std::size_t NUM_REGS = 0x258; diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index 585290d9f..4880191fc 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -3,8 +3,10 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/memory.h" #include "video_core/engines/kepler_memory.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_interface.h" namespace Tegra::Engines { @@ -15,19 +17,19 @@ KeplerMemory::KeplerMemory(VideoCore::RasterizerInterface& rasterizer, KeplerMemory::~KeplerMemory() = default; -void KeplerMemory::WriteReg(u32 method, u32 value) { - ASSERT_MSG(method < Regs::NUM_REGS, +void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) { + ASSERT_MSG(method_call.method < Regs::NUM_REGS, "Invalid KeplerMemory register, increase the size of the Regs structure"); - regs.reg_array[method] = value; + regs.reg_array[method_call.method] = method_call.argument; - switch (method) { + switch (method_call.method) { case KEPLERMEMORY_REG_INDEX(exec): { state.write_offset = 0; break; } case KEPLERMEMORY_REG_INDEX(data): { - ProcessData(value); + ProcessData(method_call.argument); break; } } @@ -47,6 +49,7 @@ void KeplerMemory::ProcessData(u32 data) { rasterizer.InvalidateRegion(dest_address, sizeof(u32)); Memory::Write32(dest_address, data); + Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); state.write_offset++; } diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index bf4a13cff..fe9ebc5b9 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -9,6 +9,7 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "video_core/gpu.h" #include "video_core/memory_manager.h" namespace VideoCore { @@ -26,7 +27,7 @@ public: ~KeplerMemory(); /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value); + void CallMethod(const GPU::MethodCall& method_call); struct Regs { static constexpr size_t NUM_REGS = 0x7F; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index a04e00ecb..b19b3a75a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -69,6 +69,15 @@ void Maxwell3D::InitializeRegisterDefaults() { // TODO(Rodrigo): Most games do not set a point size. I think this is a case of a // register carrying a default value. Assume it's OpenGL's default (1). regs.point_size = 1.0f; + + // TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a + // default of enabled fixes rendering here. + for (std::size_t color_mask = 0; color_mask < Regs::NumRenderTargets; color_mask++) { + regs.color_mask[color_mask].R.Assign(1); + regs.color_mask[color_mask].G.Assign(1); + regs.color_mask[color_mask].B.Assign(1); + regs.color_mask[color_mask].A.Assign(1); + } } void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { @@ -88,57 +97,74 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { macro_interpreter.Execute(search->second, std::move(parameters)); } -void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { +void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); // It is an error to write to a register other than the current macro's ARG register before it // has finished execution. if (executing_macro != 0) { - ASSERT(method == executing_macro + 1); + ASSERT(method_call.method == executing_macro + 1); } // Methods after 0xE00 are special, they're actually triggers for some microcode that was // uploaded to the GPU during initialization. - if (method >= MacroRegistersStart) { + if (method_call.method >= MacroRegistersStart) { // We're trying to execute a macro if (executing_macro == 0) { // A macro call must begin by writing the macro method's register, not its argument. - ASSERT_MSG((method % 2) == 0, + ASSERT_MSG((method_call.method % 2) == 0, "Can't start macro execution by writing to the ARGS register"); - executing_macro = method; + executing_macro = method_call.method; } - macro_params.push_back(value); + macro_params.push_back(method_call.argument); // Call the macro when there are no more parameters in the command buffer - if (remaining_params == 0) { + if (method_call.IsLastCall()) { CallMacroMethod(executing_macro, std::move(macro_params)); } return; } - ASSERT_MSG(method < Regs::NUM_REGS, + ASSERT_MSG(method_call.method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); if (debug_context) { debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); } - if (regs.reg_array[method] != value) { - regs.reg_array[method] = value; - if (method >= MAXWELL3D_REG_INDEX(vertex_attrib_format) && - method < MAXWELL3D_REG_INDEX(vertex_attrib_format) + regs.vertex_attrib_format.size()) { + if (regs.reg_array[method_call.method] != method_call.argument) { + regs.reg_array[method_call.method] = method_call.argument; + // Vertex format + if (method_call.method >= MAXWELL3D_REG_INDEX(vertex_attrib_format) && + method_call.method < + MAXWELL3D_REG_INDEX(vertex_attrib_format) + regs.vertex_attrib_format.size()) { dirty_flags.vertex_attrib_format = true; } + + // Vertex buffer + if (method_call.method >= MAXWELL3D_REG_INDEX(vertex_array) && + method_call.method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * 32) { + dirty_flags.vertex_array |= + 1u << ((method_call.method - MAXWELL3D_REG_INDEX(vertex_array)) >> 2); + } else if (method_call.method >= MAXWELL3D_REG_INDEX(vertex_array_limit) && + method_call.method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * 32) { + dirty_flags.vertex_array |= + 1u << ((method_call.method - MAXWELL3D_REG_INDEX(vertex_array_limit)) >> 1); + } else if (method_call.method >= MAXWELL3D_REG_INDEX(instanced_arrays) && + method_call.method < MAXWELL3D_REG_INDEX(instanced_arrays) + 32) { + dirty_flags.vertex_array |= + 1u << (method_call.method - MAXWELL3D_REG_INDEX(instanced_arrays)); + } } - switch (method) { + switch (method_call.method) { case MAXWELL3D_REG_INDEX(macros.data): { - ProcessMacroUpload(value); + ProcessMacroUpload(method_call.argument); break; } case MAXWELL3D_REG_INDEX(macros.bind): { - ProcessMacroBind(value); + ProcessMacroBind(method_call.argument); break; } case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): @@ -157,7 +183,7 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { - ProcessCBData(value); + ProcessCBData(method_call.argument); break; } case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): { @@ -261,6 +287,7 @@ void Maxwell3D::ProcessQueryGet() { query_result.timestamp = CoreTiming::GetTicks(); Memory::WriteBlock(*address, &query_result, sizeof(query_result)); } + dirty_flags.OnMemoryWrite(); break; } default: @@ -337,6 +364,7 @@ void Maxwell3D::ProcessCBData(u32 value) { memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos); Memory::Write32(*address, value); + dirty_flags.OnMemoryWrite(); // Increment the current buffer position. regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 9e480dc39..25bb7604a 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -42,6 +42,7 @@ public: static constexpr std::size_t NumVertexArrays = 32; static constexpr std::size_t NumVertexAttributes = 32; static constexpr std::size_t NumTextureSamplers = 32; + static constexpr std::size_t NumClipDistances = 8; static constexpr std::size_t MaxShaderProgram = 6; static constexpr std::size_t MaxShaderStage = 5; // Maximum number of const buffers per shader stage. @@ -389,6 +390,13 @@ public: ReverseSubtract = 3, Min = 4, Max = 5, + + // These values are used by Nouveau and some games. + AddGL = 0x8006, + SubtractGL = 0x8007, + ReverseSubtractGL = 0x8008, + MinGL = 0x800a, + MaxGL = 0x800b }; enum class Factor : u32 { @@ -583,10 +591,18 @@ public: float clear_color[4]; float clear_depth; + INSERT_PADDING_WORDS(0x3); + s32 clear_stencil; - INSERT_PADDING_WORDS(0x17); + INSERT_PADDING_WORDS(0x7); + + u32 polygon_offset_point_enable; + u32 polygon_offset_line_enable; + u32 polygon_offset_fill_enable; + + INSERT_PADDING_WORDS(0xD); std::array<ScissorTest, NumViewports> scissor_test; @@ -624,7 +640,16 @@ public: } } zeta; - INSERT_PADDING_WORDS(0x5B); + INSERT_PADDING_WORDS(0x41); + + union { + BitField<0, 4, u32> stencil; + BitField<4, 4, u32> unknown; + BitField<8, 4, u32> scissor; + BitField<12, 4, u32> viewport; + } clear_flags; + + INSERT_PADDING_WORDS(0x19); std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; @@ -712,6 +737,7 @@ public: u32 frag_color_clamp; union { + BitField<0, 1, u32> y_negate; BitField<4, 1, u32> triangle_rast_flip; } screen_y_control; @@ -719,7 +745,20 @@ public: u32 vb_element_base; - INSERT_PADDING_WORDS(0x38); + INSERT_PADDING_WORDS(0x36); + + union { + BitField<0, 1, u32> c0; + BitField<1, 1, u32> c1; + BitField<2, 1, u32> c2; + BitField<3, 1, u32> c3; + BitField<4, 1, u32> c4; + BitField<5, 1, u32> c5; + BitField<6, 1, u32> c6; + BitField<7, 1, u32> c7; + } clip_distance_enabled; + + INSERT_PADDING_WORDS(0x1); float point_size; @@ -745,7 +784,11 @@ public: } } tsc; - INSERT_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS(0x1); + + float polygon_offset_factor; + + INSERT_PADDING_WORDS(0x1); struct { u32 tic_address_high; @@ -770,7 +813,9 @@ public: u32 framebuffer_srgb; - INSERT_PADDING_WORDS(0x12); + float polygon_offset_units; + + INSERT_PADDING_WORDS(0x11); union { BitField<2, 1, u32> coord_origin; @@ -847,7 +892,9 @@ public: INSERT_PADDING_WORDS(0x7); - INSERT_PADDING_WORDS(0x20); + INSERT_PADDING_WORDS(0x1F); + + float polygon_offset_clamp; struct { u32 is_instanced[NumVertexArrays]; @@ -863,8 +910,21 @@ public: Cull cull; - INSERT_PADDING_WORDS(0x28); + u32 pixel_center_integer; + + INSERT_PADDING_WORDS(0x1); + + u32 viewport_transform_enabled; + + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 1, u32> depth_range_0_1; + BitField<3, 1, u32> depth_clamp_near; + BitField<4, 1, u32> depth_clamp_far; + } view_volume_clip_control; + INSERT_PADDING_WORDS(0x21); struct { u32 enable; LogicOperation operation; @@ -1028,6 +1088,11 @@ public: struct DirtyFlags { bool vertex_attrib_format = true; + u32 vertex_array = 0xFFFFFFFF; + + void OnMemoryWrite() { + vertex_array = 0xFFFFFFFF; + } }; DirtyFlags dirty_flags; @@ -1036,7 +1101,7 @@ public: u32 GetRegisterValue(u32 method) const; /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value, u32 remaining_params); + void CallMethod(const GPU::MethodCall& method_call); /// Returns a list of enabled textures for the specified shader stage. std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const; @@ -1120,6 +1185,9 @@ ASSERT_REG_POSITION(vertex_buffer, 0x35D); ASSERT_REG_POSITION(clear_color[0], 0x360); ASSERT_REG_POSITION(clear_depth, 0x364); ASSERT_REG_POSITION(clear_stencil, 0x368); +ASSERT_REG_POSITION(polygon_offset_point_enable, 0x370); +ASSERT_REG_POSITION(polygon_offset_line_enable, 0x371); +ASSERT_REG_POSITION(polygon_offset_fill_enable, 0x372); ASSERT_REG_POSITION(scissor_test, 0x380); ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); ASSERT_REG_POSITION(stencil_back_mask, 0x3D6); @@ -1127,6 +1195,7 @@ ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); ASSERT_REG_POSITION(color_mask_common, 0x3E4); ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB); ASSERT_REG_POSITION(zeta, 0x3F8); +ASSERT_REG_POSITION(clear_flags, 0x43E); ASSERT_REG_POSITION(vertex_attrib_format, 0x458); ASSERT_REG_POSITION(rt_control, 0x487); ASSERT_REG_POSITION(zeta_width, 0x48a); @@ -1153,10 +1222,12 @@ ASSERT_REG_POSITION(stencil_front_mask, 0x4E7); ASSERT_REG_POSITION(frag_color_clamp, 0x4EA); ASSERT_REG_POSITION(screen_y_control, 0x4EB); ASSERT_REG_POSITION(vb_element_base, 0x50D); +ASSERT_REG_POSITION(clip_distance_enabled, 0x544); ASSERT_REG_POSITION(point_size, 0x546); ASSERT_REG_POSITION(zeta_enable, 0x54E); ASSERT_REG_POSITION(multisample_control, 0x54F); ASSERT_REG_POSITION(tsc, 0x557); +ASSERT_REG_POSITION(polygon_offset_factor, 0x55b); ASSERT_REG_POSITION(tic, 0x55D); ASSERT_REG_POSITION(stencil_two_side_enable, 0x565); ASSERT_REG_POSITION(stencil_back_op_fail, 0x566); @@ -1164,13 +1235,18 @@ ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567); ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568); ASSERT_REG_POSITION(stencil_back_func_func, 0x569); ASSERT_REG_POSITION(framebuffer_srgb, 0x56E); +ASSERT_REG_POSITION(polygon_offset_units, 0x56F); ASSERT_REG_POSITION(point_coord_replace, 0x581); ASSERT_REG_POSITION(code_address, 0x582); ASSERT_REG_POSITION(draw, 0x585); ASSERT_REG_POSITION(primitive_restart, 0x591); ASSERT_REG_POSITION(index_array, 0x5F2); +ASSERT_REG_POSITION(polygon_offset_clamp, 0x61F); ASSERT_REG_POSITION(instanced_arrays, 0x620); ASSERT_REG_POSITION(cull, 0x646); +ASSERT_REG_POSITION(pixel_center_integer, 0x649); +ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B); +ASSERT_REG_POSITION(view_volume_clip_control, 0x64F); ASSERT_REG_POSITION(logic_op, 0x671); ASSERT_REG_POSITION(clear_buffers, 0x674); ASSERT_REG_POSITION(color_mask, 0x680); diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp index 8b5f08351..656db6a61 100644 --- a/src/video_core/engines/maxwell_compute.cpp +++ b/src/video_core/engines/maxwell_compute.cpp @@ -8,13 +8,13 @@ namespace Tegra::Engines { -void MaxwellCompute::WriteReg(u32 method, u32 value) { - ASSERT_MSG(method < Regs::NUM_REGS, +void MaxwellCompute::CallMethod(const GPU::MethodCall& method_call) { + ASSERT_MSG(method_call.method < Regs::NUM_REGS, "Invalid MaxwellCompute register, increase the size of the Regs structure"); - regs.reg_array[method] = value; + regs.reg_array[method_call.method] = method_call.argument; - switch (method) { + switch (method_call.method) { case MAXWELL_COMPUTE_REG_INDEX(compute): { LOG_CRITICAL(HW_GPU, "Compute shaders are not implemented"); UNREACHABLE(); diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h index 6ea934fb9..1d71f11bd 100644 --- a/src/video_core/engines/maxwell_compute.h +++ b/src/video_core/engines/maxwell_compute.h @@ -9,6 +9,7 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "video_core/gpu.h" namespace Tegra::Engines { @@ -42,7 +43,7 @@ public: "MaxwellCompute Regs has wrong size"); /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value); + void CallMethod(const GPU::MethodCall& method_call); }; #define ASSERT_REG_POSITION(field_name, position) \ diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index b8a78cf82..06462f570 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/memory.h" +#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" @@ -12,16 +14,16 @@ namespace Tegra::Engines { MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) : memory_manager(memory_manager), rasterizer{rasterizer} {} -void MaxwellDMA::WriteReg(u32 method, u32 value) { - ASSERT_MSG(method < Regs::NUM_REGS, +void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) { + ASSERT_MSG(method_call.method < Regs::NUM_REGS, "Invalid MaxwellDMA register, increase the size of the Regs structure"); - regs.reg_array[method] = value; + regs.reg_array[method_call.method] = method_call.argument; #define MAXWELLDMA_REG_INDEX(field_name) \ (offsetof(Tegra::Engines::MaxwellDMA::Regs, field_name) / sizeof(u32)) - switch (method) { + switch (method_call.method) { case MAXWELLDMA_REG_INDEX(exec): { HandleCopy(); break; @@ -54,6 +56,9 @@ void MaxwellDMA::HandleCopy() { return; } + // All copies here update the main memory, so mark all rasterizer states as invalid. + Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); + if (regs.exec.is_dst_linear && regs.exec.is_src_linear) { // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count, diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 5f3704f05..1f8cd65d2 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -24,7 +24,7 @@ public: ~MaxwellDMA() = default; /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value); + void CallMethod(const GPU::MethodCall& method_call); struct Regs { static constexpr std::size_t NUM_REGS = 0x1D6; diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 83a6fd875..5ea094e64 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -82,6 +82,8 @@ union Attribute { Position = 7, Attribute_0 = 8, Attribute_31 = 39, + ClipDistances0123 = 44, + ClipDistances4567 = 45, PointCoord = 46, // This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval @@ -153,6 +155,7 @@ enum class PredCondition : u64 { NotEqual = 5, GreaterEqual = 6, LessThanWithNan = 9, + LessEqualWithNan = 11, GreaterThanWithNan = 12, NotEqualWithNan = 13, GreaterEqualWithNan = 14, @@ -261,7 +264,7 @@ enum class FlowCondition : u64 { Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for? }; -enum class ControlCode : u64 { +enum class ConditionCode : u64 { F = 0, LT = 1, EQ = 2, @@ -365,6 +368,11 @@ enum class HalfPrecision : u64 { FMZ = 2, }; +enum class R2pMode : u64 { + Pr = 0, + Cc = 1, +}; + enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, @@ -569,7 +577,6 @@ union Instruction { BitField<39, 2, u64> tab5cb8_2; BitField<41, 3, u64> tab5c68_1; BitField<44, 2, u64> tab5c68_0; - BitField<47, 1, u64> cc; BitField<48, 1, u64> negate_b; } fmul; @@ -831,7 +838,7 @@ union Instruction { union { BitField<0, 3, u64> pred0; BitField<3, 3, u64> pred3; - BitField<8, 5, ControlCode> cc; // flag in cc + BitField<8, 5, ConditionCode> cc; // flag in cc BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred39; BitField<45, 4, PredOperation> op; // op with pred39 @@ -855,6 +862,12 @@ union Instruction { } hsetp2; union { + BitField<40, 1, R2pMode> mode; + BitField<41, 2, u64> byte; + BitField<20, 7, u64> immediate_mask; + } r2p; + + union { BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; BitField<43, 1, u64> neg_a; @@ -1036,6 +1049,7 @@ union Instruction { BitField<49, 1, u64> nodep_flag; BitField<50, 3, u64> component_mask_selector; BitField<53, 4, u64> texture_info; + BitField<60, 1, u64> fp32_flag; TextureType GetTextureType() const { // The TEXS instruction has a weird encoding for the texture type. @@ -1235,7 +1249,7 @@ union Instruction { BitField<60, 1, u64> is_b_gpr; BitField<59, 1, u64> is_c_gpr; BitField<20, 24, s64> smem_imm; - BitField<0, 5, ControlCode> flow_control_code; + BitField<0, 5, ConditionCode> flow_condition_code; Attribute attribute; Sampler sampler; @@ -1256,6 +1270,7 @@ public: BFE_C, BFE_R, BFE_IMM, + BFI_IMM_R, BRA, PBK, LD_A, @@ -1381,6 +1396,7 @@ public: PSETP, PSET, CSETP, + R2P_IMM, XMAD_IMM, XMAD_CR, XMAD_RC, @@ -1396,6 +1412,7 @@ public: ArithmeticHalf, ArithmeticHalfImmediate, Bfe, + Bfi, Shift, Ffma, Hfma2, @@ -1410,6 +1427,7 @@ public: HalfSetPredicate, PredicateSetPredicate, PredicateSetRegister, + RegisterSetPredicate, Conversion, Xmad, Unknown, @@ -1532,7 +1550,7 @@ private: INST("1110111011011---", Id::STG, Type::Memory, "STG"), INST("110000----111---", Id::TEX, Type::Memory, "TEX"), INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"), - INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"), + INST("1101-00---------", Id::TEXS, Type::Memory, "TEXS"), INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"), INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"), INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"), @@ -1613,6 +1631,7 @@ private: INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"), INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"), INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"), + INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"), INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"), INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"), @@ -1647,6 +1666,7 @@ private: INST("0101000010001---", Id::PSET, Type::PredicateSetRegister, "PSET"), INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"), INST("010100001010----", Id::CSETP, Type::PredicateSetPredicate, "CSETP"), + INST("0011100-11110---", Id::R2P_IMM, Type::RegisterSetPredicate, "R2P_IMM"), INST("0011011-00------", Id::XMAD_IMM, Type::Xmad, "XMAD_IMM"), INST("0100111---------", Id::XMAD_CR, Type::Xmad, "XMAD_CR"), INST("010100010-------", Id::XMAD_RC, Type::Xmad, "XMAD_RC"), diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h index a0e015c4b..99c34649f 100644 --- a/src/video_core/engines/shader_header.h +++ b/src/video_core/engines/shader_header.h @@ -62,7 +62,16 @@ struct Header { INSERT_PADDING_BYTES(1); // ImapSystemValuesB INSERT_PADDING_BYTES(16); // ImapGenericVector[32] INSERT_PADDING_BYTES(2); // ImapColor - INSERT_PADDING_BYTES(2); // ImapSystemValuesC + union { + BitField<0, 8, u16> clip_distances; + BitField<8, 1, u16> point_sprite_s; + BitField<9, 1, u16> point_sprite_t; + BitField<10, 1, u16> fog_coordinate; + BitField<12, 1, u16> tessellation_eval_point_u; + BitField<13, 1, u16> tessellation_eval_point_v; + BitField<14, 1, u16> instance_id; + BitField<15, 1, u16> vertex_id; + }; INSERT_PADDING_BYTES(5); // ImapFixedFncTexture[10] INSERT_PADDING_BYTES(1); // ImapReserved INSERT_PADDING_BYTES(3); // OmapSystemValuesA diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 83c7e5b0b..88c45a423 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -17,6 +17,8 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { switch (format) { case PixelFormat::ABGR8: return 4; + default: + return 4; } UNREACHABLE(); @@ -24,6 +26,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { memory_manager = std::make_unique<Tegra::MemoryManager>(); + dma_pusher = std::make_unique<Tegra::DmaPusher>(*this); maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager); fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); @@ -49,6 +52,14 @@ const MemoryManager& GPU::MemoryManager() const { return *memory_manager; } +DmaPusher& GPU::DmaPusher() { + return *dma_pusher; +} + +const DmaPusher& GPU::DmaPusher() const { + return *dma_pusher; +} + u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { ASSERT(format != RenderTargetFormat::NONE); @@ -111,4 +122,52 @@ u32 DepthFormatBytesPerPixel(DepthFormat format) { } } +enum class BufferMethods { + BindObject = 0, + CountBufferMethods = 0x40, +}; + +void GPU::CallMethod(const MethodCall& method_call) { + LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method, + method_call.subchannel); + + ASSERT(method_call.subchannel < bound_engines.size()); + + if (method_call.method == static_cast<u32>(BufferMethods::BindObject)) { + // Bind the current subchannel to the desired engine id. + LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, + method_call.argument); + bound_engines[method_call.subchannel] = static_cast<EngineID>(method_call.argument); + return; + } + + if (method_call.method < static_cast<u32>(BufferMethods::CountBufferMethods)) { + // TODO(Subv): Research and implement these methods. + LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); + return; + } + + const EngineID engine = bound_engines[method_call.subchannel]; + + switch (engine) { + case EngineID::FERMI_TWOD_A: + fermi_2d->CallMethod(method_call); + break; + case EngineID::MAXWELL_B: + maxwell_3d->CallMethod(method_call); + break; + case EngineID::MAXWELL_COMPUTE_B: + maxwell_compute->CallMethod(method_call); + break; + case EngineID::MAXWELL_DMA_COPY_A: + maxwell_dma->CallMethod(method_call); + break; + case EngineID::KEPLER_INLINE_TO_MEMORY_B: + kepler_memory->CallMethod(method_call); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented engine"); + } +} + } // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 5cc1e19ca..af5ccd1e9 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -9,6 +9,7 @@ #include <vector> #include "common/common_types.h" #include "core/hle/service/nvflinger/buffer_queue.h" +#include "video_core/dma_pusher.h" #include "video_core/memory_manager.h" namespace VideoCore { @@ -119,8 +120,23 @@ public: explicit GPU(VideoCore::RasterizerInterface& rasterizer); ~GPU(); - /// Processes a command list stored at the specified address in GPU memory. - void ProcessCommandLists(const std::vector<CommandListHeader>& commands); + struct MethodCall { + u32 method{}; + u32 argument{}; + u32 subchannel{}; + u32 method_count{}; + + bool IsLastCall() const { + return method_count <= 1; + } + + MethodCall(u32 method, u32 argument, u32 subchannel = 0, u32 method_count = 0) + : method(method), argument(argument), subchannel(subchannel), + method_count(method_count) {} + }; + + /// Calls a GPU method. + void CallMethod(const MethodCall& method_call); /// Returns a reference to the Maxwell3D GPU engine. Engines::Maxwell3D& Maxwell3D(); @@ -134,7 +150,14 @@ public: /// Returns a const reference to the GPU memory manager. const Tegra::MemoryManager& MemoryManager() const; + /// Returns a reference to the GPU DMA pusher. + Tegra::DmaPusher& DmaPusher(); + + /// Returns a const reference to the GPU DMA pusher. + const Tegra::DmaPusher& DmaPusher() const; + private: + std::unique_ptr<Tegra::DmaPusher> dma_pusher; std::unique_ptr<Tegra::MemoryManager> memory_manager; /// Mapping of command subchannels to their bound engine ids. diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index 335a8d407..9c55e9f1e 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp @@ -35,6 +35,7 @@ void MacroInterpreter::Reset() { // The next parameter index starts at 1, because $r1 already has the value of the first // parameter. next_parameter_index = 1; + carry_flag = false; } bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) { @@ -135,14 +136,28 @@ MacroInterpreter::Opcode MacroInterpreter::GetOpcode(u32 offset) const { return {macro_memory[offset + pc / sizeof(u32)]}; } -u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const { +u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) { switch (operation) { - case ALUOperation::Add: - return src_a + src_b; - // TODO(Subv): Implement AddWithCarry - case ALUOperation::Subtract: - return src_a - src_b; - // TODO(Subv): Implement SubtractWithBorrow + case ALUOperation::Add: { + const u64 result{static_cast<u64>(src_a) + src_b}; + carry_flag = result > 0xffffffff; + return static_cast<u32>(result); + } + case ALUOperation::AddWithCarry: { + const u64 result{static_cast<u64>(src_a) + src_b + (carry_flag ? 1ULL : 0ULL)}; + carry_flag = result > 0xffffffff; + return static_cast<u32>(result); + } + case ALUOperation::Subtract: { + const u64 result{static_cast<u64>(src_a) - src_b}; + carry_flag = result < 0x100000000; + return static_cast<u32>(result); + } + case ALUOperation::SubtractWithBorrow: { + const u64 result{static_cast<u64>(src_a) - src_b - (carry_flag ? 0ULL : 1ULL)}; + carry_flag = result < 0x100000000; + return static_cast<u32>(result); + } case ALUOperation::Xor: return src_a ^ src_b; case ALUOperation::Or: @@ -235,7 +250,7 @@ void MacroInterpreter::SetMethodAddress(u32 address) { } void MacroInterpreter::Send(u32 value) { - maxwell3d.WriteReg(method_address.address, value, 0); + maxwell3d.CallMethod({method_address.address, value}); // Increment the method address by the method increment. method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h index 62d1ce289..cde360288 100644 --- a/src/video_core/macro_interpreter.h +++ b/src/video_core/macro_interpreter.h @@ -117,7 +117,7 @@ private: bool Step(u32 offset, bool is_delay_slot); /// Calculates the result of an ALU operation. src_a OP src_b; - u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const; + u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b); /// Performs the result operation on the input result and stores it in the specified register /// (if necessary). @@ -165,5 +165,7 @@ private: std::vector<u32> parameters; /// Index of the next parameter that will be fetched by the 'parm' instruction. u32 next_parameter_index = 0; + + bool carry_flag{}; }; } // namespace Tegra diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 77a20bb84..47247f097 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -9,6 +9,13 @@ namespace Tegra { +MemoryManager::MemoryManager() { + // Mark the first page as reserved, so that 0 is not a valid GPUVAddr. Otherwise, games might + // try to use 0 as a valid address, which is also used to mean nullptr. This fixes a bug with + // Undertale using 0 for a render target. + PageSlot(0) = static_cast<u64>(PageStatus::Reserved); +} + GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) { const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, align, PageStatus::Unmapped)}; diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 4eb338aa2..fb03497ca 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -18,7 +18,7 @@ using GPUVAddr = u64; class MemoryManager final { public: - MemoryManager() = default; + MemoryManager(); GPUVAddr AllocateSpace(u64 size, u64 align); GPUVAddr AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align); @@ -37,6 +37,7 @@ private: enum class PageStatus : u64 { Unmapped = 0xFFFFFFFFFFFFFFFFULL, Allocated = 0xFFFFFFFFFFFFFFFEULL, + Reserved = 0xFFFFFFFFFFFFFFFDULL, }; std::optional<GPUVAddr> FindFreeBlock(GPUVAddr region_start, u64 size, u64 align, diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp new file mode 100644 index 000000000..a310491a8 --- /dev/null +++ b/src/video_core/morton.cpp @@ -0,0 +1,355 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstring> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/memory.h" +#include "video_core/morton.h" +#include "video_core/surface.h" +#include "video_core/textures/decoders.h" + +namespace VideoCore { + +using Surface::GetBytesPerPixel; +using Surface::PixelFormat; + +using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, std::size_t, VAddr); +using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>; + +template <bool morton_to_linear, PixelFormat format> +static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, + u32 tile_width_spacing, u8* buffer, std::size_t buffer_size, VAddr addr) { + constexpr u32 bytes_per_pixel = GetBytesPerPixel(format); + + // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual + // pixel values. + const u32 tile_size_x{GetDefaultBlockWidth(format)}; + const u32 tile_size_y{GetDefaultBlockHeight(format)}; + + if constexpr (morton_to_linear) { + Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, + stride, height, depth, block_height, block_depth, + tile_width_spacing); + } else { + Tegra::Texture::CopySwizzledData( + (stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y, + depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), buffer, false, + block_height, block_depth, tile_width_spacing); + } +} + +static constexpr ConversionArray morton_to_linear_fns = { + // clang-format off + MortonCopy<true, PixelFormat::ABGR8U>, + MortonCopy<true, PixelFormat::ABGR8S>, + MortonCopy<true, PixelFormat::ABGR8UI>, + MortonCopy<true, PixelFormat::B5G6R5U>, + MortonCopy<true, PixelFormat::A2B10G10R10U>, + MortonCopy<true, PixelFormat::A1B5G5R5U>, + MortonCopy<true, PixelFormat::R8U>, + MortonCopy<true, PixelFormat::R8UI>, + MortonCopy<true, PixelFormat::RGBA16F>, + MortonCopy<true, PixelFormat::RGBA16U>, + MortonCopy<true, PixelFormat::RGBA16UI>, + MortonCopy<true, PixelFormat::R11FG11FB10F>, + MortonCopy<true, PixelFormat::RGBA32UI>, + MortonCopy<true, PixelFormat::DXT1>, + MortonCopy<true, PixelFormat::DXT23>, + MortonCopy<true, PixelFormat::DXT45>, + MortonCopy<true, PixelFormat::DXN1>, + MortonCopy<true, PixelFormat::DXN2UNORM>, + MortonCopy<true, PixelFormat::DXN2SNORM>, + MortonCopy<true, PixelFormat::BC7U>, + MortonCopy<true, PixelFormat::BC6H_UF16>, + MortonCopy<true, PixelFormat::BC6H_SF16>, + MortonCopy<true, PixelFormat::ASTC_2D_4X4>, + MortonCopy<true, PixelFormat::G8R8U>, + MortonCopy<true, PixelFormat::G8R8S>, + MortonCopy<true, PixelFormat::BGRA8>, + MortonCopy<true, PixelFormat::RGBA32F>, + MortonCopy<true, PixelFormat::RG32F>, + MortonCopy<true, PixelFormat::R32F>, + MortonCopy<true, PixelFormat::R16F>, + MortonCopy<true, PixelFormat::R16U>, + MortonCopy<true, PixelFormat::R16S>, + MortonCopy<true, PixelFormat::R16UI>, + MortonCopy<true, PixelFormat::R16I>, + MortonCopy<true, PixelFormat::RG16>, + MortonCopy<true, PixelFormat::RG16F>, + MortonCopy<true, PixelFormat::RG16UI>, + MortonCopy<true, PixelFormat::RG16I>, + MortonCopy<true, PixelFormat::RG16S>, + MortonCopy<true, PixelFormat::RGB32F>, + MortonCopy<true, PixelFormat::RGBA8_SRGB>, + MortonCopy<true, PixelFormat::RG8U>, + MortonCopy<true, PixelFormat::RG8S>, + MortonCopy<true, PixelFormat::RG32UI>, + MortonCopy<true, PixelFormat::R32UI>, + MortonCopy<true, PixelFormat::ASTC_2D_8X8>, + MortonCopy<true, PixelFormat::ASTC_2D_8X5>, + MortonCopy<true, PixelFormat::ASTC_2D_5X4>, + MortonCopy<true, PixelFormat::BGRA8_SRGB>, + MortonCopy<true, PixelFormat::DXT1_SRGB>, + MortonCopy<true, PixelFormat::DXT23_SRGB>, + MortonCopy<true, PixelFormat::DXT45_SRGB>, + MortonCopy<true, PixelFormat::BC7U_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_5X5>, + MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, + MortonCopy<true, PixelFormat::ASTC_2D_10X8>, + MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, + MortonCopy<true, PixelFormat::Z32F>, + MortonCopy<true, PixelFormat::Z16>, + MortonCopy<true, PixelFormat::Z24S8>, + MortonCopy<true, PixelFormat::S8Z24>, + MortonCopy<true, PixelFormat::Z32FS8>, + // clang-format on +}; + +static constexpr ConversionArray linear_to_morton_fns = { + // clang-format off + MortonCopy<false, PixelFormat::ABGR8U>, + MortonCopy<false, PixelFormat::ABGR8S>, + MortonCopy<false, PixelFormat::ABGR8UI>, + MortonCopy<false, PixelFormat::B5G6R5U>, + MortonCopy<false, PixelFormat::A2B10G10R10U>, + MortonCopy<false, PixelFormat::A1B5G5R5U>, + MortonCopy<false, PixelFormat::R8U>, + MortonCopy<false, PixelFormat::R8UI>, + MortonCopy<false, PixelFormat::RGBA16F>, + MortonCopy<false, PixelFormat::RGBA16U>, + MortonCopy<false, PixelFormat::RGBA16UI>, + MortonCopy<false, PixelFormat::R11FG11FB10F>, + MortonCopy<false, PixelFormat::RGBA32UI>, + MortonCopy<false, PixelFormat::DXT1>, + MortonCopy<false, PixelFormat::DXT23>, + MortonCopy<false, PixelFormat::DXT45>, + MortonCopy<false, PixelFormat::DXN1>, + MortonCopy<false, PixelFormat::DXN2UNORM>, + MortonCopy<false, PixelFormat::DXN2SNORM>, + MortonCopy<false, PixelFormat::BC7U>, + MortonCopy<false, PixelFormat::BC6H_UF16>, + MortonCopy<false, PixelFormat::BC6H_SF16>, + // TODO(Subv): Swizzling ASTC formats are not supported + nullptr, + MortonCopy<false, PixelFormat::G8R8U>, + MortonCopy<false, PixelFormat::G8R8S>, + MortonCopy<false, PixelFormat::BGRA8>, + MortonCopy<false, PixelFormat::RGBA32F>, + MortonCopy<false, PixelFormat::RG32F>, + MortonCopy<false, PixelFormat::R32F>, + MortonCopy<false, PixelFormat::R16F>, + MortonCopy<false, PixelFormat::R16U>, + MortonCopy<false, PixelFormat::R16S>, + MortonCopy<false, PixelFormat::R16UI>, + MortonCopy<false, PixelFormat::R16I>, + MortonCopy<false, PixelFormat::RG16>, + MortonCopy<false, PixelFormat::RG16F>, + MortonCopy<false, PixelFormat::RG16UI>, + MortonCopy<false, PixelFormat::RG16I>, + MortonCopy<false, PixelFormat::RG16S>, + MortonCopy<false, PixelFormat::RGB32F>, + MortonCopy<false, PixelFormat::RGBA8_SRGB>, + MortonCopy<false, PixelFormat::RG8U>, + MortonCopy<false, PixelFormat::RG8S>, + MortonCopy<false, PixelFormat::RG32UI>, + MortonCopy<false, PixelFormat::R32UI>, + nullptr, + nullptr, + nullptr, + MortonCopy<false, PixelFormat::BGRA8_SRGB>, + MortonCopy<false, PixelFormat::DXT1_SRGB>, + MortonCopy<false, PixelFormat::DXT23_SRGB>, + MortonCopy<false, PixelFormat::DXT45_SRGB>, + MortonCopy<false, PixelFormat::BC7U_SRGB>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + MortonCopy<false, PixelFormat::Z32F>, + MortonCopy<false, PixelFormat::Z16>, + MortonCopy<false, PixelFormat::Z24S8>, + MortonCopy<false, PixelFormat::S8Z24>, + MortonCopy<false, PixelFormat::Z32FS8>, + // clang-format on +}; + +static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) { + switch (mode) { + case MortonSwizzleMode::MortonToLinear: + return morton_to_linear_fns[static_cast<std::size_t>(format)]; + case MortonSwizzleMode::LinearToMorton: + return linear_to_morton_fns[static_cast<std::size_t>(format)]; + } + UNREACHABLE(); +} + +/// 8x8 Z-Order coordinate from 2D coordinates +static u32 MortonInterleave(u32 x, u32 y) { + static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; + static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; + return xlut[x % 8] + ylut[y % 8]; +} + +/// Calculates the offset of the position of the pixel in Morton order +static u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { + // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each + // of which is composed of four 2x2 subtiles each of which is composed of four texels. + // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. + // texels are laid out in a 2x2 subtile like this: + // 2 3 + // 0 1 + // + // The full 8x8 tile has the texels arranged like this: + // + // 42 43 46 47 58 59 62 63 + // 40 41 44 45 56 57 60 61 + // 34 35 38 39 50 51 54 55 + // 32 33 36 37 48 49 52 53 + // 10 11 14 15 26 27 30 31 + // 08 09 12 13 24 25 28 29 + // 02 03 06 07 18 19 22 23 + // 00 01 04 05 16 17 20 21 + // + // This pattern is what's called Z-order curve, or Morton order. + + const unsigned int block_height = 8; + const unsigned int coarse_x = x & ~7; + + u32 i = MortonInterleave(x, y); + + const unsigned int offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + +static u32 MortonInterleave128(u32 x, u32 y) { + // 128x128 Z-Order coordinate from 2D coordinates + static constexpr u32 xlut[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, + 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, + 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, + 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, + 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, + 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, + 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, + 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, + 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, + 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, + 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, + 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, + 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, + 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, + 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, + 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, + 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, + 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, + 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, + 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, + 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, + 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, + 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, + 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, + 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, + 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, + 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, + 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, + 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, + 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, + 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, + 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, + 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, + 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, + }; + static constexpr u32 ylut[] = { + 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, + 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, + 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, + 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, + 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, + 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, + 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, + 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, + 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, + 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, + 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, + 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, + 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, + 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, + 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, + 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, + 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, + 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, + 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, + 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, + 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, + 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, + 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, + 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, + 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, + 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, + 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, + 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, + 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, + 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, + 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, + 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, + 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, + 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, + 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, + }; + return xlut[x % 128] + ylut[y % 128]; +} + +static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { + // Calculates the offset of the position of the pixel in Morton order + // Framebuffer images are split into 128x128 tiles. + + constexpr u32 block_height = 128; + const u32 coarse_x = x & ~127; + + const u32 i = MortonInterleave128(x, y); + + const u32 offset = coarse_x * block_height; + + return (i + offset) * bytes_per_pixel; +} + +void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, + u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, + u8* buffer, std::size_t buffer_size, VAddr addr) { + + GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth, + tile_width_spacing, buffer, buffer_size, addr); +} + +void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel, + u8* morton_data, u8* linear_data, bool morton_to_linear) { + u8* data_ptrs[2]; + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + const u32 coarse_y = y & ~127; + const u32 morton_offset = + GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + const u32 linear_pixel_index = (x + y * width) * linear_bytes_per_pixel; + + data_ptrs[morton_to_linear ? 1 : 0] = morton_data + morton_offset; + data_ptrs[morton_to_linear ? 0 : 1] = &linear_data[linear_pixel_index]; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +} // namespace VideoCore diff --git a/src/video_core/morton.h b/src/video_core/morton.h new file mode 100644 index 000000000..065f59ce3 --- /dev/null +++ b/src/video_core/morton.h @@ -0,0 +1,21 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "video_core/surface.h" + +namespace VideoCore { + +enum class MortonSwizzleMode { MortonToLinear, LinearToMorton }; + +void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride, + u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, + u8* buffer, std::size_t buffer_size, VAddr addr); + +void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel, + u8* morton_data, u8* linear_data, bool morton_to_linear); + +} // namespace VideoCore diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 075192c3f..46a6c0308 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -76,7 +76,7 @@ std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::s return std::make_tuple(uploaded_ptr, uploaded_offset); } -void OGLBufferCache::Map(std::size_t max_size) { +bool OGLBufferCache::Map(std::size_t max_size) { bool invalidate; std::tie(buffer_ptr, buffer_offset_base, invalidate) = stream_buffer.Map(static_cast<GLsizeiptr>(max_size), 4); @@ -85,6 +85,7 @@ void OGLBufferCache::Map(std::size_t max_size) { if (invalidate) { InvalidateAll(); } + return invalidate; } void OGLBufferCache::Unmap() { diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 91fca3f6c..c11acfb79 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -50,7 +50,7 @@ public: /// Reserves memory to be used by host's CPU. Returns mapped address and offset. std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4); - void Map(std::size_t max_size); + bool Map(std::size_t max_size); void Unmap(); GLuint GetHandle() const; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index ae6aaee4c..2b29fc45f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -79,6 +79,26 @@ struct DrawParameters { } }; +struct FramebufferCacheKey { + bool is_single_buffer = false; + bool stencil_enable = false; + + std::array<GLenum, Maxwell::NumRenderTargets> color_attachments{}; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors{}; + u32 colors_count = 0; + + GLuint zeta = 0; + + auto Tie() const { + return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count, + zeta); + } + + bool operator<(const FramebufferCacheKey& rhs) const { + return Tie() < rhs.Tie(); + } +}; + RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) : res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) { @@ -88,29 +108,8 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo state.texture_units[i].sampler = texture_samplers[i].sampler.handle; } - GLint ext_num; - glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num); - for (GLint i = 0; i < ext_num; i++) { - const std::string_view extension{ - reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))}; - - if (extension == "GL_ARB_direct_state_access") { - has_ARB_direct_state_access = true; - } else if (extension == "GL_ARB_multi_bind") { - has_ARB_multi_bind = true; - } else if (extension == "GL_ARB_separate_shader_objects") { - has_ARB_separate_shader_objects = true; - } else if (extension == "GL_ARB_vertex_attrib_binding") { - has_ARB_vertex_attrib_binding = true; - } - } - - ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported"); OpenGLState::ApplyDefaultState(); - // Create render framebuffer - framebuffer.Create(); - shader_program_manager = std::make_unique<GLShader::ProgramManager>(); state.draw.shader_program = 0; state.Apply(); @@ -118,10 +117,24 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment); LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!"); + CheckExtensions(); } RasterizerOpenGL::~RasterizerOpenGL() {} +void RasterizerOpenGL::CheckExtensions() { + if (!GLAD_GL_ARB_texture_filter_anisotropic && !GLAD_GL_EXT_texture_filter_anisotropic) { + LOG_WARNING( + Render_OpenGL, + "Anisotropic filter is not supported! This can cause graphical issues in some games."); + } + if (!GLAD_GL_ARB_buffer_storage) { + LOG_WARNING( + Render_OpenGL, + "Buffer storage control is not supported! This can cause performance degradation."); + } +} + void RasterizerOpenGL::SetupVertexFormat() { auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; @@ -181,15 +194,25 @@ void RasterizerOpenGL::SetupVertexFormat() { } state.draw.vertex_array = VAO.handle; state.ApplyVertexBufferState(); + + // Rebinding the VAO invalidates the vertex buffer bindings. + gpu.dirty_flags.vertex_array = 0xFFFFFFFF; } void RasterizerOpenGL::SetupVertexBuffer() { - MICROPROFILE_SCOPE(OpenGL_VB); - const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; + if (!gpu.dirty_flags.vertex_array) + return; + + MICROPROFILE_SCOPE(OpenGL_VB); + // Upload all guest vertex arrays sequentially to our buffer for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { + if (~gpu.dirty_flags.vertex_array & (1u << index)) + continue; + const auto& vertex_array = regs.vertex_array[index]; if (!vertex_array.IsEnabled()) continue; @@ -216,6 +239,8 @@ void RasterizerOpenGL::SetupVertexBuffer() { // Implicit set by glBindVertexBuffer. Stupid glstate handling... state.draw.vertex_buffer = buffer_cache.GetHandle(); + + gpu.dirty_flags.vertex_array = 0; } DrawParameters RasterizerOpenGL::SetupDraw() { @@ -274,6 +299,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { // shaders. The constbuffer bindpoint starts after the shader stage configuration bind points. u32 current_constbuffer_bindpoint = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage; u32 current_texture_bindpoint = 0; + std::array<bool, Maxwell::NumClipDistances> clip_distances{}; for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { const auto& shader_config = gpu.regs.shader_config[index]; @@ -334,12 +360,60 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode, current_texture_bindpoint); + // Workaround for Intel drivers. + // When a clip distance is enabled but not set in the shader it crops parts of the screen + // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the + // clip distances only when it's written by a shader stage. + for (std::size_t i = 0; i < Maxwell::NumClipDistances; ++i) { + clip_distances[i] |= shader->GetShaderEntries().clip_distances[i]; + } + // When VertexA is enabled, we have dual vertex shaders if (program == Maxwell::ShaderProgram::VertexA) { // VertexB was combined with VertexA, so we skip the VertexB iteration index++; } } + + SyncClipEnabled(clip_distances); +} + +void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, + OpenGLState& current_state) { + const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey); + auto& framebuffer = entry->second; + + if (is_cache_miss) + framebuffer.Create(); + + current_state.draw.draw_framebuffer = framebuffer.handle; + current_state.ApplyFramebufferState(); + + if (!is_cache_miss) + return; + + if (fbkey.is_single_buffer) { + if (fbkey.color_attachments[0] != GL_NONE) { + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0], + 0); + } + glDrawBuffer(fbkey.color_attachments[0]); + } else { + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + if (fbkey.colors[index]) { + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), + fbkey.colors[index], 0); + } + } + glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data()); + } + + if (fbkey.zeta) { + GLenum zeta_attachment = + fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0); + } } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { @@ -422,13 +496,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us // TODO(bunnei): Figure out how the below register works. According to envytools, this should be // used to enable multiple render targets. However, it is left unset on all games that I have // tested. - ASSERT_MSG(regs.rt_separate_frag_data == 0, "Unimplemented"); + UNIMPLEMENTED_IF(regs.rt_separate_frag_data != 0); // Bind the framebuffer surfaces - current_state.draw.draw_framebuffer = framebuffer.handle; - current_state.ApplyFramebufferState(); current_state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0; + FramebufferCacheKey fbkey; + if (using_color_fb) { if (single_color_target) { // Used when just a single color attachment is enabled, e.g. for clearing a color buffer @@ -444,14 +518,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion; } - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D, - color_surface != nullptr ? color_surface->Texture().handle : 0, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target)); + fbkey.is_single_buffer = true; + fbkey.color_attachments[0] = + GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target); + fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0; } else { // Multiple color attachments are enabled - std::array<GLenum, Maxwell::NumRenderTargets> buffers; for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents); @@ -466,22 +538,17 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us color_surface->GetSurfaceParams().srgb_conversion; } - buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), - GL_TEXTURE_2D, color_surface != nullptr ? color_surface->Texture().handle : 0, - 0); + fbkey.color_attachments[index] = + GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); + fbkey.colors[index] = + color_surface != nullptr ? color_surface->Texture().handle : 0; } - glDrawBuffers(regs.rt_control.count, buffers.data()); + fbkey.is_single_buffer = false; + fbkey.colors_count = regs.rt_control.count; } } else { - // No color attachments are enabled - zero out all of them - for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), GL_TEXTURE_2D, - 0, 0); - } - glDrawBuffer(GL_NONE); + // No color attachments are enabled - leave them as zero + fbkey.is_single_buffer = true; } if (depth_surface) { @@ -489,22 +556,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us // the shader doesn't actually write to it. depth_surface->MarkAsModified(true, res_cache); - if (regs.stencil_enable) { - // Attach both depth and stencil - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - depth_surface->Texture().handle, 0); - } else { - // Attach depth - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - depth_surface->Texture().handle, 0); - // Clear stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - } - } else { - // Clear both depth and stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); + fbkey.zeta = depth_surface->Texture().handle; + fbkey.stencil_enable = regs.stencil_enable; } + + SetupCachedFramebuffer(fbkey, current_state); + SyncViewport(current_state); } @@ -542,6 +599,30 @@ void RasterizerOpenGL::Clear() { ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); use_stencil = true; clear_state.stencil.test_enabled = true; + if (regs.clear_flags.stencil) { + // Stencil affects the clear so fill it with the used masks + clear_state.stencil.front.test_func = GL_ALWAYS; + clear_state.stencil.front.test_mask = regs.stencil_front_func_mask; + clear_state.stencil.front.action_stencil_fail = GL_KEEP; + clear_state.stencil.front.action_depth_fail = GL_KEEP; + clear_state.stencil.front.action_depth_pass = GL_KEEP; + clear_state.stencil.front.write_mask = regs.stencil_front_mask; + if (regs.stencil_two_side_enable) { + clear_state.stencil.back.test_func = GL_ALWAYS; + clear_state.stencil.back.test_mask = regs.stencil_back_func_mask; + clear_state.stencil.back.action_stencil_fail = GL_KEEP; + clear_state.stencil.back.action_depth_fail = GL_KEEP; + clear_state.stencil.back.action_depth_pass = GL_KEEP; + clear_state.stencil.back.write_mask = regs.stencil_back_mask; + } else { + clear_state.stencil.back.test_func = GL_ALWAYS; + clear_state.stencil.back.test_mask = 0xFFFFFFFF; + clear_state.stencil.back.write_mask = 0xFFFFFFFF; + clear_state.stencil.back.action_stencil_fail = GL_KEEP; + clear_state.stencil.back.action_depth_fail = GL_KEEP; + clear_state.stencil.back.action_depth_pass = GL_KEEP; + } + } } if (!use_color && !use_depth && !use_stencil) { @@ -553,6 +634,14 @@ void RasterizerOpenGL::Clear() { ConfigureFramebuffers(clear_state, use_color, use_depth || use_stencil, false, regs.clear_buffers.RT.Value()); + if (regs.clear_flags.scissor) { + SyncScissorTest(clear_state); + } + + if (regs.clear_flags.viewport) { + clear_state.EmulateViewportWithScissor(); + } + clear_state.Apply(); if (use_color) { @@ -573,7 +662,7 @@ void RasterizerOpenGL::DrawArrays() { return; MICROPROFILE_SCOPE(OpenGL_Drawing); - const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; ScopeAcquireGLContext acquire_context{emu_window}; @@ -588,12 +677,12 @@ void RasterizerOpenGL::DrawArrays() { SyncLogicOpState(); SyncCullMode(); SyncPrimitiveRestart(); - SyncScissorTest(); + SyncScissorTest(state); // Alpha Testing is synced on shaders. SyncTransformFeedback(); SyncPointState(); CheckAlphaTests(); - + SyncPolygonOffset(); // TODO(bunnei): Sync framebuffer_scale uniform here // TODO(bunnei): Sync scissorbox uniform(s) here @@ -626,7 +715,11 @@ void RasterizerOpenGL::DrawArrays() { // Add space for at least 18 constant buffers buffer_size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + uniform_buffer_alignment); - buffer_cache.Map(buffer_size); + bool invalidate = buffer_cache.Map(buffer_size); + if (invalidate) { + // As all cached buffers are invalidated, we need to recheck their state. + gpu.dirty_flags.vertex_array = 0xFFFFFFFF; + } SetupVertexFormat(); SetupVertexBuffer(); @@ -815,7 +908,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr } const u32 bias = config.mip_lod_bias.Value(); // Sign extend the 13-bit value. - const u32 mask = 1U << (13 - 1); + constexpr u32 mask = 1U << (13 - 1); const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f; if (lod_bias != bias_lod) { lod_bias = bias_lod; @@ -942,24 +1035,42 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) { - const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()}; + const bool geometry_shaders_enabled = + regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry)); + const std::size_t viewport_count = + geometry_shaders_enabled ? Tegra::Engines::Maxwell3D::Regs::NumViewports : 1; + for (std::size_t i = 0; i < viewport_count; i++) { auto& viewport = current_state.viewports[i]; + const auto& src = regs.viewports[i]; + const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()}; viewport.x = viewport_rect.left; viewport.y = viewport_rect.bottom; - viewport.width = static_cast<GLfloat>(viewport_rect.GetWidth()); - viewport.height = static_cast<GLfloat>(viewport_rect.GetHeight()); + viewport.width = viewport_rect.GetWidth(); + viewport.height = viewport_rect.GetHeight(); viewport.depth_range_far = regs.viewports[i].depth_range_far; viewport.depth_range_near = regs.viewports[i].depth_range_near; } + state.depth_clamp.far_plane = regs.view_volume_clip_control.depth_clamp_far != 0; + state.depth_clamp.near_plane = regs.view_volume_clip_control.depth_clamp_near != 0; } -void RasterizerOpenGL::SyncClipEnabled() { - UNREACHABLE(); +void RasterizerOpenGL::SyncClipEnabled( + const std::array<bool, Maxwell::Regs::NumClipDistances>& clip_mask) { + + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const std::array<bool, Maxwell::Regs::NumClipDistances> reg_state{ + regs.clip_distance_enabled.c0 != 0, regs.clip_distance_enabled.c1 != 0, + regs.clip_distance_enabled.c2 != 0, regs.clip_distance_enabled.c3 != 0, + regs.clip_distance_enabled.c4 != 0, regs.clip_distance_enabled.c5 != 0, + regs.clip_distance_enabled.c6 != 0, regs.clip_distance_enabled.c7 != 0}; + + for (std::size_t i = 0; i < Maxwell::Regs::NumClipDistances; ++i) { + state.clip_distance[i] = reg_state[i] && clip_mask[i]; + } } void RasterizerOpenGL::SyncClipCoef() { - UNREACHABLE(); + UNIMPLEMENTED(); } void RasterizerOpenGL::SyncCullMode() { @@ -1120,11 +1231,15 @@ void RasterizerOpenGL::SyncLogicOpState() { state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } -void RasterizerOpenGL::SyncScissorTest() { +void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) { + const bool geometry_shaders_enabled = + regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry)); + const std::size_t viewport_count = + geometry_shaders_enabled ? Tegra::Engines::Maxwell3D::Regs::NumViewports : 1; + for (std::size_t i = 0; i < viewport_count; i++) { const auto& src = regs.scissor_test[i]; - auto& dst = state.viewports[i].scissor; + auto& dst = current_state.viewports[i].scissor; dst.enabled = (src.enable != 0); if (dst.enabled == 0) { return; @@ -1152,6 +1267,16 @@ void RasterizerOpenGL::SyncPointState() { state.point.size = regs.point_size; } +void RasterizerOpenGL::SyncPolygonOffset() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0; + state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0; + state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0; + state.polygon_offset.units = regs.polygon_offset_units; + state.polygon_offset.factor = regs.polygon_offset_factor; + state.polygon_offset.clamp = regs.polygon_offset_clamp; +} + void RasterizerOpenGL::CheckAlphaTests() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 6e78ab4cd..8a891ffc7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -40,6 +40,7 @@ namespace OpenGL { struct ScreenInfo; struct DrawParameters; +struct FramebufferCacheKey; class RasterizerOpenGL : public VideoCore::RasterizerInterface { public: @@ -60,20 +61,6 @@ public: bool AccelerateDrawBatch(bool is_indexed) override; void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override; - /// OpenGL shader generated for a given Maxwell register state - struct MaxwellShader { - /// OpenGL shader resource - OGLProgram shader; - }; - - struct VertexShader { - OGLShader shader; - }; - - struct FragmentShader { - OGLShader shader; - }; - /// Maximum supported size that a constbuffer can have in bytes. static constexpr std::size_t MaxConstbufferSize = 0x10000; static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0, @@ -91,19 +78,20 @@ private: void SyncWithConfig(const Tegra::Texture::TSCEntry& info); private: - Tegra::Texture::TextureFilter mag_filter; - Tegra::Texture::TextureFilter min_filter; - Tegra::Texture::TextureMipmapFilter mip_filter; - Tegra::Texture::WrapMode wrap_u; - Tegra::Texture::WrapMode wrap_v; - Tegra::Texture::WrapMode wrap_p; - bool uses_depth_compare; - Tegra::Texture::DepthCompareFunc depth_compare_func; - GLvec4 border_color; - float min_lod; - float max_lod; - float lod_bias; - float max_anisotropic; + Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest; + Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest; + Tegra::Texture::TextureMipmapFilter mip_filter = Tegra::Texture::TextureMipmapFilter::None; + Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge; + Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge; + Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge; + bool uses_depth_compare = false; + Tegra::Texture::DepthCompareFunc depth_compare_func = + Tegra::Texture::DepthCompareFunc::Always; + GLvec4 border_color = {}; + float min_lod = 0.0f; + float max_lod = 16.0f; + float lod_bias = 0.0f; + float max_anisotropic = 1.0f; }; /** @@ -141,7 +129,8 @@ private: void SyncViewport(OpenGLState& current_state); /// Syncs the clip enabled status to match the guest state - void SyncClipEnabled(); + void SyncClipEnabled( + const std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances>& clip_mask); /// Syncs the clip coefficients to match the guest state void SyncClipCoef(); @@ -171,7 +160,7 @@ private: void SyncMultiSampleState(); /// Syncs the scissor test state to match the guest state - void SyncScissorTest(); + void SyncScissorTest(OpenGLState& current_state); /// Syncs the transform feedback state to match the guest state void SyncTransformFeedback(); @@ -182,13 +171,15 @@ private: /// Syncs Color Mask void SyncColorMask(); + /// Syncs the polygon offsets + void SyncPolygonOffset(); + /// Check asserts for alpha testing. void CheckAlphaTests(); - bool has_ARB_direct_state_access = false; - bool has_ARB_multi_bind = false; - bool has_ARB_separate_shader_objects = false; - bool has_ARB_vertex_attrib_binding = false; + /// Check for extension that are not strictly required + /// but are needed for correct emulation + void CheckExtensions(); OpenGLState state; @@ -205,11 +196,12 @@ private: OGLVertexArray> vertex_array_cache; + std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache; + std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers; static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; OGLBufferCache buffer_cache; - OGLFramebuffer framebuffer; PrimitiveAssembler primitive_assembler{buffer_cache}; GLint uniform_buffer_alignment; @@ -224,6 +216,8 @@ private: void SetupShaders(GLenum primitive_mode); + void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state); + enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 9ca82c06c..5f4cdd119 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -15,6 +15,7 @@ #include "core/memory.h" #include "core/settings.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_state.h" @@ -22,10 +23,11 @@ #include "video_core/surface.h" #include "video_core/textures/astc.h" #include "video_core/textures/decoders.h" -#include "video_core/utils.h" namespace OpenGL { +using VideoCore::MortonSwizzle; +using VideoCore::MortonSwizzleMode; using VideoCore::Surface::ComponentTypeFromDepthFormat; using VideoCore::Surface::ComponentTypeFromRenderTarget; using VideoCore::Surface::ComponentTypeFromTexture; @@ -95,6 +97,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), params.srgb_conversion); @@ -160,6 +163,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.block_width = 1 << config.memory_layout.block_width; params.block_height = 1 << config.memory_layout.block_height; params.block_depth = 1 << config.memory_layout.block_depth; + params.tile_width_spacing = 1; params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; @@ -195,6 +199,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.block_width = 1 << std::min(block_width, 5U); params.block_height = 1 << std::min(block_height, 5U); params.block_depth = 1 << std::min(block_depth, 5U); + params.tile_width_spacing = 1; params.pixel_format = PixelFormatFromDepthFormat(format); params.component_type = ComponentTypeFromDepthFormat(format); params.type = GetFormatType(params.pixel_format); @@ -221,6 +226,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, + params.tile_width_spacing = 1; params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; @@ -265,11 +271,11 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN2UNORM {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM - {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + {GL_COMPRESSED_RGBA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // BC7U - {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, - ComponentType::Float, true}, // BC6H_UF16 - {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + true}, // BC6H_UF16 + {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, true}, // BC6H_SF16 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U @@ -306,8 +312,8 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex true}, // DXT23_SRGB {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXT45_SRGB - {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, - ComponentType::UNorm, true}, // BC7U_SRGB + {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // BC7U_SRGB {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB @@ -346,7 +352,7 @@ static GLenum SurfaceTargetToGL(SurfaceTarget target) { case SurfaceTarget::TextureCubemap: return GL_TEXTURE_CUBE_MAP; case SurfaceTarget::TextureCubeArray: - return GL_TEXTURE_CUBE_MAP_ARRAY_ARB; + return GL_TEXTURE_CUBE_MAP_ARRAY; } LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target)); UNREACHABLE(); @@ -370,174 +376,7 @@ MathUtil::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const { return {0, actual_height, MipWidth(mip_level), 0}; } -template <bool morton_to_gl, PixelFormat format> -void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer, - std::size_t gl_buffer_size, VAddr addr) { - constexpr u32 bytes_per_pixel = GetBytesPerPixel(format); - - // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual - // pixel values. - const u32 tile_size_x{GetDefaultBlockWidth(format)}; - const u32 tile_size_y{GetDefaultBlockHeight(format)}; - - if (morton_to_gl) { - Tegra::Texture::UnswizzleTexture(gl_buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, - stride, height, depth, block_height, block_depth); - } else { - Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x, - (height + tile_size_y - 1) / tile_size_y, depth, - bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), - gl_buffer, false, block_height, block_depth); - } -} - -using GLConversionArray = std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), - VideoCore::Surface::MaxPixelFormat>; - -static constexpr GLConversionArray morton_to_gl_fns = { - // clang-format off - MortonCopy<true, PixelFormat::ABGR8U>, - MortonCopy<true, PixelFormat::ABGR8S>, - MortonCopy<true, PixelFormat::ABGR8UI>, - MortonCopy<true, PixelFormat::B5G6R5U>, - MortonCopy<true, PixelFormat::A2B10G10R10U>, - MortonCopy<true, PixelFormat::A1B5G5R5U>, - MortonCopy<true, PixelFormat::R8U>, - MortonCopy<true, PixelFormat::R8UI>, - MortonCopy<true, PixelFormat::RGBA16F>, - MortonCopy<true, PixelFormat::RGBA16U>, - MortonCopy<true, PixelFormat::RGBA16UI>, - MortonCopy<true, PixelFormat::R11FG11FB10F>, - MortonCopy<true, PixelFormat::RGBA32UI>, - MortonCopy<true, PixelFormat::DXT1>, - MortonCopy<true, PixelFormat::DXT23>, - MortonCopy<true, PixelFormat::DXT45>, - MortonCopy<true, PixelFormat::DXN1>, - MortonCopy<true, PixelFormat::DXN2UNORM>, - MortonCopy<true, PixelFormat::DXN2SNORM>, - MortonCopy<true, PixelFormat::BC7U>, - MortonCopy<true, PixelFormat::BC6H_UF16>, - MortonCopy<true, PixelFormat::BC6H_SF16>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4>, - MortonCopy<true, PixelFormat::G8R8U>, - MortonCopy<true, PixelFormat::G8R8S>, - MortonCopy<true, PixelFormat::BGRA8>, - MortonCopy<true, PixelFormat::RGBA32F>, - MortonCopy<true, PixelFormat::RG32F>, - MortonCopy<true, PixelFormat::R32F>, - MortonCopy<true, PixelFormat::R16F>, - MortonCopy<true, PixelFormat::R16U>, - MortonCopy<true, PixelFormat::R16S>, - MortonCopy<true, PixelFormat::R16UI>, - MortonCopy<true, PixelFormat::R16I>, - MortonCopy<true, PixelFormat::RG16>, - MortonCopy<true, PixelFormat::RG16F>, - MortonCopy<true, PixelFormat::RG16UI>, - MortonCopy<true, PixelFormat::RG16I>, - MortonCopy<true, PixelFormat::RG16S>, - MortonCopy<true, PixelFormat::RGB32F>, - MortonCopy<true, PixelFormat::RGBA8_SRGB>, - MortonCopy<true, PixelFormat::RG8U>, - MortonCopy<true, PixelFormat::RG8S>, - MortonCopy<true, PixelFormat::RG32UI>, - MortonCopy<true, PixelFormat::R32UI>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4>, - MortonCopy<true, PixelFormat::BGRA8_SRGB>, - MortonCopy<true, PixelFormat::DXT1_SRGB>, - MortonCopy<true, PixelFormat::DXT23_SRGB>, - MortonCopy<true, PixelFormat::DXT45_SRGB>, - MortonCopy<true, PixelFormat::BC7U_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5>, - MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8>, - MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>, - MortonCopy<true, PixelFormat::Z32F>, - MortonCopy<true, PixelFormat::Z16>, - MortonCopy<true, PixelFormat::Z24S8>, - MortonCopy<true, PixelFormat::S8Z24>, - MortonCopy<true, PixelFormat::Z32FS8>, - // clang-format on -}; - -static constexpr GLConversionArray gl_to_morton_fns = { - // clang-format off - MortonCopy<false, PixelFormat::ABGR8U>, - MortonCopy<false, PixelFormat::ABGR8S>, - MortonCopy<false, PixelFormat::ABGR8UI>, - MortonCopy<false, PixelFormat::B5G6R5U>, - MortonCopy<false, PixelFormat::A2B10G10R10U>, - MortonCopy<false, PixelFormat::A1B5G5R5U>, - MortonCopy<false, PixelFormat::R8U>, - MortonCopy<false, PixelFormat::R8UI>, - MortonCopy<false, PixelFormat::RGBA16F>, - MortonCopy<false, PixelFormat::RGBA16U>, - MortonCopy<false, PixelFormat::RGBA16UI>, - MortonCopy<false, PixelFormat::R11FG11FB10F>, - MortonCopy<false, PixelFormat::RGBA32UI>, - MortonCopy<false, PixelFormat::DXT1>, - MortonCopy<false, PixelFormat::DXT23>, - MortonCopy<false, PixelFormat::DXT45>, - MortonCopy<false, PixelFormat::DXN1>, - MortonCopy<false, PixelFormat::DXN2UNORM>, - MortonCopy<false, PixelFormat::DXN2SNORM>, - MortonCopy<false, PixelFormat::BC7U>, - MortonCopy<false, PixelFormat::BC6H_UF16>, - MortonCopy<false, PixelFormat::BC6H_SF16>, - // TODO(Subv): Swizzling ASTC formats are not supported - nullptr, - MortonCopy<false, PixelFormat::G8R8U>, - MortonCopy<false, PixelFormat::G8R8S>, - MortonCopy<false, PixelFormat::BGRA8>, - MortonCopy<false, PixelFormat::RGBA32F>, - MortonCopy<false, PixelFormat::RG32F>, - MortonCopy<false, PixelFormat::R32F>, - MortonCopy<false, PixelFormat::R16F>, - MortonCopy<false, PixelFormat::R16U>, - MortonCopy<false, PixelFormat::R16S>, - MortonCopy<false, PixelFormat::R16UI>, - MortonCopy<false, PixelFormat::R16I>, - MortonCopy<false, PixelFormat::RG16>, - MortonCopy<false, PixelFormat::RG16F>, - MortonCopy<false, PixelFormat::RG16UI>, - MortonCopy<false, PixelFormat::RG16I>, - MortonCopy<false, PixelFormat::RG16S>, - MortonCopy<false, PixelFormat::RGB32F>, - MortonCopy<false, PixelFormat::RGBA8_SRGB>, - MortonCopy<false, PixelFormat::RG8U>, - MortonCopy<false, PixelFormat::RG8S>, - MortonCopy<false, PixelFormat::RG32UI>, - MortonCopy<false, PixelFormat::R32UI>, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::BGRA8_SRGB>, - MortonCopy<false, PixelFormat::DXT1_SRGB>, - MortonCopy<false, PixelFormat::DXT23_SRGB>, - MortonCopy<false, PixelFormat::DXT45_SRGB>, - MortonCopy<false, PixelFormat::BC7U_SRGB>, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - MortonCopy<false, PixelFormat::Z32F>, - MortonCopy<false, PixelFormat::Z16>, - MortonCopy<false, PixelFormat::Z24S8>, - MortonCopy<false, PixelFormat::S8Z24>, - MortonCopy<false, PixelFormat::Z32FS8>, - // clang-format on -}; - -void SwizzleFunc(const GLConversionArray& functions, const SurfaceParams& params, +void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params, std::vector<u8>& gl_buffer, u32 mip_level) { u32 depth = params.MipDepth(mip_level); if (params.target == SurfaceTarget::Texture2D) { @@ -550,154 +389,22 @@ void SwizzleFunc(const GLConversionArray& functions, const SurfaceParams& params const u64 layer_size = params.LayerMemorySize(); const u64 gl_size = params.LayerSizeGL(mip_level); for (u32 i = 0; i < params.depth; i++) { - functions[static_cast<std::size_t>(params.pixel_format)]( - params.MipWidth(mip_level), params.MipBlockHeight(mip_level), - params.MipHeight(mip_level), params.MipBlockDepth(mip_level), 1, - gl_buffer.data() + offset_gl, gl_size, params.addr + offset); + MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), + params.MipBlockHeight(mip_level), params.MipHeight(mip_level), + params.MipBlockDepth(mip_level), params.tile_width_spacing, 1, + gl_buffer.data() + offset_gl, gl_size, params.addr + offset); offset += layer_size; offset_gl += gl_size; } } else { const u64 offset = params.GetMipmapLevelOffset(mip_level); - functions[static_cast<std::size_t>(params.pixel_format)]( - params.MipWidth(mip_level), params.MipBlockHeight(mip_level), - params.MipHeight(mip_level), params.MipBlockDepth(mip_level), depth, gl_buffer.data(), - gl_buffer.size(), params.addr + offset); + MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), + params.MipBlockHeight(mip_level), params.MipHeight(mip_level), + params.MipBlockDepth(mip_level), depth, params.tile_width_spacing, + gl_buffer.data(), gl_buffer.size(), params.addr + offset); } } -MICROPROFILE_DEFINE(OpenGL_BlitSurface, "OpenGL", "BlitSurface", MP_RGB(128, 192, 64)); -static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface, - GLuint read_fb_handle, GLuint draw_fb_handle, GLenum src_attachment = 0, - GLenum dst_attachment = 0, std::size_t cubemap_face = 0) { - MICROPROFILE_SCOPE(OpenGL_BlitSurface); - - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - OpenGLState prev_state{OpenGLState::GetCurState()}; - SCOPE_EXIT({ prev_state.Apply(); }); - - OpenGLState state; - state.draw.read_framebuffer = read_fb_handle; - state.draw.draw_framebuffer = draw_fb_handle; - // Set sRGB enabled if the destination surfaces need it - state.framebuffer_srgb.enabled = dst_params.srgb_conversion; - state.ApplyFramebufferState(); - - u32 buffers{}; - - if (src_params.type == SurfaceType::ColorTexture) { - switch (src_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - src_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - src_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - SurfaceTargetToGL(src_params.target), - src_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(src_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - switch (dst_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - dst_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - dst_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - SurfaceTargetToGL(dst_params.target), - dst_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(dst_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - buffers = GL_COLOR_BUFFER_BIT; - } else if (src_params.type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - buffers = GL_DEPTH_BUFFER_BIT; - } else if (src_params.type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - - buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - } - - const auto& rect{src_params.GetRect()}; - glBlitFramebuffer(rect.left, rect.bottom, rect.right, rect.top, rect.left, rect.bottom, - rect.right, rect.top, buffers, - buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); - - return true; -} - static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) { const auto& src_params{src_surface->GetSurfaceParams()}; const auto& dst_params{dst_surface->GetSurfaceParams()}; @@ -726,7 +433,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes); glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); - glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB); + glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW); if (source_format.compressed) { glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, static_cast<GLsizei>(src_params.size_in_bytes), nullptr); @@ -996,15 +703,16 @@ void CachedSurface::LoadGLBuffer() { ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", params.block_width, static_cast<u32>(params.target)); for (u32 i = 0; i < params.max_mip_level; i++) - SwizzleFunc(morton_to_gl_fns, params, gl_buffer[i], i); + SwizzleFunc(MortonSwizzleMode::MortonToLinear, params, gl_buffer[i], i); } else { const auto texture_src_data{Memory::GetPointer(params.addr)}; const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl}; gl_buffer[0].assign(texture_src_data, texture_src_data_end); } - for (u32 i = 0; i < params.max_mip_level; i++) + for (u32 i = 0; i < params.max_mip_level; i++) { ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer[i], params.pixel_format, params.MipWidth(i), params.MipHeight(i), params.MipDepth(i)); + } } MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); @@ -1035,7 +743,7 @@ void CachedSurface::FlushGLBuffer() { ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", params.block_width, static_cast<u32>(params.target)); - SwizzleFunc(gl_to_morton_fns, params, gl_buffer[0], 0); + SwizzleFunc(MortonSwizzleMode::LinearToMorton, params, gl_buffer[0], 0); } else { std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer[0].data(), GetSizeInBytes()); } @@ -1275,6 +983,31 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) { return surface; } +void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface, + const Surface& dst_surface) { + const auto& init_params{src_surface->GetSurfaceParams()}; + const auto& dst_params{dst_surface->GetSurfaceParams()}; + VAddr address = init_params.addr; + const std::size_t layer_size = dst_params.LayerMemorySize(); + for (u32 layer = 0; layer < dst_params.depth; layer++) { + for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) { + const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap); + const Surface& copy = TryGet(sub_address); + if (!copy) + continue; + const auto& src_params{copy->GetSurfaceParams()}; + const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))}; + const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))}; + + glCopyImageSubData(copy->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, + 0, 0, dst_surface->Texture().handle, + SurfaceTargetToGL(dst_params.target), mipmap, 0, 0, layer, width, + height, 1); + } + address += layer_size; + } +} + void RasterizerCacheOpenGL::FermiCopySurface( const Tegra::Engines::Fermi2D::Regs::Surface& src_config, const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) { @@ -1299,7 +1032,10 @@ void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface) { const auto& src_params{src_surface->GetSurfaceParams()}; const auto& dst_params{dst_surface->GetSurfaceParams()}; - FlushRegion(src_params.addr, dst_params.MemorySize()); + + // Flush enough memory for both the source and destination surface + FlushRegion(src_params.addr, std::max(src_params.MemorySize(), dst_params.MemorySize())); + LoadSurface(dst_surface); } @@ -1325,26 +1061,17 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, return new_surface; } - // If the format is the same, just do a framebuffer blit. This is significantly faster than - // using PBOs. The is also likely less accurate, as textures will be converted rather than - // reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate - // surface copy, where pixels are reinterpreted as a new format (without conversion). This - // code path uses OpenGL PBOs and is quite slow. - const bool is_blit{old_params.pixel_format == new_params.pixel_format}; - switch (new_params.target) { case SurfaceTarget::Texture2D: - if (is_blit) { - BlitSurface(old_surface, new_surface, read_framebuffer.handle, draw_framebuffer.handle); - } else { - CopySurface(old_surface, new_surface, copy_pbo.handle); - } + CopySurface(old_surface, new_surface, copy_pbo.handle); break; - case SurfaceTarget::TextureCubemap: case SurfaceTarget::Texture3D: + AccurateCopySurface(old_surface, new_surface); + break; + case SurfaceTarget::TextureCubemap: case SurfaceTarget::Texture2DArray: case SurfaceTarget::TextureCubeArray: - AccurateCopySurface(old_surface, new_surface); + FastLayeredCopySurface(old_surface, new_surface); break; default: LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 494f6b903..c710aa245 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -196,9 +196,15 @@ struct SurfaceParams { /// Checks if surfaces are compatible for caching bool IsCompatibleSurface(const SurfaceParams& other) const { - return std::tie(pixel_format, type, width, height, target, depth) == - std::tie(other.pixel_format, other.type, other.width, other.height, other.target, - other.depth); + if (std::tie(pixel_format, type, width, height, target, depth, is_tiled) == + std::tie(other.pixel_format, other.type, other.width, other.height, other.target, + other.depth, other.is_tiled)) { + if (!is_tiled) + return true; + return std::tie(block_height, block_depth, tile_width_spacing) == + std::tie(other.block_height, other.block_depth, other.tile_width_spacing); + } + return false; } /// Initializes parameters for caching, should be called after everything has been initialized @@ -208,6 +214,7 @@ struct SurfaceParams { u32 block_width; u32 block_height; u32 block_depth; + u32 tile_width_spacing; PixelFormat pixel_format; ComponentType component_type; SurfaceType type; @@ -350,6 +357,7 @@ private: /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface); + void FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface); /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have /// previously been used. This is to prevent surfaces from being constantly created and diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index a85a7c0c5..aea6bf1af 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <boost/functional/hash.hpp> #include "common/assert.h" +#include "common/hash.h" #include "core/core.h" #include "core/memory.h" #include "video_core/engines/maxwell_3d.h" @@ -66,14 +68,17 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) // stage here. setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB))); case Maxwell::ShaderProgram::VertexB: + CalculateProperties(); program_result = GLShader::GenerateVertexShader(setup); gl_type = GL_VERTEX_SHADER; break; case Maxwell::ShaderProgram::Geometry: + CalculateProperties(); program_result = GLShader::GenerateGeometryShader(setup); gl_type = GL_GEOMETRY_SHADER; break; case Maxwell::ShaderProgram::Fragment: + CalculateProperties(); program_result = GLShader::GenerateFragmentShader(setup); gl_type = GL_FRAGMENT_SHADER; break; @@ -84,6 +89,7 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) } entries = program_result.second; + shader_length = entries.shader_length; if (program_type != Maxwell::ShaderProgram::Geometry) { OGLShader shader; @@ -139,6 +145,46 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, return target_program.handle; }; +static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) { + // sched instructions appear once every 4 instructions. + static constexpr std::size_t SchedPeriod = 4; + const std::size_t absolute_offset = offset - main_offset; + return (absolute_offset % SchedPeriod) == 0; +} + +static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { + constexpr std::size_t start_offset = 10; + std::size_t offset = start_offset; + std::size_t size = start_offset * sizeof(u64); + while (offset < program.size()) { + const u64 inst = program[offset]; + if (!IsSchedInstruction(offset, start_offset)) { + if (inst == 0 || (inst >> 52) == 0x50b) { + break; + } + } + size += sizeof(inst); + offset++; + } + return size; +} + +void CachedShader::CalculateProperties() { + setup.program.real_size = CalculateProgramSize(setup.program.code); + setup.program.real_size_b = 0; + setup.program.unique_identifier = Common::CityHash64( + reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size); + if (program_type == Maxwell::ShaderProgram::VertexA) { + std::size_t seed = 0; + boost::hash_combine(seed, setup.program.unique_identifier); + setup.program.real_size_b = CalculateProgramSize(setup.program.code_b); + const u64 identifier_b = Common::CityHash64( + reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b); + boost::hash_combine(seed, identifier_b); + setup.program.unique_identifier = static_cast<u64>(seed); + } +} + ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {} Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index ffbf21831..b4ef6030d 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -30,7 +30,7 @@ public: } std::size_t GetSizeInBytes() const override { - return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64); + return shader_length; } // We do not have to flush this cache as things in it are never modified by us. @@ -81,7 +81,10 @@ private: GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology, u32 max_vertices, const std::string& debug_name); + void CalculateProperties(); + VAddr addr; + std::size_t shader_length; Maxwell::ShaderProgram program_type; GLShader::ShaderSetup setup; GLShader::ShaderEntries entries; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 5fde22ad4..a5cfa0070 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -34,11 +34,30 @@ constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); constexpr u32 MAX_GEOMETRY_BUFFERS = 6; constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested +static const char* INTERNAL_FLAG_NAMES[] = {"zero_flag", "sign_flag", "carry_flag", + "overflow_flag"}; + +enum class InternalFlag : u64 { + ZeroFlag = 0, + SignFlag = 1, + CarryFlag = 2, + OverflowFlag = 3, + Amount +}; + class DecompileFail : public std::runtime_error { public: using std::runtime_error::runtime_error; }; +/// Generates code to use for a swizzle operation. +static std::string GetSwizzle(u64 elem) { + ASSERT(elem <= 3); + std::string swizzle = "."; + swizzle += "xyzw"[elem]; + return swizzle; +} + /// Translate topology static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { switch (topology) { @@ -49,8 +68,7 @@ static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { case Tegra::Shader::OutputTopology::TriangleStrip: return "triangle_strip"; default: - LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology)); return "points"; } } @@ -85,7 +103,8 @@ struct Subroutine { class ControlFlowAnalyzer { public: ControlFlowAnalyzer(const ProgramCode& program_code, u32 main_offset, const std::string& suffix) - : program_code(program_code) { + : program_code(program_code), shader_coverage_begin(main_offset), + shader_coverage_end(main_offset + 1) { // Recursively finds all subroutines. const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END, suffix); @@ -97,10 +116,16 @@ public: return std::move(subroutines); } + std::size_t GetShaderLength() const { + return shader_coverage_end * sizeof(u64); + } + private: const ProgramCode& program_code; std::set<Subroutine> subroutines; std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; + u32 shader_coverage_begin; + u32 shader_coverage_end; /// Adds and analyzes a new subroutine if it is not added yet. const Subroutine& AddSubroutine(u32 begin, u32 end, const std::string& suffix) { @@ -142,6 +167,9 @@ private: return exit_method; for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) { + shader_coverage_begin = std::min(shader_coverage_begin, offset); + shader_coverage_end = std::max(shader_coverage_end, offset + 1); + const Instruction instr = {program_code[offset]}; if (const auto opcode = OpCode::Decode(instr)) { switch (opcode->get().GetId()) { @@ -167,8 +195,8 @@ private: case OpCode::Id::SSY: case OpCode::Id::PBK: { // The SSY and PBK use a similar encoding as the BRA instruction. - ASSERT_MSG(instr.bra.constant_buffer == 0, - "Constant buffer branching is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer branching is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. @@ -181,14 +209,53 @@ private: } }; +template <typename T> +class ShaderScopedScope { +public: + explicit ShaderScopedScope(T& writer, std::string_view begin_expr, std::string end_expr) + : writer(writer), end_expr(std::move(end_expr)) { + + if (begin_expr.empty()) { + writer.AddLine('{'); + } else { + writer.AddExpression(begin_expr); + writer.AddLine(" {"); + } + ++writer.scope; + } + + ShaderScopedScope(const ShaderScopedScope&) = delete; + + ~ShaderScopedScope() { + --writer.scope; + if (end_expr.empty()) { + writer.AddLine('}'); + } else { + writer.AddExpression("} "); + writer.AddExpression(end_expr); + writer.AddLine(';'); + } + } + + ShaderScopedScope& operator=(const ShaderScopedScope&) = delete; + +private: + T& writer; + std::string end_expr; +}; + class ShaderWriter { public: - void AddLine(std::string_view text) { + void AddExpression(std::string_view text) { DEBUG_ASSERT(scope >= 0); if (!text.empty()) { AppendIndentation(); } shader_source += text; + } + + void AddLine(std::string_view text) { + AddExpression(text); AddNewLine(); } @@ -208,6 +275,11 @@ public: return std::move(shader_source); } + ShaderScopedScope<ShaderWriter> Scope(std::string_view begin_expr = {}, + std::string end_expr = {}) { + return ShaderScopedScope(*this, begin_expr, end_expr); + } + int scope = 0; private: @@ -258,14 +330,6 @@ private: const std::string& suffix; }; -enum class InternalFlag : u64 { - ZeroFlag = 0, - CarryFlag = 1, - OverflowFlag = 2, - NaNFlag = 3, - Amount -}; - /** * Used to manage shader registers that are emulated with GLSL. This class keeps track of the state * of all registers (e.g. whether they are currently being used as Floats or Integers), and @@ -299,8 +363,7 @@ public: // Default - do nothing return value; default: - LOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size)); - UNREACHABLE(); + UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size)); } } @@ -363,7 +426,7 @@ public: u64 value_num_components, bool is_saturated = false, u64 dest_elem = 0, Register::Size size = Register::Size::Word, bool sets_cc = false) { - ASSERT_MSG(!is_saturated, "Unimplemented"); + UNIMPLEMENTED_IF(is_saturated); const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"}; @@ -373,7 +436,7 @@ public: if (sets_cc) { const std::string zero_condition = "( " + ConvertIntegerSize(value, size) + " == 0 )"; SetInternalFlag(InternalFlag::ZeroFlag, zero_condition); - LOG_WARNING(HW_GPU, "Control Codes Imcomplete."); + LOG_WARNING(HW_GPU, "Condition codes implementation is incomplete."); } } @@ -392,7 +455,7 @@ public: Tegra::Shader::HalfMerge merge, u64 dest_num_components, u64 value_num_components, bool is_saturated = false, u64 dest_elem = 0) { - ASSERT_MSG(!is_saturated, "Unimplemented"); + UNIMPLEMENTED_IF(is_saturated); const std::string result = [&]() { switch (merge) { @@ -456,24 +519,25 @@ public: shader.AddLine("lmem[" + index + "] = " + func + '(' + value + ");"); } - std::string GetControlCode(const Tegra::Shader::ControlCode cc) const { + std::string GetConditionCode(const Tegra::Shader::ConditionCode cc) const { switch (cc) { - case Tegra::Shader::ControlCode::NEU: + case Tegra::Shader::ConditionCode::NEU: return "!(" + GetInternalFlag(InternalFlag::ZeroFlag) + ')'; default: - LOG_CRITICAL(HW_GPU, "Unimplemented Control Code {}", static_cast<u32>(cc)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc)); return "false"; } } - std::string GetInternalFlag(const InternalFlag ii) const { - const u32 code = static_cast<u32>(ii); - return "internalFlag_" + std::to_string(code) + suffix; + std::string GetInternalFlag(const InternalFlag flag) const { + const auto index = static_cast<u32>(flag); + ASSERT(index < static_cast<u32>(InternalFlag::Amount)); + + return std::string(INTERNAL_FLAG_NAMES[index]) + '_' + suffix; } - void SetInternalFlag(const InternalFlag ii, const std::string& value) const { - shader.AddLine(GetInternalFlag(ii) + " = " + value + ';'); + void SetInternalFlag(const InternalFlag flag, const std::string& value) const { + shader.AddLine(GetInternalFlag(flag) + " = " + value + ';'); } /** @@ -488,27 +552,43 @@ public: const Register& buf_reg) { const std::string dest = GetOutputAttribute(attribute); const std::string src = GetRegisterAsFloat(val_reg); + if (dest.empty()) + return; - if (!dest.empty()) { - // Can happen with unknown/unimplemented output attributes, in which case we ignore the - // instruction for now. - if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { - // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry - // shader. These instructions use a dirty register as buffer index, to avoid some - // drivers from complaining about out of boundary writes, guard them. - const std::string buf_index{"((" + GetRegisterAsInteger(buf_reg) + ") % " + - std::to_string(MAX_GEOMETRY_BUFFERS) + ')'}; - shader.AddLine("amem[" + buf_index + "][" + - std::to_string(static_cast<u32>(attribute)) + ']' + - GetSwizzle(elem) + " = " + src + ';'); - } else { - if (attribute == Attribute::Index::PointSize) { - fixed_pipeline_output_attributes_used.insert(attribute); - shader.AddLine(dest + " = " + src + ';'); - } else { - shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); - } - } + // Can happen with unknown/unimplemented output attributes, in which case we ignore the + // instruction for now. + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry + // shader. These instructions use a dirty register as buffer index, to avoid some + // drivers from complaining about out of boundary writes, guard them. + const std::string buf_index{"((" + GetRegisterAsInteger(buf_reg) + ") % " + + std::to_string(MAX_GEOMETRY_BUFFERS) + ')'}; + shader.AddLine("amem[" + buf_index + "][" + + std::to_string(static_cast<u32>(attribute)) + ']' + GetSwizzle(elem) + + " = " + src + ';'); + return; + } + + switch (attribute) { + case Attribute::Index::ClipDistances0123: + case Attribute::Index::ClipDistances4567: { + const u64 index = (attribute == Attribute::Index::ClipDistances4567 ? 4 : 0) + elem; + UNIMPLEMENTED_IF_MSG( + ((header.vtg.clip_distances >> index) & 1) == 0, + "Shader is setting gl_ClipDistance{} without enabling it in the header", index); + + clip_distances[index] = true; + fixed_pipeline_output_attributes_used.insert(attribute); + shader.AddLine(dest + '[' + std::to_string(index) + "] = " + src + ';'); + break; + } + case Attribute::Index::PointSize: + fixed_pipeline_output_attributes_used.insert(attribute); + shader.AddLine(dest + " = " + src + ';'); + break; + default: + shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + break; } } @@ -575,6 +655,11 @@ public: return used_samplers; } + /// Returns an array of the used clip distances. + const std::array<bool, Maxwell::NumClipDistances>& GetClipDistances() const { + return clip_distances; + } + /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if /// necessary. std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, @@ -624,8 +709,8 @@ private: /// Generates declarations for internal flags. void GenerateInternalFlags() { - for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) { - const InternalFlag code = static_cast<InternalFlag>(ii); + for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) { + const InternalFlag code = static_cast<InternalFlag>(flag); declarations.AddLine("bool " + GetInternalFlag(code) + " = false;"); } declarations.AddNewLine(); @@ -728,12 +813,19 @@ private: void GenerateVertex() { if (stage != Maxwell3D::Regs::ShaderStage::Vertex) return; + bool clip_distances_declared = false; + declarations.AddLine("out gl_PerVertex {"); ++declarations.scope; declarations.AddLine("vec4 gl_Position;"); for (auto& o : fixed_pipeline_output_attributes_used) { if (o == Attribute::Index::PointSize) declarations.AddLine("float gl_PointSize;"); + if (!clip_distances_declared && (o == Attribute::Index::ClipDistances0123 || + o == Attribute::Index::ClipDistances4567)) { + declarations.AddLine("float gl_ClipDistance[];"); + clip_distances_declared = true; + } } --declarations.scope; declarations.AddLine("};"); @@ -761,8 +853,7 @@ private: u64 dest_num_components, u64 value_num_components, u64 dest_elem, bool precise) { if (reg == Register::ZeroIndex) { - LOG_CRITICAL(HW_GPU, "Cannot set Register::ZeroIndex"); - UNREACHABLE(); + // Setting RZ is a nop in hardware. return; } @@ -777,14 +868,12 @@ private: } if (precise && stage != Maxwell3D::Regs::ShaderStage::Fragment) { - shader.AddLine('{'); - ++shader.scope; + const auto scope = shader.Scope(); + // This avoids optimizations of constant propagation and keeps the code as the original // Sadly using the precise keyword causes "linking" errors on fragment shaders. shader.AddLine("precise float tmp = " + src + ';'); shader.AddLine(dest + " = tmp;"); - --shader.scope; - shader.AddLine('}'); } else { shader.AddLine(dest + " = " + src + ';'); } @@ -834,11 +923,12 @@ private: // vertex shader, and what's the value of the fourth element when inside a Tess Eval // shader. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); - return "vec4(0, 0, uintBitsToFloat(instance_id.x), uintBitsToFloat(gl_VertexID))"; + // Config pack's first value is instance_id. + return "vec4(0, 0, uintBitsToFloat(config_pack[0]), uintBitsToFloat(gl_VertexID))"; case Attribute::Index::FrontFacing: // TODO(Subv): Find out what the values are for the other elements. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); - return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))"; + return "vec4(0, 0, 0, intBitsToFloat(gl_FrontFacing ? -1 : 0))"; default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -847,16 +937,13 @@ private: if (declr_input_attribute.count(attribute) == 0) { declr_input_attribute[attribute] = input_mode; } else { - if (declr_input_attribute[attribute] != input_mode) { - LOG_CRITICAL(HW_GPU, "Same Input multiple input modes"); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(declr_input_attribute[attribute] != input_mode, + "Multiple input modes for the same attribute"); } return GeometryPass("input_attribute_" + std::to_string(index)); } - LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); } return "vec4(0, 0, 0, 0)"; @@ -882,24 +969,20 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled Ipa InterpMode: {}", static_cast<u32>(interp_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled IPA interp mode: {}", static_cast<u32>(interp_mode)); } } switch (sample_mode) { - case Tegra::Shader::IpaSampleMode::Centroid: { - // Note not implemented, it can be implemented with the "centroid " keyword in glsl; - LOG_CRITICAL(HW_GPU, "Ipa Sampler Mode: centroid, not implemented"); - UNREACHABLE(); + case Tegra::Shader::IpaSampleMode::Centroid: + // It can be implemented with the "centroid " keyword in glsl + UNIMPLEMENTED_MSG("Unimplemented IPA sampler mode centroid"); break; - } - case Tegra::Shader::IpaSampleMode::Default: { + case Tegra::Shader::IpaSampleMode::Default: // Default, n/a break; - } default: { - LOG_CRITICAL(HW_GPU, "Unhandled Ipa SampleMode: {}", static_cast<u32>(sample_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented IPA sampler mode: {}", static_cast<u32>(sample_mode)); + break; } } return out; @@ -912,6 +995,10 @@ private: return "gl_PointSize"; case Attribute::Index::Position: return "position"; + case Attribute::Index::ClipDistances0123: + case Attribute::Index::ClipDistances4567: { + return "gl_ClipDistance"; + } default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -920,20 +1007,11 @@ private: return "output_attribute_" + std::to_string(index); } - LOG_CRITICAL(HW_GPU, "Unhandled output attribute: {}", index); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled output attribute={}", index); return {}; } } - /// Generates code to use for a swizzle operation. - static std::string GetSwizzle(u64 elem) { - ASSERT(elem <= 3); - std::string swizzle = "."; - swizzle += "xyzw"[elem]; - return swizzle; - } - ShaderWriter& shader; ShaderWriter& declarations; std::vector<GLSLRegister> regs; @@ -945,15 +1023,17 @@ private: const std::string& suffix; const Tegra::Shader::Header& header; std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used; + std::array<bool, Maxwell::NumClipDistances> clip_distances{}; u64 local_memory_size; }; class GLSLGenerator { public: GLSLGenerator(const std::set<Subroutine>& subroutines, const ProgramCode& program_code, - u32 main_offset, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix) + u32 main_offset, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix, + std::size_t shader_length) : subroutines(subroutines), program_code(program_code), main_offset(main_offset), - stage(stage), suffix(suffix) { + stage(stage), suffix(suffix), shader_length(shader_length) { std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); local_memory_size = header.GetLocalMemorySize(); regs.SetLocalMemory(local_memory_size); @@ -966,7 +1046,8 @@ public: /// Returns entries in the shader that are useful for external functions ShaderEntries GetEntries() const { - return {regs.GetConstBuffersDeclarations(), regs.GetSamplers()}; + return {regs.GetConstBuffersDeclarations(), regs.GetSamplers(), regs.GetClipDistances(), + shader_length}; } private: @@ -1071,19 +1152,26 @@ private: const std::string& op_a, const std::string& op_b) const { using Tegra::Shader::PredCondition; static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = { - {PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="}, - {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"}, - {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="}, - {PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="}, - {PredCondition::GreaterThanWithNan, ">"}, {PredCondition::GreaterEqualWithNan, ">="}}; + {PredCondition::LessThan, "<"}, + {PredCondition::Equal, "=="}, + {PredCondition::LessEqual, "<="}, + {PredCondition::GreaterThan, ">"}, + {PredCondition::NotEqual, "!="}, + {PredCondition::GreaterEqual, ">="}, + {PredCondition::LessThanWithNan, "<"}, + {PredCondition::NotEqualWithNan, "!="}, + {PredCondition::LessEqualWithNan, "<="}, + {PredCondition::GreaterThanWithNan, ">"}, + {PredCondition::GreaterEqualWithNan, ">="}}; const auto& comparison{PredicateComparisonStrings.find(condition)}; - ASSERT_MSG(comparison != PredicateComparisonStrings.end(), - "Unknown predicate comparison operation"); + UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonStrings.end(), + "Unknown predicate comparison operation"); std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'}; if (condition == PredCondition::LessThanWithNan || condition == PredCondition::NotEqualWithNan || + condition == PredCondition::LessEqualWithNan || condition == PredCondition::GreaterThanWithNan || condition == PredCondition::GreaterEqualWithNan) { predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')'; @@ -1107,7 +1195,7 @@ private: }; auto op = PredicateOperationStrings.find(operation); - ASSERT_MSG(op != PredicateOperationStrings.end(), "Unknown predicate operation"); + UNIMPLEMENTED_IF_MSG(op == PredicateOperationStrings.end(), "Unknown predicate operation"); return op->second; } @@ -1205,8 +1293,7 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(logic_op)); } if (dest != Tegra::Shader::Register::ZeroIndex) { @@ -1224,9 +1311,8 @@ private: SetPredicate(static_cast<u64>(predicate), '(' + result + ") != 0"); break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented predicate result mode: {}", - static_cast<u32>(predicate_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}", + static_cast<u32>(predicate_mode)); } } @@ -1257,14 +1343,7 @@ private: regs.SetRegisterToInteger(dest, true, 0, result, 1, 1); } - void WriteTexsInstruction(const Instruction& instr, const std::string& coord, - const std::string& texture) { - // Add an extra scope and declare the texture coords inside to prevent - // overwriting them in case they are used as outputs of the texs instruction. - shader.AddLine('{'); - ++shader.scope; - shader.AddLine(coord); - + void WriteTexsInstructionFloat(const Instruction& instr, const std::string& texture) { // TEXS has two destination registers and a swizzle. The first two elements in the swizzle // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 @@ -1287,26 +1366,51 @@ private: ++written_components; } + } - --shader.scope; - shader.AddLine('}'); + void WriteTexsInstructionHalfFloat(const Instruction& instr, const std::string& texture) { + // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half + // float instruction). + + std::array<std::string, 4> components; + u32 written_components = 0; + + for (u32 component = 0; component < 4; ++component) { + if (!instr.texs.IsComponentEnabled(component)) + continue; + components[written_components++] = texture + GetSwizzle(component); + } + if (written_components == 0) + return; + + const auto BuildComponent = [&](std::string low, std::string high, bool high_enabled) { + return "vec2(" + low + ", " + (high_enabled ? high : "0") + ')'; + }; + + regs.SetRegisterToHalfFloat( + instr.gpr0, 0, BuildComponent(components[0], components[1], written_components > 1), + Tegra::Shader::HalfMerge::H0_H1, 1, 1); + + if (written_components > 2) { + ASSERT(instr.texs.HasTwoDestinations()); + regs.SetRegisterToHalfFloat( + instr.gpr28, 0, + BuildComponent(components[2], components[3], written_components > 3), + Tegra::Shader::HalfMerge::H0_H1, 1, 1); + } } static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) { switch (texture_type) { - case Tegra::Shader::TextureType::Texture1D: { + case Tegra::Shader::TextureType::Texture1D: return 1; - } - case Tegra::Shader::TextureType::Texture2D: { + case Tegra::Shader::TextureType::Texture2D: return 2; - } case Tegra::Shader::TextureType::Texture3D: - case Tegra::Shader::TextureType::TextureCube: { + case Tegra::Shader::TextureType::TextureCube: return 3; - } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type)); return 0; } } @@ -1316,12 +1420,10 @@ private: * top. */ void EmitPushToFlowStack(u32 target) { - shader.AddLine('{'); - ++shader.scope; + const auto scope = shader.Scope(); + shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;"); shader.AddLine("flow_stack_top++;"); - --shader.scope; - shader.AddLine('}'); } /* @@ -1329,20 +1431,18 @@ private: * popped address and decrementing the stack top. */ void EmitPopFromFlowStack() { - shader.AddLine('{'); - ++shader.scope; + const auto scope = shader.Scope(); + shader.AddLine("flow_stack_top--;"); shader.AddLine("jmp_to = flow_stack[flow_stack_top];"); shader.AddLine("break;"); - --shader.scope; - shader.AddLine('}'); } /// Writes the output values from a fragment shader to the corresponding GLSL output variables. void EmitFragmentOutputsWrite() { ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); - ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); + UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Samplemask write is unimplemented"); shader.AddLine("if (alpha_test[0] != 0) {"); ++shader.scope; @@ -1408,7 +1508,7 @@ private: case Tegra::Shader::VideoType::Size32: // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when // this type is used (1 * 1 + 0 == 0x5b800000). Until a better - // explanation is found: assert. + // explanation is found: abort. UNIMPLEMENTED(); return zero; case Tegra::Shader::VideoType::Invalid: @@ -1447,6 +1547,254 @@ private: } } + std::pair<size_t, std::string> ValidateAndGetCoordinateElement( + const Tegra::Shader::TextureType texture_type, const bool depth_compare, + const bool is_array, const bool lod_bias_enabled, size_t max_coords, size_t max_inputs) { + const size_t coord_count = TextureCoordinates(texture_type); + + size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0); + const size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0); + if (total_coord_count > max_coords || total_reg_count > max_inputs) { + UNIMPLEMENTED_MSG("Unsupported Texture operation"); + total_coord_count = std::min(total_coord_count, max_coords); + } + // 1D.DC opengl is using a vec3 but 2nd component is ignored later. + total_coord_count += + (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) + ? 1 + : 0; + + constexpr std::array<const char*, 5> coord_container{ + {"", "float coord = (", "vec2 coord = vec2(", "vec3 coord = vec3(", + "vec4 coord = vec4("}}; + + return std::pair<size_t, std::string>(coord_count, coord_container[total_coord_count]); + } + + std::string GetTextureCode(const Tegra::Shader::Instruction& instr, + const Tegra::Shader::TextureType texture_type, + const Tegra::Shader::TextureProcessMode process_mode, + const bool depth_compare, const bool is_array, + const size_t bias_offset) { + + if ((texture_type == Tegra::Shader::TextureType::Texture3D && + (is_array || depth_compare)) || + (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && + depth_compare)) { + UNIMPLEMENTED_MSG("This method is not supported."); + } + + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, depth_compare); + + const bool lod_needed = process_mode == Tegra::Shader::TextureProcessMode::LZ || + process_mode == Tegra::Shader::TextureProcessMode::LL || + process_mode == Tegra::Shader::TextureProcessMode::LLA; + + const bool gl_lod_supported = !( + (texture_type == Tegra::Shader::TextureType::Texture2D && is_array && depth_compare) || + (texture_type == Tegra::Shader::TextureType::TextureCube && !is_array && + depth_compare)); + + const std::string read_method = lod_needed && gl_lod_supported ? "textureLod(" : "texture("; + std::string texture = read_method + sampler + ", coord"; + + if (process_mode != Tegra::Shader::TextureProcessMode::None) { + if (process_mode == Tegra::Shader::TextureProcessMode::LZ) { + if (gl_lod_supported) { + texture += ", 0"; + } else { + // Lod 0 is emulated by a big negative bias + // in scenarios that are not supported by glsl + texture += ", -1000"; + } + } else { + // If present, lod or bias are always stored in the register indexed by the + // gpr20 + // field with an offset depending on the usage of the other registers + texture += ',' + regs.GetRegisterAsFloat(instr.gpr20.Value() + bias_offset); + } + } + texture += ")"; + return texture; + } + + std::pair<std::string, std::string> GetTEXCode( + const Instruction& instr, const Tegra::Shader::TextureType texture_type, + const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare, + const bool is_array) { + const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None && + process_mode != Tegra::Shader::TextureProcessMode::LZ); + + const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement( + texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5); + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + + std::string coord = coord_dcl; + for (size_t i = 0; i < coord_count;) { + coord += regs.GetRegisterAsFloat(coord_register + i); + ++i; + if (i != coord_count) { + coord += ','; + } + } + // 1D.DC in opengl the 2nd component is ignored. + if (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) { + coord += ",0.0"; + } + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 + // or in the next register if lod or bias are used + const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); + coord += ',' + regs.GetRegisterAsFloat(depth_register); + } + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + return std::make_pair( + coord, GetTextureCode(instr, texture_type, process_mode, depth_compare, is_array, 0)); + } + + std::pair<std::string, std::string> GetTEXSCode( + const Instruction& instr, const Tegra::Shader::TextureType texture_type, + const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare, + const bool is_array) { + const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None && + process_mode != Tegra::Shader::TextureProcessMode::LZ); + + const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement( + texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4); + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + const u64 last_coord_register = + (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2)) + ? static_cast<u64>(instr.gpr20.Value()) + : coord_register + 1; + + std::string coord = coord_dcl; + for (size_t i = 0; i < coord_count; ++i) { + const bool last = (i == (coord_count - 1)) && (coord_count > 1); + coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i); + if (i < coord_count - 1) { + coord += ','; + } + } + + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 + // or in the next register if lod or bias are used + const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0); + coord += ',' + regs.GetRegisterAsFloat(depth_register); + } + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + + return std::make_pair(coord, + GetTextureCode(instr, texture_type, process_mode, depth_compare, + is_array, (coord_count > 2 ? 1 : 0))); + } + + std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr, + const Tegra::Shader::TextureType texture_type, + const bool depth_compare, const bool is_array) { + + const size_t coord_count = TextureCoordinates(texture_type); + const size_t total_coord_count = coord_count + (is_array ? 1 : 0); + const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); + + constexpr std::array<const char*, 5> coord_container{ + {"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}}; + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + + std::string coord = coord_container[total_coord_count]; + for (size_t i = 0; i < coord_count;) { + coord += regs.GetRegisterAsFloat(coord_register + i); + ++i; + if (i != coord_count) { + coord += ','; + } + } + + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, depth_compare); + + std::string texture = "textureGather(" + sampler + ", coord, "; + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 + texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')'; + } else { + texture += std::to_string(instr.tld4.component) + ')'; + } + return std::make_pair(coord, texture); + } + + std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr, + const Tegra::Shader::TextureType texture_type, + const bool is_array) { + + const size_t coord_count = TextureCoordinates(texture_type); + const size_t total_coord_count = coord_count + (is_array ? 1 : 0); + const bool lod_enabled = + instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL; + + constexpr std::array<const char*, 4> coord_container{ + {"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}}; + + std::string coord = coord_container[total_coord_count]; + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + + // if is array gpr20 is used + const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value(); + + const u64 last_coord_register = + ((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array + ? static_cast<u64>(instr.gpr20.Value()) + : coord_register + 1; + + for (size_t i = 0; i < coord_count; ++i) { + const bool last = (i == (coord_count - 1)) && (coord_count > 1); + coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i); + if (i < coord_count - 1) { + coord += ','; + } + } + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + + const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + std::string texture = "texelFetch(" + sampler + ", coords"; + + if (lod_enabled) { + // When lod is used always is in grp20 + texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')'; + } else { + texture += ", 0)"; + } + return std::make_pair(coord, texture); + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -1464,8 +1812,7 @@ private: // Decoding failure if (!opcode) { - LOG_CRITICAL(HW_GPU, "Unhandled instruction: {0:x}", instr.value); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled instruction: {0:x}", instr.value); return offset + 1; } @@ -1473,8 +1820,8 @@ private: fmt::format("// {}: {} (0x{:016x})", offset, opcode->get().GetName(), instr.value)); using Tegra::Shader::Pred; - ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, - "NeverExecute predicate not implemented"); + UNIMPLEMENTED_IF_MSG(instr.pred.full_pred == Pred::NeverExecute, + "NeverExecute predicate not implemented"); // Some instructions (like SSY) don't have a predicate field, they are always // unconditionally executed. @@ -1517,37 +1864,36 @@ private: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. - ASSERT_MSG(instr.fmul.tab5cb8_2 == 0, "FMUL tab5cb8_2({}) is not implemented", - instr.fmul.tab5cb8_2.Value()); - ASSERT_MSG(instr.fmul.tab5c68_1 == 0, "FMUL tab5cb8_1({}) is not implemented", - instr.fmul.tab5c68_1.Value()); - ASSERT_MSG(instr.fmul.tab5c68_0 == 1, "FMUL tab5cb8_0({}) is not implemented", - instr.fmul.tab5c68_0 - .Value()); // SMO typical sends 1 here which seems to be the default - ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0, + "FMUL tab5cb8_2({}) is not implemented", + instr.fmul.tab5cb8_2.Value()); + UNIMPLEMENTED_IF_MSG(instr.fmul.tab5c68_1 != 0, + "FMUL tab5cb8_1({}) is not implemented", + instr.fmul.tab5c68_1.Value()); + UNIMPLEMENTED_IF_MSG( + instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented", + instr.fmul.tab5c68_0 + .Value()); // SMO typical sends 1 here which seems to be the default + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in FMUL is not implemented"); op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMUL Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in FADD is not implemented"); + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::MUFU: { @@ -1582,15 +1928,17 @@ private: instr.alu.saturate_d, 0, true); break; default: - LOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}", - static_cast<unsigned>(instr.sub_op.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}", + static_cast<unsigned>(instr.sub_op.Value())); } break; } case OpCode::Id::FMNMX_C: case OpCode::Id::FMNMX_R: case OpCode::Id::FMNMX_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in FMNMX is not implemented"); + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); @@ -1601,10 +1949,6 @@ private: '(' + condition + ") ? min(" + parameters + ") : max(" + parameters + ')', 1, 1, false, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMNMX Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::RRO_C: @@ -1617,9 +1961,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled arithmetic instruction: {}", opcode->get().GetName()); } } break; @@ -1631,17 +1973,19 @@ private: break; } case OpCode::Id::FMUL32_IMM: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "Condition codes generation in FMUL32 is not implemented"); + regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1, instr.fmul32.saturate, 0, true); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMUL32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::FADD32I: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "Condition codes generation in FADD32I is not implemented"); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); std::string op_b = GetImmediate32(instr); @@ -1662,23 +2006,22 @@ private: } regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, false, 0, true); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "FADD32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } } break; } case OpCode::Type::Bfe: { - ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.bfe.negate_b); std::string op_a = instr.bfe.negate_a ? "-" : ""; op_a += regs.GetRegisterAsInteger(instr.gpr8); switch (opcode->get().GetId()) { case OpCode::Id::BFE_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in BFE is not implemented"); + std::string inner_shift = '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')'; std::string outer_shift = @@ -1686,20 +2029,35 @@ private: std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')'; regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "BFE Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled BFE instruction: {}", opcode->get().GetName()); } } break; } + case OpCode::Type::Bfi: { + UNIMPLEMENTED_IF(instr.generates_cc); + + const auto [base, packed_shift] = [&]() -> std::tuple<std::string, std::string> { + switch (opcode->get().GetId()) { + case OpCode::Id::BFI_IMM_R: + return {regs.GetRegisterAsInteger(instr.gpr39, 0, false), + std::to_string(instr.alu.GetSignedImm20_20())}; + default: + UNREACHABLE(); + } + }(); + const std::string offset = '(' + packed_shift + " & 0xff)"; + const std::string bits = "((" + packed_shift + " >> 8) & 0xff)"; + const std::string insert = regs.GetRegisterAsInteger(instr.gpr8, 0, false); + regs.SetRegisterToInteger( + instr.gpr0, false, 0, + "bitfieldInsert(" + base + ", " + insert + ", " + offset + ", " + bits + ')', 1, 1); + break; + } case OpCode::Type::Shift: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); std::string op_b; @@ -1719,6 +2077,9 @@ private: case OpCode::Id::SHR_C: case OpCode::Id::SHR_R: case OpCode::Id::SHR_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in SHR is not implemented"); + if (!instr.shift.is_signed) { // Logical shift right op_a = "uint(" + op_a + ')'; @@ -1727,24 +2088,17 @@ private: // Cast to int is superfluous for arithmetic shift, it's only for a logical shift regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "SHR Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::SHL_C: case OpCode::Id::SHL_R: case OpCode::Id::SHL_IMM: + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in SHL is not implemented"); regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "SHL Generates an unhandled Control Code"); - UNREACHABLE(); - } break; default: { - LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled shift instruction: {}", opcode->get().GetName()); } } break; @@ -1755,17 +2109,19 @@ private: switch (opcode->get().GetId()) { case OpCode::Id::IADD32I: + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "Condition codes generation in IADD32I is not implemented"); + if (instr.iadd32i.negate_a) op_a = "-(" + op_a + ')'; regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.iadd32i.saturate != 0); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; case OpCode::Id::LOP32I: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "Condition codes generation in LOP32I is not implemented"); + if (instr.alu.lop32i.invert_a) op_a = "~(" + op_a + ')'; @@ -1775,16 +2131,11 @@ private: WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b, Tegra::Shader::PredicateResultMode::None, Tegra::Shader::Pred::UnusedIndex); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP32I Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled ArithmeticIntegerImmediate instruction: {}", + opcode->get().GetName()); } } break; @@ -1807,6 +2158,9 @@ private: case OpCode::Id::IADD_C: case OpCode::Id::IADD_R: case OpCode::Id::IADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in IADD is not implemented"); + if (instr.alu_integer.negate_a) op_a = "-(" + op_a + ')'; @@ -1815,15 +2169,14 @@ private: regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::IADD3_C: case OpCode::Id::IADD3_R: case OpCode::Id::IADD3_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in IADD3 is not implemented"); + std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); auto apply_height = [](auto height, auto& oprand) { @@ -1837,9 +2190,8 @@ private: oprand = "((" + oprand + ") >> 16)"; break; default: - LOG_CRITICAL(HW_GPU, "Unhandled IADD3 height: {}", - static_cast<u32>(height.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", + static_cast<u32>(height.Value())); } }; @@ -1880,16 +2232,14 @@ private: } regs.SetRegisterToInteger(instr.gpr0, true, 0, result, 1, 1); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD3 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::ISCADD_C: case OpCode::Id::ISCADD_R: case OpCode::Id::ISCADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in ISCADD is not implemented"); + if (instr.alu_integer.negate_a) op_a = "-(" + op_a + ')'; @@ -1900,10 +2250,6 @@ private: regs.SetRegisterToInteger(instr.gpr0, true, 0, "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "ISCADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::POPC_C: @@ -1927,6 +2273,9 @@ private: case OpCode::Id::LOP_C: case OpCode::Id::LOP_R: case OpCode::Id::LOP_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in LOP is not implemented"); + if (instr.alu.lop.invert_a) op_a = "~(" + op_a + ')'; @@ -1935,15 +2284,14 @@ private: WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b, instr.alu.lop.pred_result_mode, instr.alu.lop.pred48); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::LOP3_C: case OpCode::Id::LOP3_R: case OpCode::Id::LOP3_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in LOP3 is not implemented"); + const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); std::string lut; @@ -1954,17 +2302,15 @@ private: } WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP3 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::IMNMX_C: case OpCode::Id::IMNMX_R: case OpCode::Id::IMNMX_IMM: { - ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.imnmx.exchange != Tegra::Shader::IMinMaxExchange::None); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in IMNMX is not implemented"); + const std::string condition = GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0); const std::string parameters = op_a + ',' + op_b; @@ -1972,10 +2318,6 @@ private: '(' + condition + ") ? min(" + parameters + ") : max(" + parameters + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IMNMX Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::LEA_R2: @@ -2030,24 +2372,19 @@ private: op_b = regs.GetRegisterAsInteger(instr.gpr8); op_a = std::to_string(instr.lea.imm.entry_a); op_c = std::to_string(instr.lea.imm.entry_b); - LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled LEA subinstruction: {}", opcode->get().GetName()); } } - if (instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex)) { - LOG_ERROR(HW_GPU, "Unhandled LEA Predicate"); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex), + "Unhandled LEA Predicate"); const std::string value = '(' + op_a + " + (" + op_b + "*(1 << " + op_c + ")))"; regs.SetRegisterToInteger(instr.gpr0, true, 0, value, 1, 1); break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled ArithmeticInteger instruction: {}", + opcode->get().GetName()); } } @@ -2056,7 +2393,7 @@ private: case OpCode::Type::ArithmeticHalf: { if (opcode->get().GetId() == OpCode::Id::HADD2_C || opcode->get().GetId() == OpCode::Id::HADD2_R) { - ASSERT_MSG(instr.alu_half.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half.ftz != 0); } const bool negate_a = opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0; @@ -2094,9 +2431,8 @@ private: case OpCode::Id::HMUL2_R: return '(' + op_a + " * " + op_b + ')'; default: - LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", + opcode->get().GetName()); return std::string("0"); } }(); @@ -2107,10 +2443,10 @@ private: } case OpCode::Type::ArithmeticHalfImmediate: { if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) { - ASSERT_MSG(instr.alu_half_imm.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half_imm.ftz != 0); } else { - ASSERT_MSG(instr.alu_half_imm.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half_imm.precision != + Tegra::Shader::HalfPrecision::None); } const std::string op_a = GetHalfFloat( @@ -2140,11 +2476,14 @@ private: std::string op_b = instr.ffma.negate_b ? "-" : ""; std::string op_c = instr.ffma.negate_c ? "-" : ""; - ASSERT_MSG(instr.ffma.cc == 0, "FFMA cc not implemented"); - ASSERT_MSG(instr.ffma.tab5980_0 == 1, "FFMA tab5980_0({}) not implemented", - instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO - ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented", - instr.ffma.tab5980_1.Value()); + UNIMPLEMENTED_IF_MSG(instr.ffma.cc != 0, "FFMA cc not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.ffma.tab5980_0 != 1, "FFMA tab5980_0({}) not implemented", + instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO + UNIMPLEMENTED_IF_MSG(instr.ffma.tab5980_1 != 0, "FFMA tab5980_1({}) not implemented", + instr.ffma.tab5980_1.Value()); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in FFMA is not implemented"); switch (opcode->get().GetId()) { case OpCode::Id::FFMA_CR: { @@ -2170,27 +2509,19 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled FFMA instruction: {}", opcode->get().GetName()); } } regs.SetRegisterToFloat(instr.gpr0, 0, "fma(" + op_a + ", " + op_b + ", " + op_c + ')', 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FFMA Generates an unhandled Control Code"); - UNREACHABLE(); - } - break; } case OpCode::Type::Hfma2: { if (opcode->get().GetId() == OpCode::Id::HFMA2_RR) { - ASSERT_MSG(instr.hfma2.rr.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.hfma2.rr.precision != Tegra::Shader::HalfPrecision::None); } else { - ASSERT_MSG(instr.hfma2.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.hfma2.precision != Tegra::Shader::HalfPrecision::None); } const bool saturate = opcode->get().GetId() == OpCode::Id::HFMA2_RR ? instr.hfma2.rr.saturate != 0 @@ -2240,7 +2571,7 @@ private: case OpCode::Type::Conversion: { switch (opcode->get().GetId()) { case OpCode::Id::I2I_R: { - ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.selector); std::string op_a = regs.GetRegisterAsInteger( instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); @@ -2260,10 +2591,11 @@ private: } case OpCode::Id::I2F_R: case OpCode::Id::I2F_C: { - ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); - ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); - - std::string op_a{}; + UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.selector); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in I2F is not implemented"); + std::string op_a; if (instr.is_b_gpr) { op_a = @@ -2286,16 +2618,13 @@ private: } regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "I2F Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::F2F_R: { - ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); - ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in F2F is not implemented"); std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); if (instr.conversion.abs_a) { @@ -2322,23 +2651,19 @@ private: op_a = "trunc(" + op_a + ')'; break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented f2f rounding mode {}", - static_cast<u32>(instr.conversion.f2f.rounding.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}", + static_cast<u32>(instr.conversion.f2f.rounding.Value())); break; } regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "F2F Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::F2I_R: case OpCode::Id::F2I_C: { - ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in F2I is not implemented"); std::string op_a{}; if (instr.is_b_gpr) { @@ -2369,9 +2694,8 @@ private: op_a = "trunc(" + op_a + ')'; break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented f2i rounding mode {}", - static_cast<u32>(instr.conversion.f2i.rounding.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented F2I rounding mode {}", + static_cast<u32>(instr.conversion.f2i.rounding.Value())); break; } @@ -2383,16 +2707,10 @@ private: regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, 1, false, 0, instr.conversion.dest_size); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "F2I Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); } } break; @@ -2401,10 +2719,10 @@ private: switch (opcode->get().GetId()) { case OpCode::Id::LD_A: { // Note: Shouldn't this be interp mode flat? As in no interpolation made. - ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, - "Indirect attribute loads are not supported"); - ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, - "Unaligned attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex, + "Indirect attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0, + "Unaligned attribute loads are not supported"); Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Perspective, Tegra::Shader::IpaSampleMode::Default}; @@ -2431,12 +2749,9 @@ private: break; } case OpCode::Id::LD_C: { - ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.ld_c.unknown != 0); - // Add an extra scope and declare the index register inside to prevent - // overwriting it in case it is used as an output of the LD instruction. - shader.AddLine("{"); - ++shader.scope; + const auto scope = shader.Scope(); shader.AddLine("uint index = (" + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " / 4) & (MAX_CONSTBUFFER_ELEMENTS - 1);"); @@ -2459,20 +2774,16 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled type: {}", - static_cast<unsigned>(instr.ld_c.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled type: {}", + static_cast<unsigned>(instr.ld_c.type.Value())); } - - --shader.scope; - shader.AddLine("}"); break; } case OpCode::Id::LD_L: { - // Add an extra scope and declare the index register inside to prevent - // overwriting it in case it is used as an output of the LD instruction. - shader.AddLine('{'); - ++shader.scope; + UNIMPLEMENTED_IF_MSG(instr.ld_l.unknown == 1, "LD_L Unhandled mode: {}", + static_cast<unsigned>(instr.ld_l.unknown.Value())); + + const auto scope = shader.Scope(); std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " + std::to_string(instr.smem_imm.Value()) + ')'; @@ -2481,31 +2792,21 @@ private: const std::string op_a = regs.GetLocalMemoryAsFloat("index"); - if (instr.ld_l.unknown != 1) { - LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}", - static_cast<unsigned>(instr.ld_l.unknown.Value())); - UNREACHABLE(); - } - switch (instr.ldst_sl.type.Value()) { case Tegra::Shader::StoreType::Bytes32: regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); break; default: - LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}", - static_cast<unsigned>(instr.ldst_sl.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("LD_L Unhandled type: {}", + static_cast<unsigned>(instr.ldst_sl.type.Value())); } - - --shader.scope; - shader.AddLine('}'); break; } case OpCode::Id::ST_A: { - ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, - "Indirect attribute loads are not supported"); - ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, - "Unaligned attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex, + "Indirect attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0, + "Unaligned attribute loads are not supported"); u64 next_element = instr.attribute.fmt20.element; u64 next_index = static_cast<u64>(instr.attribute.fmt20.index.Value()); @@ -2530,476 +2831,218 @@ private: break; } case OpCode::Id::ST_L: { - // Add an extra scope and declare the index register inside to prevent - // overwriting it in case it is used as an output of the LD instruction. - shader.AddLine('{'); - ++shader.scope; + UNIMPLEMENTED_IF_MSG(instr.st_l.unknown == 0, "ST_L Unhandled mode: {}", + static_cast<unsigned>(instr.st_l.unknown.Value())); + + const auto scope = shader.Scope(); std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " + std::to_string(instr.smem_imm.Value()) + ')'; shader.AddLine("uint index = (" + op + " / 4);"); - if (instr.st_l.unknown != 0) { - LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}", - static_cast<unsigned>(instr.st_l.unknown.Value())); - UNREACHABLE(); - } - switch (instr.ldst_sl.type.Value()) { case Tegra::Shader::StoreType::Bytes32: regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0)); break; default: - LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}", - static_cast<unsigned>(instr.ldst_sl.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("ST_L Unhandled type: {}", + static_cast<unsigned>(instr.ldst_sl.type.Value())); } - - --shader.scope; - shader.AddLine('}'); break; } case OpCode::Id::TEX: { Tegra::Shader::TextureType texture_type{instr.tex.texture_type}; - std::string coord; const bool is_array = instr.tex.array != 0; - - ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - const bool depth_compare = instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - u32 num_coordinates = TextureCoordinates(texture_type); - if (depth_compare) - num_coordinates += 1; - - switch (num_coordinates) { - case 1: { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + index + ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - coord = "float coords = " + x + ';'; - } - break; - } - case 2: { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - } - break; - } - case 3: { - if (depth_compare) { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string y = regs.GetRegisterAsFloat(instr.gpr20); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); - coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + - ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; - } - } else { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3); - coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + - ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; - } - } - break; - } - default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); + const auto process_mode = instr.tex.GetTextureProcessMode(); + UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); - // Fallback to interpreting as a 2D texture for now - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - texture_type = Tegra::Shader::TextureType::Texture2D; - } - // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias - // or lod. - std::string op_c; + const auto [coord, texture] = + GetTEXCode(instr, texture_type, process_mode, depth_compare, is_array); - const std::string sampler = - GetSampler(instr.sampler, texture_type, is_array, depth_compare); - // Add an extra scope and declare the texture coords inside to prevent - // overwriting them in case they are used as outputs of the texs instruction. - - shader.AddLine("{"); - ++shader.scope; + const auto scope = shader.Scope(); shader.AddLine(coord); - std::string texture; - switch (instr.tex.GetTextureProcessMode()) { - case Tegra::Shader::TextureProcessMode::None: { - texture = "texture(" + sampler + ", coords)"; - break; - } - case Tegra::Shader::TextureProcessMode::LZ: { - texture = "textureLod(" + sampler + ", coords, 0.0)"; - break; - } - case Tegra::Shader::TextureProcessMode::LB: - case Tegra::Shader::TextureProcessMode::LBA: { - if (depth_compare) { - if (is_array) - op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2); - else - op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); - } else { - op_c = regs.GetRegisterAsFloat(instr.gpr20); - } - // TODO: Figure if A suffix changes the equation at all. - texture = "texture(" + sampler + ", coords, " + op_c + ')'; - break; - } - case Tegra::Shader::TextureProcessMode::LL: - case Tegra::Shader::TextureProcessMode::LLA: { - if (num_coordinates <= 2) { - op_c = regs.GetRegisterAsFloat(instr.gpr20); - } else { - op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); - } - // TODO: Figure if A suffix changes the equation at all. - texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; - break; - } - default: { - texture = "texture(" + sampler + ", coords)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.tex.GetTextureProcessMode())); - UNREACHABLE(); - } - } - if (!depth_compare) { + if (depth_compare) { + regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); + } else { + shader.AddLine("vec4 texture_tmp = " + texture + ';'); std::size_t dest_elem{}; for (std::size_t elem = 0; elem < 4; ++elem) { if (!instr.tex.IsComponentEnabled(elem)) { // Skip disabled components continue; } - regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); + regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false, + dest_elem); ++dest_elem; } - } else { - regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); } - --shader.scope; - shader.AddLine("}"); break; } case OpCode::Id::TEXS: { - std::string coord; Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()}; - bool is_array{instr.texs.IsArrayTexture()}; - - ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - + const bool is_array{instr.texs.IsArrayTexture()}; const bool depth_compare = instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - u32 num_coordinates = TextureCoordinates(texture_type); - if (depth_compare) - num_coordinates += 1; - - switch (num_coordinates) { - case 2: { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string y = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - } - break; - } - case 3: { - if (is_array) { - const std::string index = regs.GetRegisterAsInteger(instr.gpr8); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = - "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + ");"; - } else { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; - } - break; - } - default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); + const auto process_mode = instr.texs.GetTextureProcessMode(); - // Fallback to interpreting as a 2D texture for now - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - texture_type = Tegra::Shader::TextureType::Texture2D; - is_array = false; - } - const std::string sampler = - GetSampler(instr.sampler, texture_type, is_array, depth_compare); - std::string texture; - switch (instr.texs.GetTextureProcessMode()) { - case Tegra::Shader::TextureProcessMode::None: { - texture = "texture(" + sampler + ", coords)"; - break; - } - case Tegra::Shader::TextureProcessMode::LZ: { - if (depth_compare && is_array) { - texture = "texture(" + sampler + ", coords)"; - } else { - texture = "textureLod(" + sampler + ", coords, 0.0)"; - } - break; - } - case Tegra::Shader::TextureProcessMode::LL: { - const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); - texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; - break; - } - default: { - texture = "texture(" + sampler + ", coords)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.texs.GetTextureProcessMode())); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + + const auto scope = shader.Scope(); + + auto [coord, texture] = + GetTEXSCode(instr, texture_type, process_mode, depth_compare, is_array); + + shader.AddLine(coord); + + if (depth_compare) { + texture = "vec4(" + texture + ')'; } - if (!depth_compare) { - WriteTexsInstruction(instr, coord, texture); + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + + if (instr.texs.fp32_flag) { + WriteTexsInstructionFloat(instr, "texture_tmp"); } else { - WriteTexsInstruction(instr, coord, "vec4(" + texture + ')'); + WriteTexsInstructionHalfFloat(instr, "texture_tmp"); } break; } case OpCode::Id::TLDS: { - std::string coord; const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; const bool is_array{instr.tlds.IsArrayTexture()}; - ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D); - ASSERT(is_array == false); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), + "MZ is not implemented"); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), - "MZ is not implemented"); + const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array); - u32 op_c_offset = 0; + const auto scope = shader.Scope(); - switch (texture_type) { - case Tegra::Shader::TextureType::Texture1D: { - const std::string x = regs.GetRegisterAsInteger(instr.gpr8); - coord = "int coords = " + x + ';'; - break; - } - case Tegra::Shader::TextureType::Texture2D: { - if (is_array) { - LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture"); - UNREACHABLE(); - } else { - const std::string x = regs.GetRegisterAsInteger(instr.gpr8); - const std::string y = regs.GetRegisterAsInteger(instr.gpr20); - coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; - op_c_offset = 1; - } - break; - } - default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); - UNREACHABLE(); - } - const std::string sampler = - GetSampler(instr.sampler, texture_type, is_array, false); - std::string texture = "texelFetch(" + sampler + ", coords, 0)"; - switch (instr.tlds.GetTextureProcessMode()) { - case Tegra::Shader::TextureProcessMode::LZ: { - texture = "texelFetch(" + sampler + ", coords, 0)"; - break; - } - case Tegra::Shader::TextureProcessMode::LL: { - const std::string op_c = - regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset); - texture = "texelFetch(" + sampler + ", coords, " + op_c + ')'; - break; - } - default: { - texture = "texelFetch(" + sampler + ", coords, 0)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.tlds.GetTextureProcessMode())); - UNREACHABLE(); - } - } - WriteTexsInstruction(instr, coord, texture); + shader.AddLine(coord); + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + WriteTexsInstructionFloat(instr, "texture_tmp"); break; } case OpCode::Id::TLD4: { - ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); - ASSERT(instr.tld4.array == 0); - std::string coord; - - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), - "NDV is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), - "PTP is not implemented"); + + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), + "PTP is not implemented"); + + auto texture_type = instr.tld4.texture_type.Value(); const bool depth_compare = instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - auto texture_type = instr.tld4.texture_type.Value(); - u32 num_coordinates = TextureCoordinates(texture_type); - if (depth_compare) - num_coordinates += 1; + const bool is_array = instr.tld4.array != 0; - switch (num_coordinates) { - case 2: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - break; - } - case 3: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; - break; - } - default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; - texture_type = Tegra::Shader::TextureType::Texture2D; - } + const auto [coord, texture] = + GetTLD4Code(instr, texture_type, depth_compare, is_array); + + const auto scope = shader.Scope(); - const std::string sampler = - GetSampler(instr.sampler, texture_type, false, depth_compare); - // Add an extra scope and declare the texture coords inside to prevent - // overwriting them in case they are used as outputs of the texs instruction. - shader.AddLine("{"); - ++shader.scope; shader.AddLine(coord); - const std::string texture = "textureGather(" + sampler + ", coords, " + - std::to_string(instr.tld4.component) + ')'; - if (!depth_compare) { - std::size_t dest_elem{}; - for (std::size_t elem = 0; elem < 4; ++elem) { - if (!instr.tex.IsComponentEnabled(elem)) { - // Skip disabled components - continue; - } - regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); - ++dest_elem; + std::size_t dest_elem{}; + + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + for (std::size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; } - } else { - regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); + regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false, + dest_elem); + ++dest_elem; } - --shader.scope; - shader.AddLine("}"); break; } case OpCode::Id::TLD4S: { - ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + + const auto scope = shader.Scope(); + + std::string coords; const bool depth_compare = instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); - // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. + const std::string sampler = GetSampler( instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare); - std::string coord; - if (!depth_compare) { - coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; - } else { - // Note: TLD4S coordinate encoding works just like TEXS's - const std::string op_c = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec3 coords = vec3(" + op_a + ", " + op_c + ", " + op_b + ");"; - } - const std::string texture = "textureGather(" + sampler + ", coords, " + - std::to_string(instr.tld4s.component) + ')'; + + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + coords = "vec2 coords = vec2(" + op_a + ", "; + std::string texture = "textureGather(" + sampler + ", coords, "; if (!depth_compare) { - WriteTexsInstruction(instr, coord, texture); + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); + coords += op_b + ");"; + texture += std::to_string(instr.tld4s.component) + ')'; } else { - WriteTexsInstruction(instr, coord, "vec4(" + texture + ')'); + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); + coords += op_b + ");"; + texture += op_c + ')'; } + shader.AddLine(coords); + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + WriteTexsInstructionFloat(instr, "texture_tmp"); break; } case OpCode::Id::TXQ: { - ASSERT_MSG(!instr.txq.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.txq.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); - // TODO: the new commits on the texture refactor, change the way samplers work. + const auto scope = shader.Scope(); + + // TODO: The new commits on the texture refactor, change the way samplers work. // Sadly, not all texture instructions specify the type of texture their sampler // uses. This must be fixed at a later instance. const std::string sampler = GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false); switch (instr.txq.query_type) { case Tegra::Shader::TextureQueryType::Dimension: { - const std::string texture = "textureQueryLevels(" + sampler + ')'; - regs.SetRegisterToInteger(instr.gpr0, true, 0, texture, 1, 1); + const std::string texture = "textureSize(" + sampler + ", " + + regs.GetRegisterAsInteger(instr.gpr8) + ')'; + const std::string mip_level = "textureQueryLevels(" + sampler + ')'; + shader.AddLine("ivec2 sizes = " + texture + ';'); + + regs.SetRegisterToInteger(instr.gpr0.Value() + 0, true, 0, "sizes.x", 1, 1); + regs.SetRegisterToInteger(instr.gpr0.Value() + 1, true, 0, "sizes.y", 1, 1); + regs.SetRegisterToInteger(instr.gpr0.Value() + 2, true, 0, "0", 1, 1); + regs.SetRegisterToInteger(instr.gpr0.Value() + 3, true, 0, mip_level, 1, 1); break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled texture query type: {}", - static_cast<u32>(instr.txq.query_type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture query type: {}", + static_cast<u32>(instr.txq.query_type.Value())); } } break; } case OpCode::Id::TMML: { - ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), - "NDV is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const bool is_array = instr.tmml.array != 0; @@ -3007,47 +3050,38 @@ private: const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); - // TODO: add coordinates for different samplers once other texture types are + const auto scope = shader.Scope(); + + // TODO: Add coordinates for different samplers once other texture types are // implemented. - std::string coord; switch (texture_type) { case Tegra::Shader::TextureType::Texture1D: { - coord = "float coords = " + x + ';'; + shader.AddLine("float coords = " + x + ';'); break; } case Tegra::Shader::TextureType::Texture2D: { const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); // Fallback to interpreting as a 2D texture for now const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); texture_type = Tegra::Shader::TextureType::Texture2D; } - // Add an extra scope and declare the texture coords inside to prevent - // overwriting them in case they are used as outputs of the texs instruction. - shader.AddLine('{'); - ++shader.scope; - shader.AddLine(coord); + const std::string texture = "textureQueryLod(" + sampler + ", coords)"; - const std::string tmp = "vec2 tmp = " + texture + "*vec2(256.0, 256.0);"; - shader.AddLine(tmp); + shader.AddLine("vec2 tmp = " + texture + " * vec2(256.0, 256.0);"); regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(tmp.y)", 1, 1); regs.SetRegisterToInteger(instr.gpr0.Value() + 1, false, 0, "uint(tmp.x)", 1, 1); - --shader.scope; - shader.AddLine('}'); break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName()); } } break; @@ -3133,7 +3167,7 @@ private: break; } case OpCode::Type::HalfSetPredicate: { - ASSERT_MSG(instr.hsetp2.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.hsetp2.ftz != 0); const std::string op_a = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hsetp2.type_a, @@ -3178,6 +3212,9 @@ private: break; } case OpCode::Type::PredicateSetRegister: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in PSET is not implemented"); + const std::string op_a = GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0); const std::string op_b = @@ -3198,12 +3235,6 @@ private: const std::string value = '(' + result + ") ? 1.0 : 0.0"; regs.SetRegisterToFloat(instr.gpr0, 0, value, 1, 1); } - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "PSET Generates an unhandled Control Code"); - UNREACHABLE(); - } - break; } case OpCode::Type::PredicateSetPredicate: { @@ -3241,25 +3272,51 @@ private: const std::string pred = GetPredicateCondition(instr.csetp.pred39, instr.csetp.neg_pred39 != 0); const std::string combiner = GetPredicateCombiner(instr.csetp.op); - const std::string control_code = regs.GetControlCode(instr.csetp.cc); + const std::string condition_code = regs.GetConditionCode(instr.csetp.cc); if (instr.csetp.pred3 != static_cast<u64>(Pred::UnusedIndex)) { SetPredicate(instr.csetp.pred3, - '(' + control_code + ") " + combiner + " (" + pred + ')'); + '(' + condition_code + ") " + combiner + " (" + pred + ')'); } if (instr.csetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { SetPredicate(instr.csetp.pred0, - "!(" + control_code + ") " + combiner + " (" + pred + ')'); + "!(" + condition_code + ") " + combiner + " (" + pred + ')'); } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled predicate instruction: {}", opcode->get().GetName()); } } break; } + case OpCode::Type::RegisterSetPredicate: { + UNIMPLEMENTED_IF(instr.r2p.mode != Tegra::Shader::R2pMode::Pr); + + const std::string apply_mask = [&]() { + switch (opcode->get().GetId()) { + case OpCode::Id::R2P_IMM: + return std::to_string(instr.r2p.immediate_mask); + default: + UNREACHABLE(); + } + }(); + const std::string mask = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + + " >> " + std::to_string(instr.r2p.byte) + ')'; + + constexpr u64 programmable_preds = 7; + for (u64 pred = 0; pred < programmable_preds; ++pred) { + const auto shift = std::to_string(1 << pred); + + shader.AddLine("if ((" + apply_mask + " & " + shift + ") != 0) {"); + ++shader.scope; + + SetPredicate(pred, '(' + mask + " & " + shift + ") != 0"); + + --shader.scope; + shader.AddLine('}'); + } + break; + } case OpCode::Type::FloatSet: { const std::string op_a = GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), instr.fset.abs_a != 0, instr.fset.neg_a != 0); @@ -3297,6 +3354,10 @@ private: regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1, 1); } + if (instr.generates_cc.Value() != 0) { + regs.SetInternalFlag(InternalFlag::ZeroFlag, predicate); + LOG_WARNING(HW_GPU, "FSET Condition Code is incomplete"); + } break; } case OpCode::Type::IntegerSet: { @@ -3335,7 +3396,7 @@ private: break; } case OpCode::Type::HalfSet: { - ASSERT_MSG(instr.hset2.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.hset2.ftz != 0); const std::string op_a = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hset2.type_a, @@ -3379,15 +3440,17 @@ private: break; } case OpCode::Type::Xmad: { - ASSERT_MSG(!instr.xmad.sign_a, "Unimplemented"); - ASSERT_MSG(!instr.xmad.sign_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.xmad.sign_a); + UNIMPLEMENTED_IF(instr.xmad.sign_b); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in XMAD is not implemented"); std::string op_a{regs.GetRegisterAsInteger(instr.gpr8, 0, instr.xmad.sign_a)}; std::string op_b; std::string op_c; // TODO(bunnei): Needs to be fixed once op_a or op_b is signed - ASSERT_MSG(instr.xmad.sign_a == instr.xmad.sign_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.xmad.sign_a != instr.xmad.sign_b); const bool is_signed{instr.xmad.sign_a == 1}; bool is_merge{}; @@ -3420,8 +3483,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName()); } } @@ -3457,9 +3519,8 @@ private: op_c = "((" + op_c + ") + (" + src2 + "<< 16))"; break; default: { - LOG_CRITICAL(HW_GPU, "Unhandled XMAD mode: {}", - static_cast<u32>(instr.xmad.mode.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled XMAD mode: {}", + static_cast<u32>(instr.xmad.mode.Value())); } } @@ -3469,25 +3530,19 @@ private: } regs.SetRegisterToInteger(instr.gpr0, is_signed, 0, sum, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "XMAD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { switch (opcode->get().GetId()) { case OpCode::Id::EXIT: { + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, + "EXIT condition code used: {}", static_cast<u32>(cc)); + if (stage == Maxwell3D::Regs::ShaderStage::Fragment) { EmitFragmentOutputsWrite(); } - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "EXIT Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } - switch (instr.flow.cond) { case Tegra::Shader::FlowCondition::Always: shader.AddLine("return true;"); @@ -3502,26 +3557,24 @@ private: case Tegra::Shader::FlowCondition::Fcsm_Tr: // TODO(bunnei): What is this used for? If we assume this conditon is not // satisifed, dual vertex shaders in Farming Simulator make more sense - LOG_CRITICAL(HW_GPU, "Skipping unknown FlowCondition::Fcsm_Tr"); + UNIMPLEMENTED_MSG("Skipping unknown FlowCondition::Fcsm_Tr"); break; default: - LOG_CRITICAL(HW_GPU, "Unhandled flow condition: {}", - static_cast<u32>(instr.flow.cond.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled flow condition: {}", + static_cast<u32>(instr.flow.cond.Value())); } break; } case OpCode::Id::KIL: { - ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); + UNIMPLEMENTED_IF(instr.flow.cond != Tegra::Shader::FlowCondition::Always); + + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, + "KIL condition code used: {}", static_cast<u32>(cc)); // Enclose "discard" in a conditional, so that GLSL compilation does not complain // about unexecuted instructions that may follow this. - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "KIL Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } shader.AddLine("if (true) {"); ++shader.scope; shader.AddLine("discard;"); @@ -3531,7 +3584,8 @@ private: break; } case OpCode::Id::OUT_R: { - ASSERT(instr.gpr20.Value() == Register::ZeroIndex); + UNIMPLEMENTED_IF_MSG(instr.gpr20.Value() != Register::ZeroIndex, + "Stream buffer is not supported"); ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, "OUT is expected to be used in a geometry shader."); @@ -3557,19 +3611,23 @@ private: regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1); break; } + case Tegra::Shader::SystemVariable::Ydirection: { + // Config pack's third value is Y_NEGATE's state. + regs.SetRegisterToFloat(instr.gpr0, 0, "uintBitsToFloat(config_pack[2])", 1, 1); + break; + } default: { - LOG_CRITICAL(HW_GPU, "Unhandled system move: {}", - static_cast<u32>(instr.sys20.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled system move: {}", + static_cast<u32>(instr.sys20.Value())); } } break; } case OpCode::Id::ISBERD: { - ASSERT(instr.isberd.o == 0); - ASSERT(instr.isberd.skew == 0); - ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None); - ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None); + UNIMPLEMENTED_IF(instr.isberd.o != 0); + UNIMPLEMENTED_IF(instr.isberd.skew != 0); + UNIMPLEMENTED_IF(instr.isberd.shift != Tegra::Shader::IsberdShift::None); + UNIMPLEMENTED_IF(instr.isberd.mode != Tegra::Shader::IsberdMode::None); ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, "ISBERD is expected to be used in a geometry shader."); LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); @@ -3577,15 +3635,21 @@ private: break; } case OpCode::Id::BRA: { - ASSERT_MSG(instr.bra.constant_buffer == 0, - "BRA with constant buffers are not implemented"); - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "BRA Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "BRA with constant buffers are not implemented"); + + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; const u32 target = offset + instr.bra.GetBranchTarget(); - shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); + if (cc != Tegra::Shader::ConditionCode::T) { + const std::string condition_code = regs.GetConditionCode(cc); + shader.AddLine("if (" + condition_code + "){"); + shader.scope++; + shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); + shader.scope--; + shader.AddLine('}'); + } else { + shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); + } break; } case OpCode::Id::IPA: { @@ -3606,7 +3670,8 @@ private: // The SSY opcode tells the GPU where to re-converge divergent execution paths, it // sets the target of the jump that the SYNC instruction will make. The SSY opcode // has a similar structure to the BRA opcode. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer flow is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); EmitPushToFlowStack(target); @@ -3616,29 +3681,28 @@ private: // PBK pushes to a stack the address where BRK will jump to. This shares stack with // SSY but using SYNC on a PBK address will kill the shader execution. We don't // emulate this because it's very unlikely a driver will emit such invalid shader. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer PBK is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); EmitPushToFlowStack(target); break; } case OpCode::Id::SYNC: { + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, + "SYNC condition code used: {}", static_cast<u32>(cc)); + // The SYNC opcode jumps to the address previously set by the SSY opcode - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "SYNC Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } EmitPopFromFlowStack(); break; } case OpCode::Id::BRK: { // The BRK opcode jumps to the address previously set by the PBK opcode - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "BRK Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, + "BRK condition code used: {}", static_cast<u32>(cc)); + EmitPopFromFlowStack(); break; } @@ -3649,6 +3713,9 @@ private: break; } case OpCode::Id::VMAD: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "Condition codes generation in VMAD is not implemented"); + const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1; const std::string op_a = GetVideoOperandA(instr); const std::string op_b = GetVideoOperandB(instr); @@ -3668,11 +3735,6 @@ private: regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1, instr.vmad.saturate == 1, 0, Register::Size::Word, instr.vmad.cc); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "VMAD Generates an unhandled Control Code"); - UNREACHABLE(); - } - break; } case OpCode::Id::VSETP: { @@ -3699,10 +3761,7 @@ private: } break; } - default: { - LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->get().GetName()); - UNREACHABLE(); - } + default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); } } break; @@ -3827,6 +3886,7 @@ private: Maxwell3D::Regs::ShaderStage stage; const std::string& suffix; u64 local_memory_size; + std::size_t shader_length; ShaderWriter shader; ShaderWriter declarations; @@ -3845,9 +3905,10 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u Maxwell3D::Regs::ShaderStage stage, const std::string& suffix) { try { - const auto subroutines = - ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines(); - GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix); + ControlFlowAnalyzer analyzer(program_code, main_offset, suffix); + const auto subroutines = analyzer.GetSubroutines(); + GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix, + analyzer.GetShaderLength()); return ProgramResult{generator.GetShaderCode(), generator.GetEntries()}; } catch (const DecompileFail& exception) { LOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what()); @@ -3855,4 +3916,4 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u return {}; } -} // namespace OpenGL::GLShader::Decompiler +} // namespace OpenGL::GLShader::Decompiler
\ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index eea090e52..5d0819dc5 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <fmt/format.h> #include "common/assert.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" @@ -16,6 +17,8 @@ static constexpr u32 PROGRAM_OFFSET{10}; ProgramResult GenerateVertexShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: VS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += R"( @@ -24,8 +27,7 @@ layout (location = 0) out vec4 position; layout(std140) uniform vs_config { vec4 viewport_flip; - uvec4 instance_id; - uvec4 flip_stage; + uvec4 config_pack; // instance_id, flip_stage, y_direction, padding uvec4 alpha_test; }; )"; @@ -63,7 +65,8 @@ void main() { out += R"( // Check if the flip stage is VertexB - if (flip_stage[0] == 1) { + // Config pack's second value is flip_stage + if (config_pack[1] == 1) { // Viewport can be flipped, which is unsupported by glViewport position.xy *= viewport_flip.xy; } @@ -71,7 +74,7 @@ void main() { // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0 // For now, this is here to bring order in lieu of proper emulation - if (flip_stage[0] == 1) { + if (config_pack[1] == 1) { position.w = 1.0; } } @@ -84,6 +87,8 @@ void main() { ProgramResult GenerateGeometryShader(const ShaderSetup& setup) { // Version is intentionally skipped in shader generation, it's added by the lazy compilation. std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: GS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += "bool exec_geometry();\n"; @@ -101,8 +106,7 @@ layout (location = 0) out vec4 position; layout (std140) uniform gs_config { vec4 viewport_flip; - uvec4 instance_id; - uvec4 flip_stage; + uvec4 config_pack; // instance_id, flip_stage, y_direction, padding uvec4 alpha_test; }; @@ -118,6 +122,8 @@ void main() { ProgramResult GenerateFragmentShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: FS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += "bool exec_fragment();\n"; @@ -139,8 +145,7 @@ layout (location = 0) in vec4 position; layout (std140) uniform fs_config { vec4 viewport_flip; - uvec4 instance_id; - uvec4 flip_stage; + uvec4 config_pack; // instance_id, flip_stage, y_direction, padding uvec4 alpha_test; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 520b9d4e3..fcc20d3b4 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -163,6 +163,8 @@ private: struct ShaderEntries { std::vector<ConstBufferEntry> const_buffer_entries; std::vector<SamplerEntry> texture_samplers; + std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> clip_distances; + std::size_t shader_length; }; using ProgramResult = std::pair<std::string, ShaderEntries>; @@ -175,6 +177,9 @@ struct ShaderSetup { struct { ProgramCode code; ProgramCode code_b; // Used for dual vertex shaders + u64 unique_identifier; + std::size_t real_size; + std::size_t real_size_b; } program; /// Used in scenarios where we have a dual vertex shaders diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 8b8869ecb..6a30c28d2 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -27,16 +27,18 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh alpha_test.func = func; alpha_test.ref = regs.alpha_test_ref; - // We only assign the instance to the first component of the vector, the rest is just padding. - instance_id[0] = state.current_instance; + instance_id = state.current_instance; // Assign in which stage the position has to be flipped // (the last stage before the fragment shader). if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) { - flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry); + flip_stage = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry); } else { - flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB); + flip_stage = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB); } + + // Y_NEGATE controls what value S2R returns for the Y_DIRECTION system value. + y_direction = regs.screen_y_control.y_negate == 0 ? 1.f : -1.f; } } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 9a5d7e289..4970aafed 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -21,8 +21,11 @@ using Tegra::Engines::Maxwell3D; struct MaxwellUniformData { void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage); alignas(16) GLvec4 viewport_flip; - alignas(16) GLuvec4 instance_id; - alignas(16) GLuvec4 flip_stage; + struct alignas(16) { + GLuint instance_id; + GLuint flip_stage; + GLfloat y_direction; + }; struct alignas(16) { GLuint enabled; GLuint func; @@ -30,7 +33,7 @@ struct MaxwellUniformData { GLuint padding; } alpha_test; }; -static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); @@ -57,6 +60,17 @@ public: } void ApplyTo(OpenGLState& state) { + UpdatePipeline(); + state.draw.shader_program = 0; + state.draw.program_pipeline = pipeline.handle; + state.geometry_shaders.enabled = (gs != 0); + } + +private: + void UpdatePipeline() { + // Avoid updating the pipeline when values have no changed + if (old_vs == vs && old_fs == fs && old_gs == gs) + return; // Workaround for AMD bug glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, @@ -65,14 +79,16 @@ public: glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs); glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs); glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs); - state.draw.shader_program = 0; - state.draw.program_pipeline = pipeline.handle; - state.geometry_shaders.enabled = (gs != 0); + + // Update the old values + old_vs = vs; + old_fs = fs; + old_gs = gs; } -private: OGLPipeline pipeline; GLuint vs{}, fs{}, gs{}; + GLuint old_vs{}, old_fs{}, old_gs{}; }; } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index d9910c6e8..dc0a5ed5e 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -92,6 +92,14 @@ OpenGLState::OpenGLState() { point.size = 1; fragment_color_clamp.enabled = false; + depth_clamp.far_plane = false; + depth_clamp.near_plane = false; + polygon_offset.fill_enable = false; + polygon_offset.line_enable = false; + polygon_offset.point_enable = false; + polygon_offset.factor = 0.0f; + polygon_offset.units = 0.0f; + polygon_offset.clamp = 0.0f; } void OpenGLState::ApplyDefaultState() { @@ -140,7 +148,7 @@ void OpenGLState::ApplyCulling() const { } void OpenGLState::ApplyColorMask() const { - if (GLAD_GL_ARB_viewport_array && independant_blend.enabled) { + if (independant_blend.enabled) { for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { const auto& updated = color_mask[i]; const auto& current = cur_state.color_mask[i]; @@ -233,16 +241,40 @@ void OpenGLState::ApplyStencilTest() const { config_stencil(GL_BACK, stencil.back, cur_state.stencil.back); } } +// Viewport does not affects glClearBuffer so emulate viewport using scissor test +void OpenGLState::EmulateViewportWithScissor() { + auto& current = viewports[0]; + if (current.scissor.enabled) { + const GLint left = std::max(current.x, current.scissor.x); + const GLint right = + std::max(current.x + current.width, current.scissor.x + current.scissor.width); + const GLint bottom = std::max(current.y, current.scissor.y); + const GLint top = + std::max(current.y + current.height, current.scissor.y + current.scissor.height); + current.scissor.x = std::max(left, 0); + current.scissor.y = std::max(bottom, 0); + current.scissor.width = std::max(right - left, 0); + current.scissor.height = std::max(top - bottom, 0); + } else { + current.scissor.enabled = true; + current.scissor.x = current.x; + current.scissor.y = current.y; + current.scissor.width = current.width; + current.scissor.height = current.height; + } +} void OpenGLState::ApplyViewport() const { - if (GLAD_GL_ARB_viewport_array && geometry_shaders.enabled) { + if (geometry_shaders.enabled) { for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports); i++) { const auto& current = cur_state.viewports[i]; const auto& updated = viewports[i]; if (updated.x != current.x || updated.y != current.y || updated.width != current.width || updated.height != current.height) { - glViewportIndexedf(i, updated.x, updated.y, updated.width, updated.height); + glViewportIndexedf( + i, static_cast<GLfloat>(updated.x), static_cast<GLfloat>(updated.y), + static_cast<GLfloat>(updated.width), static_cast<GLfloat>(updated.height)); } if (updated.depth_range_near != current.depth_range_near || updated.depth_range_far != current.depth_range_far) { @@ -270,8 +302,7 @@ void OpenGLState::ApplyViewport() const { const auto& updated = viewports[0]; if (updated.x != current.x || updated.y != current.y || updated.width != current.width || updated.height != current.height) { - glViewport(static_cast<GLint>(updated.x), static_cast<GLint>(updated.y), - static_cast<GLsizei>(updated.width), static_cast<GLsizei>(updated.height)); + glViewport(updated.x, updated.y, updated.width, updated.height); } if (updated.depth_range_near != current.depth_range_near || updated.depth_range_far != current.depth_range_far) { @@ -339,14 +370,14 @@ void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const { if (blend_changed || updated.src_rgb_func != current.src_rgb_func || updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func || updated.dst_a_func != current.dst_a_func) { - glBlendFuncSeparateiARB(static_cast<GLuint>(target), updated.src_rgb_func, - updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func); + glBlendFuncSeparatei(static_cast<GLuint>(target), updated.src_rgb_func, + updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func); } if (blend_changed || updated.rgb_equation != current.rgb_equation || updated.a_equation != current.a_equation) { - glBlendEquationSeparateiARB(static_cast<GLuint>(target), updated.rgb_equation, - updated.a_equation); + glBlendEquationSeparatei(static_cast<GLuint>(target), updated.rgb_equation, + updated.a_equation); } } @@ -383,6 +414,55 @@ void OpenGLState::ApplyLogicOp() const { } } +void OpenGLState::ApplyPolygonOffset() const { + + const bool fill_enable_changed = + polygon_offset.fill_enable != cur_state.polygon_offset.fill_enable; + const bool line_enable_changed = + polygon_offset.line_enable != cur_state.polygon_offset.line_enable; + const bool point_enable_changed = + polygon_offset.point_enable != cur_state.polygon_offset.point_enable; + const bool factor_changed = polygon_offset.factor != cur_state.polygon_offset.factor; + const bool units_changed = polygon_offset.units != cur_state.polygon_offset.units; + const bool clamp_changed = polygon_offset.clamp != cur_state.polygon_offset.clamp; + + if (fill_enable_changed) { + if (polygon_offset.fill_enable) { + glEnable(GL_POLYGON_OFFSET_FILL); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } + } + + if (line_enable_changed) { + if (polygon_offset.line_enable) { + glEnable(GL_POLYGON_OFFSET_LINE); + } else { + glDisable(GL_POLYGON_OFFSET_LINE); + } + } + + if (point_enable_changed) { + if (polygon_offset.point_enable) { + glEnable(GL_POLYGON_OFFSET_POINT); + } else { + glDisable(GL_POLYGON_OFFSET_POINT); + } + } + + if ((polygon_offset.fill_enable || polygon_offset.line_enable || polygon_offset.point_enable) && + (factor_changed || units_changed || clamp_changed)) { + + if (GLAD_GL_EXT_polygon_offset_clamp && polygon_offset.clamp != 0) { + glPolygonOffsetClamp(polygon_offset.factor, polygon_offset.units, polygon_offset.clamp); + } else { + glPolygonOffset(polygon_offset.factor, polygon_offset.units); + UNIMPLEMENTED_IF_MSG(polygon_offset.clamp != 0, + "Unimplemented Depth polygon offset clamp."); + } + } +} + void OpenGLState::ApplyTextures() const { for (std::size_t i = 0; i < std::size(texture_units); ++i) { const auto& texture_unit = texture_units[i]; @@ -446,6 +526,21 @@ void OpenGLState::ApplyVertexBufferState() const { } } +void OpenGLState::ApplyDepthClamp() const { + if (depth_clamp.far_plane == cur_state.depth_clamp.far_plane && + depth_clamp.near_plane == cur_state.depth_clamp.near_plane) { + return; + } + if (depth_clamp.far_plane != depth_clamp.near_plane) { + UNIMPLEMENTED_MSG("Unimplemented Depth Clamp Separation!"); + } + if (depth_clamp.far_plane || depth_clamp.near_plane) { + glEnable(GL_DEPTH_CLAMP); + } else { + glDisable(GL_DEPTH_CLAMP); + } +} + void OpenGLState::Apply() const { ApplyFramebufferState(); ApplyVertexBufferState(); @@ -477,11 +572,9 @@ void OpenGLState::Apply() const { if (point.size != cur_state.point.size) { glPointSize(point.size); } - if (GLAD_GL_ARB_color_buffer_float) { - if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) { - glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB, - fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE); - } + if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) { + glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB, + fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE); } if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) { if (multisample_control.alpha_to_coverage) { @@ -497,7 +590,7 @@ void OpenGLState::Apply() const { glDisable(GL_SAMPLE_ALPHA_TO_ONE); } } - + ApplyDepthClamp(); ApplyColorMask(); ApplyViewport(); ApplyStencilTest(); @@ -509,6 +602,7 @@ void OpenGLState::Apply() const { ApplyLogicOp(); ApplyTextures(); ApplySamplers(); + ApplyPolygonOffset(); cur_state = *this; } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index bdc743b0f..439bfbc98 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -49,6 +49,11 @@ public: } fragment_color_clamp; struct { + bool far_plane; + bool near_plane; + } depth_clamp; // GL_DEPTH_CLAMP + + struct { bool enabled; // viewports arrays are only supported when geometry shaders are enabled. } geometry_shaders; @@ -156,10 +161,10 @@ public: } draw; struct viewport { - GLfloat x; - GLfloat y; - GLfloat width; - GLfloat height; + GLint x; + GLint y; + GLint width; + GLint height; GLfloat depth_range_near; // GL_DEPTH_RANGE GLfloat depth_range_far; // GL_DEPTH_RANGE struct { @@ -176,7 +181,16 @@ public: float size; // GL_POINT_SIZE } point; - std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE + struct { + bool point_enable; + bool line_enable; + bool fill_enable; + GLfloat units; + GLfloat factor; + GLfloat clamp; + } polygon_offset; + + std::array<bool, 8> clip_distance; // GL_CLIP_DISTANCE OpenGLState(); @@ -206,6 +220,7 @@ public: OpenGLState& ResetBuffer(GLuint handle); OpenGLState& ResetVertexArray(GLuint handle); OpenGLState& ResetFramebuffer(GLuint handle); + void EmulateViewportWithScissor(); private: static OpenGLState cur_state; @@ -225,6 +240,8 @@ private: void ApplyLogicOp() const; void ApplyTextures() const; void ApplySamplers() const; + void ApplyDepthClamp() const; + void ApplyPolygonOffset() const; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 065b3929c..a8833c06e 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -218,14 +218,19 @@ inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) { inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { switch (equation) { case Maxwell::Blend::Equation::Add: + case Maxwell::Blend::Equation::AddGL: return GL_FUNC_ADD; case Maxwell::Blend::Equation::Subtract: + case Maxwell::Blend::Equation::SubtractGL: return GL_FUNC_SUBTRACT; case Maxwell::Blend::Equation::ReverseSubtract: + case Maxwell::Blend::Equation::ReverseSubtractGL: return GL_FUNC_REVERSE_SUBTRACT; case Maxwell::Blend::Equation::Min: + case Maxwell::Blend::Equation::MinGL: return GL_MIN; case Maxwell::Blend::Equation::Max: + case Maxwell::Blend::Equation::MaxGL: return GL_MAX; } LOG_ERROR(Render_OpenGL, "Unimplemented blend equation={}", static_cast<u32>(equation)); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index ea38da932..4fd0d66c5 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -19,9 +19,9 @@ #include "core/settings.h" #include "core/telemetry_session.h" #include "core/tracer/recorder.h" +#include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/renderer_opengl.h" -#include "video_core/utils.h" namespace OpenGL { @@ -304,6 +304,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, gl_framebuffer_data.resize(texture.width * texture.height * 4); break; default: + internal_format = GL_RGBA; + texture.gl_format = GL_RGBA; + texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + gl_framebuffer_data.resize(texture.width * texture.height * 4); + LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}", + static_cast<u32>(framebuffer.pixel_format)); UNREACHABLE(); } @@ -484,7 +490,7 @@ bool RendererOpenGL::Init() { Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model); Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version); - if (!GLAD_GL_VERSION_3_3) { + if (!GLAD_GL_VERSION_4_3) { return false; } diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 0dd3eb2e4..e23cfecbc 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -125,6 +125,75 @@ enum class SurfaceTarget { TextureCubeArray, }; +constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ + 1, // ABGR8U + 1, // ABGR8S + 1, // ABGR8UI + 1, // B5G6R5U + 1, // A2B10G10R10U + 1, // A1B5G5R5U + 1, // R8U + 1, // R8UI + 1, // RGBA16F + 1, // RGBA16U + 1, // RGBA16UI + 1, // R11FG11FB10F + 1, // RGBA32UI + 4, // DXT1 + 4, // DXT23 + 4, // DXT45 + 4, // DXN1 + 4, // DXN2UNORM + 4, // DXN2SNORM + 4, // BC7U + 4, // BC6H_UF16 + 4, // BC6H_SF16 + 4, // ASTC_2D_4X4 + 1, // G8R8U + 1, // G8R8S + 1, // BGRA8 + 1, // RGBA32F + 1, // RG32F + 1, // R32F + 1, // R16F + 1, // R16U + 1, // R16S + 1, // R16UI + 1, // R16I + 1, // RG16 + 1, // RG16F + 1, // RG16UI + 1, // RG16I + 1, // RG16S + 1, // RGB32F + 1, // RGBA8_SRGB + 1, // RG8U + 1, // RG8S + 1, // RG32UI + 1, // R32UI + 4, // ASTC_2D_8X8 + 4, // ASTC_2D_8X5 + 4, // ASTC_2D_5X4 + 1, // BGRA8_SRGB + 4, // DXT1_SRGB + 4, // DXT23_SRGB + 4, // DXT45_SRGB + 4, // BC7U_SRGB + 4, // ASTC_2D_4X4_SRGB + 4, // ASTC_2D_8X8_SRGB + 4, // ASTC_2D_8X5_SRGB + 4, // ASTC_2D_5X4_SRGB + 4, // ASTC_2D_5X5 + 4, // ASTC_2D_5X5_SRGB + 4, // ASTC_2D_10X8 + 4, // ASTC_2D_10X8_SRGB + 1, // Z32F + 1, // Z16 + 1, // Z24S8 + 1, // S8Z24 + 1, // Z32FS8 +}}; + /** * Gets the compression factor for the specified PixelFormat. This applies to just the * "compressed width" and "compressed height", not the overall compression factor of a @@ -135,304 +204,237 @@ static constexpr u32 GetCompressionFactor(PixelFormat format) { if (format == PixelFormat::Invalid) return 0; - constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // G8R8U - 1, // G8R8S - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG32UI - 1, // R32UI - 4, // ASTC_2D_8X8 - 4, // ASTC_2D_8X5 - 4, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 4, // ASTC_2D_4X4_SRGB - 4, // ASTC_2D_8X8_SRGB - 4, // ASTC_2D_8X5_SRGB - 4, // ASTC_2D_5X4_SRGB - 4, // ASTC_2D_5X5 - 4, // ASTC_2D_5X5_SRGB - 4, // ASTC_2D_10X8 - 4, // ASTC_2D_10X8_SRGB - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 - }}; - ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size()); return compression_factor_table[static_cast<std::size_t>(format)]; } +constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ + 1, // ABGR8U + 1, // ABGR8S + 1, // ABGR8UI + 1, // B5G6R5U + 1, // A2B10G10R10U + 1, // A1B5G5R5U + 1, // R8U + 1, // R8UI + 1, // RGBA16F + 1, // RGBA16U + 1, // RGBA16UI + 1, // R11FG11FB10F + 1, // RGBA32UI + 4, // DXT1 + 4, // DXT23 + 4, // DXT45 + 4, // DXN1 + 4, // DXN2UNORM + 4, // DXN2SNORM + 4, // BC7U + 4, // BC6H_UF16 + 4, // BC6H_SF16 + 4, // ASTC_2D_4X4 + 1, // G8R8U + 1, // G8R8S + 1, // BGRA8 + 1, // RGBA32F + 1, // RG32F + 1, // R32F + 1, // R16F + 1, // R16U + 1, // R16S + 1, // R16UI + 1, // R16I + 1, // RG16 + 1, // RG16F + 1, // RG16UI + 1, // RG16I + 1, // RG16S + 1, // RGB32F + 1, // RGBA8_SRGB + 1, // RG8U + 1, // RG8S + 1, // RG32UI + 1, // R32UI + 8, // ASTC_2D_8X8 + 8, // ASTC_2D_8X5 + 5, // ASTC_2D_5X4 + 1, // BGRA8_SRGB + 4, // DXT1_SRGB + 4, // DXT23_SRGB + 4, // DXT45_SRGB + 4, // BC7U_SRGB + 4, // ASTC_2D_4X4_SRGB + 8, // ASTC_2D_8X8_SRGB + 8, // ASTC_2D_8X5_SRGB + 5, // ASTC_2D_5X4_SRGB + 5, // ASTC_2D_5X5 + 5, // ASTC_2D_5X5_SRGB + 10, // ASTC_2D_10X8 + 10, // ASTC_2D_10X8_SRGB + 1, // Z32F + 1, // Z16 + 1, // Z24S8 + 1, // S8Z24 + 1, // Z32FS8 +}}; + static constexpr u32 GetDefaultBlockWidth(PixelFormat format) { if (format == PixelFormat::Invalid) return 0; - constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // G8R8U - 1, // G8R8S - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG32UI - 1, // R32UI - 8, // ASTC_2D_8X8 - 8, // ASTC_2D_8X5 - 5, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 4, // ASTC_2D_4X4_SRGB - 8, // ASTC_2D_8X8_SRGB - 8, // ASTC_2D_8X5_SRGB - 5, // ASTC_2D_5X4_SRGB - 5, // ASTC_2D_5X5 - 5, // ASTC_2D_5X5_SRGB - 10, // ASTC_2D_10X8 - 10, // ASTC_2D_10X8_SRGB - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 - }}; + ASSERT(static_cast<std::size_t>(format) < block_width_table.size()); return block_width_table[static_cast<std::size_t>(format)]; } +constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ + 1, // ABGR8U + 1, // ABGR8S + 1, // ABGR8UI + 1, // B5G6R5U + 1, // A2B10G10R10U + 1, // A1B5G5R5U + 1, // R8U + 1, // R8UI + 1, // RGBA16F + 1, // RGBA16U + 1, // RGBA16UI + 1, // R11FG11FB10F + 1, // RGBA32UI + 4, // DXT1 + 4, // DXT23 + 4, // DXT45 + 4, // DXN1 + 4, // DXN2UNORM + 4, // DXN2SNORM + 4, // BC7U + 4, // BC6H_UF16 + 4, // BC6H_SF16 + 4, // ASTC_2D_4X4 + 1, // G8R8U + 1, // G8R8S + 1, // BGRA8 + 1, // RGBA32F + 1, // RG32F + 1, // R32F + 1, // R16F + 1, // R16U + 1, // R16S + 1, // R16UI + 1, // R16I + 1, // RG16 + 1, // RG16F + 1, // RG16UI + 1, // RG16I + 1, // RG16S + 1, // RGB32F + 1, // RGBA8_SRGB + 1, // RG8U + 1, // RG8S + 1, // RG32UI + 1, // R32UI + 8, // ASTC_2D_8X8 + 5, // ASTC_2D_8X5 + 4, // ASTC_2D_5X4 + 1, // BGRA8_SRGB + 4, // DXT1_SRGB + 4, // DXT23_SRGB + 4, // DXT45_SRGB + 4, // BC7U_SRGB + 4, // ASTC_2D_4X4_SRGB + 8, // ASTC_2D_8X8_SRGB + 5, // ASTC_2D_8X5_SRGB + 4, // ASTC_2D_5X4_SRGB + 5, // ASTC_2D_5X5 + 5, // ASTC_2D_5X5_SRGB + 8, // ASTC_2D_10X8 + 8, // ASTC_2D_10X8_SRGB + 1, // Z32F + 1, // Z16 + 1, // Z24S8 + 1, // S8Z24 + 1, // Z32FS8 +}}; + static constexpr u32 GetDefaultBlockHeight(PixelFormat format) { if (format == PixelFormat::Invalid) return 0; - constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // G8R8U - 1, // G8R8S - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG32UI - 1, // R32UI - 8, // ASTC_2D_8X8 - 5, // ASTC_2D_8X5 - 4, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 4, // ASTC_2D_4X4_SRGB - 8, // ASTC_2D_8X8_SRGB - 5, // ASTC_2D_8X5_SRGB - 4, // ASTC_2D_5X4_SRGB - 5, // ASTC_2D_5X5 - 5, // ASTC_2D_5X5_SRGB - 8, // ASTC_2D_10X8 - 8, // ASTC_2D_10X8_SRGB - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 - }}; - ASSERT(static_cast<std::size_t>(format) < block_height_table.size()); return block_height_table[static_cast<std::size_t>(format)]; } +constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ + 32, // ABGR8U + 32, // ABGR8S + 32, // ABGR8UI + 16, // B5G6R5U + 32, // A2B10G10R10U + 16, // A1B5G5R5U + 8, // R8U + 8, // R8UI + 64, // RGBA16F + 64, // RGBA16U + 64, // RGBA16UI + 32, // R11FG11FB10F + 128, // RGBA32UI + 64, // DXT1 + 128, // DXT23 + 128, // DXT45 + 64, // DXN1 + 128, // DXN2UNORM + 128, // DXN2SNORM + 128, // BC7U + 128, // BC6H_UF16 + 128, // BC6H_SF16 + 128, // ASTC_2D_4X4 + 16, // G8R8U + 16, // G8R8S + 32, // BGRA8 + 128, // RGBA32F + 64, // RG32F + 32, // R32F + 16, // R16F + 16, // R16U + 16, // R16S + 16, // R16UI + 16, // R16I + 32, // RG16 + 32, // RG16F + 32, // RG16UI + 32, // RG16I + 32, // RG16S + 96, // RGB32F + 32, // RGBA8_SRGB + 16, // RG8U + 16, // RG8S + 64, // RG32UI + 32, // R32UI + 128, // ASTC_2D_8X8 + 128, // ASTC_2D_8X5 + 128, // ASTC_2D_5X4 + 32, // BGRA8_SRGB + 64, // DXT1_SRGB + 128, // DXT23_SRGB + 128, // DXT45_SRGB + 128, // BC7U + 128, // ASTC_2D_4X4_SRGB + 128, // ASTC_2D_8X8_SRGB + 128, // ASTC_2D_8X5_SRGB + 128, // ASTC_2D_5X4_SRGB + 128, // ASTC_2D_5X5 + 128, // ASTC_2D_5X5_SRGB + 128, // ASTC_2D_10X8 + 128, // ASTC_2D_10X8_SRGB + 32, // Z32F + 16, // Z16 + 32, // Z24S8 + 32, // S8Z24 + 64, // Z32FS8 +}}; + static constexpr u32 GetFormatBpp(PixelFormat format) { if (format == PixelFormat::Invalid) return 0; - constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ - 32, // ABGR8U - 32, // ABGR8S - 32, // ABGR8UI - 16, // B5G6R5U - 32, // A2B10G10R10U - 16, // A1B5G5R5U - 8, // R8U - 8, // R8UI - 64, // RGBA16F - 64, // RGBA16U - 64, // RGBA16UI - 32, // R11FG11FB10F - 128, // RGBA32UI - 64, // DXT1 - 128, // DXT23 - 128, // DXT45 - 64, // DXN1 - 128, // DXN2UNORM - 128, // DXN2SNORM - 128, // BC7U - 128, // BC6H_UF16 - 128, // BC6H_SF16 - 128, // ASTC_2D_4X4 - 16, // G8R8U - 16, // G8R8S - 32, // BGRA8 - 128, // RGBA32F - 64, // RG32F - 32, // R32F - 16, // R16F - 16, // R16U - 16, // R16S - 16, // R16UI - 16, // R16I - 32, // RG16 - 32, // RG16F - 32, // RG16UI - 32, // RG16I - 32, // RG16S - 96, // RGB32F - 32, // RGBA8_SRGB - 16, // RG8U - 16, // RG8S - 64, // RG32UI - 32, // R32UI - 128, // ASTC_2D_8X8 - 128, // ASTC_2D_8X5 - 128, // ASTC_2D_5X4 - 32, // BGRA8_SRGB - 64, // DXT1_SRGB - 128, // DXT23_SRGB - 128, // DXT45_SRGB - 128, // BC7U - 128, // ASTC_2D_4X4_SRGB - 128, // ASTC_2D_8X8_SRGB - 128, // ASTC_2D_8X5_SRGB - 128, // ASTC_2D_5X4_SRGB - 128, // ASTC_2D_5X5 - 128, // ASTC_2D_5X5_SRGB - 128, // ASTC_2D_10X8 - 128, // ASTC_2D_10X8_SRGB - 32, // Z32F - 16, // Z16 - 32, // Z24S8 - 32, // S8Z24 - 64, // Z32FS8 - }}; - ASSERT(static_cast<std::size_t>(format) < bpp_table.size()); return bpp_table[static_cast<std::size_t>(format)]; } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 7eabd34f1..bbae9285f 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -127,7 +127,8 @@ void FastProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const template <bool fast> void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle, const u32 width, const u32 height, const u32 depth, const u32 bytes_per_pixel, - const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth) { + const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth, + const u32 width_spacing) { auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); }; const u32 stride_x = width * out_bytes_per_pixel; const u32 layer_z = height * stride_x; @@ -137,7 +138,8 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool const u32 block_x_elements = gob_elements_x; const u32 block_y_elements = gob_elements_y * block_height; const u32 block_z_elements = gob_elements_z * block_depth; - const u32 blocks_on_x = div_ceil(width, block_x_elements); + const u32 aligned_width = Common::AlignUp(width, gob_elements_x * width_spacing); + const u32 blocks_on_x = div_ceil(aligned_width, block_x_elements); const u32 blocks_on_y = div_ceil(height, block_y_elements); const u32 blocks_on_z = div_ceil(depth, block_z_elements); const u32 xy_block_size = gob_size * block_height; @@ -169,13 +171,15 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data, - bool unswizzle, u32 block_height, u32 block_depth) { + bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) { if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % fast_swizzle_align == 0) { SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth); + bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, + width_spacing); } else { SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth); + bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, + width_spacing); } } @@ -228,19 +232,19 @@ u32 BytesPerPixel(TextureFormat format) { void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, - u32 block_depth) { + u32 block_depth, u32 width_spacing) { CopySwizzledData((width + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true, - block_height, block_depth); + block_height, block_depth, width_spacing); } std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, - u32 block_height, u32 block_depth) { + u32 block_height, u32 block_depth, u32 width_spacing) { std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel, - width, height, depth, block_height, block_depth); + width, height, depth, block_height, block_depth, width_spacing); return unswizzled_data; } diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index f4ef7c73e..85b7e9f7b 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -22,19 +22,20 @@ inline std::size_t GetGOBSize() { void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height = TICEntry::DefaultBlockHeight, - u32 block_depth = TICEntry::DefaultBlockHeight); + u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0); /** * Unswizzles a swizzled texture without changing its format. */ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height = TICEntry::DefaultBlockHeight, - u32 block_depth = TICEntry::DefaultBlockHeight); + u32 block_depth = TICEntry::DefaultBlockHeight, + u32 width_spacing = 0); /// Copies texture data from a buffer and performs swizzling/unswizzling as necessary. void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, - bool unswizzle, u32 block_height, u32 block_depth); + bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing); /** * Decodes an unswizzled texture into a A8R8G8B8 texture. diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index ffa08f5c1..e7c78bee2 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -166,6 +166,8 @@ struct TICEntry { BitField<3, 3, u32> block_height; BitField<6, 3, u32> block_depth; + BitField<10, 3, u32> tile_width_spacing; + // High 16 bits of the pitch value BitField<0, 16, u32> pitch_high; BitField<26, 1, u32> use_header_opt_control; diff --git a/src/video_core/utils.h b/src/video_core/utils.h deleted file mode 100644 index e0a14d48f..000000000 --- a/src/video_core/utils.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace VideoCore { - -// 8x8 Z-Order coordinate from 2D coordinates -static inline u32 MortonInterleave(u32 x, u32 y) { - static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; - static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; - return xlut[x % 8] + ylut[y % 8]; -} - -/** - * Calculates the offset of the position of the pixel in Morton order - */ -static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { - // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each - // of which is composed of four 2x2 subtiles each of which is composed of four texels. - // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. - // texels are laid out in a 2x2 subtile like this: - // 2 3 - // 0 1 - // - // The full 8x8 tile has the texels arranged like this: - // - // 42 43 46 47 58 59 62 63 - // 40 41 44 45 56 57 60 61 - // 34 35 38 39 50 51 54 55 - // 32 33 36 37 48 49 52 53 - // 10 11 14 15 26 27 30 31 - // 08 09 12 13 24 25 28 29 - // 02 03 06 07 18 19 22 23 - // 00 01 04 05 16 17 20 21 - // - // This pattern is what's called Z-order curve, or Morton order. - - const unsigned int block_height = 8; - const unsigned int coarse_x = x & ~7; - - u32 i = VideoCore::MortonInterleave(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - -static inline u32 MortonInterleave128(u32 x, u32 y) { - // 128x128 Z-Order coordinate from 2D coordinates - static constexpr u32 xlut[] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, - 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, - 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, - 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, - 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, - 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, - 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, - 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, - 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, - 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, - 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, - 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, - 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, - 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, - 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, - 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, - 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, - 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, - 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, - 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, - 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, - 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, - 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, - 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, - 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, - 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, - 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, - 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, - 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, - 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, - 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, - 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, - 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, - 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, - }; - static constexpr u32 ylut[] = { - 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, - 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, - 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, - 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, - 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, - 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, - 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, - 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, - 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, - 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, - 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, - 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, - 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, - 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, - 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, - 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, - 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, - 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, - 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, - 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, - 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, - 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, - 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, - 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, - 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, - 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, - 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, - 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, - 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, - 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, - 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, - 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, - 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, - 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, - 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, - }; - return xlut[x % 128] + ylut[y % 128]; -} - -static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { - // Calculates the offset of the position of the pixel in Morton order - // Framebuffer images are split into 128x128 tiles. - - const unsigned int block_height = 128; - const unsigned int coarse_x = x & ~127; - - u32 i = MortonInterleave128(x, y); - - const unsigned int offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - -static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, - u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, - bool morton_to_gl) { - u8* data_ptrs[2]; - for (unsigned y = 0; y < height; ++y) { - for (unsigned x = 0; x < width; ++x) { - const u32 coarse_y = y & ~127; - u32 morton_offset = - GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; - u32 gl_pixel_index = (x + y * width) * gl_bytes_per_pixel; - - data_ptrs[morton_to_gl] = morton_data + morton_offset; - data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; - - memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - -} // namespace VideoCore diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f9ca2948e..3232aa8fb 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu Info.plist about_dialog.cpp about_dialog.h + applets/software_keyboard.cpp + applets/software_keyboard.h bootmanager.cpp bootmanager.h compatibility_list.cpp @@ -27,8 +29,16 @@ add_executable(yuzu configuration/configure_graphics.h configuration/configure_input.cpp configuration/configure_input.h + configuration/configure_input_player.cpp + configuration/configure_input_player.h + configuration/configure_mouse_advanced.cpp + configuration/configure_mouse_advanced.h configuration/configure_system.cpp configuration/configure_system.h + configuration/configure_per_general.cpp + configuration/configure_per_general.h + configuration/configure_touchscreen_advanced.cpp + configuration/configure_touchscreen_advanced.h configuration/configure_web.cpp configuration/configure_web.h debugger/graphics/graphics_breakpoint_observer.cpp @@ -76,7 +86,11 @@ set(UIS configuration/configure_general.ui configuration/configure_graphics.ui configuration/configure_input.ui + configuration/configure_input_player.ui + configuration/configure_mouse_advanced.ui + configuration/configure_per_general.ui configuration/configure_system.ui + configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui hotkeys.ui main.ui diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp new file mode 100644 index 000000000..8a26fdff1 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.cpp @@ -0,0 +1,152 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <mutex> +#include <QDialogButtonBox> +#include <QFont> +#include <QLabel> +#include <QLineEdit> +#include <QVBoxLayout> +#include "core/hle/lock.h" +#include "yuzu/applets/software_keyboard.h" +#include "yuzu/main.h" + +QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( + Core::Frontend::SoftwareKeyboardParameters parameters) + : parameters(std::move(parameters)) {} + +QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { + if (input.size() > parameters.max_length) + return Invalid; + if (parameters.disable_space && input.contains(' ')) + return Invalid; + if (parameters.disable_address && input.contains('@')) + return Invalid; + if (parameters.disable_percent && input.contains('%')) + return Invalid; + if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) + return Invalid; + if (parameters.disable_number && + std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { + return Invalid; + } + + if (parameters.disable_download_code && + std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { + return Invalid; + } + + return Acceptable; +} + +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) + : QDialog(parent), parameters(std::move(parameters_)) { + layout = new QVBoxLayout; + + header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); + header_label->setFont({header_label->font().family(), 11, QFont::Bold}); + if (header_label->text().isEmpty()) + header_label->setText(tr("Enter text:")); + + sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); + sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), + sub_label->font().weight(), true}); + sub_label->setHidden(parameters.sub_text.empty()); + + guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); + guide_label->setHidden(parameters.guide_text.empty()); + + length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); + length_label->setAlignment(Qt::AlignRight); + length_label->setFont({length_label->font().family(), 8}); + + line_edit = new QLineEdit; + line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); + line_edit->setMaxLength(static_cast<int>(parameters.max_length)); + line_edit->setText(QString::fromStdU16String(parameters.initial_text)); + line_edit->setCursorPosition( + parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size())); + line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); + + connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { + length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); + }); + + buttons = new QDialogButtonBox; + buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + buttons->addButton(parameters.submit_text.empty() + ? tr("OK") + : QString::fromStdU16String(parameters.submit_text), + QDialogButtonBox::AcceptRole); + + connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject); + layout->addWidget(header_label); + layout->addWidget(sub_label); + layout->addWidget(guide_label); + layout->addWidget(length_label); + layout->addWidget(line_edit); + layout->addWidget(buttons); + setLayout(layout); + setWindowTitle(tr("Software Keyboard")); +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; + +void QtSoftwareKeyboardDialog::accept() { + ok = true; + text = line_edit->text().toStdU16String(); + QDialog::accept(); +} + +void QtSoftwareKeyboardDialog::reject() { + ok = false; + text.clear(); + QDialog::reject(); +} + +std::u16string QtSoftwareKeyboardDialog::GetText() const { + return text; +} + +bool QtSoftwareKeyboardDialog::GetStatus() const { + return ok; +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, + &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, + &QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); +} + +QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out, + Core::Frontend::SoftwareKeyboardParameters parameters) const { + text_output = std::move(out); + emit MainWindowGetText(parameters); +} + +void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const { + this->finished_check = std::move(finished_check); + emit MainWindowTextCheckDialog(error_message); +} + +void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + text_output(text); +} + +void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + finished_check(); +} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h new file mode 100644 index 000000000..c63720ba4 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.h @@ -0,0 +1,79 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> +#include <QValidator> +#include "common/assert.h" +#include "core/frontend/applets/software_keyboard.h" + +class GMainWindow; +class QDialogButtonBox; +class QLabel; +class QLineEdit; +class QVBoxLayout; +class QtSoftwareKeyboard; + +class QtSoftwareKeyboardValidator final : public QValidator { +public: + explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); + State validate(QString& input, int& pos) const override; + +private: + Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboardDialog final : public QDialog { + Q_OBJECT + +public: + QtSoftwareKeyboardDialog(QWidget* parent, + Core::Frontend::SoftwareKeyboardParameters parameters); + ~QtSoftwareKeyboardDialog() override; + + void accept() override; + void reject() override; + + std::u16string GetText() const; + bool GetStatus() const; + +private: + bool ok = false; + std::u16string text; + + QDialogButtonBox* buttons; + QLabel* header_label; + QLabel* sub_label; + QLabel* guide_label; + QLabel* length_label; + QLineEdit* line_edit; + QVBoxLayout* layout; + + Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { + Q_OBJECT + +public: + explicit QtSoftwareKeyboard(GMainWindow& parent); + ~QtSoftwareKeyboard() override; + + void RequestText(std::function<void(std::optional<std::u16string>)> out, + Core::Frontend::SoftwareKeyboardParameters parameters) const override; + void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const override; + +signals: + void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; + void MainWindowTextCheckDialog(std::u16string error_message) const; + +private: + void MainWindowFinishedText(std::optional<std::u16string> text); + void MainWindowFinishedCheckDialog(); + + mutable std::function<void(std::optional<std::u16string>)> text_output; + mutable std::function<void()> finished_check; +}; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 39eef8858..384e17921 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -310,7 +310,7 @@ void GRenderWindow::InitRenderTarget() { // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose QGLFormat fmt; - fmt.setVersion(3, 3); + fmt.setVersion(4, 3); fmt.setProfile(QGLFormat::CoreProfile); fmt.setSwapInterval(false); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index be69fb831..eb2077b0f 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,6 +5,7 @@ #include <QSettings> #include "common/file_util.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" #include "yuzu/configuration/config.h" #include "yuzu/ui_settings.h" @@ -47,40 +48,310 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config: }, }}; -void Config::ReadValues() { - qt_config->beginGroup("Controls"); +const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons = + { + Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> Config::default_keyboard_keys = { + 0, + 0, + 0, + 0, + Qt::Key_A, + Qt::Key_B, + Qt::Key_C, + Qt::Key_D, + Qt::Key_E, + Qt::Key_F, + Qt::Key_G, + Qt::Key_H, + Qt::Key_I, + Qt::Key_J, + Qt::Key_K, + Qt::Key_L, + Qt::Key_M, + Qt::Key_N, + Qt::Key_O, + Qt::Key_P, + Qt::Key_Q, + Qt::Key_R, + Qt::Key_S, + Qt::Key_T, + Qt::Key_U, + Qt::Key_V, + Qt::Key_W, + Qt::Key_X, + Qt::Key_Y, + Qt::Key_Z, + Qt::Key_1, + Qt::Key_2, + Qt::Key_3, + Qt::Key_4, + Qt::Key_5, + Qt::Key_6, + Qt::Key_7, + Qt::Key_8, + Qt::Key_9, + Qt::Key_0, + Qt::Key_Enter, + Qt::Key_Escape, + Qt::Key_Backspace, + Qt::Key_Tab, + Qt::Key_Space, + Qt::Key_Minus, + Qt::Key_Equal, + Qt::Key_BracketLeft, + Qt::Key_BracketRight, + Qt::Key_Backslash, + Qt::Key_Dead_Tilde, + Qt::Key_Semicolon, + Qt::Key_Apostrophe, + Qt::Key_Dead_Grave, + Qt::Key_Comma, + Qt::Key_Period, + Qt::Key_Slash, + Qt::Key_CapsLock, + + Qt::Key_F1, + Qt::Key_F2, + Qt::Key_F3, + Qt::Key_F4, + Qt::Key_F5, + Qt::Key_F6, + Qt::Key_F7, + Qt::Key_F8, + Qt::Key_F9, + Qt::Key_F10, + Qt::Key_F11, + Qt::Key_F12, + + Qt::Key_SysReq, + Qt::Key_ScrollLock, + Qt::Key_Pause, + Qt::Key_Insert, + Qt::Key_Home, + Qt::Key_PageUp, + Qt::Key_Delete, + Qt::Key_End, + Qt::Key_PageDown, + Qt::Key_Right, + Qt::Key_Left, + Qt::Key_Down, + Qt::Key_Up, + + Qt::Key_NumLock, + Qt::Key_Slash, + Qt::Key_Asterisk, + Qt::Key_Minus, + Qt::Key_Plus, + Qt::Key_Enter, + Qt::Key_1, + Qt::Key_2, + Qt::Key_3, + Qt::Key_4, + Qt::Key_5, + Qt::Key_6, + Qt::Key_7, + Qt::Key_8, + Qt::Key_9, + Qt::Key_0, + Qt::Key_Period, + + 0, + 0, + Qt::Key_PowerOff, + Qt::Key_Equal, + + Qt::Key_F13, + Qt::Key_F14, + Qt::Key_F15, + Qt::Key_F16, + Qt::Key_F17, + Qt::Key_F18, + Qt::Key_F19, + Qt::Key_F20, + Qt::Key_F21, + Qt::Key_F22, + Qt::Key_F23, + Qt::Key_F24, + + Qt::Key_Open, + Qt::Key_Help, + Qt::Key_Menu, + 0, + Qt::Key_Stop, + Qt::Key_AudioRepeat, + Qt::Key_Undo, + Qt::Key_Cut, + Qt::Key_Copy, + Qt::Key_Paste, + Qt::Key_Find, + Qt::Key_VolumeMute, + Qt::Key_VolumeUp, + Qt::Key_VolumeDown, + Qt::Key_CapsLock, + Qt::Key_NumLock, + Qt::Key_ScrollLock, + Qt::Key_Comma, + + Qt::Key_ParenLeft, + Qt::Key_ParenRight, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default_keyboard_mods = { + Qt::Key_Control, Qt::Key_Shift, Qt::Key_Alt, Qt::Key_ApplicationLeft, + Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, +}; + +void Config::ReadPlayerValues() { + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + auto& player = Settings::values.players[p]; + + player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); + + player.type = static_cast<Settings::ControllerType>( + qt_config + ->value(QString("player_%1_type").arg(p), + static_cast<u8>(Settings::ControllerType::DualJoycon)) + .toUInt()); + + player.body_color_left = qt_config + ->value(QString("player_%1_body_color_left").arg(p), + Settings::JOYCON_BODY_NEON_BLUE) + .toUInt(); + player.body_color_right = qt_config + ->value(QString("player_%1_body_color_right").arg(p), + Settings::JOYCON_BODY_NEON_RED) + .toUInt(); + player.button_color_left = qt_config + ->value(QString("player_%1_button_color_left").arg(p), + Settings::JOYCON_BUTTONS_NEON_BLUE) + .toUInt(); + player.button_color_right = qt_config + ->value(QString("player_%1_button_color_right").arg(p), + Settings::JOYCON_BUTTONS_NEON_RED) + .toUInt(); + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + player.buttons[i] = + qt_config + ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player.buttons[i].empty()) + player.buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + player.analogs[i] = + qt_config + ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player.analogs[i].empty()) + player.analogs[i] = default_param; + } + } + + std::stable_partition( + Settings::values.players.begin(), + Settings::values.players.begin() + + Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), + [](const auto& player) { return player.connected; }); +} + +void Config::ReadDebugValues() { + Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool(); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.buttons[i] = + Settings::values.debug_pad_buttons[i] = qt_config - ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) + ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i], + QString::fromStdString(default_param)) .toString() .toStdString(); - if (Settings::values.buttons[i].empty()) - Settings::values.buttons[i] = default_param; + if (Settings::values.debug_pad_buttons[i].empty()) + Settings::values.debug_pad_buttons[i] = default_param; } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.analogs[i] = + Settings::values.debug_pad_analogs[i] = qt_config - ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) + ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i], + QString::fromStdString(default_param)) .toString() .toStdString(); - if (Settings::values.analogs[i].empty()) - Settings::values.analogs[i] = default_param; + if (Settings::values.debug_pad_analogs[i].empty()) + Settings::values.debug_pad_analogs[i] = default_param; } +} + +void Config::ReadKeyboardValues() { + Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool(); + + std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), + Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); + std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), + Settings::values.keyboard_keys.begin() + + Settings::NativeKeyboard::LeftControlKey, + InputCommon::GenerateKeyboardParam); + std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), + Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); +} + +void Config::ReadMouseValues() { + Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool(); + + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); + Settings::values.mouse_buttons[i] = + qt_config + ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.mouse_buttons[i].empty()) + Settings::values.mouse_buttons[i] = default_param; + } +} + +void Config::ReadTouchscreenValues() { + Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool(); + Settings::values.touchscreen.device = + qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString(); + + Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt(); + Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt(); + Settings::values.touchscreen.diameter_x = + qt_config->value("touchscreen_diameter_x", 15).toUInt(); + Settings::values.touchscreen.diameter_y = + qt_config->value("touchscreen_diameter_y", 15).toUInt(); + qt_config->endGroup(); +} + +void Config::ReadValues() { + qt_config->beginGroup("Controls"); + + ReadPlayerValues(); + ReadDebugValues(); + ReadKeyboardValues(); + ReadMouseValues(); + ReadTouchscreenValues(); Settings::values.motion_device = qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") .toString() .toStdString(); - Settings::values.touch_device = - qt_config->value("touch_device", "engine:emu_window").toString().toStdString(); - - qt_config->endGroup(); qt_config->beginGroup("Core"); Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); @@ -126,6 +397,11 @@ void Config::ReadValues() { .toStdString()); qt_config->endGroup(); + qt_config->beginGroup("Core"); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); + Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("System"); Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); @@ -153,6 +429,7 @@ void Config::ReadValues() { Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString(); + Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool(); Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool(); qt_config->endGroup(); @@ -164,6 +441,21 @@ void Config::ReadValues() { Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); qt_config->endGroup(); + const auto size = qt_config->beginReadArray("DisabledAddOns"); + for (int i = 0; i < size; ++i) { + qt_config->setArrayIndex(i); + const auto title_id = qt_config->value("title_id", 0).toULongLong(); + std::vector<std::string> out; + const auto d_size = qt_config->beginReadArray("disabled"); + for (int j = 0; j < d_size; ++j) { + qt_config->setArrayIndex(j); + out.push_back(qt_config->value("d", "").toString().toStdString()); + } + qt_config->endArray(); + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + qt_config->endArray(); + qt_config->beginGroup("UI"); UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); UISettings::values.enable_discord_presence = @@ -230,18 +522,79 @@ void Config::ReadValues() { qt_config->endGroup(); } -void Config::SaveValues() { - qt_config->beginGroup("Controls"); +void Config::SavePlayerValues() { + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + const auto& player = Settings::values.players[p]; + + qt_config->setValue(QString("player_%1_connected").arg(p), player.connected); + qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type)); + + qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left); + qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right); + qt_config->setValue(QString("player_%1_button_color_left").arg(p), + player.button_color_left); + qt_config->setValue(QString("player_%1_button_color_right").arg(p), + player.button_color_right); + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + qt_config->setValue(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(player.buttons[i])); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + qt_config->setValue(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(player.analogs[i])); + } + } +} + +void Config::SaveDebugValues() { + qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(Settings::values.buttons[i])); + qt_config->setValue(QString("debug_pad_") + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_buttons[i])); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(Settings::values.analogs[i])); + qt_config->setValue(QString("debug_pad_") + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_analogs[i])); } +} + +void Config::SaveMouseValues() { + qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled); + + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + qt_config->setValue(QString("mouse_") + + QString::fromStdString(Settings::NativeMouseButton::mapping[i]), + QString::fromStdString(Settings::values.mouse_buttons[i])); + } +} + +void Config::SaveTouchscreenValues() { + qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled); + qt_config->setValue("touchscreen_device", + QString::fromStdString(Settings::values.touchscreen.device)); + + qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger); + qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle); + qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x); + qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y); +} + +void Config::SaveValues() { + qt_config->beginGroup("Controls"); + + SavePlayerValues(); + SaveDebugValues(); + SaveMouseValues(); + SaveTouchscreenValues(); + qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); - qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device)); + qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled); + qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -280,7 +633,6 @@ void Config::SaveValues() { qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); qt_config->setValue("enable_nfc", Settings::values.enable_nfc); qt_config->setValue("current_user", Settings::values.current_user); - qt_config->setValue("language_index", Settings::values.language_index); qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); @@ -297,6 +649,7 @@ void Config::SaveValues() { qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args)); + qt_config->setValue("dump_exefs", Settings::values.dump_exefs); qt_config->setValue("dump_nso", Settings::values.dump_nso); qt_config->endGroup(); @@ -307,6 +660,21 @@ void Config::SaveValues() { qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); qt_config->endGroup(); + qt_config->beginWriteArray("DisabledAddOns"); + int i = 0; + for (const auto& elem : Settings::values.disabled_addons) { + qt_config->setArrayIndex(i); + qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); + qt_config->beginWriteArray("disabled"); + for (std::size_t j = 0; j < elem.second.size(); ++j) { + qt_config->setArrayIndex(j); + qt_config->setValue("d", QString::fromStdString(elem.second[j])); + } + qt_config->endArray(); + ++i; + } + qt_config->endArray(); + qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 9c99c1b75..a1c27bbf9 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -22,10 +22,24 @@ public: static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> + default_mouse_buttons; + static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; + static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; private: void ReadValues(); + void ReadPlayerValues(); + void ReadDebugValues(); + void ReadKeyboardValues(); + void ReadMouseValues(); + void ReadTouchscreenValues(); + void SaveValues(); + void SavePlayerValues(); + void SaveDebugValues(); + void SaveMouseValues(); + void SaveTouchscreenValues(); std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index eb1da0f9e..5d9ccc6e8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->output_sink_combo_box->clear(); ui->output_sink_combo_box->addItem("auto"); - for (const auto& sink_detail : AudioCore::g_sink_details) { - ui->output_sink_combo_box->addItem(sink_detail.id); + for (const char* id : AudioCore::GetSinkIDs()) { + ui->output_sink_combo_box->addItem(id); } connect(ui->volume_slider, &QSlider::valueChanged, this, @@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) { ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); - for (const auto& device : device_list) { + for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { ui->audio_device_combo_box->addItem(QString::fromStdString(device)); } } diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 207f9dfb3..8771421c0 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -16,15 +16,14 @@ class ConfigureAudio : public QWidget { public: explicit ConfigureAudio(QWidget* parent = nullptr); - ~ConfigureAudio(); + ~ConfigureAudio() override; void applyConfiguration(); void retranslateUi(); -public slots: +private: void updateAudioDevices(int sink_index); -private: void setConfiguration(); void setOutputSinkFromSinkID(); void setAudioDeviceFromDeviceID(); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index fd5876b41..aa7de7b54 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -34,6 +34,7 @@ void ConfigureDebug::setConfiguration() { ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); + ui->dump_exefs->setChecked(Settings::values.dump_exefs); ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso); } @@ -43,6 +44,7 @@ void ConfigureDebug::applyConfiguration() { UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); + Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index d167eb996..c6420b18c 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -16,13 +16,12 @@ class ConfigureDebug : public QWidget { public: explicit ConfigureDebug(QWidget* parent = nullptr); - ~ConfigureDebug(); + ~ConfigureDebug() override; void applyConfiguration(); private: void setConfiguration(); -private: std::unique_ptr<Ui::ConfigureDebug> ui; }; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 9c5b702f8..758a92335 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -145,6 +145,16 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="dump_exefs"> + <property name="whatsThis"> + <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string> + </property> + <property name="text"> + <string>Dump ExeFS</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index bbbdacc29..f6df7b827 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -18,13 +18,12 @@ class ConfigureDialog : public QDialog { public: explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); - ~ConfigureDialog(); + ~ConfigureDialog() override; void applyConfiguration(); private: void setConfiguration(); -private: std::unique_ptr<Ui::ConfigureDialog> ui; }; diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index bbf7e25f1..bf3f1cdfa 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -16,7 +16,7 @@ class ConfigureGameList : public QWidget { public: explicit ConfigureGameList(QWidget* parent = nullptr); - ~ConfigureGameList(); + ~ConfigureGameList() override; void applyConfiguration(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index c22742007..92a441308 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -3,10 +3,6 @@ // Refer to the license.txt file included. #include "core/core.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applet_ae.h" -#include "core/hle/service/am/applet_oe.h" -#include "core/hle/service/sm/sm.h" #include "core/settings.h" #include "ui_configure_general.h" #include "yuzu/configuration/configure_general.h" @@ -36,7 +32,6 @@ void ConfigureGeneral::setConfiguration() { ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); - ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); ui->enable_nfc->setChecked(Settings::values.enable_nfc); } @@ -44,33 +39,6 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { ui->widget->Populate(registry); } -void ConfigureGeneral::OnDockedModeChanged(bool last_state, bool new_state) { - if (last_state == new_state) { - return; - } - - Core::System& system{Core::System::GetInstance()}; - if (!system.IsPoweredOn()) { - return; - } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - // Message queue is shared between these services, we just need to signal an operation - // change to one and it will handle both automatically - auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); - auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); - bool has_signalled = false; - - if (applet_oe != nullptr) { - applet_oe->GetMessageQueue()->OperationModeChanged(); - has_signalled = true; - } - - if (applet_ae != nullptr && !has_signalled) { - applet_ae->GetMessageQueue()->OperationModeChanged(); - } -} - void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); @@ -78,9 +46,5 @@ void ConfigureGeneral::applyConfiguration() { ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); - const bool pre_docked_mode = Settings::values.use_docked_mode; - Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); - Settings::values.enable_nfc = ui->enable_nfc->isChecked(); } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 2210d48da..59738af40 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -18,14 +18,13 @@ class ConfigureGeneral : public QWidget { public: explicit ConfigureGeneral(QWidget* parent = nullptr); - ~ConfigureGeneral(); + ~ConfigureGeneral() override; void PopulateHotkeyList(const HotkeyRegistry& registry); void applyConfiguration(); private: void setConfiguration(); - void OnDockedModeChanged(bool last_state, bool new_state); std::unique_ptr<Ui::ConfigureGeneral> ui; }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index b82fffde8..bf37446c6 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>300</width> - <height>377</height> + <height>407</height> </rect> </property> <property name="windowTitle"> @@ -72,13 +72,6 @@ <item> <layout class="QVBoxLayout" name="EmulationVerticalLayout"> <item> - <widget class="QCheckBox" name="use_docked_mode"> - <property name="text"> - <string>Enable docked mode</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="enable_nfc"> <property name="text"> <string>Enable NFC</string> diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 9bda26fd6..d6ffc6fde 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -16,14 +16,13 @@ class ConfigureGraphics : public QWidget { public: explicit ConfigureGraphics(QWidget* parent = nullptr); - ~ConfigureGraphics(); + ~ConfigureGraphics() override; void applyConfiguration(); private: void setConfiguration(); -private: std::unique_ptr<Ui::ConfigureGraphics> ui; QColor bg_color; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 91fcad994..e278cdd05 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -23,31 +23,31 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QCheckBox" name="toggle_frame_limit"> - <property name="text"> - <string>Limit Speed Percent</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="frame_limit"> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>9999</number> - </property> - <property name="value"> - <number>100</number> - </property> - </widget> - </item> - </layout> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QCheckBox" name="toggle_frame_limit"> + <property name="text"> + <string>Limit Speed Percent</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="frame_limit"> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>9999</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QCheckBox" name="use_accurate_gpu_emulation"> @@ -61,7 +61,7 @@ <item> <widget class="QLabel" name="label"> <property name="text"> - <string>Internal Resolution:(Currently does nothing.)</string> + <string>Internal Resolution</string> </property> </widget> </item> @@ -96,27 +96,27 @@ </item> </layout> </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_6"> - <item> - <widget class="QLabel" name="bg_label"> - <property name="text"> - <string>Background Color:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="bg_button"> - <property name="maximumSize"> - <size> - <width>40</width> - <height>16777215</height> - </size> - </property> - </widget> - </item> - </layout> - </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="bg_label"> + <property name="text"> + <string>Background Color:</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="bg_button"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 42a7beac6..830d26115 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -4,339 +4,209 @@ #include <algorithm> #include <memory> -#include <utility> -#include <QMenu> -#include <QMessageBox> + #include <QTimer> -#include "common/param_package.h" -#include "input_common/main.h" -#include "yuzu/configuration/config.h" + +#include "configuration/configure_touchscreen_advanced.h" +#include "core/core.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/sm/sm.h" +#include "ui_configure_input.h" +#include "ui_configure_input_player.h" #include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_mouse_advanced.h" -const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> - ConfigureInput::analog_sub_buttons{{ - "up", - "down", - "left", - "right", - "modifier", - }}; - -static QString getKeyName(int key_code) { - switch (key_code) { - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - return ""; - default: - return QKeySequence(key_code).toString(); - } -} +namespace { +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { + parent.applyConfiguration(); + Dialog dialog(&parent, std::forward<Args>(args)...); -static void SetAnalogButton(const Common::ParamPackage& input_param, - Common::ParamPackage& analog_param, const std::string& button_name) { - if (analog_param.Get("engine", "") != "analog_from_button") { - analog_param = { - {"engine", "analog_from_button"}, - {"modifier_scale", "0.5"}, - }; + const auto res = dialog.exec(); + if (res == QDialog::Accepted) { + dialog.applyConfiguration(); } - analog_param.Set(button_name, input_param.Serialize()); } - -static QString ButtonToText(const Common::ParamPackage& param) { - if (!param.Has("engine")) { - return QObject::tr("[not set]"); - } else if (param.Get("engine", "") == "keyboard") { - return getKeyName(param.Get("code", 0)); - } else if (param.Get("engine", "") == "sdl") { - if (param.Has("hat")) { - return QString(QObject::tr("Hat %1 %2")) - .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); - } - if (param.Has("axis")) { - return QString(QObject::tr("Axis %1%2")) - .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); - } - if (param.Has("button")) { - return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); - } - return QString(); - } else { - return QObject::tr("[unknown]"); - } -}; - -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { - if (!param.Has("engine")) { - return QObject::tr("[not set]"); - } else if (param.Get("engine", "") == "analog_from_button") { - return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); - } else if (param.Get("engine", "") == "sdl") { - if (dir == "modifier") { - return QString(QObject::tr("[unused]")); - } - - if (dir == "left" || dir == "right") { - return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); - } else if (dir == "up" || dir == "down") { - return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); - } - return QString(); - } else { - return QObject::tr("[unknown]"); - } -}; +} // Anonymous namespace ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { - + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, - ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, - ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, - ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, - ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, - ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, - ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, + + players_controller = { + ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox, + ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox, }; - analog_map_buttons = {{ - { - ui->buttonLStickUp, - ui->buttonLStickDown, - ui->buttonLStickLeft, - ui->buttonLStickRight, - ui->buttonLStickMod, - }, - { - ui->buttonRStickUp, - ui->buttonRStickDown, - ui->buttonRStickLeft, - ui->buttonRStickRight, - ui->buttonRStickMod, - }, - }}; - - analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; - - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (!button_map[button_id]) - continue; - button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button_map[button_id], &QPushButton::released, [=]() { - handleClick( - button_map[button_id], - [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, - InputCommon::Polling::DeviceType::Button); - }); - connect(button_map[button_id], &QPushButton::customContextMenuRequested, - [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - }); - } + players_configure = { + ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure, + ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure, + }; - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (!analog_map_buttons[analog_id][sub_button_id]) - continue; - analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( - Qt::CustomContextMenu); - connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { - handleClick(analog_map_buttons[analog_id][sub_button_id], - [=](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(analog_map_buttons[analog_id][sub_button_id], - &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( - analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - }); - context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( - menu_location)); - }); - } - connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { - QMessageBox::information(this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically.")); - handleClick( - analog_map_stick[analog_id], - [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, - InputCommon::Polling::DeviceType::Analog); - }); + for (auto* controller_box : players_controller) { + controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon", + "Single Left Joycon"}); } - connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + this->loadConfiguration(); + updateUIEnabled(); - timeout_timer->setSingleShot(true); - connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + connect(ui->restore_defaults_button, &QPushButton::pressed, this, + &ConfigureInput::restoreDefaults); - connect(poll_timer.get(), &QTimer::timeout, [this]() { - Common::ParamPackage params; - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine")) { - setPollingResult(params, false); - return; - } - } - }); + for (auto* enabled : players_controller) + connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureInput::updateUIEnabled); + connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->handheld_connected, &QCheckBox::stateChanged, this, + &ConfigureInput::updateUIEnabled); + connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, + &ConfigureInput::updateUIEnabled); - this->loadConfiguration(); + for (std::size_t i = 0; i < players_configure.size(); ++i) { + connect(players_configure[i], &QPushButton::pressed, this, + [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); }); + } - // TODO(wwylele): enable this when we actually emulate it - ui->buttonHome->setEnabled(false); -} + connect(ui->handheld_configure, &QPushButton::pressed, this, + [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); }); -void ConfigureInput::applyConfiguration() { - std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); -} + connect(ui->debug_configure, &QPushButton::pressed, this, + [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); }); -void ConfigureInput::loadConfiguration() { - std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), - buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), - analogs_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - updateButtonLabels(); + connect(ui->mouse_advanced, &QPushButton::pressed, this, + [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); + + connect(ui->touchscreen_advanced, &QPushButton::pressed, this, + [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); } -void ConfigureInput::restoreDefaults() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - } +ConfigureInput::~ConfigureInput() = default; - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); - } +void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { + if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) { + ui->handheld_connected->setChecked(false); } - updateButtonLabels(); -} -void ConfigureInput::ClearAll() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (button_map[button_id] && button_map[button_id]->isEnabled()) - buttons_param[button_id].Clear(); + if (last_state == new_state) { + return; } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (analog_map_buttons[analog_id][sub_button_id] && - analog_map_buttons[analog_id][sub_button_id]->isEnabled()) - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); - } + + Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return; } - updateButtonLabels(); -} + Service::SM::ServiceManager& sm = system.ServiceManager(); -void ConfigureInput::updateButtonLabels() { - for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { - button_map[button]->setText(ButtonToText(buttons_param[button])); + // Message queue is shared between these services, we just need to signal an operation + // change to one and it will handle both automatically + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->OperationModeChanged(); + has_signalled = true; } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (analog_map_buttons[analog_id][sub_button_id]) { - analog_map_buttons[analog_id][sub_button_id]->setText( - AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - } - } - analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->OperationModeChanged(); } } -void ConfigureInput::handleClick(QPushButton* button, - std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type) { - button->setText(tr("[press key]")); - button->setFocus(); - - input_setter = new_input_setter; - - device_pollers = InputCommon::Polling::GetPollers(type); +void ConfigureInput::applyConfiguration() { + for (std::size_t i = 0; i < players_controller.size(); ++i) { + const auto controller_type_index = players_controller[i]->currentIndex(); - // Keyboard keys can only be used as button devices - want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + Settings::values.players[i].connected = controller_type_index != 0; - for (auto& poller : device_pollers) { - poller->Start(); + if (controller_type_index > 0) { + Settings::values.players[i].type = + static_cast<Settings::ControllerType>(controller_type_index - 1); + } else { + Settings::values.players[i].type = Settings::ControllerType::DualJoycon; + } } - grabKeyboard(); - grabMouse(); - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + const bool pre_docked_mode = Settings::values.use_docked_mode; + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + Settings::values + .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] + .connected = ui->handheld_connected->isChecked(); + Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); + Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); + Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); + Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); } -void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool abort) { - releaseKeyboard(); - releaseMouse(); - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); +void ConfigureInput::updateUIEnabled() { + bool hit_disabled = false; + for (auto* player : players_controller) { + player->setDisabled(hit_disabled); + if (hit_disabled) + player->setCurrentIndex(0); + if (!hit_disabled && player->currentIndex() == 0) + hit_disabled = true; } - if (!abort) { - (*input_setter)(params); + for (std::size_t i = 0; i < players_controller.size(); ++i) { + players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); } - updateButtonLabels(); - input_setter = {}; + ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); + ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && + !ui->use_docked_mode->isChecked()); + ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); + ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); + ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); } -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) - return; +void ConfigureInput::loadConfiguration() { + std::stable_partition( + Settings::values.players.begin(), + Settings::values.players.begin() + + Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), + [](const auto& player) { return player.connected; }); + + for (std::size_t i = 0; i < players_controller.size(); ++i) { + const auto connected = Settings::values.players[i].connected; + players_controller[i]->setCurrentIndex( + connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); + } + + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); + ui->handheld_connected->setChecked( + Settings::values + .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] + .connected); + ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); + ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); + ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); + ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + + updateUIEnabled(); +} - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_keys) { - setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } +void ConfigureInput::restoreDefaults() { + players_controller[0]->setCurrentIndex(2); + + for (std::size_t i = 1; i < players_controller.size(); ++i) { + players_controller[i]->setCurrentIndex(0); } - setPollingResult({}, true); + + ui->use_docked_mode->setCheckState(Qt::Unchecked); + ui->handheld_connected->setCheckState(Qt::Unchecked); + ui->mouse_enabled->setCheckState(Qt::Unchecked); + ui->keyboard_enabled->setCheckState(Qt::Unchecked); + ui->debug_enabled->setCheckState(Qt::Unchecked); + ui->touchscreen_enabled->setCheckState(Qt::Checked); + updateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 32c7183f9..1649e4c0b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -5,18 +5,11 @@ #pragma once #include <array> -#include <functional> #include <memory> -#include <optional> -#include <string> -#include <unordered_map> #include <QKeyEvent> #include <QWidget> -#include "common/param_package.h" -#include "core/settings.h" -#include "input_common/main.h" #include "ui_configure_input.h" class QPushButton; @@ -32,62 +25,23 @@ class ConfigureInput : public QWidget { public: explicit ConfigureInput(QWidget* parent = nullptr); + ~ConfigureInput() override; /// Save all button configurations to settings file void applyConfiguration(); private: - std::unique_ptr<Ui::ConfigureInput> ui; - - std::unique_ptr<QTimer> timeout_timer; - std::unique_ptr<QTimer> poll_timer; - - /// This will be the the setting function when an input is awaiting configuration. - std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; - - std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; - std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; - - static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; - - /// Each button input is represented by a QPushButton. - std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; - - /// A group of five QPushButtons represent one analog input. The buttons each represent up, - /// down, left, right, and modifier, respectively. - std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> - analog_map_buttons; + void updateUIEnabled(); - /// Analog inputs are also represented each with a single button, used to configure with an - /// actual analog stick - std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; - - static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; - - std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; - - /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, - /// keyboard events are ignored. - bool want_keyboard_keys = false; + void OnDockedModeChanged(bool last_state, bool new_state); /// Load configuration settings. void loadConfiguration(); /// Restore all buttons to their default values. void restoreDefaults(); - /// Clear all input configuration - void ClearAll(); - /// Update UI to reflect current configuration. - void updateButtonLabels(); - - /// Called when the button was pressed. - void handleClick(QPushButton* button, - std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type); - - /// Finish polling and configure input using the input_setter - void setPollingResult(const Common::ParamPackage& params, bool abort); + std::unique_ptr<Ui::ConfigureInput> ui; - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; + std::array<QComboBox*, 8> players_controller; + std::array<QPushButton*, 8> players_configure; }; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8a019a693..dae8277bc 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>343</width> - <height>677</height> + <width>473</width> + <height>685</height> </rect> </property> <property name="windowTitle"> @@ -15,740 +15,470 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> - <layout class="QGridLayout" name="buttons"> - <item row="3" column="1"> - <widget class="QGroupBox" name="misc"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Misc.</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> + <string>Players</string> </property> - <layout class="QGridLayout" name="gridLayout_6"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> - <item> - <widget class="QLabel" name="labelMinus"> - <property name="text"> - <string>Minus:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonMinus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="2"> + <widget class="QComboBox" name="player1_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> - <item> - <widget class="QLabel" name="labelPlus"> - <property name="text"> - <string>Plus:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonPlus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="3"> + <widget class="QPushButton" name="player1_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> - <item> - <widget class="QLabel" name="labelHome"> - <property name="text"> - <string>Home:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonHome"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Controller Type</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> - <item> - <widget class="QLabel" name="labelScrCap"> - <property name="text"> - <string>Screen -Capture:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonScreenshot"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="2"> + <widget class="QComboBox" name="player2_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> </item> - <item row="2" column="1"> - <spacer name="verticalSpacer"> + <item row="3" column="2"> + <widget class="QComboBox" name="player3_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QComboBox" name="player4_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QComboBox" name="player5_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QComboBox" name="player6_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="7" column="2"> + <widget class="QComboBox" name="player7_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="8" column="2"> + <widget class="QComboBox" name="player8_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QPushButton" name="player2_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QPushButton" name="player3_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QPushButton" name="player4_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QPushButton" name="player5_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="6" column="3"> + <widget class="QPushButton" name="player6_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="7" column="3"> + <widget class="QPushButton" name="player7_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="8" column="3"> + <widget class="QPushButton" name="player8_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> <property name="orientation"> - <enum>Qt::Vertical</enum> + <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> - <width>20</width> - <height>40</height> + <width>40</width> + <height>20</height> </size> </property> </spacer> </item> - </layout> - </widget> - </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="faceButtons"> - <property name="title"> - <string>Face Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> - <item> - <widget class="QLabel" name="labelA"> - <property name="text"> - <string>A:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonA"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="4"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> - <item> - <widget class="QLabel" name="labelB"> - <property name="text"> - <string>B:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonB"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="minimumSize"> + <size> + <width>55</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Player 1</string> + </property> + </widget> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> - <item> - <widget class="QLabel" name="labelX"> - <property name="text"> - <string>X:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonX"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Player 2</string> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> - <item> - <widget class="QLabel" name="labelY"> - <property name="text"> - <string>Y:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonY"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="3" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Player 3</string> + </property> + </widget> </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <widget class="QGroupBox" name="Dpad"> - <property name="title"> - <string>Directional Pad</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="4" column="1"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Player 4</string> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="5" column="1"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Player 5</string> + </property> + </widget> </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="6" column="1"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Player 6</string> + </property> + </widget> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="7" column="1"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Player 7</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Player 8</string> + </property> + </widget> </item> </layout> </widget> </item> - <item row="3" column="0"> - <widget class="QGroupBox" name="shoulderButtons"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Shoulder Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> + <string>Handheld</string> </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> - <item> - <widget class="QLabel" name="labelL"> - <property name="text"> - <string>L:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="2"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>72</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> - <item> - <widget class="QLabel" name="labelR"> - <property name="text"> - <string>R:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="4"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="3"> + <widget class="QPushButton" name="handheld_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> - <item> - <widget class="QLabel" name="labelZL"> - <property name="text"> - <string>ZL:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonZL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> - <item> - <widget class="QLabel" name="labelZR"> - <property name="text"> - <string>ZR:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonZR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> - <item> - <widget class="QLabel" name="labelSL"> - <property name="text"> - <string>SL:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonSL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="handheld_connected"> + <property name="text"> + <string>Joycons Docked</string> + </property> + </widget> </item> - <item row="2" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> - <item> - <widget class="QLabel" name="labelSR"> - <property name="text"> - <string>SR:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonSR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="1"> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Use Docked Mode</string> + </property> + </widget> </item> </layout> </widget> </item> - <item row="1" column="1"> - <widget class="QGroupBox" name="RStick"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Right Stick</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - <property name="flat"> - <bool>false</bool> + <string>Other</string> </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_5"> + <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QPushButton" name="buttonRStickAnalog"> + <widget class="QCheckBox" name="keyboard_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> <property name="text"> - <string>Set Analog Stick</string> + <string>Keyboard</string> </property> </widget> </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="0"> - <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickPressed"> - <property name="text"> - <string>Pressed:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStick"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickMod"> - <property name="text"> - <string>Modifier:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="debug_enabled"> + <property name="text"> + <string>Debug Controller</string> + </property> + </widget> </item> - </layout> - </widget> - </item> - <item row="1" column="0"> - <widget class="QGroupBox" name="LStick"> - <property name="title"> - <string>Left Stick</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QPushButton" name="buttonLStickAnalog"> + <item row="3" column="1"> + <widget class="QCheckBox" name="touchscreen_enabled"> <property name="text"> - <string>Set Analog Stick</string> + <string>Touchscreen</string> </property> </widget> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="mouse_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Mouse</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer_8"> + <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"> - <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0"> - <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickMod"> - <property name="text"> - <string>Modifier:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="3" column="3"> + <widget class="QPushButton" name="touchscreen_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> </item> - <item row="3" column="1"> - <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> - <item> - <widget class="QLabel" name="labelLStickPressed"> - <property name="text"> - <string>Pressed:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStick"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="3"> + <widget class="QPushButton" name="debug_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QPushButton" name="mouse_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> </item> </layout> </widget> </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <spacer name="horizontalSpacer"> + <spacer name="verticalSpacer"> <property name="orientation"> - <enum>Qt::Horizontal</enum> + <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> - <height>20</height> + <width>20</width> + <height>40</height> </size> </property> </spacer> </item> <item> - <widget class="QPushButton" name="buttonClearAll"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Clear All</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRestoreDefaults"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Restore Defaults</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> </item> </layout> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp new file mode 100644 index 000000000..7dadd83c1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -0,0 +1,500 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QColorDialog> +#include <QMenu> +#include <QMessageBox> +#include <QTimer> +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_input_player.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input_player.h" + +const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> + ConfigureInputPlayer::analog_sub_buttons{{ + "up", + "down", + "left", + "right", + "modifier", + }}; + +static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) { + const int index1 = grid->indexOf(item); + const int index2 = grid->indexOf(onTopOf); + int row, column, rowSpan, columnSpan; + grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan); + grid->takeAt(index1); + grid->addWidget(item, row, column, rowSpan, columnSpan); +} + +static QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static void SetAnalogButton(const Common::ParamPackage& input_param, + Common::ParamPackage& analog_param, const std::string& button_name) { + if (analog_param.Get("engine", "") != "analog_from_button") { + analog_param = { + {"engine", "analog_from_button"}, + {"modifier_scale", "0.5"}, + }; + } + analog_param.Set(button_name, input_param.Serialize()); +} + +static QString ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "keyboard") { + return GetKeyName(param.Get("code", 0)); + } else if (param.Get("engine", "") == "sdl") { + if (param.Has("hat")) { + return QString(QObject::tr("Hat %1 %2")) + .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("axis")) { + return QString(QObject::tr("Axis %1%2")) + .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("button")) { + return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +}; + +static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "analog_from_button") { + return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); + } else if (param.Get("engine", "") == "sdl") { + if (dir == "modifier") { + return QString(QObject::tr("[unused]")); + } + + if (dir == "left" || dir == "right") { + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); + } else if (dir == "up" || dir == "down") { + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +}; + +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), + debug(debug), timeout_timer(std::make_unique<QTimer>()), + poll_timer(std::make_unique<QTimer>()) { + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, + ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, + ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, + ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, + ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, + ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, + ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, + }; + + analog_map_buttons = {{ + { + ui->buttonLStickUp, + ui->buttonLStickDown, + ui->buttonLStickLeft, + ui->buttonLStickRight, + ui->buttonLStickMod, + }, + { + ui->buttonRStickUp, + ui->buttonRStickDown, + ui->buttonRStickLeft, + ui->buttonRStickRight, + ui->buttonRStickMod, + }, + }}; + + debug_hidden = { + ui->buttonSL, ui->labelSL, + ui->buttonSR, ui->labelSR, + ui->buttonLStick, ui->labelLStickPressed, + ui->buttonRStick, ui->labelRStickPressed, + ui->buttonHome, ui->labelHome, + ui->buttonScreenshot, ui->labelScreenshot, + }; + + auto layout = Settings::values.players[player_index].type; + if (debug) + layout = Settings::ControllerType::DualJoycon; + + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoycon: + layout_hidden = { + ui->buttonSL, + ui->labelSL, + ui->buttonSR, + ui->labelSR, + }; + break; + case Settings::ControllerType::LeftJoycon: + layout_hidden = { + ui->right_body_button, + ui->right_buttons_button, + ui->right_body_label, + ui->right_buttons_label, + ui->buttonR, + ui->labelR, + ui->buttonZR, + ui->labelZR, + ui->labelHome, + ui->buttonHome, + ui->buttonPlus, + ui->labelPlus, + ui->RStick, + ui->faceButtons, + }; + break; + case Settings::ControllerType::RightJoycon: + layout_hidden = { + ui->left_body_button, ui->left_buttons_button, + ui->left_body_label, ui->left_buttons_label, + ui->buttonL, ui->labelL, + ui->buttonZL, ui->labelZL, + ui->labelScreenshot, ui->buttonScreenshot, + ui->buttonMinus, ui->labelMinus, + ui->LStick, ui->Dpad, + }; + break; + } + + if (debug || layout == Settings::ControllerType::ProController) { + ui->controller_color->hide(); + } else { + if (layout == Settings::ControllerType::LeftJoycon || + layout == Settings::ControllerType::RightJoycon) { + ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0}); + + LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad); + LayerGridElements(ui->buttons, ui->misc, ui->RStick); + LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons); + LayerGridElements(ui->buttons, ui->RStick, ui->LStick); + } + } + + for (auto* widget : layout_hidden) + widget->setVisible(false); + + analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (!button_map[button_id]) + continue; + button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick( + button_map[button_id], + [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + InputCommon::Polling::DeviceType::Button); + }); + connect(button_map[button_id], &QPushButton::customContextMenuRequested, + [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (!analog_map_buttons[analog_id][sub_button_id]) + continue; + analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( + Qt::CustomContextMenu); + connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map_buttons[analog_id][sub_button_id], + [=](const Common::ParamPackage& params) { + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }, + InputCommon::Polling::DeviceType::Button); + }); + connect(analog_map_buttons[analog_id][sub_button_id], + &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + }); + context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( + menu_location)); + }); + } + connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { + QMessageBox::information(this, tr("Information"), + tr("After pressing OK, first move your joystick horizontally, " + "and then vertically.")); + handleClick( + analog_map_stick[analog_id], + [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, + InputCommon::Polling::DeviceType::Analog); + }); + } + + connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this]() { + Common::ParamPackage params; + for (auto& poller : device_pollers) { + params = poller->GetNextInput(); + if (params.Has("engine")) { + setPollingResult(params, false); + return; + } + } + }); + + controller_color_buttons = { + ui->left_body_button, + ui->left_buttons_button, + ui->right_body_button, + ui->right_buttons_button, + }; + + for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) { + connect(controller_color_buttons[i], &QPushButton::clicked, this, + [this, i] { OnControllerButtonClick(static_cast<int>(i)); }); + } + + this->loadConfiguration(); + this->resize(0, 0); + + // TODO(wwylele): enable this when we actually emulate it + ui->buttonHome->setEnabled(false); +} + +ConfigureInputPlayer::~ConfigureInputPlayer() = default; + +void ConfigureInputPlayer::applyConfiguration() { + auto& buttons = + debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons; + auto& analogs = + debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs; + + std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + if (debug) + return; + + std::array<u32, 4> colors{}; + std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(), + [](QColor color) { return color.rgb(); }); + + Settings::values.players[player_index].body_color_left = colors[0]; + Settings::values.players[player_index].button_color_left = colors[1]; + Settings::values.players[player_index].body_color_right = colors[2]; + Settings::values.players[player_index].button_color_right = colors[3]; +} + +void ConfigureInputPlayer::OnControllerButtonClick(int i) { + const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]); + if (!new_bg_color.isValid()) + return; + controller_colors[i] = new_bg_color; + controller_color_buttons[i]->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +} + +void ConfigureInputPlayer::loadConfiguration() { + if (debug) { + std::transform(Settings::values.debug_pad_buttons.begin(), + Settings::values.debug_pad_buttons.end(), buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.debug_pad_analogs.begin(), + Settings::values.debug_pad_analogs.end(), analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + } else { + std::transform(Settings::values.players[player_index].buttons.begin(), + Settings::values.players[player_index].buttons.end(), buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.players[player_index].analogs.begin(), + Settings::values.players[player_index].analogs.end(), analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + } + + updateButtonLabels(); + + if (debug) + return; + + std::array<u32, 4> colors = { + Settings::values.players[player_index].body_color_left, + Settings::values.players[player_index].button_color_left, + Settings::values.players[player_index].body_color_right, + Settings::values.players[player_index].button_color_right, + }; + + std::transform(colors.begin(), colors.end(), controller_colors.begin(), + [](u32 rgb) { return QColor::fromRgb(rgb); }); + + for (std::size_t i = 0; i < colors.size(); ++i) { + controller_color_buttons[i]->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); + } +} + +void ConfigureInputPlayer::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); +} + +void ConfigureInputPlayer::ClearAll() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id] && button_map[button_id]->isEnabled()) + buttons_param[button_id].Clear(); + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map_buttons[analog_id][sub_button_id] && + analog_map_buttons[analog_id][sub_button_id]->isEnabled()) + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + } + } + + updateButtonLabels(); +} + +void ConfigureInputPlayer::updateButtonLabels() { + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + button_map[button]->setText(ButtonToText(buttons_param[button])); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map_buttons[analog_id][sub_button_id]) { + analog_map_buttons[analog_id][sub_button_id]->setText( + AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + } + analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + } +} + +void ConfigureInputPlayer::handleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + button->setText(tr("[press key]")); + button->setFocus(); + + const auto iter = std::find(button_map.begin(), button_map.end(), button); + ASSERT(iter != button_map.end()); + const auto index = std::distance(button_map.begin(), iter); + ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + + input_setter = new_input_setter; + + device_pollers = InputCommon::Polling::GetPollers(type); + + // Keyboard keys can only be used as button devices + want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + + for (auto& poller : device_pollers) { + poller->Start(); + } + + grabKeyboard(); + grabMouse(); + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + +void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) { + releaseKeyboard(); + releaseMouse(); + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + if (!abort) { + (*input_setter)(params); + } + + updateButtonLabels(); + input_setter = std::nullopt; +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_keys) { + setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h new file mode 100644 index 000000000..7a53f6715 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.h @@ -0,0 +1,104 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <optional> +#include <string> + +#include <QDialog> +#include <QKeyEvent> + +#include "common/param_package.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputPlayer; +} + +class ConfigureInputPlayer : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false); + ~ConfigureInputPlayer() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + void OnControllerButtonClick(int i); + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Clear all input configuration + void ClearAll(); + + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type); + + /// Finish polling and configure input using the input_setter + void setPollingResult(const Common::ParamPackage& params, bool abort); + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; + + std::unique_ptr<Ui::ConfigureInputPlayer> ui; + + std::size_t player_index; + bool debug; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + + /// Each button input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + + std::vector<QWidget*> debug_hidden; + std::vector<QWidget*> layout_hidden; + + /// A group of five QPushButtons represent one analog input. The buttons each represent up, + /// down, left, right, and modifier, respectively. + std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> + analog_map_buttons; + + /// Analog inputs are also represented each with a single button, used to configure with an + /// actual analog stick + std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, + /// keyboard events are ignored. + bool want_keyboard_keys = false; + + std::array<QPushButton*, 4> controller_color_buttons; + std::array<QColor, 4> controller_colors; +}; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui new file mode 100644 index 000000000..42db020be --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.ui @@ -0,0 +1,1164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputPlayer</class> + <widget class="QDialog" name="ConfigureInputPlayer"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>408</width> + <height>731</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Input</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QGridLayout" name="buttons"> + <item row="1" column="1"> + <widget class="QGroupBox" name="RStick"> + <property name="title"> + <string>Right Stick</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickRight"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QPushButton" name="buttonRStickAnalog"> + <property name="text"> + <string>Set Analog Stick</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickLeft"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickPressed"> + <property name="text"> + <string>Pressed:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStick"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickMod"> + <property name="text"> + <string>Modifier:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickMod"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="Dpad"> + <property name="title"> + <string>Directional Pad</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadLeft"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadRight"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="faceButtons"> + <property name="title"> + <string>Face Buttons</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout"> + <item> + <widget class="QLabel" name="labelA"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>A:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonA"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout"> + <item> + <widget class="QLabel" name="labelB"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>B:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonB"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout"> + <item> + <widget class="QLabel" name="labelX"> + <property name="text"> + <string>X:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonX"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout"> + <item> + <widget class="QLabel" name="labelY"> + <property name="text"> + <string>Y:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonY"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QGroupBox" name="controller_color"> + <property name="title"> + <string>Controller Color</string> + </property> + <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0"> + <item row="0" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="left_body_label"> + <property name="text"> + <string>Left Body</string> + </property> + </widget> + </item> + <item row="0" column="6"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="left_buttons_label"> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Left Buttons</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QPushButton" name="right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="right_body_label"> + <property name="text"> + <string>Right Body</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QLabel" name="right_buttons_label"> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Right Buttons</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QPushButton" name="right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="LStick"> + <property name="title"> + <string>Left Stick</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="2"> + <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickRight"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QPushButton" name="buttonLStickAnalog"> + <property name="text"> + <string>Set Analog Stick</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2"> + <item> + <widget class="QLabel" name="labelLStickLeft"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="2"> + <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="2"> + <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickMod"> + <property name="text"> + <string>Modifier:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickMod"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> + <item> + <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickPressed"> + <property name="text"> + <string>Pressed:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStick"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QGroupBox" name="shoulderButtons"> + <property name="title"> + <string>Shoulder Buttons</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelSL"> + <property name="text"> + <string>SL:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonSL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelZR"> + <property name="text"> + <string>ZR:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonZR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelSR"> + <property name="text"> + <string>SR:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonSR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelZL"> + <property name="text"> + <string>ZL:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonZL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelL"> + <property name="text"> + <string>L:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelR"> + <property name="text"> + <string>R:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="3" column="1"> + <widget class="QGroupBox" name="misc"> + <property name="title"> + <string>Misc.</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout"> + <item> + <widget class="QLabel" name="labelMinus"> + <property name="text"> + <string>Minus:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonMinus"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout"> + <item> + <widget class="QLabel" name="labelPlus"> + <property name="text"> + <string>Plus:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonPlus"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout"> + <item> + <widget class="QLabel" name="labelHome"> + <property name="text"> + <string>Home:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonHome"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout"> + <item> + <widget class="QLabel" name="labelScreenshot"> + <property name="text"> + <string>Screen Capture:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonScreenshot"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="buttonClearAll"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInputPlayer</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>371</x> + <y>730</y> + </hint> + <hint type="destinationlabel"> + <x>229</x> + <y>375</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureInputPlayer</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>371</x> + <y>730</y> + </hint> + <hint type="destinationlabel"> + <x>229</x> + <y>375</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp new file mode 100644 index 000000000..ef857035e --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -0,0 +1,213 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> + +#include <QKeyEvent> +#include <QMenu> +#include <QTimer> + +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_mouse_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_mouse_advanced.h" + +static QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static QString ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "keyboard") { + return GetKeyName(param.Get("code", 0)); + } else if (param.Get("engine", "") == "sdl") { + if (param.Has("hat")) { + return QString(QObject::tr("Hat %1 %2")) + .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("axis")) { + return QString(QObject::tr("Axis %1%2")) + .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("button")) { + return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +} + +ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button, + }; + + for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { + if (!button_map[button_id]) + continue; + button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick( + button_map[button_id], + [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + InputCommon::Polling::DeviceType::Button); + }); + connect(button_map[button_id], &QPushButton::customContextMenuRequested, + [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = + Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_mouse_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); + } + + connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this]() { + Common::ParamPackage params; + for (auto& poller : device_pollers) { + params = poller->GetNextInput(); + if (params.Has("engine")) { + setPollingResult(params, false); + return; + } + } + }); + + loadConfiguration(); + resize(0, 0); +} + +ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default; + +void ConfigureMouseAdvanced::applyConfiguration() { + std::transform(buttons_param.begin(), buttons_param.end(), + Settings::values.mouse_buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); +} + +void ConfigureMouseAdvanced::loadConfiguration() { + std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), + buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; + } + + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::ClearAll() { + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + if (button_map[i] && button_map[i]->isEnabled()) + buttons_param[i].Clear(); + } + + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::updateButtonLabels() { + for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) { + button_map[button]->setText(ButtonToText(buttons_param[button])); + } +} + +void ConfigureMouseAdvanced::handleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + button->setText(tr("[press key]")); + button->setFocus(); + + const auto iter = std::find(button_map.begin(), button_map.end(), button); + ASSERT(iter != button_map.end()); + const auto index = std::distance(button_map.begin(), iter); + ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + + input_setter = new_input_setter; + + device_pollers = InputCommon::Polling::GetPollers(type); + + // Keyboard keys can only be used as button devices + want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + + for (auto& poller : device_pollers) { + poller->Start(); + } + + grabKeyboard(); + grabMouse(); + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + +void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) { + releaseKeyboard(); + releaseMouse(); + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + if (!abort) { + (*input_setter)(params); + } + + updateButtonLabels(); + input_setter = std::nullopt; +} + +void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_keys) { + setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h new file mode 100644 index 000000000..e04da4bf2 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -0,0 +1,68 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <optional> +#include <QDialog> + +#include "core/settings.h" + +class QCheckBox; +class QPushButton; +class QTimer; + +namespace Ui { +class ConfigureMouseAdvanced; +} + +class ConfigureMouseAdvanced : public QDialog { + Q_OBJECT + +public: + explicit ConfigureMouseAdvanced(QWidget* parent); + ~ConfigureMouseAdvanced() override; + + void applyConfiguration(); + +private: + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Clear all input configuration + void ClearAll(); + + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type); + + /// Finish polling and configure input using the input_setter + void setPollingResult(const Common::ParamPackage& params, bool abort); + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; + + std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map; + std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param; + + std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, + /// keyboard events are ignored. + bool want_keyboard_keys = false; +}; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui new file mode 100644 index 000000000..08245ecf0 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMouseAdvanced</class> + <widget class="QDialog" name="ConfigureMouseAdvanced"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>250</width> + <height>261</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Mouse</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Mouse Buttons</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="4"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="3"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="right_button"> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <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>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Middle:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="middle_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_4"> + <property name="minimumSize"> + <size> + <width>54</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Back:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="back_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="left_button"> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="3"> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Forward:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="forward_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="buttonClearAll"> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureMouseAdvanced</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>124</x> + <y>266</y> + </hint> + <hint type="destinationlabel"> + <x>124</x> + <y>143</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureMouseAdvanced</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>124</x> + <y>266</y> + </hint> + <hint type="destinationlabel"> + <x>124</x> + <y>143</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..80109b434 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,170 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QHeaderView> +#include <QMenu> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" +#include "yuzu/util/util.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) + : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("Properties")); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 2); + item_model->setHeaderData(0, Qt::Horizontal, "Patch Name"); + item_model->setHeaderData(1, Qt::Horizontal, "Version"); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + ui->scrollArea->setLayout(layout); + + scene = new QGraphicsScene; + ui->icon_view->setScene(scene); + + connect(item_model, &QStandardItemModel::itemChanged, + [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + + this->loadConfiguration(); +} + +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + +void ConfigurePerGameGeneral::applyConfiguration() { + std::vector<std::string> disabled_addons; + + for (const auto& item : list_items) { + const auto disabled = item.front()->checkState() == Qt::Unchecked; + if (disabled) + disabled_addons.push_back(item.front()->text().toStdString()); + } + + Settings::values.disabled_addons[title_id] = disabled_addons; +} + +void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + this->loadConfiguration(); +} + +void ConfigurePerGameGeneral::loadConfiguration() { + if (file == nullptr) + return; + + const auto loader = Loader::GetLoader(file); + + ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str()); + + FileSys::PatchManager pm{title_id}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); + + std::string developer; + if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(developer)); + + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + if (control.second != nullptr) { + scene->clear(); + + QPixmap map; + const auto bytes = control.second->ReadAllBytes(); + map.loadFromData(bytes.data(), bytes.size()); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector<u8> bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + scene->clear(); + + QPixmap map; + map.loadFromData(bytes.data(), bytes.size()); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + FileSys::VirtualFile update_raw; + loader->ReadUpdateRaw(update_raw); + + const auto& disabled = Settings::values.disabled_addons[title_id]; + + for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { + QStandardItem* first_item = new QStandardItem; + const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); + first_item->setText(name); + first_item->setCheckable(true); + + const auto patch_disabled = + std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + + first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); + + list_items.push_back(QList<QStandardItem*>{ + first_item, new QStandardItem{QString::fromStdString(patch.second)}}); + item_model->appendRow(list_items.back()); + } + + tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + + ui->display_filename->setText(QString::fromStdString(file->GetName())); + + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..a4494446c --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,50 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QKeyEvent> +#include <QList> +#include <QWidget> + +#include "core/file_sys/vfs_types.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); + ~ConfigurePerGameGeneral() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + + void loadFromFile(FileSys::VirtualFile file); + +private: + std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; + FileSys::VirtualFile file; + u64 title_id; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameGeneral</class> + <widget class="QDialog" name="ConfigurePerGameGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>520</height> + </rect> + </property> + <property name="windowTitle"> + <string>ConfigurePerGameGeneral</string> + </property> + <layout class="QHBoxLayout" name="HorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <widget class="QGroupBox" name="GeneralGroupBox"> + <property name="title"> + <string>Info</string> + </property> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="6" column="1" colspan="2"> + <widget class="QLineEdit" name="display_filename"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="display_name"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Developer</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="2"> + <widget class="QLineEdit" name="display_size"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Filename</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="display_version"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="display_format"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_developer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Title ID</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="display_title_id"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="2" rowspan="5"> + <widget class="QGraphicsView" name="icon_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="sizeAdjustPolicy"> + <enum>QAbstractScrollArea::AdjustToContents</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Add-Ons</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>350</width> + <height>169</height> + </rect> + </property> + </widget> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp new file mode 100644 index 000000000..9c1561e9d --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -0,0 +1,42 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "ui_configure_touchscreen_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_touchscreen_advanced.h" + +ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) { + ui->setupUi(this); + + connect(ui->restore_defaults_button, &QPushButton::pressed, this, + &ConfigureTouchscreenAdvanced::restoreDefaults); + + loadConfiguration(); + resize(0, 0); +} + +ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default; + +void ConfigureTouchscreenAdvanced::applyConfiguration() { + Settings::values.touchscreen.finger = ui->finger_box->value(); + Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value(); + Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value(); + Settings::values.touchscreen.rotation_angle = ui->angle_box->value(); +} + +void ConfigureTouchscreenAdvanced::loadConfiguration() { + ui->finger_box->setValue(Settings::values.touchscreen.finger); + ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x); + ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y); + ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle); +} + +void ConfigureTouchscreenAdvanced::restoreDefaults() { + ui->finger_box->setValue(0); + ui->diameter_x_box->setValue(15); + ui->diameter_y_box->setValue(15); + ui->angle_box->setValue(0); +} diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h new file mode 100644 index 000000000..41cd255fb --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -0,0 +1,32 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include <QWidget> +#include "yuzu/configuration/config.h" + +namespace Ui { +class ConfigureTouchscreenAdvanced; +} + +class ConfigureTouchscreenAdvanced : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTouchscreenAdvanced(QWidget* parent); + ~ConfigureTouchscreenAdvanced() override; + + void applyConfiguration(); + +private: + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + + std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui; +}; diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui new file mode 100644 index 000000000..1171c2dd1 --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTouchscreenAdvanced</class> + <widget class="QDialog" name="ConfigureTouchscreenAdvanced"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>298</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Touchscreen</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="minimumSize"> + <size> + <width>280</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Touch Parameters</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Touch Diameter Y</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Finger</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Touch Diameter X</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QSpinBox" name="finger_box"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Rotational Angle</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSpinBox" name="diameter_x_box"/> + </item> + <item row="2" column="2"> + <widget class="QSpinBox" name="diameter_y_box"/> + </item> + <item row="3" column="2"> + <widget class="QSpinBox" name="angle_box"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureTouchscreenAdvanced</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>140</x> + <y>318</y> + </hint> + <hint type="destinationlabel"> + <x>140</x> + <y>169</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureTouchscreenAdvanced</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>140</x> + <y>318</y> + </hint> + <hint type="destinationlabel"> + <x>140</x> + <y>169</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index 7741ab95d..7752ae4a1 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -17,18 +17,17 @@ class ConfigureWeb : public QWidget { public: explicit ConfigureWeb(QWidget* parent = nullptr); - ~ConfigureWeb(); + ~ConfigureWeb() override; void applyConfiguration(); void retranslateUi(); -public slots: +private: void RefreshTelemetryID(); void OnLoginChanged(); void VerifyLogin(); void OnLoginVerified(); -private: void setConfiguration(); bool user_verified = true; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 0c831c9f4..6b3a757e0 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -7,10 +7,10 @@ #include "common/assert.h" #include "core/core.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() return item_list; } -WaitTreeText::WaitTreeText(const QString& t) : text(t) {} +WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {} WaitTreeText::~WaitTreeText() = default; QString WaitTreeText::GetText() const { @@ -153,8 +153,8 @@ QString WaitTreeWaitObject::GetText() const { std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) { switch (object.GetHandleType()) { - case Kernel::HandleType::Event: - return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object)); + case Kernel::HandleType::ReadableEvent: + return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object)); case Kernel::HandleType::Timer: return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object)); case Kernel::HandleType::Thread: @@ -332,7 +332,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { return list; } -WaitTreeEvent::WaitTreeEvent(const Kernel::Event& object) : WaitTreeWaitObject(object) {} +WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object) : WaitTreeWaitObject(object) {} WaitTreeEvent::~WaitTreeEvent() = default; std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const { @@ -340,7 +340,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>( tr("reset type = %1") - .arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).GetResetType())))); + .arg(GetResetTypeQString( + static_cast<const Kernel::ReadableEvent&>(object).GetResetType())))); return list; } diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 331f89885..e639ef412 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -17,8 +17,8 @@ class EmuThread; namespace Kernel { +class ReadableEvent; class WaitObject; -class Event; class Thread; class Timer; } // namespace Kernel @@ -52,7 +52,7 @@ private: class WaitTreeText : public WaitTreeItem { Q_OBJECT public: - explicit WaitTreeText(const QString& text); + explicit WaitTreeText(QString text); ~WaitTreeText() override; QString GetText() const override; @@ -144,7 +144,7 @@ public: class WaitTreeEvent : public WaitTreeWaitObject { Q_OBJECT public: - explicit WaitTreeEvent(const Kernel::Event& object); + explicit WaitTreeEvent(const Kernel::ReadableEvent& object); ~WaitTreeEvent() override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 11a8c390b..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -214,6 +214,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) tree_view->setEditTriggers(QHeaderView::NoEditTriggers); tree_view->setUniformRowHeights(true); tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + tree_view->setStyleSheet("QTreeView{ border: none; }"); item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); @@ -332,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); + context_menu.addSeparator(); + QAction* properties = context_menu.addAction(tr("Properties")); open_save_location->setEnabled(program_id != 0); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -345,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); connect(navigate_to_gamedb_entry, &QAction::triggered, [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); + connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 05e115e19..b317eb2fc 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -70,6 +70,7 @@ signals: void CopyTIDRequested(u64 program_id); void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); + void OpenPerGameGeneralRequested(const std::string& file); private slots: void onTextChanged(const QString& newText); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 362902e46..b37710f59 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, FileSys::VirtualFile update_raw; loader.ReadUpdateRaw(update_raw); for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { - const bool is_update = kv.first == "Update"; + const bool is_update = kv.first == "Update" || kv.first == "[D] Update"; if (!updatable && is_update) { continue; } @@ -86,6 +86,37 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, out.chop(1); return out; } + +QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, + const std::vector<u8>& icon, Loader::AppLoader& loader, + u64 program_id, const CompatibilityList& compatibility_list, + const FileSys::PatchManager& patch) { + const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + + // The game list uses this as compatibility number for untested games + QString compatibility{"99"}; + if (it != compatibility_list.end()) { + compatibility = it->second.first; + } + + const auto file_type = loader.GetFileType(); + const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); + + QList<QStandardItem*> list{ + new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), + file_type_string, program_id), + new GameListItemCompat(compatibility), + new GameListItem(file_type_string), + new GameListItemSize(FileUtil::GetSize(path)), + }; + + if (UISettings::values.show_add_ons) { + list.insert( + 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()))); + } + + return list; +} } // Anonymous namespace GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, @@ -97,11 +128,11 @@ GameListWorker::~GameListWorker() = default; void GameListWorker::AddInstalledTitlesToGameList() { const auto cache = Service::FileSystem::GetUnionContents(); - const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Program); + const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Program); for (const auto& game : installed_games) { - const auto file = cache->GetEntryUnparsed(game); + const auto file = cache.GetEntryUnparsed(game); std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); if (!loader) continue; @@ -112,40 +143,19 @@ void GameListWorker::AddInstalledTitlesToGameList() { loader->ReadProgramId(program_id); const FileSys::PatchManager patch{program_id}; - const auto control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); + const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); if (control != nullptr) GetMetadataFromControlNCA(patch, *control, icon, name); - auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - - // The game list uses this as compatibility number for untested games - QString compatibility("99"); - if (it != compatibility_list.end()) - compatibility = it->second.first; - - QList<QStandardItem*> list{ - new GameListItemPath( - FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), - program_id), - new GameListItemCompat(compatibility), - new GameListItem( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), - new GameListItemSize(file->GetSize()), - }; - - if (UISettings::values.show_add_ons) { - list.insert(2, new GameListItem(FormatPatchNameVersions(patch, *loader))); - } - - emit EntryReady(list); + emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, + compatibility_list, patch)); } - const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Control); + const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Control); for (const auto& entry : control_data) { - auto nca = cache->GetEntry(entry); + auto nca = cache.GetEntry(entry); if (nca != nullptr) { nca_control_map.insert_or_assign(entry.title_id, std::move(nca)); } @@ -155,14 +165,14 @@ void GameListWorker::AddInstalledTitlesToGameList() { void GameListWorker::FillControlMap(const std::string& dir_path) { const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (stop_processing) - return false; // Breaks the callback loop. + if (stop_processing) { + // Breaks the callback loop + return false; + } - bool is_dir = FileUtil::IsDirectory(physical_name); - QFileInfo file_info(physical_name.c_str()); - if (!is_dir && file_info.suffix().toStdString() == "nca") { + const std::string physical_name = directory + DIR_SEP + virtual_name; + const QFileInfo file_info(QString::fromStdString(physical_name)); + if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) { auto nca = std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); if (nca->GetType() == FileSys::NCAContentType::Control) { @@ -179,20 +189,25 @@ void GameListWorker::FillControlMap(const std::string& dir_path) { void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (stop_processing) - return false; // Breaks the callback loop. + if (stop_processing) { + // Breaks the callback loop. + return false; + } - bool is_dir = FileUtil::IsDirectory(physical_name); + const std::string physical_name = directory + DIR_SEP + virtual_name; + const bool is_dir = FileUtil::IsDirectory(physical_name); if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { - std::unique_ptr<Loader::AppLoader> loader = - Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); - if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || - loader->GetFileType() == Loader::FileType::Error) && - !UISettings::values.show_unknown)) + auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (!loader) { return true; + } + + const auto file_type = loader->GetFileType(); + if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) && + !UISettings::values.show_unknown) { + return true; + } std::vector<u8> icon; const auto res1 = loader->ReadIcon(icon); @@ -214,30 +229,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign } } - auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - - // The game list uses this as compatibility number for untested games - QString compatibility("99"); - if (it != compatibility_list.end()) - compatibility = it->second.first; - - QList<QStandardItem*> list{ - new GameListItemPath( - FormatGameName(physical_name), icon, QString::fromStdString(name), - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), - program_id), - new GameListItemCompat(compatibility), - new GameListItem( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), - new GameListItemSize(FileUtil::GetSize(physical_name)), - }; - - if (UISettings::values.show_add_ons) { - list.insert(2, new GameListItem(FormatPatchNameVersions( - patch, *loader, loader->IsRomFSUpdatable()))); - } - - emit EntryReady(std::move(list)); + emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, + compatibility_list, patch)); } else if (is_dir && recursion > 0) { watch_list.append(QString::fromStdString(physical_name)); AddFstEntriesToGameList(physical_name, recursion - 1); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4b969119c..90b212ba5 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,9 +8,12 @@ #include <thread> // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/software_keyboard.h" +#include "configuration/configure_per_general.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applets/applets.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. @@ -59,6 +62,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" +#include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_ldr.h" @@ -204,6 +208,27 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::SoftwareKeyboardGetText( + const Core::Frontend::SoftwareKeyboardParameters& parameters) { + QtSoftwareKeyboardDialog dialog(this, parameters); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + if (!dialog.GetStatus()) { + emit SoftwareKeyboardFinishedText(std::nullopt); + return; + } + + emit SoftwareKeyboardFinishedText(dialog.GetText()); +} + +void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) { + QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message)); + emit SoftwareKeyboardFinishedCheckDialog(); +} + void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui.action_Report_Compatibility->setVisible(true); @@ -417,6 +442,8 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); + connect(game_list, &GameList::OpenPerGameGeneralRequested, this, + &GMainWindow::OnGameListOpenPerGameProperties); connect(this, &GMainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); @@ -494,32 +521,20 @@ void GMainWindow::OnDisplayTitleBars(bool show) { QStringList GMainWindow::GetUnsupportedGLExtensions() { QStringList unsupported_ext; - if (!GLAD_GL_ARB_program_interface_query) - unsupported_ext.append("ARB_program_interface_query"); - if (!GLAD_GL_ARB_separate_shader_objects) - unsupported_ext.append("ARB_separate_shader_objects"); - if (!GLAD_GL_ARB_vertex_attrib_binding) - unsupported_ext.append("ARB_vertex_attrib_binding"); + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.append("ARB_direct_state_access"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); - if (!GLAD_GL_ARB_base_instance) - unsupported_ext.append("ARB_base_instance"); - if (!GLAD_GL_ARB_texture_storage) - unsupported_ext.append("ARB_texture_storage"); if (!GLAD_GL_ARB_multi_bind) unsupported_ext.append("ARB_multi_bind"); - if (!GLAD_GL_ARB_copy_image) - unsupported_ext.append("ARB_copy_image"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) unsupported_ext.append("EXT_texture_compression_s3tc"); if (!GLAD_GL_ARB_texture_compression_rgtc) unsupported_ext.append("ARB_texture_compression_rgtc"); - if (!GLAD_GL_ARB_texture_compression_bptc) - unsupported_ext.append("ARB_texture_compression_bptc"); if (!GLAD_GL_ARB_depth_buffer_float) unsupported_ext.append("ARB_depth_buffer_float"); @@ -538,8 +553,8 @@ bool GMainWindow::LoadROM(const QString& filename) { render_window->MakeCurrent(); if (!gladLoadGL()) { - QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"), - tr("Your GPU may not support OpenGL 3.3, or you do not " + QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"), + tr("Your GPU may not support OpenGL 4.3, or you do not " "have the latest graphics driver.")); return false; } @@ -559,6 +574,8 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetGPUDebugContext(debug_context); + system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); + const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; const auto drd_callout = @@ -893,7 +910,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } const auto installed = Service::FileSystem::GetUnionContents(); - auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id); + const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); if (!romfs_title_id) { failed(); @@ -908,7 +925,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (*romfs_title_id == program_id) { romfs = file; } else { - romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); + romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); } const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); @@ -974,6 +991,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); } +void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { + u64 title_id{}; + const auto v_file = Core::GetGameFileFromPath(vfs, file); + const auto loader = Loader::GetLoader(v_file); + if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { + QMessageBox::information(this, tr("Properties"), + tr("The game properties could not be loaded.")); + return; + } + + ConfigurePerGameGeneral dialog(this, title_id); + dialog.loadFromFile(v_file); + auto result = dialog.exec(); + if (result == QDialog::Accepted) { + dialog.applyConfiguration(); + + const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); + if (reload) { + game_list->PopulateAsync(UISettings::values.gamedir, + UISettings::values.gamedir_deepscan); + } + + config->Save(); + } +} + void GMainWindow::OnMenuLoadFile() { const QString extensions = QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); @@ -1093,14 +1136,14 @@ void GMainWindow::OnMenuInstallToNAND() { return; } const auto res = - Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy); + Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy); if (res == FileSys::InstallResult::Success) { success(); } else { if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (overwrite()) { const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( - nsp, true, qt_raw_copy); + *nsp, true, qt_raw_copy); if (res2 == FileSys::InstallResult::Success) { success(); } else { @@ -1155,10 +1198,10 @@ void GMainWindow::OnMenuInstallToNAND() { FileSys::InstallResult res; if (index >= static_cast<size_t>(FileSys::TitleType::Application)) { res = Service::FileSystem::GetUserNANDContents()->InstallEntry( - nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); } else { res = Service::FileSystem::GetSystemNANDContents()->InstallEntry( - nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); } if (res == FileSys::InstallResult::Success) { @@ -1166,7 +1209,7 @@ void GMainWindow::OnMenuInstallToNAND() { } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (overwrite()) { const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( - nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); + *nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); if (res2 == FileSys::InstallResult::Success) { success(); } else { @@ -1228,8 +1271,13 @@ void GMainWindow::OnMenuRecentFile() { void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + + qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( + "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); + qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); + connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 929250e8c..ca9c50367 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -29,6 +29,10 @@ class ProfilerWidget; class WaitTreeWidget; enum class GameListOpenTarget; +namespace Core::Frontend { +struct SoftwareKeyboardParameters; +} // namespace Core::Frontend + namespace FileSys { class RegisteredCacheUnion; class VfsFilesystem; @@ -95,6 +99,13 @@ signals: // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); + void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); + void SoftwareKeyboardFinishedCheckDialog(); + +public slots: + void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); + void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); + private: void InitializeWidgets(); void InitializeDebugWidgets(); @@ -157,6 +168,7 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); + void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 96f1ef636..fe0d1eebf 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> +#include <sstream> #include <SDL.h> #include <inih/cpp/INIReader.h> #include "common/file_util.h" @@ -65,54 +66,246 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> }, }}; +static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons = { + SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_APOSTROPHE, + SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, +}; + +static const std::array<int, 0x8A> keyboard_keys = { + 0, + 0, + 0, + 0, + SDL_SCANCODE_A, + SDL_SCANCODE_B, + SDL_SCANCODE_C, + SDL_SCANCODE_D, + SDL_SCANCODE_E, + SDL_SCANCODE_F, + SDL_SCANCODE_G, + SDL_SCANCODE_H, + SDL_SCANCODE_I, + SDL_SCANCODE_J, + SDL_SCANCODE_K, + SDL_SCANCODE_L, + SDL_SCANCODE_M, + SDL_SCANCODE_N, + SDL_SCANCODE_O, + SDL_SCANCODE_P, + SDL_SCANCODE_Q, + SDL_SCANCODE_R, + SDL_SCANCODE_S, + SDL_SCANCODE_T, + SDL_SCANCODE_U, + SDL_SCANCODE_V, + SDL_SCANCODE_W, + SDL_SCANCODE_X, + SDL_SCANCODE_Y, + SDL_SCANCODE_Z, + SDL_SCANCODE_1, + SDL_SCANCODE_2, + SDL_SCANCODE_3, + SDL_SCANCODE_4, + SDL_SCANCODE_5, + SDL_SCANCODE_6, + SDL_SCANCODE_7, + SDL_SCANCODE_8, + SDL_SCANCODE_9, + SDL_SCANCODE_0, + SDL_SCANCODE_RETURN, + SDL_SCANCODE_ESCAPE, + SDL_SCANCODE_BACKSPACE, + SDL_SCANCODE_TAB, + SDL_SCANCODE_SPACE, + SDL_SCANCODE_MINUS, + SDL_SCANCODE_EQUALS, + SDL_SCANCODE_LEFTBRACKET, + SDL_SCANCODE_RIGHTBRACKET, + SDL_SCANCODE_BACKSLASH, + 0, + SDL_SCANCODE_SEMICOLON, + SDL_SCANCODE_APOSTROPHE, + SDL_SCANCODE_GRAVE, + SDL_SCANCODE_COMMA, + SDL_SCANCODE_PERIOD, + SDL_SCANCODE_SLASH, + SDL_SCANCODE_CAPSLOCK, + + SDL_SCANCODE_F1, + SDL_SCANCODE_F2, + SDL_SCANCODE_F3, + SDL_SCANCODE_F4, + SDL_SCANCODE_F5, + SDL_SCANCODE_F6, + SDL_SCANCODE_F7, + SDL_SCANCODE_F8, + SDL_SCANCODE_F9, + SDL_SCANCODE_F10, + SDL_SCANCODE_F11, + SDL_SCANCODE_F12, + + 0, + SDL_SCANCODE_SCROLLLOCK, + SDL_SCANCODE_PAUSE, + SDL_SCANCODE_INSERT, + SDL_SCANCODE_HOME, + SDL_SCANCODE_PAGEUP, + SDL_SCANCODE_DELETE, + SDL_SCANCODE_END, + SDL_SCANCODE_PAGEDOWN, + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_UP, + + SDL_SCANCODE_NUMLOCKCLEAR, + SDL_SCANCODE_KP_DIVIDE, + SDL_SCANCODE_KP_MULTIPLY, + SDL_SCANCODE_KP_MINUS, + SDL_SCANCODE_KP_PLUS, + SDL_SCANCODE_KP_ENTER, + SDL_SCANCODE_KP_1, + SDL_SCANCODE_KP_2, + SDL_SCANCODE_KP_3, + SDL_SCANCODE_KP_4, + SDL_SCANCODE_KP_5, + SDL_SCANCODE_KP_6, + SDL_SCANCODE_KP_7, + SDL_SCANCODE_KP_8, + SDL_SCANCODE_KP_9, + SDL_SCANCODE_KP_0, + SDL_SCANCODE_KP_PERIOD, + + 0, + 0, + SDL_SCANCODE_POWER, + SDL_SCANCODE_KP_EQUALS, + + SDL_SCANCODE_F13, + SDL_SCANCODE_F14, + SDL_SCANCODE_F15, + SDL_SCANCODE_F16, + SDL_SCANCODE_F17, + SDL_SCANCODE_F18, + SDL_SCANCODE_F19, + SDL_SCANCODE_F20, + SDL_SCANCODE_F21, + SDL_SCANCODE_F22, + SDL_SCANCODE_F23, + SDL_SCANCODE_F24, + + 0, + SDL_SCANCODE_HELP, + SDL_SCANCODE_MENU, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SDL_SCANCODE_KP_COMMA, + SDL_SCANCODE_KP_LEFTPAREN, + SDL_SCANCODE_KP_RIGHTPAREN, + 0, + 0, + 0, + 0, +}; + +static const std::array<int, 8> keyboard_mods{ + SDL_SCANCODE_LCTRL, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_LALT, SDL_SCANCODE_LGUI, + SDL_SCANCODE_RCTRL, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_RALT, SDL_SCANCODE_RGUI, +}; + void Config::ReadValues() { // Controls + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + const auto group = fmt::format("ControlsP{}", p); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.players[p].buttons[i] = + sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param); + if (Settings::values.players[p].buttons[i].empty()) + Settings::values.players[p].buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.players[p].analogs[i] = + sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param); + if (Settings::values.players[p].analogs[i].empty()) + Settings::values.players[p].analogs[i] = default_param; + } + } + + Settings::values.mouse_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "mouse_enabled", false); + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); + Settings::values.mouse_buttons[i] = sdl2_config->Get( + "ControlsGeneral", std::string("mouse_") + Settings::NativeMouseButton::mapping[i], + default_param); + if (Settings::values.mouse_buttons[i].empty()) + Settings::values.mouse_buttons[i] = default_param; + } + + Settings::values.motion_device = sdl2_config->Get( + "ControlsGeneral", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); + + Settings::values.keyboard_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "keyboard_enabled", false); + + Settings::values.debug_pad_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "debug_pad_enabled", false); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.buttons[i] = - sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param); - if (Settings::values.buttons[i].empty()) - Settings::values.buttons[i] = default_param; + Settings::values.debug_pad_buttons[i] = sdl2_config->Get( + "ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i], + default_param); + if (Settings::values.debug_pad_buttons[i].empty()) + Settings::values.debug_pad_buttons[i] = default_param; } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.analogs[i] = - sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param); - if (Settings::values.analogs[i].empty()) - Settings::values.analogs[i] = default_param; + Settings::values.debug_pad_analogs[i] = sdl2_config->Get( + "ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i], + default_param); + if (Settings::values.debug_pad_analogs[i].empty()) + Settings::values.debug_pad_analogs[i] = default_param; } - Settings::values.motion_device = sdl2_config->Get( - "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); - Settings::values.touch_device = - sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); + Settings::values.touchscreen.enabled = + sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); + Settings::values.touchscreen.device = + sdl2_config->Get("ControlsGeneral", "touch_device", "engine:emu_window"); + Settings::values.touchscreen.finger = + sdl2_config->GetInteger("ControlsGeneral", "touch_finger", 0); + Settings::values.touchscreen.rotation_angle = + sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0); + Settings::values.touchscreen.diameter_x = + sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); + Settings::values.touchscreen.diameter_y = + sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); - // Core - Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); - Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); - - // Renderer - Settings::values.resolution_factor = - (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); - Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); - Settings::values.frame_limit = - static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); - Settings::values.use_accurate_gpu_emulation = - sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); - - Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); - Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); - Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); - - // Audio - Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); - Settings::values.enable_audio_stretching = - sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); - Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); - Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); + std::transform(keyboard_keys.begin(), keyboard_keys.end(), + Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); + std::transform(keyboard_mods.begin(), keyboard_mods.end(), + Settings::values.keyboard_keys.begin() + + Settings::NativeKeyboard::LeftControlKey, + InputCommon::GenerateKeyboardParam); + std::transform(keyboard_mods.begin(), keyboard_mods.end(), + Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); // Data Storage Settings::values.use_virtual_sd = @@ -139,6 +332,30 @@ void Config::ReadValues() { Settings::values.rng_seed = std::nullopt; } + // Core + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); + Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + + // Renderer + Settings::values.resolution_factor = + (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); + Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); + Settings::values.frame_limit = + static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); + Settings::values.use_accurate_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); + + Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); + Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); + Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); + + // Audio + Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); + Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); + Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); // Miscellaneous @@ -150,8 +367,26 @@ void Config::ReadValues() { Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); + Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::stoul(line, nullptr, 16); + const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector<std::string> out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + // Web Service Settings::values.enable_telemetry = sdl2_config->GetBoolean("WebService", "enable_telemetry", true); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index ecf625e7b..0f3f8da50 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -206,6 +206,8 @@ log_filter = *:Trace # Port for listening to GDB connections. use_gdbstub=false gdbstub_port=24689 +# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them +dump_exefs=false # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them dump_nso=false @@ -219,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org # See https://profile.yuzu-emu.org/ for more info yuzu_username = yuzu_token = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey )"; } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index a9ad92a80..a557f2884 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -111,32 +111,20 @@ void EmuWindow_SDL2::Fullscreen() { bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { std::vector<std::string> unsupported_ext; - if (!GLAD_GL_ARB_program_interface_query) - unsupported_ext.push_back("ARB_program_interface_query"); - if (!GLAD_GL_ARB_separate_shader_objects) - unsupported_ext.push_back("ARB_separate_shader_objects"); - if (!GLAD_GL_ARB_vertex_attrib_binding) - unsupported_ext.push_back("ARB_vertex_attrib_binding"); + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.push_back("ARB_direct_state_access"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); - if (!GLAD_GL_ARB_base_instance) - unsupported_ext.push_back("ARB_base_instance"); - if (!GLAD_GL_ARB_texture_storage) - unsupported_ext.push_back("ARB_texture_storage"); if (!GLAD_GL_ARB_multi_bind) unsupported_ext.push_back("ARB_multi_bind"); - if (!GLAD_GL_ARB_copy_image) - unsupported_ext.push_back("ARB_copy_image"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) unsupported_ext.push_back("EXT_texture_compression_s3tc"); if (!GLAD_GL_ARB_texture_compression_rgtc) unsupported_ext.push_back("ARB_texture_compression_rgtc"); - if (!GLAD_GL_ARB_texture_compression_bptc) - unsupported_ext.push_back("ARB_texture_compression_bptc"); if (!GLAD_GL_ARB_depth_buffer_float) unsupported_ext.push_back("ARB_depth_buffer_float"); @@ -157,7 +145,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { exit(1); } - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); |