mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-27 19:11:48 +01:00
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 <juangerman-13@hotmail.com>
This commit is contained in:
parent
015d633aae
commit
70ad4498a2
@ -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
|
||||
|
77
app/src/main/cpp/skyline/input.cpp
Normal file
77
app/src/main/cpp/skyline/input.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <common/signal.h>
|
||||
#include <loader/loader.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "input.h"
|
||||
|
||||
namespace skyline::input {
|
||||
Input::Input(const DeviceState &state)
|
||||
: state{state},
|
||||
kHid{std::make_shared<kernel::type::KSharedMemory>(state, sizeof(HidSharedMemory))},
|
||||
hid{reinterpret_cast<HidSharedMemory *>(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<void(UpdateCallback &)> callback;
|
||||
|
||||
UpdateCallback(std::chrono::milliseconds period, std::function<void(UpdateCallback &)> 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<UpdateCallback, 2> 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -24,11 +24,14 @@ namespace skyline::input {
|
||||
NpadManager npad;
|
||||
TouchManager touch;
|
||||
|
||||
Input(const DeviceState &state)
|
||||
: state(state),
|
||||
kHid(std::make_shared<kernel::type::KSharedMemory>(state, sizeof(HidSharedMemory))),
|
||||
hid(reinterpret_cast<HidSharedMemory *>(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();
|
||||
};
|
||||
}
|
||||
|
@ -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<i16>::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
|
||||
|
@ -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
|
||||
|
@ -16,20 +16,12 @@ namespace skyline::input {
|
||||
}
|
||||
|
||||
void TouchManager::SetState(span<TouchScreenPoint> 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<u32>(host.attribute);
|
||||
guest.index = static_cast<u32>(host.id);
|
||||
guest.positionX = static_cast<u32>(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<u8>(section.header.entryCount + 1), constant::HidEntryCount);
|
||||
|
@ -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<TouchScreenPoint> touchPoints);
|
||||
|
||||
/**
|
||||
* @brief Writes the current state of the touch screen to HID shared memory
|
||||
*/
|
||||
void UpdateSharedMemory();
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user