input: reduce pad state latency for single-sample reads (#4508)

* input: reduce pad state latency for single-sample reads

* Restore original disconnected pad handling

* input: restore axis smoothing default in FinalizeUpdate

Explicitly passing false to GameController::Axis() unconditionally
disabled smoothing for all input sources. Controller hardware is
unaffected (the <16-unit guard in UpdateAxisSmoothing already skips
interpolation for fine-grained inputs), but KBM users rely on it for
smooth analog motion (e.g. Bloodborne). Revert to the default smooth=true.

* input: correct controller state copyright year
This commit is contained in:
Arthur Cuesta
2026-07-29 10:31:19 +03:00
committed by GitHub
parent 9894036b3f
commit cc728e1c7c
9 changed files with 315 additions and 391 deletions
+1
View File
@@ -1188,6 +1188,7 @@ set(IMGUI src/imgui/imgui_config.h
set(INPUT src/input/controller.cpp
src/input/controller.h
src/input/controller_state.cpp
src/input/input_handler.cpp
src/input/input_handler.h
src/input/input_mouse.cpp
+9 -1
View File
@@ -3,7 +3,10 @@
#pragma once
#include <optional>
#include <utility>
#include <vector>
#include "common/types.h"
template <class T>
@@ -38,8 +41,13 @@ public:
return m_storage[m_begin];
}
void Clear() {
m_begin = 0;
m_size = 0;
}
private:
u64 m_begin = 0;
u64 m_size = 0;
std::vector<T> m_storage;
};
};
+2 -5
View File
@@ -86,11 +86,8 @@ bool ReadControllerState(Libraries::UserService::OrbisUserServiceUserId user_id,
if (has_state) {
*has_state = false;
}
Input::State pad_state{};
bool connected = false;
int connected_count = 0;
(*controllers)[index]->ReadState(&pad_state, &connected, &connected_count);
if (!connected) {
Input::State pad_state = (*controllers)[index]->ReadState();
if (!pad_state.connected) {
return false;
}
if (has_state) {
+25 -95
View File
@@ -13,6 +13,7 @@
#include "pad.h"
#include <algorithm>
#include <array>
#include <optional>
namespace Libraries::Pad {
@@ -137,10 +138,7 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
if (it == handle_to_controller_map.end()) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
bool connected = false;
int connected_count = 0;
Input::State state{};
it->second->ReadState(&state, &connected, &connected_count);
const Input::State state = it->second->ReadState();
std::memset(pInfo, 0, sizeof(OrbisPadControllerInformation));
pInfo->touchPadInfo.pixelDensity = 1;
@@ -149,10 +147,10 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->stickInfo.deadZoneLeft = 1;
pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_CONNECTION_TYPE_LOCAL;
pInfo->connectedCount = static_cast<u8>(std::clamp(connected_count, 0, 0xff));
pInfo->connectedCount = state.connected_count;
pInfo->deviceClass = OrbisPadDeviceClass::Standard;
pInfo->connected = connected;
if (connected) {
pInfo->connected = state.connected;
if (state.connected) {
pInfo->deviceClass = EmulatorSettings.IsUsingSpecialPad()
? (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass()
: OrbisPadDeviceClass::Standard;
@@ -367,9 +365,8 @@ int PS4_SYSV_ABI scePadOutputReport() {
return ORBIS_OK;
}
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller,
Input::State* states, s32 num, bool connected, u32 connected_count) {
if (!connected) {
int ProcessStates(OrbisPadData* pData, const Input::State* states, s32 num) {
if (num > 0 && !states[0].connected) {
pData[0] = {};
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
pData[0].connected = false;
@@ -378,15 +375,15 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& contro
const bool gamepad_input_intercepted = ImGui::Core::IsGamepadInputCaptured();
for (int i = 0; i < num; i++) {
pData[i] = {};
if (gamepad_input_intercepted) {
pData[i] = {};
pData[i].buttons = OrbisPadButtonDataOffset::Intercepted;
pData[i].leftStick = {128, 128};
pData[i].rightStick = {128, 128};
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
pData[i].connected = connected;
pData[i].connected = states[i].connected;
pData[i].timestamp = states[i].time;
pData[i].connectedCount = connected_count;
pData[i].connectedCount = states[i].connected_count;
pData[i].deviceUniqueDataLen = 0;
continue;
}
@@ -404,65 +401,10 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& contro
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
const auto gyro_poll_rate = controller.accel_poll_rate;
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
now - controller.GetLastUpdate())
.count() /
1000000.0f;
controller.SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameControllers::CalculateOrientation(pData->acceleration, pData->angularVelocity,
deltaTime, lastOrientation, outputOrientation);
pData[i].orientation = outputOrientation;
controller.SetLastOrientation(outputOrientation);
}
pData[i].orientation = states[i].orientation;
pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
if (handle == 1) {
if (controller.GetTouchCount() >= 127) {
controller.SetTouchCount(0);
}
if (controller.GetSecondaryTouchCount() >= 127) {
controller.SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
controller.SetTouchCount(controller.GetTouchCount() + 1);
controller.SetSecondaryTouchCount(controller.GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
} else {
if (controller.WasSecondaryTouchReset()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
controller.UnsetSecondaryTouchResetBool();
}
}
}
controller.SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
}
} else {
states[i].touchpad[0].ID = 1;
states[i].touchpad[1].ID = 2;
}
if (!states[i].touchpad[0].state && states[i].touchpad[1].state) {
pData[i].touchData.touch[0].x = states[i].touchpad[1].x;
pData[i].touchData.touch[0].y = states[i].touchpad[1].y;
@@ -476,14 +418,11 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& contro
pData[i].touchData.touch[1].id = states[i].touchpad[1].ID;
}
if (Common::ElfInfo::Instance().FirmwareVer() > Common::ElfInfo::FW_350) {
pData[i].touchData.time_since_touch_held_down =
controller.last_touch_down_timestamp == 0
? 0
: states[i].time - controller.last_touch_down_timestamp;
pData[i].touchData.time_since_touch_held_down = states[i].touch_time_since_held_down;
}
pData[i].connected = connected;
pData[i].connected = states[i].connected;
pData[i].timestamp = states[i].time;
pData[i].connectedCount = connected_count;
pData[i].connectedCount = states[i].connected_count;
pData[i].deviceUniqueDataLen = 0;
}
@@ -492,17 +431,17 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& contro
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
int connected_count = 0;
bool connected = false;
std::vector<Input::State> states(64);
if (pData == nullptr || num < 1 || num > ORBIS_PAD_MAX_DATA_NUM) {
return ORBIS_PAD_ERROR_INVALID_ARG;
}
auto it = handle_to_controller_map.find(handle);
if (it == handle_to_controller_map.end()) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto& controller = *it->second;
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected,
connected_count);
std::array<Input::State, ORBIS_PAD_MAX_DATA_NUM> states;
const int ret_num = controller.ReadStates(states.data(), num);
return ProcessStates(pData, states.data(), ret_num);
}
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
@@ -527,17 +466,8 @@ int PS4_SYSV_ABI scePadReadHistory() {
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto it = handle_to_controller_map.find(handle);
if (it == handle_to_controller_map.end()) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto& controller = *it->second;
int connected_count = 0;
bool connected = false;
Input::State state;
controller.ReadState(&state, &connected, &connected_count);
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
return ORBIS_OK;
const int result = scePadRead(handle, pData, 1);
return result < 0 ? result : ORBIS_OK;
}
int PS4_SYSV_ABI scePadReadStateExt() {
@@ -583,9 +513,7 @@ int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto& controller = *it->second;
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
controller.SetLastOrientation(defaultOrientation);
controller.SetLastUpdate(std::chrono::steady_clock::now());
controller.ResetOrientation();
return ORBIS_OK;
}
@@ -816,6 +744,8 @@ int PS4_SYSV_ABI Func_EF103E845B6F0420() {
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
Common::Singleton<GameControllers>::Instance()->TryOpenSDLControllers();
LIB_FUNCTION("6ncge5+l5Qs", "libScePad", 1, "libScePad", scePadClose);
LIB_FUNCTION("kazv1NzSB8c", "libScePad", 1, "libScePad", scePadConnectPort);
LIB_FUNCTION("AcslpN1jHR8", "libScePad", 1, "libScePad",
+1
View File
@@ -14,6 +14,7 @@ class SymbolsResolver;
namespace Libraries::Pad {
constexpr int ORBIS_PAD_MAX_TOUCH_NUM = 2;
constexpr int ORBIS_PAD_MAX_DATA_NUM = 64;
constexpr int ORBIS_PAD_MAX_DEVICE_UNIQUE_DATA_SIZE = 12;
constexpr int ORBIS_PAD_PORT_TYPE_STANDARD = 0;
+176 -239
View File
@@ -1,16 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <mutex>
#include <unordered_set>
#include <utility>
#include <SDL3/SDL.h>
#include <common/elf_info.h>
#include <common/singleton.h>
#include "common/logging/log.h"
#include "controller.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "core/user_settings.h"
#include "input/controller.h"
@@ -19,135 +21,195 @@ namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
namespace {
void CalculateOrientation(const Libraries::Pad::OrbisFVector3& angular_velocity, float delta_time,
const Libraries::Pad::OrbisFQuaternion& last_orientation,
Libraries::Pad::OrbisFQuaternion& orientation) {
if (delta_time > 1.0f) {
orientation = last_orientation;
return;
}
Libraries::Pad::OrbisFQuaternion q = last_orientation;
const Libraries::Pad::OrbisFQuaternion omega = {angular_velocity.x, angular_velocity.y,
angular_velocity.z, 0.0f};
const Libraries::Pad::OrbisFQuaternion q_omega = {
q.w * omega.x + q.x * omega.w + q.y * omega.z - q.z * omega.y,
q.w * omega.y + q.y * omega.w + q.z * omega.x - q.x * omega.z,
q.w * omega.z + q.z * omega.w + q.x * omega.y - q.y * omega.x,
q.w * omega.w - q.x * omega.x - q.y * omega.y - q.z * omega.z};
const Libraries::Pad::OrbisFQuaternion q_dot = {0.5f * q_omega.x, 0.5f * q_omega.y,
0.5f * q_omega.z, 0.5f * q_omega.w};
q.x += q_dot.x * delta_time;
q.y += q_dot.y * delta_time;
q.z += q_dot.z * delta_time;
q.w += q_dot.w * delta_time;
const float norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
q.x /= norm;
q.y /= norm;
q.z /= norm;
q.w /= norm;
orientation = q;
}
void State::OnAxis(Axis axis, int value, bool smooth) {
auto const i = std::to_underlying(axis);
// forcibly finish the previous smoothing task by jumping to the end
axes[i] = axis_smoothing_end_values[i];
axis_smoothing_start_times[i] = time;
axis_smoothing_start_values[i] = axes[i];
axis_smoothing_end_values[i] = value;
axis_smoothing_flags[i] = smooth;
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
touchpad[touchIndex].state = isDown;
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
touchpad[touchIndex].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
void State::UpdateAxisSmoothing() {
for (int i = 0; i < std::to_underlying(Axis::AxisMax); i++) {
// if it's not to be smoothed or close enough, just jump to the end
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
if (axes[i] != axis_smoothing_end_values[i]) {
axes[i] = axis_smoothing_end_values[i];
}
continue;
}
auto now = Libraries::Kernel::sceKernelGetProcessTime();
f32 t =
std::clamp((now - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
}
}
} // namespace
GameController::GameController() : m_states_queue(64) {}
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
*isConnected = m_connected;
*connectedCount = m_connected_count;
*state = m_state;
State GameController::ReadState() {
std::lock_guard lock{m_state_mutex};
return m_state;
}
int GameController::ReadStates(State* states, int states_num, bool* isConnected,
int* connectedCount) {
*isConnected = m_connected;
*connectedCount = m_connected_count;
int ret_num = 0;
if (m_connected) {
std::lock_guard lg(m_states_queue_mutex);
for (int i = 0; i < states_num; i++) {
auto o_state = m_states_queue.Pop();
if (!o_state) {
break;
}
states[ret_num++] = *o_state;
}
int GameController::ReadStates(State* states, int states_num) {
std::lock_guard lock{m_state_mutex};
if (states_num <= 0) {
return 0;
}
return ret_num;
if (!m_state.connected) {
states[0] = m_state;
return 1;
}
if (states_num == 1) {
// Retained history can make a later multi-sample read return up to 64 stale reports, so
// mixed single- and multi-sample reads require dedicated tests.
states[0] = m_state;
return 1;
}
int read_count = 0;
while (read_count < states_num) {
auto state = m_states_queue.Pop();
if (!state) {
break;
}
states[read_count++] = std::move(*state);
}
return read_count;
}
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
std::lock_guard lock{m_state_mutex};
m_state.OnButton(button, is_pressed);
PushState();
PushStateLocked();
}
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
m_state.OnAxis(axis, value, smooth);
PushState();
}
void GameController::Gyro(int id) {
m_state.OnGyro(gyro_buf);
PushState();
}
void GameController::Acceleration(int id) {
m_state.OnAccel(accel_buf);
PushState();
std::lock_guard lock{m_state_mutex};
const u64 timestamp = Libraries::Kernel::sceKernelGetProcessTime();
m_state.OnAxis(axis, value, timestamp, smooth);
PushStateLocked(timestamp);
}
void GameController::UpdateGyro(const float gyro[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::lock_guard lock{m_state_mutex};
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
}
void GameController::UpdateAcceleration(const float acceleration[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::lock_guard lock{m_state_mutex};
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
}
void GameController::UpdateAxisSmoothing() {
m_state.UpdateAxisSmoothing();
void GameController::PollState() {
std::lock_guard lock{m_state_mutex};
PushStateLocked();
}
void GameController::ResetOrientation() {
std::lock_guard lock{m_state_mutex};
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
m_last_orientation_update = 0;
PushStateLocked();
}
void GameController::SetTouchpadState(int touch_index, bool touch_down, float x, float y) {
if (touch_index < 0 || touch_index >= 2) {
return;
}
std::lock_guard lock{m_state_mutex};
const u64 timestamp = Libraries::Kernel::sceKernelGetProcessTime();
const bool was_pressed = m_state.touchpad[0].state || m_state.touchpad[1].state;
auto& touch = m_state.touchpad[touch_index];
if (touch_down && !touch.state) {
touch.ID = m_next_touch_id;
m_next_touch_id = m_next_touch_id == 127 ? 1 : m_next_touch_id + 1;
}
m_state.OnTouchpad(touch_index, touch_down, x, y);
const bool is_pressed = m_state.touchpad[0].state || m_state.touchpad[1].state;
if (!was_pressed && is_pressed) {
m_touch_down_timestamp = timestamp;
} else if (was_pressed && !is_pressed) {
m_touch_down_timestamp = 0;
}
PushStateLocked(timestamp);
}
void GameController::ConnectController(SDL_Gamepad* pad) {
std::lock_guard lock{m_state_mutex};
m_sdl_gamepad = pad;
m_states_queue.Clear();
if (!m_state.connected) {
++m_state.connected_count;
if (m_state.connected_count == 0) {
m_state.connected_count = 1;
}
}
m_state.connected = true;
m_last_orientation_update = 0;
PushStateLocked();
}
void GameController::DisconnectController() {
std::lock_guard lock{m_state_mutex};
m_states_queue.Clear();
m_sdl_gamepad = nullptr;
const u8 connected_count = m_state.connected_count;
m_state = {};
m_state.connected_count = connected_count;
std::fill(gyro_buf, gyro_buf + 3, 0.0f);
std::fill(accel_buf, accel_buf + 3, 0.0f);
accel_buf[1] = 9.81f;
m_next_touch_id = 1;
m_touch_down_timestamp = 0;
m_state.connected = false;
m_last_orientation_update = 0;
PushStateLocked();
}
void GameController::UpdateOrientationLocked(u64 timestamp) {
if (m_last_orientation_update == 0 || timestamp <= m_last_orientation_update) {
m_last_orientation_update = timestamp;
return;
}
const float delta_time =
static_cast<float>(timestamp - m_last_orientation_update) / 1'000'000.f;
Libraries::Pad::OrbisFQuaternion orientation{};
CalculateOrientation(m_state.angularVelocity, delta_time, m_state.orientation, orientation);
m_state.orientation = orientation;
m_last_orientation_update = timestamp;
}
void GameController::PushStateLocked(u64 timestamp) {
if (timestamp == 0) {
timestamp = Libraries::Kernel::sceKernelGetProcessTime();
}
m_state.UpdateAxisSmoothing(timestamp);
m_state.OnGyro(gyro_buf);
m_state.OnAccel(accel_buf);
UpdateOrientationLocked(timestamp);
m_state.time = timestamp;
m_state.touch_time_since_held_down =
m_touch_down_timestamp == 0 ? 0 : timestamp - m_touch_down_timestamp;
m_states_queue.Push(m_state);
}
void GameController::SetLightBarRGB(u8 const r, u8 const g, u8 const b) {
@@ -198,77 +260,11 @@ bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
return true;
}
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
bool was_pressed = m_state.touchpad[0].state || m_state.touchpad[1].state;
m_state.OnTouchpad(touchIndex, touchDown, x, y);
PushState();
if (!m_state.touchpad[0].state && !m_state.touchpad[1].state && was_pressed) {
last_touch_down_timestamp = 0;
} else if ((m_state.touchpad[0].state || m_state.touchpad[1].state) && !was_pressed) {
last_touch_down_timestamp = m_state.time;
}
}
}
void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation) {
// avoid wildly off values coming from elapsed time between two samples
// being too high, such as on the first time the controller is polled
if (deltaTime > 1.0f) {
orientation = lastOrientation;
return;
}
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
0.0f};
Libraries::Pad::OrbisFQuaternion qω = {q.w * ω.x + q.x * ω.w + q.y * ω.z - q.z * ω.y,
q.w * ω.y + q.y * ω.w + q.z * ω.x - q.x * ω.z,
q.w * ω.z + q.z * ω.w + q.x * ω.y - q.y * ω.x,
q.w * ω.w - q.x * ω.x - q.y * ω.y - q.z * ω.z};
Libraries::Pad::OrbisFQuaternion qDot = {0.5f * qω.x, 0.5f * qω.y, 0.5f * qω.z, 0.5f * qω.w};
q.x += qDot.x * deltaTime;
q.y += qDot.y * deltaTime;
q.z += qDot.z * deltaTime;
q.w += qDot.w * deltaTime;
float norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
q.x /= norm;
q.y /= norm;
q.z /= norm;
q.w /= norm;
orientation.x = q.x;
orientation.y = q.y;
orientation.z = q.z;
orientation.w = q.w;
LOG_DEBUG(Lib_Pad, "Calculated orientation: {:.2f} {:.2f} {:.2f} {:.2f}", orientation.x,
orientation.y, orientation.z, orientation.w);
}
void GameController::ConnectController(SDL_Gamepad* pad) {
m_sdl_gamepad = pad;
m_connected_count = 1;
m_connected = true;
}
void GameController::DisconnectController() {
m_sdl_gamepad = nullptr;
m_connected_count = 0;
m_connected = false;
}
bool is_first_check = true;
static bool is_first_check = true;
void GameControllers::TryOpenSDLControllers() {
using namespace Libraries::UserService;
int controller_count;
s32 move_count = 0;
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
LOG_INFO(Input, "{} controllers are currently connected", controller_count);
@@ -280,7 +276,6 @@ void GameControllers::TryOpenSDLControllers() {
if (pad) {
SDL_JoystickID id = SDL_GetGamepadID(pad);
bool still_connected = false;
ControllerType type = ControllerType::Standard;
for (int j = 0; j < controller_count; j++) {
if (new_joysticks[j] == id) {
still_connected = true;
@@ -290,7 +285,6 @@ void GameControllers::TryOpenSDLControllers() {
}
}
if (!still_connected) {
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
SDL_CloseGamepad(pad);
controllers[i]->DisconnectController();
controllers[i]->user_id = -1;
@@ -326,17 +320,17 @@ void GameControllers::TryOpenSDLControllers() {
c->ConnectController(pad);
if (EmulatorSettings.IsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
c->gyro_poll_rate =
const float poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
c->user_id);
}
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
c->accel_poll_rate =
const float poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
LOG_INFO(Input, "Accel initialized, poll rate: {}", poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
c->user_id);
@@ -348,7 +342,7 @@ void GameControllers::TryOpenSDLControllers() {
}
if (is_first_check) [[unlikely]] {
is_first_check = false;
if (controller_count - move_count == 0) {
if (controller_count == 0) {
auto u = UserManagement.GetUserByPlayerIndex(1);
controllers[0]->user_id = u->user_id;
controllers[0]->ConnectController(nullptr);
@@ -357,63 +351,6 @@ void GameControllers::TryOpenSDLControllers() {
}
SDL_free(new_joysticks);
}
u8 GameController::GetTouchCount() {
return m_touch_count;
}
void GameController::SetTouchCount(u8 touchCount) {
m_touch_count = touchCount;
}
u8 GameController::GetSecondaryTouchCount() {
return m_secondary_touch_count;
}
void GameController::SetSecondaryTouchCount(u8 touchCount) {
m_secondary_touch_count = touchCount;
if (touchCount == 0) {
m_was_secondary_reset = true;
}
}
u8 GameController::GetPreviousTouchNum() {
return m_previous_touchnum;
}
void GameController::SetPreviousTouchNum(u8 touchNum) {
m_previous_touchnum = touchNum;
}
bool GameController::WasSecondaryTouchReset() {
return m_was_secondary_reset;
}
void GameController::UnsetSecondaryTouchResetBool() {
m_was_secondary_reset = false;
}
void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) {
m_orientation = orientation;
}
Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() {
return m_orientation;
}
std::chrono::steady_clock::time_point GameController::GetLastUpdate() {
return m_last_update;
}
void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) {
m_last_update = lastUpdate;
}
void GameController::PushState() {
std::lock_guard lg(m_states_queue_mutex);
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
m_states_queue.Push(m_state);
}
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
auto g = SDL_GetGamepadFromID(id);
ASSERT(g != nullptr);
+19 -47
View File
@@ -3,9 +3,10 @@
#pragma once
#include <array>
#include <mutex>
#include <optional>
#include <utility>
#include <vector>
#include <SDL3/SDL_gamepad.h>
#include "SDL3/SDL_joystick.h"
@@ -14,14 +15,8 @@
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
struct SDL_Gamepad;
namespace Input {
enum class ControllerType {
Standard,
};
enum class Axis {
LeftX = 0,
LeftY = 1,
@@ -63,19 +58,22 @@ private:
public:
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
void OnAxis(Axis, int, bool smooth = true);
void OnAxis(Axis, int, u64 timestamp, bool smooth = true);
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
void OnGyro(const float[3]);
void OnAccel(const float[3]);
void UpdateAxisSmoothing();
void UpdateAxisSmoothing(u64 timestamp);
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
u64 time = 0;
AxisArray<s32> axes{axis_defaults};
TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}};
TouchpadEntry touchpad[2]{};
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, -9.81f, 0.0f};
Libraries::Pad::OrbisFVector3 angularVelocity = {0.0f, 0.0f, 0.0f};
Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f};
u64 touch_time_since_held_down{};
bool connected{};
u8 connected_count{};
};
inline int GetAxis(int min, int max, int value) {
@@ -92,16 +90,15 @@ public:
void ConnectController(SDL_Gamepad* pad);
void DisconnectController();
void ReadState(State* state, bool* isConnected, int* connectedCount);
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
State ReadState();
int ReadStates(State* states, int states_num);
void Button(Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
void Axis(Input::Axis axis, int value, bool smooth = true);
void Gyro(int id);
void Acceleration(int id);
void UpdateGyro(const float gyro[3]);
void UpdateAcceleration(const float acceleration[3]);
void UpdateAxisSmoothing();
void PollState();
void ResetOrientation();
void SetLightBarRGB(u8 const r, u8 const g, u8 const b);
void SetLightBarRGB(Colour const c);
Colour GetLightBarRGB();
@@ -109,44 +106,24 @@ public:
bool SetVibration(u8 smallMotor, u8 largeMotor);
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
u8 GetTouchCount();
void SetTouchCount(u8 touchCount);
u8 GetSecondaryTouchCount();
void SetSecondaryTouchCount(u8 touchCount);
u8 GetPreviousTouchNum();
void SetPreviousTouchNum(u8 touchNum);
bool WasSecondaryTouchReset();
void UnsetSecondaryTouchResetBool();
void SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation);
Libraries::Pad::OrbisFQuaternion GetLastOrientation();
std::chrono::steady_clock::time_point GetLastUpdate();
void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
float gyro_poll_rate;
float accel_poll_rate;
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
s32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
SDL_Gamepad* m_sdl_gamepad = nullptr;
u64 last_touch_down_timestamp = 0;
private:
void PushState();
// m_state_mutex must be held by the caller.
void PushStateLocked(u64 timestamp = 0);
void UpdateOrientationLocked(u64 timestamp);
bool m_connected = false;
int m_connected_count = 0;
u8 m_touch_count = 0;
u8 m_secondary_touch_count = 0;
u8 m_previous_touchnum = 0;
bool m_was_secondary_reset = false;
std::chrono::steady_clock::time_point m_last_update = {};
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
u8 m_next_touch_id{1};
u64 m_touch_down_timestamp{};
u64 m_last_orientation_update{};
Colour colour;
std::optional<Colour> override_colour{};
State m_state;
std::mutex m_states_queue_mutex;
std::mutex m_state_mutex;
RingBufferQueue<State> m_states_queue;
};
@@ -169,11 +146,6 @@ public:
static std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
static std::optional<u8> GetControllerIndexFromControllerID(s32 controller_id);
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation);
void SetControllerCustomColor(s32 i, u8 r, u8 g, u8 b) {
// reset to ensure the next function always runs, even if there already was a preexisting
// override colour before
+81
View File
@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cmath>
#include <utility>
#include "common/types.h"
#include "core/libraries/pad/pad.h"
#include "input/controller.h"
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool is_pressed) {
if (is_pressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
}
void State::OnAxis(Axis axis, int value, u64 timestamp, bool smooth) {
const auto index = std::to_underlying(axis);
axes[index] = axis_smoothing_end_values[index];
axis_smoothing_start_times[index] = timestamp;
axis_smoothing_start_values[index] = axes[index];
axis_smoothing_end_values[index] = value;
axis_smoothing_flags[index] = smooth;
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
}
void State::OnTouchpad(int touch_index, bool is_down, float x, float y) {
touchpad[touch_index].state = is_down;
touchpad[touch_index].x = static_cast<u16>(x * 1920);
touchpad[touch_index].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
void State::UpdateAxisSmoothing(u64 timestamp) {
for (int i = 0; i < std::to_underlying(Axis::AxisMax); ++i) {
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
axes[i] = axis_smoothing_end_values[i];
continue;
}
const f32 t = std::clamp(
(timestamp - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
}
}
} // namespace Input
+1 -4
View File
@@ -83,9 +83,7 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
controller->UpdateAxisSmoothing();
controller->Gyro(0);
controller->Acceleration(0);
controller->PollState();
return interval;
}
@@ -181,7 +179,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controller
// input handler init-s
Input::ControllerOutput::LinkJoystickAxes();
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
controllers.TryOpenSDLControllers();
if (EmulatorSettings.IsBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");