diff --git a/src/xenia/hid/input_system.cc b/src/xenia/hid/input_system.cc index 588faefe3..91a466886 100644 --- a/src/xenia/hid/input_system.cc +++ b/src/xenia/hid/input_system.cc @@ -7,6 +7,8 @@ ****************************************************************************** */ +#include "xenia/base/logging.h" + #include "xenia/hid/input_system.h" #include "xenia/base/profiling.h" @@ -18,6 +20,13 @@ namespace hid { DEFINE_bool(vibration, true, "Toggle controller vibration.", "HID"); +DEFINE_double(left_stick_deadzone_percentage, 0.0, + "Defines deadzone level for left stick. Allowed range [0.0-1.0].", + "HID"); +DEFINE_double( + right_stick_deadzone_percentage, 0.0, + "Defines deadzone level for right stick. Allowed range [0.0-1.0].", "HID"); + InputSystem::InputSystem(xe::ui::Window* window) : window_(window) {} InputSystem::~InputSystem() = default; @@ -28,15 +37,30 @@ void InputSystem::AddDriver(std::unique_ptr driver) { drivers_.push_back(std::move(driver)); } -void InputSystem::UpdateUsedSlot(uint8_t slot, bool connected) { +void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot, + bool connected) { if (slot == 0xFF) { slot = 0; } - if (connected) { - connected_slot |= (1 << slot); - } else { - connected_slot &= ~(1 << slot); + if (connected_slots.test(slot) == connected) { + // No state change, so nothing to do. + return; + } + + XELOGI(controller_slot_state_change_message[connected].c_str(), slot); + connected_slots.flip(slot); + + if (driver) { + X_INPUT_CAPABILITIES capabilities = {}; + const X_RESULT result = driver->GetCapabilities(slot, 0, &capabilities); + if (result != X_STATUS_SUCCESS) { + return; + } + + controllers_max_joystick_value[slot] = { + {capabilities.gamepad.thumb_lx, capabilities.gamepad.thumb_ly}, + {capabilities.gamepad.thumb_rx, capabilities.gamepad.thumb_ry}}; } } @@ -51,11 +75,11 @@ X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags, any_connected = true; } if (result == X_ERROR_SUCCESS) { - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(driver.get(), user_index, any_connected); return result; } } - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(nullptr, user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -69,11 +93,12 @@ X_RESULT InputSystem::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { any_connected = true; } if (result == X_ERROR_SUCCESS) { - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(driver.get(), user_index, any_connected); + AdjustDeadzoneLevels(user_index, &out_state->gamepad); return result; } } - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(nullptr, user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -88,11 +113,11 @@ X_RESULT InputSystem::SetState(uint32_t user_index, any_connected = true; } if (result == X_ERROR_SUCCESS) { - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(driver.get(), user_index, any_connected); return result; } } - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(nullptr, user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -107,11 +132,11 @@ X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags, any_connected = true; } if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) { - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(driver.get(), user_index, any_connected); return result; } } - UpdateUsedSlot(user_index, any_connected); + UpdateUsedSlot(nullptr, user_index, any_connected); return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED; } @@ -120,11 +145,73 @@ void InputSystem::ToggleVibration() { // Send instant update to vibration state to prevent awaiting for next tick. X_INPUT_VIBRATION vibration = X_INPUT_VIBRATION(); - for (uint8_t user_index = 0; user_index < 4; user_index++) { + for (uint8_t user_index = 0; user_index < max_allowed_controllers; + user_index++) { SetState(user_index, &vibration); } } +void InputSystem::AdjustDeadzoneLevels(const uint8_t slot, + X_INPUT_GAMEPAD* gamepad) { + if (slot > max_allowed_controllers) { + return; + } + + // Left stick + if (cvars::left_stick_deadzone_percentage > 0.0 && + cvars::left_stick_deadzone_percentage < 1.0) { + const double deadzone_lx_percentage = + controllers_max_joystick_value[slot].first.first * + cvars::left_stick_deadzone_percentage; + const double deadzone_ly_percentage = + controllers_max_joystick_value[slot].first.second * + cvars::left_stick_deadzone_percentage; + + const double theta = std::atan2(static_cast(gamepad->thumb_ly), + static_cast(gamepad->thumb_lx)); + + const double deadzone_y_value = std::sin(theta) * deadzone_ly_percentage; + const double deadzone_x_value = std::cos(theta) * deadzone_lx_percentage; + + if (gamepad->thumb_ly > -deadzone_y_value && + gamepad->thumb_ly < deadzone_y_value) { + gamepad->thumb_ly = 0; + } + + if (gamepad->thumb_lx > -deadzone_x_value && + gamepad->thumb_lx < deadzone_x_value) { + gamepad->thumb_lx = 0; + } + } + + // Right stick + if (cvars::right_stick_deadzone_percentage > 0.0 && + cvars::right_stick_deadzone_percentage < 1.0) { + const double deadzone_rx_percentage = + controllers_max_joystick_value[slot].second.first * + cvars::right_stick_deadzone_percentage; + const double deadzone_ry_percentage = + controllers_max_joystick_value[slot].second.second * + cvars::right_stick_deadzone_percentage; + + const double theta = std::atan2(static_cast(gamepad->thumb_ry), + static_cast(gamepad->thumb_rx)); + + const double deadzone_y_value = std::sin(theta) * deadzone_ry_percentage; + const double deadzone_x_value = std::cos(theta) * deadzone_rx_percentage; + + if (gamepad->thumb_ry > -deadzone_y_value && + gamepad->thumb_ry < deadzone_y_value) { + gamepad->thumb_ry = 0; + } + + if (gamepad->thumb_rx > -deadzone_x_value && + gamepad->thumb_rx < deadzone_x_value) { + gamepad->thumb_rx = 0; + } + } +} + X_INPUT_VIBRATION InputSystem::ModifyVibrationLevel( X_INPUT_VIBRATION* vibration) { X_INPUT_VIBRATION modified_vibration = *vibration; diff --git a/src/xenia/hid/input_system.h b/src/xenia/hid/input_system.h index 333116499..998cbf540 100644 --- a/src/xenia/hid/input_system.h +++ b/src/xenia/hid/input_system.h @@ -10,6 +10,7 @@ #ifndef XENIA_HID_INPUT_SYSTEM_H_ #define XENIA_HID_INPUT_SYSTEM_H_ +#include #include #include #include "xenia/base/mutex.h" @@ -26,6 +27,8 @@ class Window; namespace xe { namespace hid { +static constexpr uint8_t max_allowed_controllers = 4; + class InputSystem { public: explicit InputSystem(xe::ui::Window* window); @@ -45,18 +48,32 @@ class InputSystem { X_INPUT_KEYSTROKE* out_keystroke); void ToggleVibration(); - void UpdateUsedSlot(uint8_t slot, bool connected); - uint8_t GetConnectedSlots() const { return connected_slot; } + + const std::bitset GetConnectedSlots() const { + return connected_slots; + } std::unique_lock lock(); private: + typedef std::pair joystick_value; + + const std::string controller_slot_state_change_message[2] = { + "Controller disconnected from slot {}.", + "New controller connected to slot {}."}; + + void UpdateUsedSlot(InputDriver* driver, uint8_t slot, bool connected); + void AdjustDeadzoneLevels(const uint8_t slot, X_INPUT_GAMEPAD* gamepad); + X_INPUT_VIBRATION ModifyVibrationLevel(X_INPUT_VIBRATION* vibration); + xe::ui::Window* window_ = nullptr; std::vector> drivers_; - X_INPUT_VIBRATION ModifyVibrationLevel(X_INPUT_VIBRATION* vibration); - uint8_t connected_slot = 0b0001; + std::bitset connected_slots = {}; + std::array, max_allowed_controllers> + controllers_max_joystick_value = {}; + xe_unlikely_mutex lock_; }; diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 1f766ea65..027ebb502 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -1042,7 +1042,7 @@ bool KernelState::Restore(ByteStream* stream) { return true; } -uint8_t KernelState::GetConnectedUsers() const { +std::bitset<4> KernelState::GetConnectedUsers() const { auto input_sys = emulator_->input_system(); auto lock = input_sys->lock(); @@ -1109,10 +1109,10 @@ void KernelState::EmulateCPInterruptDPC(uint32_t interrupt_callback, } void KernelState::UpdateUsedUserProfiles() { - const uint8_t used_slots_bitmask = GetConnectedUsers(); + const std::bitset<4> used_slots = GetConnectedUsers(); for (uint32_t i = 1; i < cvars::max_signed_profiles; i++) { - bool is_used = used_slots_bitmask & (1 << i); + bool is_used = used_slots.test(i); if (IsUserSignedIn(i) && !is_used) { user_profiles_.erase(i); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 587569f00..5777714ab 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -11,6 +11,7 @@ #define XENIA_KERNEL_KERNEL_STATE_H_ #include +#include #include #include #include @@ -193,7 +194,7 @@ class KernelState { return content_manager_.get(); } - uint8_t GetConnectedUsers() const; + std::bitset<4> GetConnectedUsers() const; void UpdateUsedUserProfiles(); bool IsUserSignedIn(uint32_t index) const {