From 70ad4498a2921db87108a410dad75872391791b3 Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Wed, 31 Aug 2022 22:33:42 +0530 Subject: [PATCH] Write HID LIFO entries at fixed intervals Certain titles depend on HID LIFO entries being written out at a fixed frequency rather than on actual state change, not doing this can lead to applications freezing till the LIFO is filled up to maximum size, this behavior is seen in Super Mario Odyssey. In other cases such as Metroid Dread, the game can run into race conditions that would lead to crashes, these were worked around by smashing a button during loading prior. This commit introduces a thread which sleeps and wakes up occasionally to write LIFO entries into HID shared memory at the desired frequencies. This alleviates any issues as it fills up the LIFO instantly and correctly emulates HID Shared Memory behavior expected by the guest. Co-authored-by: Narr the Reg --- app/CMakeLists.txt | 1 + app/src/main/cpp/skyline/input.cpp | 77 +++++++++ app/src/main/cpp/skyline/input.h | 15 +- .../main/cpp/skyline/input/npad_device.cpp | 151 ++++++++---------- app/src/main/cpp/skyline/input/npad_device.h | 12 +- app/src/main/cpp/skyline/input/touch.cpp | 30 ++-- app/src/main/cpp/skyline/input/touch.h | 6 + 7 files changed, 189 insertions(+), 103 deletions(-) create mode 100644 app/src/main/cpp/skyline/input.cpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b0e2f70c..41204432 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -198,6 +198,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/soc/gm20b/engines/maxwell_dma.cpp ${source_DIR}/skyline/soc/gm20b/engines/maxwell/initialization.cpp ${source_DIR}/skyline/soc/gm20b/engines/fermi_2d.cpp + ${source_DIR}/skyline/input.cpp ${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/input/npad_device.cpp ${source_DIR}/skyline/input/touch.cpp diff --git a/app/src/main/cpp/skyline/input.cpp b/app/src/main/cpp/skyline/input.cpp new file mode 100644 index 00000000..8466ded5 --- /dev/null +++ b/app/src/main/cpp/skyline/input.cpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "input.h" + +namespace skyline::input { + Input::Input(const DeviceState &state) + : state{state}, + kHid{std::make_shared(state, sizeof(HidSharedMemory))}, + hid{reinterpret_cast(kHid->host.data())}, + npad{state, hid}, + touch{state, hid}, + updateThread{&Input::UpdateThread, this} {} + + void Input::UpdateThread() { + if (int result{pthread_setname_np(pthread_self(), "Sky-Input")}) + Logger::Warn("Failed to set the thread name: {}", strerror(result)); + + try { + signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler); + + struct UpdateCallback { + std::chrono::milliseconds period; + std::chrono::steady_clock::time_point next; + std::function callback; + + UpdateCallback(std::chrono::milliseconds period, std::function callback) + : period{period}, next{std::chrono::steady_clock::now() + period}, callback{std::move(callback)} {} + + void operator()() { + callback(*this); + next += period; + } + }; + + constexpr std::chrono::milliseconds NPadUpdatePeriod{4}; //!< The period at which a Joy-Con is updated (250Hz) + constexpr std::chrono::milliseconds TouchUpdatePeriod{4}; //!< The period at which the touch screen is updated (250Hz) + + std::array updateCallbacks{ + UpdateCallback{NPadUpdatePeriod, [&](UpdateCallback &callback) { + for (auto &pad : npad.npads) + pad.UpdateSharedMemory(); + }}, + UpdateCallback{TouchUpdatePeriod, [&](UpdateCallback &callback) { + touch.UpdateSharedMemory(); + }}, + }; + + while (true) { + auto now{std::chrono::steady_clock::now()}, next{updateCallbacks[0].next}; + for (auto &callback : updateCallbacks) { + if (now >= callback.next) + callback(); + + if (callback.next < next) + next = callback.next; + } + std::this_thread::sleep_until(next); + } + } catch (const signal::SignalException &e) { + Logger::Error("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames)); + if (state.process) + state.process->Kill(false); + else + std::rethrow_exception(std::current_exception()); + } catch (const std::exception &e) { + Logger::Error(e.what()); + if (state.process) + state.process->Kill(false); + else + std::rethrow_exception(std::current_exception()); + } + } +} diff --git a/app/src/main/cpp/skyline/input.h b/app/src/main/cpp/skyline/input.h index f753d777..2f6a8a5c 100644 --- a/app/src/main/cpp/skyline/input.h +++ b/app/src/main/cpp/skyline/input.h @@ -24,11 +24,14 @@ namespace skyline::input { NpadManager npad; TouchManager touch; - Input(const DeviceState &state) - : state(state), - kHid(std::make_shared(state, sizeof(HidSharedMemory))), - hid(reinterpret_cast(kHid->host.data())), - npad(state, hid), - touch(state, hid) {} + Input(const DeviceState &state); + + private: + std::thread updateThread; //!< A thread that handles delivering HID shared memory updates at a fixed rate + + /** + * @brief The entry point for the update thread, this handles timing and delegation to the shared memory managers + */ + void UpdateThread(); }; } diff --git a/app/src/main/cpp/skyline/input/npad_device.cpp b/app/src/main/cpp/skyline/input/npad_device.cpp index fa119282..3f5cb8c9 100644 --- a/app/src/main/cpp/skyline/input/npad_device.cpp +++ b/app/src/main/cpp/skyline/input/npad_device.cpp @@ -146,10 +146,7 @@ namespace skyline::input { type = newType; controllerInfo = &GetControllerInfo(); - GetNextEntry(*controllerInfo); - GetNextEntry(section.defaultController); - globalTimestamp++; - + UpdateSharedMemory(); updateEvent->Signal(); } @@ -186,7 +183,7 @@ namespace skyline::input { } } - NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) { + void NpadDevice::WriteNextEntry(NpadControllerInfo &info, NpadControllerState entry) { auto &lastEntry{info.state.at(info.header.currentEntry)}; info.header.timestamp = util::GetTimeTicks(); @@ -194,30 +191,33 @@ namespace skyline::input { info.header.maxEntry = info.header.entryCount; info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0; - auto &entry{info.state.at(info.header.currentEntry)}; + auto &nextEntry{info.state.at(info.header.currentEntry)}; - entry.globalTimestamp = globalTimestamp; - entry.localTimestamp = lastEntry.localTimestamp + 1; - entry.buttons = lastEntry.buttons; - entry.leftX = lastEntry.leftX; - entry.leftY = lastEntry.leftY; - entry.rightX = lastEntry.rightX; - entry.rightY = lastEntry.rightY; - entry.status.raw = connectionState.raw; - - return entry; + nextEntry.globalTimestamp = globalTimestamp; + nextEntry.localTimestamp = lastEntry.localTimestamp + 1; + nextEntry.buttons = entry.buttons; + nextEntry.leftX = entry.leftX; + nextEntry.leftY = entry.leftY; + nextEntry.rightX = entry.rightX; + nextEntry.rightY = entry.rightY; + nextEntry.status.raw = connectionState.raw; } - void NpadDevice::SetButtonState(NpadButton mask, bool pressed) { + void NpadDevice::UpdateSharedMemory() { if (!connectionState.connected) return; - auto &entry{GetNextEntry(*controllerInfo)}; + WriteNextEntry(*controllerInfo, controllerState); + WriteNextEntry(section.defaultController, defaultState); + globalTimestamp++; + } + + void NpadDevice::SetButtonState(NpadButton mask, bool pressed) { if (pressed) - entry.buttons.raw |= mask.raw; + controllerState.buttons.raw |= mask.raw; else - entry.buttons.raw &= ~mask.raw; + controllerState.buttons.raw &= ~mask.raw; if (manager.orientation == NpadJoyOrientation::Horizontal && (type == NpadControllerType::JoyconLeft || type == NpadControllerType::JoyconRight)) { NpadButton orientedMask{}; @@ -252,109 +252,98 @@ namespace skyline::input { mask = orientedMask; } - auto &defaultEntry{GetNextEntry(section.defaultController)}; if (pressed) - defaultEntry.buttons.raw |= mask.raw; + defaultState.buttons.raw |= mask.raw; else - defaultEntry.buttons.raw &= ~mask.raw; - - globalTimestamp++; + defaultState.buttons.raw &= ~mask.raw; } void NpadDevice::SetAxisValue(NpadAxisId axis, i32 value) { - if (!connectionState.connected) - return; - - auto &controllerEntry{GetNextEntry(*controllerInfo)}; - auto &defaultEntry{GetNextEntry(section.defaultController)}; - constexpr i16 threshold{std::numeric_limits::max() / 2}; // A 50% deadzone for the stick buttons if (manager.orientation == NpadJoyOrientation::Vertical || (type != NpadControllerType::JoyconLeft && type != NpadControllerType::JoyconRight)) { switch (axis) { case NpadAxisId::LX: - controllerEntry.leftX = value; - defaultEntry.leftX = value; + controllerState.leftX = value; + defaultState.leftX = value; - controllerEntry.buttons.leftStickLeft = controllerEntry.leftX <= -threshold; - defaultEntry.buttons.leftStickLeft = controllerEntry.buttons.leftStickLeft; + controllerState.buttons.leftStickLeft = controllerState.leftX <= -threshold; + defaultState.buttons.leftStickLeft = controllerState.buttons.leftStickLeft; - controllerEntry.buttons.leftStickRight = controllerEntry.leftX >= threshold; - defaultEntry.buttons.leftStickRight = controllerEntry.buttons.leftStickRight; + controllerState.buttons.leftStickRight = controllerState.leftX >= threshold; + defaultState.buttons.leftStickRight = controllerState.buttons.leftStickRight; break; case NpadAxisId::LY: - controllerEntry.leftY = value; - defaultEntry.leftY = value; + controllerState.leftY = value; + defaultState.leftY = value; - defaultEntry.buttons.leftStickUp = controllerEntry.buttons.leftStickUp; - controllerEntry.buttons.leftStickUp = controllerEntry.leftY >= threshold; + defaultState.buttons.leftStickUp = controllerState.buttons.leftStickUp; + controllerState.buttons.leftStickUp = controllerState.leftY >= threshold; - controllerEntry.buttons.leftStickDown = controllerEntry.leftY <= -threshold; - defaultEntry.buttons.leftStickDown = controllerEntry.buttons.leftStickDown; + controllerState.buttons.leftStickDown = controllerState.leftY <= -threshold; + defaultState.buttons.leftStickDown = controllerState.buttons.leftStickDown; break; case NpadAxisId::RX: - controllerEntry.rightX = value; - defaultEntry.rightX = value; + controllerState.rightX = value; + defaultState.rightX = value; - controllerEntry.buttons.rightStickLeft = controllerEntry.rightX <= -threshold; - defaultEntry.buttons.rightStickLeft = controllerEntry.buttons.rightStickLeft; + controllerState.buttons.rightStickLeft = controllerState.rightX <= -threshold; + defaultState.buttons.rightStickLeft = controllerState.buttons.rightStickLeft; - controllerEntry.buttons.rightStickRight = controllerEntry.rightX >= threshold; - defaultEntry.buttons.rightStickRight = controllerEntry.buttons.rightStickRight; + controllerState.buttons.rightStickRight = controllerState.rightX >= threshold; + defaultState.buttons.rightStickRight = controllerState.buttons.rightStickRight; break; case NpadAxisId::RY: - controllerEntry.rightY = value; - defaultEntry.rightY = value; + controllerState.rightY = value; + defaultState.rightY = value; - controllerEntry.buttons.rightStickUp = controllerEntry.rightY >= threshold; - defaultEntry.buttons.rightStickUp = controllerEntry.buttons.rightStickUp; + controllerState.buttons.rightStickUp = controllerState.rightY >= threshold; + defaultState.buttons.rightStickUp = controllerState.buttons.rightStickUp; - controllerEntry.buttons.rightStickDown = controllerEntry.rightY <= -threshold; - defaultEntry.buttons.rightStickDown = controllerEntry.buttons.rightStickDown; + controllerState.buttons.rightStickDown = controllerState.rightY <= -threshold; + defaultState.buttons.rightStickDown = controllerState.buttons.rightStickDown; break; } } else { switch (axis) { case NpadAxisId::LX: - controllerEntry.leftY = value; - controllerEntry.buttons.leftStickUp = controllerEntry.leftY >= threshold; - controllerEntry.buttons.leftStickDown = controllerEntry.leftY <= -threshold; + controllerState.leftY = value; + controllerState.buttons.leftStickUp = controllerState.leftY >= threshold; + controllerState.buttons.leftStickDown = controllerState.leftY <= -threshold; - defaultEntry.leftX = value; - defaultEntry.buttons.leftStickLeft = defaultEntry.leftX <= -threshold; - defaultEntry.buttons.leftStickRight = defaultEntry.leftX >= threshold; + defaultState.leftX = value; + defaultState.buttons.leftStickLeft = defaultState.leftX <= -threshold; + defaultState.buttons.leftStickRight = defaultState.leftX >= threshold; break; case NpadAxisId::LY: - controllerEntry.leftX = -value; - controllerEntry.buttons.leftStickLeft = controllerEntry.leftX <= -threshold; - controllerEntry.buttons.leftStickRight = controllerEntry.leftX >= threshold; + controllerState.leftX = -value; + controllerState.buttons.leftStickLeft = controllerState.leftX <= -threshold; + controllerState.buttons.leftStickRight = controllerState.leftX >= threshold; - defaultEntry.leftY = value; - defaultEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold; - defaultEntry.buttons.leftStickDown = defaultEntry.leftY <= -threshold; + defaultState.leftY = value; + defaultState.buttons.leftStickUp = defaultState.leftY >= threshold; + defaultState.buttons.leftStickDown = defaultState.leftY <= -threshold; break; case NpadAxisId::RX: - controllerEntry.rightY = value; - controllerEntry.buttons.rightStickUp = controllerEntry.rightY >= threshold; - controllerEntry.buttons.rightStickDown = controllerEntry.rightY <= -threshold; + controllerState.rightY = value; + controllerState.buttons.rightStickUp = controllerState.rightY >= threshold; + controllerState.buttons.rightStickDown = controllerState.rightY <= -threshold; - defaultEntry.rightX = value; - defaultEntry.buttons.rightStickLeft = defaultEntry.rightX <= -threshold; - defaultEntry.buttons.rightStickRight = defaultEntry.rightX >= threshold; + defaultState.rightX = value; + defaultState.buttons.rightStickLeft = defaultState.rightX <= -threshold; + defaultState.buttons.rightStickRight = defaultState.rightX >= threshold; break; case NpadAxisId::RY: - controllerEntry.rightX = -value; - controllerEntry.buttons.rightStickLeft = controllerEntry.rightX <= -threshold; - controllerEntry.buttons.rightStickRight = controllerEntry.rightX >= threshold; + controllerState.rightX = -value; + controllerState.buttons.rightStickLeft = controllerState.rightX <= -threshold; + controllerState.buttons.rightStickRight = controllerState.rightX >= threshold; - defaultEntry.rightY = value; - defaultEntry.buttons.rightStickUp = defaultEntry.rightY >= threshold; - defaultEntry.buttons.rightStickDown = defaultEntry.rightY <= -threshold; + defaultState.rightY = value; + defaultState.buttons.rightStickUp = defaultState.rightY >= threshold; + defaultState.buttons.rightStickDown = defaultState.rightY <= -threshold; break; } } - - globalTimestamp++; } constexpr jlong MsInSecond{1000}; //!< The amount of milliseconds in a single second of time diff --git a/app/src/main/cpp/skyline/input/npad_device.h b/app/src/main/cpp/skyline/input/npad_device.h index b6447891..b4204d28 100644 --- a/app/src/main/cpp/skyline/input/npad_device.h +++ b/app/src/main/cpp/skyline/input/npad_device.h @@ -136,13 +136,14 @@ namespace skyline::input { NpadSection §ion; //!< The section in HID shared memory for this controller NpadControllerInfo *controllerInfo{}; //!< The NpadControllerInfo for this controller's type u64 globalTimestamp{}; //!< An incrementing timestamp that's common across all sections + NpadControllerState controllerState{}, defaultState{}; //!< The current state of the controller (normal and default) /** - * @brief Updates the headers and creates a new entry in HID Shared Memory + * @brief Updates the headers and writes a new entry in HID Shared Memory * @param info The controller info of the NPad that needs to be updated - * @return The next entry that has been created with values from the last entry + * @param entry An entry with the state of the controller */ - NpadControllerState &GetNextEntry(NpadControllerInfo &info); + void WriteNextEntry(NpadControllerInfo &info, NpadControllerState entry); /** * @return The NpadControllerInfo for this controller based on its type @@ -180,6 +181,11 @@ namespace skyline::input { */ void Disconnect(); + /** + * @brief Writes the current state of the controller to HID shared memory + */ + void UpdateSharedMemory(); + /** * @brief Changes the state of buttons to the specified state * @param mask A bit-field mask of all the buttons to change diff --git a/app/src/main/cpp/skyline/input/touch.cpp b/app/src/main/cpp/skyline/input/touch.cpp index 674d733b..79bb0d43 100644 --- a/app/src/main/cpp/skyline/input/touch.cpp +++ b/app/src/main/cpp/skyline/input/touch.cpp @@ -16,20 +16,12 @@ namespace skyline::input { } void TouchManager::SetState(span touchPoints) { - if (!activated) - return; - - const auto &lastEntry{section.entries[section.header.currentEntry]}; - auto entryIndex{(section.header.currentEntry != constant::HidEntryCount - 1) ? section.header.currentEntry + 1 : 0}; - auto &entry{section.entries[entryIndex]}; - touchPoints = touchPoints.first(std::min(touchPoints.size(), entry.data.size())); - entry.globalTimestamp = lastEntry.globalTimestamp + 1; - entry.localTimestamp = lastEntry.localTimestamp + 1; - entry.touchCount = touchPoints.size(); + touchPoints = touchPoints.first(std::min(touchPoints.size(), screenState.data.size())); + screenState.touchCount = touchPoints.size(); for (size_t i{}; i < touchPoints.size(); i++) { const auto &host{touchPoints[i]}; - auto &guest{entry.data[i]}; + auto &guest{screenState.data[i]}; guest.attribute.raw = static_cast(host.attribute); guest.index = static_cast(host.id); guest.positionX = static_cast(host.x); @@ -40,8 +32,20 @@ namespace skyline::input { } // Clear unused touch points - for (size_t i{touchPoints.size()}; i < entry.data.size(); i++) - entry.data[i] = {}; + for (size_t i{touchPoints.size()}; i < screenState.data.size(); i++) + screenState.data[i] = {}; + } + + void TouchManager::UpdateSharedMemory() { + if (!activated) + return; + + const auto &lastEntry{section.entries[section.header.currentEntry]}; + auto entryIndex{(section.header.currentEntry != constant::HidEntryCount - 1) ? section.header.currentEntry + 1 : 0}; + auto &entry{section.entries[entryIndex]}; + entry = screenState; + entry.globalTimestamp = lastEntry.globalTimestamp + 1; + entry.localTimestamp = lastEntry.localTimestamp + 1; section.header.timestamp = util::GetTimeTicks(); section.header.entryCount = std::min(static_cast(section.header.entryCount + 1), constant::HidEntryCount); diff --git a/app/src/main/cpp/skyline/input/touch.h b/app/src/main/cpp/skyline/input/touch.h index 5aaeb0fd..16ac2b0f 100644 --- a/app/src/main/cpp/skyline/input/touch.h +++ b/app/src/main/cpp/skyline/input/touch.h @@ -30,6 +30,7 @@ namespace skyline::input { const DeviceState &state; bool activated{}; TouchScreenSection §ion; + TouchScreenState screenState{}; //!< The current state of the touch screen public: /** @@ -40,5 +41,10 @@ namespace skyline::input { void Activate(); void SetState(span touchPoints); + + /** + * @brief Writes the current state of the touch screen to HID shared memory + */ + void UpdateSharedMemory(); }; }