diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b19e831fe9..64d01cdd7d 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -53,30 +53,29 @@ static std::array, Settings::NativeButton:: buttons; static std::unique_ptr circle_pad; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; - PadState state; - state.hex = 0; + DirectionState state{false, false, false, false}; if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { float t = std::abs(static_cast(circle_pad_y) / circle_pad_x); if (circle_pad_x != 0 && t < TAN60) { if (circle_pad_x > 0) - state.circle_right.Assign(1); + state.right = true; else - state.circle_left.Assign(1); + state.left = true; } if (circle_pad_x == 0 || t > TAN30) { if (circle_pad_y > 0) - state.circle_up.Assign(1); + state.up = true; else - state.circle_down.Assign(1); + state.down = true; } } @@ -125,7 +124,11 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position s16 circle_pad_x = static_cast(circle_pad_x_f * MAX_CIRCLEPAD_POS); s16 circle_pad_y = static_cast(circle_pad_y_f * MAX_CIRCLEPAD_POS); - state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b505cdcd50..1ef972e70c 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -176,6 +176,16 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) +struct DirectionState { + bool up; + bool down; + bool left; + bool right; +}; + +/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); + /** * HID::GetIPCHandles service function * Inputs: diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7ac34a990e..f06dd552f5 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -25,6 +25,11 @@ void Shutdown() { ShutdownRST(); } +void ReloadInputDevices() { + ReloadInputDevicesUser(); + ReloadInputDevicesRST(); +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index c741498e2d..6be3e950ca 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -16,5 +16,8 @@ void Init(); /// Shutdown IR service void Shutdown(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 3f1275c53c..b22f6548cc 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,16 +2,135 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include "common/bit_field.h" +#include "core/core_timing.h" +#include "core/frontend/input.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" +#include "core/settings.h" namespace Service { namespace IR { -static Kernel::SharedPtr handle_event; +union PadState { + u32 hex; + + BitField<14, 1, u32> zl; + BitField<15, 1, u32> zr; + + BitField<24, 1, u32> c_stick_right; + BitField<25, 1, u32> c_stick_left; + BitField<26, 1, u32> c_stick_up; + BitField<27, 1, u32> c_stick_down; +}; + +struct PadDataEntry { + PadState current_state; + PadState delta_additions; + PadState delta_removals; + + s16 c_stick_x; + s16 c_stick_y; +}; + +struct SharedMem { + u64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + u64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; + INSERT_PADDING_WORDS(1); + std::array entries; ///< Last 8 pad entries +}; + +static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); + +static Kernel::SharedPtr update_event; static Kernel::SharedPtr shared_memory; +static u32 next_pad_index; +static int update_callback; +static std::unique_ptr zl; +static std::unique_ptr zr; +static std::unique_ptr c_stick; +static std::atomic is_device_reload_pending; +static bool raw_c_stick; +static int update_period; + +static void LoadInputDevices() { + zl = Input::CreateDevice( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr = Input::CreateDevice( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +static void UnloadInputDevices() { + zl = nullptr; + zr = nullptr; + c_stick = nullptr; +} + +static void UpdateCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast(shared_memory->GetPointer()); + + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + state.zl.Assign(zl->GetStatus()); + state.zr.Assign(zr->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_POS = 0x9C; // Max value for a c-stick position + const s16 c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_POS); + const s16 c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_POS); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } + + // TODO (wwylele): implement raw C-stick data for raw_c_stick = true + + const u32 last_entry_index = mem->index; + mem->index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->entries.size(); + + // Get the previous Pad state + PadState old_state{mem->entries[last_entry_index].current_state}; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {state.hex ^ old_state.hex}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->entries[mem->index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.c_stick_x = c_stick_x; + pad_entry.c_stick_y = c_stick_y; + + // If we just updated index 0, provide a new timestamp + if (mem->index == 0) { + mem->index_reset_ticks_previous = mem->index_reset_ticks; + mem->index_reset_ticks = CoreTiming::GetTicks(); + } + + update_event->Signal(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback); +} /** * IR::GetHandles service function @@ -22,18 +141,52 @@ static Kernel::SharedPtr shared_memory; * 4 : Event handle */ static void GetHandles(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(), + Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom()); +} - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x4000000; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); +/** + * IR::Initialize service function + * Inputs: + * 1 : pad state update period in ms + * 2 : bool output raw c-stick data + */ +static void Initialize(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 0); + update_period = static_cast(rp.Pop()); + raw_c_stick = rp.Pop(); + + if (raw_c_stick) + LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); + + next_pad_index = 0; + is_device_reload_pending.store(true); + CoreTiming::ScheduleEvent(msToCycles(update_period), update_callback); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_INFO(Service_IR, "called. update_period=%d, raw_c_stick=%d", update_period, raw_c_stick); +} + +static void Shutdown(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + + CoreTiming::UnscheduleEvent(update_callback, 0); + UnloadInputDevices(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + LOG_INFO(Service_IR, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, - {0x00020080, nullptr, "Initialize"}, - {0x00030000, nullptr, "Shutdown"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030000, Shutdown, "Shutdown"}, {0x00090000, nullptr, "WriteToTwoFields"}, }; @@ -42,18 +195,24 @@ IR_RST_Interface::IR_RST_Interface() { } void InitRST() { + // Note: these two kernel objects are available before Initialize service function is called. using namespace Kernel; - shared_memory = - SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, MemoryRegion::BASE, "IRRST:SharedMemory"); + update_event = Event::Create(ResetType::OneShot, "IRRST:UpdateEvent"); - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); + update_callback = CoreTiming::RegisterEvent("IRRST:UpdateCallBack", UpdateCallback); } void ShutdownRST() { shared_memory = nullptr; - handle_event = nullptr; + update_event = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevicesRST() { + is_device_reload_pending.store(true); } } // namespace IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 75b7326279..d932bb7e59 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,5 +21,8 @@ public: void InitRST(); void ShutdownRST(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index bccf6bce7a..226af0083b 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -542,7 +542,7 @@ void ShutdownUser() { receive_event = nullptr; } -void ReloadInputDevices() { +void ReloadInputDevicesUser() { if (extra_hid) extra_hid->RequestInputDevicesReload(); } diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 2401346e8e..930650406f 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -52,7 +52,7 @@ void InitUser(); void ShutdownUser(); /// Reload input devices. Used when input configuration changed -void ReloadInputDevices(); +void ReloadInputDevicesUser(); } // namespace IR } // namespace Service diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 3d22c0afa9..d2e7c6b97f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,7 +5,7 @@ #include "audio_core/audio_core.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" -#include "core/hle/service/ir/ir_user.h" +#include "core/hle/service/ir/ir.h" #include "settings.h" #include "video_core/video_core.h"