diff --git a/CMakeLists.txt b/CMakeLists.txt index e124062bd..2126a2e25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/common/ring_buffer_queue.h b/src/common/ring_buffer_queue.h index 00c4dcc91..79a5ac941 100644 --- a/src/common/ring_buffer_queue.h +++ b/src/common/ring_buffer_queue.h @@ -3,7 +3,10 @@ #pragma once +#include +#include #include + #include "common/types.h" template @@ -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 m_storage; -}; \ No newline at end of file +}; diff --git a/src/core/libraries/ime/ime_ui_shared.cpp b/src/core/libraries/ime/ime_ui_shared.cpp index 45dbbfe26..242e4a0be 100644 --- a/src/core/libraries/ime/ime_ui_shared.cpp +++ b/src/core/libraries/ime/ime_ui_shared.cpp @@ -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) { diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 1d2039a73..02a19e8eb 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -13,6 +13,7 @@ #include "pad.h" #include +#include #include 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(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( - 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 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 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::Instance()->TryOpenSDLControllers(); + LIB_FUNCTION("6ncge5+l5Qs", "libScePad", 1, "libScePad", scePadClose); LIB_FUNCTION("kazv1NzSB8c", "libScePad", 1, "libScePad", scePadConnectPort); LIB_FUNCTION("AcslpN1jHR8", "libScePad", 1, "libScePad", diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index d88c82071..2ceebc11d 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -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; diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 5cbf01aad..b9d97619e 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,16 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include +#include +#include #include +#include + #include -#include -#include + #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(x * 1920); - touchpad[touchIndex].y = static_cast(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(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); diff --git a/src/input/controller.h b/src/input/controller.h index ec6663053..6505cc04d 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -3,9 +3,10 @@ #pragma once +#include #include +#include #include -#include #include #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 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 override_colour{}; State m_state; - std::mutex m_states_queue_mutex; + std::mutex m_state_mutex; RingBufferQueue m_states_queue; }; @@ -169,11 +146,6 @@ public: static std::optional GetControllerIndexFromUserID(s32 user_id); static std::optional 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 diff --git a/src/input/controller_state.cpp b/src/input/controller_state.cpp new file mode 100644 index 000000000..27fbf0300 --- /dev/null +++ b/src/input/controller_state.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#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(x * 1920); + touchpad[touch_index].y = static_cast(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 diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 070281ac0..9c6124a55 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -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(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");