[HID] Added support for joystick deadzones

- Simplified logic for connected controllers
This commit is contained in:
Gliniak 2024-06-01 14:54:01 +02:00
parent b3f2ab0e96
commit ce990e2828
4 changed files with 127 additions and 22 deletions

View File

@ -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<InputDriver> 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<double>(gamepad->thumb_ly),
static_cast<double>(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<double>(gamepad->thumb_ry),
static_cast<double>(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;

View File

@ -10,6 +10,7 @@
#ifndef XENIA_HID_INPUT_SYSTEM_H_
#define XENIA_HID_INPUT_SYSTEM_H_
#include <bitset>
#include <memory>
#include <vector>
#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<max_allowed_controllers> GetConnectedSlots() const {
return connected_slots;
}
std::unique_lock<xe_unlikely_mutex> lock();
private:
typedef std::pair<uint16_t, uint16_t> 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<std::unique_ptr<InputDriver>> drivers_;
X_INPUT_VIBRATION ModifyVibrationLevel(X_INPUT_VIBRATION* vibration);
uint8_t connected_slot = 0b0001;
std::bitset<max_allowed_controllers> connected_slots = {};
std::array<std::pair<joystick_value, joystick_value>, max_allowed_controllers>
controllers_max_joystick_value = {};
xe_unlikely_mutex lock_;
};

View File

@ -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);

View File

@ -11,6 +11,7 @@
#define XENIA_KERNEL_KERNEL_STATE_H_
#include <atomic>
#include <bitset>
#include <condition_variable>
#include <functional>
#include <list>
@ -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 {