From 3db7b9ae0d169ee86a440195036c95b73f934b9d Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 30 Oct 2023 20:41:39 +0100 Subject: [PATCH 1/6] Add features for virtual pyro gamepad. --- application/input/input.hpp | 1 + application/input/input_linux.cpp | 1 + video/pyro_protocol.h | 34 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/application/input/input.hpp b/application/input/input.hpp index 2bbd399ab..5c492e14a 100644 --- a/application/input/input.hpp +++ b/application/input/input.hpp @@ -47,6 +47,7 @@ enum class JoypadKey RightThumb, Start, Select, + Mode, Count, Unknown }; diff --git a/application/input/input_linux.cpp b/application/input/input_linux.cpp index 31f129d57..8b6f46b36 100644 --- a/application/input/input_linux.cpp +++ b/application/input/input_linux.cpp @@ -189,6 +189,7 @@ void LinuxInputManager::setup_joypad_remapper(int fd, unsigned index) remapper.register_button(BTN_THUMBR, JoypadKey::RightThumb, JoypadAxis::Unknown); remapper.register_button(BTN_TL, JoypadKey::LeftShoulder, JoypadAxis::Unknown); remapper.register_button(BTN_TR, JoypadKey::RightShoulder, JoypadAxis::Unknown); + remapper.register_button(BTN_MODE, JoypadKey::Mode, JoypadAxis::Unknown); remapper.register_axis(ABS_X, JoypadAxis::LeftX, 1.0f, JoypadKey::Unknown, JoypadKey::Unknown); remapper.register_axis(ABS_Y, JoypadAxis::LeftY, 1.0f, JoypadKey::Unknown, JoypadKey::Unknown); remapper.register_axis(ABS_RX, JoypadAxis::RightX, 1.0f, JoypadKey::Unknown, JoypadKey::Unknown); diff --git a/video/pyro_protocol.h b/video/pyro_protocol.h index edb1e385c..3352dd7eb 100644 --- a/video/pyro_protocol.h +++ b/video/pyro_protocol.h @@ -51,6 +51,38 @@ struct pyro_progress_report uint64_t total_received_key_frames; }; +struct pyro_phase_offset +{ + // Tells server that ideally we should have received frame at + // an offset from the time it was actually received. + // If positive, server will slow down slightly, if negative, speed up. + int32_t ideal_phase_offset_us; +}; + +typedef enum pyro_pad_button_bits +{ + PYRO_PAD_SOUTH_BIT = 1 << 0, + PYRO_PAD_EAST_BIT = 1 << 1, + PYRO_PAD_WEST_BIT = 1 << 2, + PYRO_PAD_NORTH_BIT = 1 << 3, + PYRO_PAD_TL_BIT = 1 << 4, + PYRO_PAD_TR_BIT = 1 << 5, + PYRO_PAD_THUMBL_BIT = 1 << 6, + PYRO_PAD_THUMBR_BIT = 1 << 7, + PYRO_PAD_START_BIT = 1 << 8, + PYRO_PAD_SELECT_BIT = 1 << 9, + PYRO_PAD_MODE_BIT = 1 << 10 +} pyro_pad_button_bits; + +struct pyro_gamepad_state +{ + uint16_t buttons; + int16_t axis_lx, axis_ly; + int16_t axis_rx, axis_ry; + uint8_t lz, rz; + int8_t hat_x, hat_y; +}; + #define PYRO_MAX_UDP_DATAGRAM_SIZE (PYRO_MAX_PAYLOAD_SIZE + sizeof(struct pyro_codec_parameters)) // TCP: Server to client @@ -71,6 +103,8 @@ typedef enum pyro_message_type // Returns nothing. Must be received by server every 5 seconds or connection is dropped. PYRO_MESSAGE_PROGRESS = PYRO_MAKE_MESSAGE_TYPE(6, sizeof(struct pyro_progress_report)), PYRO_MESSAGE_CODEC_PARAMETERS = PYRO_MAKE_MESSAGE_TYPE(7, sizeof(struct pyro_codec_parameters)), + PYRO_MESSAGE_PHASE_OFFSET = PYRO_MAKE_MESSAGE_TYPE(8, sizeof(struct pyro_phase_offset)), + PYRO_MESSAGE_GAMEPAD_STATE = PYRO_MAKE_MESSAGE_TYPE(9, sizeof(struct pyro_gamepad_state)), PYRO_MESSAGE_MAX_INT = INT32_MAX, } pyro_message_type; From 652f14299a13cfedee9ed6a11c4b596e7208ef8a Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 30 Oct 2023 20:59:58 +0100 Subject: [PATCH 2/6] Add seq field to gamepad update. --- video/pyro_protocol.h | 1 + 1 file changed, 1 insertion(+) diff --git a/video/pyro_protocol.h b/video/pyro_protocol.h index 3352dd7eb..a68f89cb5 100644 --- a/video/pyro_protocol.h +++ b/video/pyro_protocol.h @@ -76,6 +76,7 @@ typedef enum pyro_pad_button_bits struct pyro_gamepad_state { + uint16_t seq; uint16_t buttons; int16_t axis_lx, axis_ly; int16_t axis_rx, axis_ry; From 3db0ef63d29261e20ee9ee048dce3ab4a56394b3 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 30 Oct 2023 21:43:20 +0100 Subject: [PATCH 3/6] Add way for application to use custom gamepad polling mechanism. --- application/application.hpp | 9 +++++++ application/platforms/application_glfw.cpp | 26 ++++++++++++------- .../platforms/application_khr_display.cpp | 6 ++--- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/application/application.hpp b/application/application.hpp index fce6fa6d7..2f68baf42 100644 --- a/application/application.hpp +++ b/application/application.hpp @@ -81,6 +81,15 @@ class Application return 720; } + virtual bool enable_joypad_input_manager() + { + // If false, disable polling for gamepads and other devices. + // Keyboard/mouse input is still handled as normal. + // Only relevant to override for special use cases. + // TODO: Figure out something more elegant when more use cases arise. + return true; + } + bool poll(); void run_frame(); diff --git a/application/platforms/application_glfw.cpp b/application/platforms/application_glfw.cpp index 033353a0b..4d04dc972 100644 --- a/application/platforms/application_glfw.cpp +++ b/application/platforms/application_glfw.cpp @@ -171,17 +171,21 @@ struct WSIPlatformGLFW : GraniteWSIPlatform bool alive(Vulkan::WSI &) override { process_events_async_thread(); + + if (enabled_input_manager) + { #if defined(HAVE_LINUX_INPUT) || defined(HAVE_XINPUT_WINDOWS) - input_manager.poll(); + input_manager.poll(); #endif - // Convenient equivalent to pressing escape on the keyboard or something. - if (get_input_tracker().joykey_pressed(0, JoypadKey::Start) && - get_input_tracker().joykey_pressed(0, JoypadKey::Select) && - get_input_tracker().joykey_pressed(0, JoypadKey::LeftShoulder) && - get_input_tracker().joykey_pressed(0, JoypadKey::RightShoulder)) - { - return false; + // Convenient equivalent to pressing escape on the keyboard or something. + if (get_input_tracker().joykey_pressed(0, JoypadKey::Start) && + get_input_tracker().joykey_pressed(0, JoypadKey::Select) && + get_input_tracker().joykey_pressed(0, JoypadKey::LeftShoulder) && + get_input_tracker().joykey_pressed(0, JoypadKey::RightShoulder)) + { + return false; + } } return !request_tear_down.load(); @@ -191,7 +195,8 @@ struct WSIPlatformGLFW : GraniteWSIPlatform { process_events_async_thread(); #if defined(HAVE_LINUX_INPUT) || defined(HAVE_XINPUT_WINDOWS) - input_manager.poll(); + if (enabled_input_manager) + input_manager.poll(); #endif get_input_tracker().dispatch_current_state(get_frame_timer().get_frame_time()); } @@ -368,9 +373,11 @@ struct WSIPlatformGLFW : GraniteWSIPlatform dispatch_running_events(); } + if (app->enable_joypad_input_manager()) { GRANITE_SCOPED_TIMELINE_EVENT("glfw-init-input-managers"); init_input_managers(); + enabled_input_manager = true; } { @@ -482,6 +489,7 @@ struct WSIPlatformGLFW : GraniteWSIPlatform std::atomic_bool request_tear_down; bool async_loop_alive = false; + bool enabled_input_manager = false; #ifdef HAVE_LINUX_INPUT LinuxInputManager input_manager; diff --git a/application/platforms/application_khr_display.cpp b/application/platforms/application_khr_display.cpp index c8b432c8d..61f865722 100644 --- a/application/platforms/application_khr_display.cpp +++ b/application/platforms/application_khr_display.cpp @@ -89,7 +89,7 @@ static bool vulkan_update_display_mode(unsigned *width, unsigned *height, const struct WSIPlatformDisplay : Granite::GraniteWSIPlatform { public: - bool init(unsigned width_, unsigned height_) + bool init(unsigned width_, unsigned height_, bool enable_joypad) { width = width_; height = height_; @@ -125,7 +125,7 @@ struct WSIPlatformDisplay : Granite::GraniteWSIPlatform #ifdef HAVE_LINUX_INPUT if (!input_manager.init( - LINUX_INPUT_MANAGER_JOYPAD_BIT | + (enable_joypad ? LINUX_INPUT_MANAGER_JOYPAD_BIT : 0) | LINUX_INPUT_MANAGER_KEYBOARD_BIT | LINUX_INPUT_MANAGER_MOUSE_BIT | LINUX_INPUT_MANAGER_TOUCHPAD_BIT, @@ -370,7 +370,7 @@ int application_main( if (app) { auto platform = std::make_unique(); - if (!platform->init(1280, 720)) + if (!platform->init(1280, 720, app->enable_joypad_input_manager())) return 1; if (!app->init_platform(std::move(platform)) || !app->init_wsi()) return 1; From 47c3c1cb992baa896ba036e785c69709fa299c85 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 30 Oct 2023 21:48:36 +0100 Subject: [PATCH 4/6] Fallback to blocking poll when thread group is not used. --- application/input/input_linux.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/application/input/input_linux.cpp b/application/input/input_linux.cpp index 8b6f46b36..e7ab4a540 100644 --- a/application/input/input_linux.cpp +++ b/application/input/input_linux.cpp @@ -450,15 +450,26 @@ bool LinuxInputManager::enqueue_open_devices(DeviceType type, InputCallback call if (!deferred.enumerate) return false; - // This can take 200ms or so for some reason ... - deferred.task = GRANITE_THREAD_GROUP()->create_task([type, enumerate = deferred.enumerate.get()] { + if (auto *tg = GRANITE_THREAD_GROUP()) + { + // This can take 200ms or so for some reason ... + deferred.task = tg->create_task([type, enumerate = deferred.enumerate.get()] { + const char *type_str = get_device_type_string(type); + udev_enumerate_add_match_property(enumerate, type_str, "1"); + udev_enumerate_scan_devices(enumerate); + }); + deferred.task->set_desc("udev-scan-devices"); + deferred.task->flush(); + deferred_init.push_back(std::move(deferred)); + } + else + { const char *type_str = get_device_type_string(type); - udev_enumerate_add_match_property(enumerate, type_str, "1"); - udev_enumerate_scan_devices(enumerate); - }); - deferred.task->set_desc("udev-scan-devices"); - deferred.task->flush(); - deferred_init.push_back(std::move(deferred)); + udev_enumerate_add_match_property(deferred.enumerate.get(), type_str, "1"); + udev_enumerate_scan_devices(deferred.enumerate.get()); + deferred_init.push_back(std::move(deferred)); + } + return true; } From 1317c2e219f931682a64ca7cf30bb00b0b1c357f Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 30 Oct 2023 21:50:42 +0100 Subject: [PATCH 5/6] Add more safety when THREAD_GROUP does not exist. --- util/timeline_trace_file.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/timeline_trace_file.hpp b/util/timeline_trace_file.hpp index 0dfc6a0cb..7bfc2b64c 100644 --- a/util/timeline_trace_file.hpp +++ b/util/timeline_trace_file.hpp @@ -83,7 +83,7 @@ class TimelineTraceFile #define GRANITE_MACRO_CONCAT_IMPL(a, b) a##b #define GRANITE_MACRO_CONCAT(a, b) GRANITE_MACRO_CONCAT_IMPL(a, b) #define GRANITE_SCOPED_TIMELINE_EVENT(str) \ - ::Util::TimelineTraceFile::ScopedEvent GRANITE_MACRO_CONCAT(_timeline_scoped_count_, __COUNTER__){GRANITE_THREAD_GROUP()->get_timeline_trace_file(), str} + ::Util::TimelineTraceFile::ScopedEvent GRANITE_MACRO_CONCAT(_timeline_scoped_count_, __COUNTER__){GRANITE_THREAD_GROUP() ? GRANITE_THREAD_GROUP()->get_timeline_trace_file() : nullptr, str} #define GRANITE_SCOPED_TIMELINE_EVENT_FILE(file, str) \ ::Util::TimelineTraceFile::ScopedEvent GRANITE_MACRO_CONCAT(_timeline_scoped_count_, __COUNTER__){file, str} #else From dbadf14e64f634e4f098a422f8bef888f066876e Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Tue, 31 Oct 2023 00:04:23 +0100 Subject: [PATCH 6/6] Put together a really scuffed dinput8 implementation for PS4 controller. --- application/input/CMakeLists.txt | 1 + application/input/xinput_windows.cpp | 148 +++++++++++++++++++-- application/input/xinput_windows.hpp | 16 ++- application/platforms/application_glfw.cpp | 2 +- 4 files changed, 157 insertions(+), 10 deletions(-) diff --git a/application/input/CMakeLists.txt b/application/input/CMakeLists.txt index 2d26bf67f..b8c1760e5 100644 --- a/application/input/CMakeLists.txt +++ b/application/input/CMakeLists.txt @@ -14,6 +14,7 @@ if (${CMAKE_SYSTEM} MATCHES "Linux") endif() elseif (WIN32) target_sources(granite-input PRIVATE xinput_windows.cpp xinput_windows.hpp) + target_link_libraries(granite-input PRIVATE dinput8 dxguid) endif() if (HOTPLUG_UDEV_FOUND) diff --git a/application/input/xinput_windows.cpp b/application/input/xinput_windows.cpp index af34b62d3..21463b468 100644 --- a/application/input/xinput_windows.cpp +++ b/application/input/xinput_windows.cpp @@ -32,8 +32,63 @@ using namespace Util; namespace Granite { -bool XInputManager::init(Granite::InputTracker *tracker_) +XInputManager::~XInputManager() { + for (auto *dev : pDevice) + if (dev) + dev->Release(); + if (pDI) + pDI->Release(); +} + +// Really should just move to SDL ... But need quick hack to make PS4 controllers work :') + +static BOOL CALLBACK enum_callback(const DIDEVICEINSTANCEA *inst, void *p) +{ + auto *manager = static_cast(p); + return manager->di_enum_callback(inst); +} + +BOOL XInputManager::di_enum_callback(const DIDEVICEINSTANCEA *inst) +{ + // Different PIDs for wireless dongle and cabled connection. + if (inst->guidProduct.Data1 != 195036492 && inst->guidProduct.Data1 != 164365644) + { + LOGI("Enumerated DI input device that is not PS4 controller. Bailing ...\n"); + return DIENUM_CONTINUE; + } + + LOGI("Enumerated PS4 controller.\n"); + + unsigned index = trailing_ones(active_pads); + if (index >= 4) + return DIENUM_STOP; + + if (FAILED(pDI->CreateDevice(inst->guidInstance, &pDevice[index], nullptr))) + return DIENUM_CONTINUE; + + if (FAILED(pDevice[index]->SetDataFormat(&c_dfDIJoystick2))) + { + pDevice[index]->Release(); + pDevice[index] = nullptr; + return DIENUM_CONTINUE; + } + + if (FAILED(pDevice[index]->SetCooperativeLevel(hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) + { + pDevice[index]->Release(); + pDevice[index] = nullptr; + return DIENUM_CONTINUE; + } + + active_pads |= 1u << index; + tracker->enable_joypad(index); + return DIENUM_CONTINUE; +} + +bool XInputManager::init(Granite::InputTracker *tracker_, HWND hwnd_) +{ + hwnd = hwnd_; if (!lib) lib = DynamicLibrary("xinput1_4"); if (!lib) @@ -47,6 +102,13 @@ bool XInputManager::init(Granite::InputTracker *tracker_) for (unsigned i = 0; i < 4; i++) try_polling_device(i); + HRESULT hr; + if (FAILED(hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8A, (void**)&pDI, nullptr))) + return false; + + if (FAILED(pDI->EnumDevices(DI8DEVCLASS_GAMECTRL, enum_callback, this, DIEDFL_ATTACHEDONLY))) + return false; + return true; } @@ -77,15 +139,85 @@ bool XInputManager::poll() if ((active_pads & (1u << i)) == 0) continue; - XINPUT_STATE new_state; - memset(&new_state, 0, sizeof(new_state)); - if (pXInputGetState(i, &new_state) != ERROR_DEVICE_NOT_CONNECTED) - create_events(i, new_state); + if (pDevice[i]) + { + if (FAILED(pDevice[i]->Poll()) && FAILED(pDevice[i]->Acquire()) && FAILED(pDevice[i]->Poll())) + { + tracker->disable_joypad(i); + active_pads &= ~(1u << i); + pDevice[i]->Release(); + pDevice[i] = nullptr; + } + else + { + DIJOYSTATE2 joy_state = {}; + if (SUCCEEDED(pDevice[i]->GetDeviceState(sizeof(DIJOYSTATE2), &joy_state))) + { + // Hardcoded for PS4 dinput, yaaaay <_< + static const JoypadKey joykey_mapping[] = + { + JoypadKey::West, JoypadKey::South, JoypadKey::East, JoypadKey::North, + JoypadKey::LeftShoulder, JoypadKey::RightShoulder, + JoypadKey::Unknown, JoypadKey::Unknown, + JoypadKey::Select, JoypadKey::Start, + JoypadKey::LeftThumb, JoypadKey::RightThumb, + JoypadKey::Mode + }; + + for (unsigned j = 0; j < sizeof(joykey_mapping) / sizeof(joykey_mapping[0]); j++) + { + if (joykey_mapping[j] == JoypadKey::Unknown) + continue; + + tracker->joypad_key_state( + i, joykey_mapping[j], joy_state.rgbButtons[j] ? JoypadKeyState::Pressed : JoypadKeyState::Released); + } + + float lx = 2.0f * joy_state.lX / float(0xffff) - 1.0f; + float ly = 2.0f * joy_state.lY / float(0xffff) - 1.0f; + float rx = 2.0f * joy_state.lZ / float(0xffff) - 1.0f; + float ry = 2.0f * joy_state.lRz / float(0xffff) - 1.0f; + float lt = joy_state.lRx / float(0xffff); + float rt = joy_state.lRy / float(0xffff); + + tracker->joyaxis_state(i, JoypadAxis::LeftX, lx); + tracker->joyaxis_state(i, JoypadAxis::LeftY, ly); + tracker->joyaxis_state(i, JoypadAxis::RightX, rx); + tracker->joyaxis_state(i, JoypadAxis::RightY, ry); + tracker->joyaxis_state(i, JoypadAxis::LeftTrigger, lt); + tracker->joyaxis_state(i, JoypadAxis::RightTrigger, rt); + + int pov = joy_state.rgdwPOV[0]; + + bool left = false, right = false, up = false, down = false; + if (pov >= 0) + { + pov /= 100; + up = pov > 270 || pov < 90; + right = pov > 0 && pov < 180; + down = pov > 90 && pov < 270; + left = pov > 180; + } + + tracker->joypad_key_state(i, JoypadKey::Right, right ? JoypadKeyState::Pressed : JoypadKeyState::Released); + tracker->joypad_key_state(i, JoypadKey::Down, down ? JoypadKeyState::Pressed : JoypadKeyState::Released); + tracker->joypad_key_state(i, JoypadKey::Up, up ? JoypadKeyState::Pressed : JoypadKeyState::Released); + tracker->joypad_key_state(i, JoypadKey::Left, left ? JoypadKeyState::Pressed : JoypadKeyState::Released); + } + } + } else { - tracker->disable_joypad(i); - memset(&pads[i], 0, sizeof(pads[i])); - active_pads &= ~(1u << i); + XINPUT_STATE new_state; + memset(&new_state, 0, sizeof(new_state)); + if (pXInputGetState(i, &new_state) != ERROR_DEVICE_NOT_CONNECTED) + create_events(i, new_state); + else + { + tracker->disable_joypad(i); + memset(&pads[i], 0, sizeof(pads[i])); + active_pads &= ~(1u << i); + } } } diff --git a/application/input/xinput_windows.hpp b/application/input/xinput_windows.hpp index b2b499da5..34e0b5023 100644 --- a/application/input/xinput_windows.hpp +++ b/application/input/xinput_windows.hpp @@ -27,6 +27,9 @@ #include #include +#define DIRECTINPUT_VERSION 0x800 +#include + #include "dynamic_library.hpp" namespace Granite @@ -34,14 +37,25 @@ namespace Granite class XInputManager { public: - bool init(InputTracker *tracker); + ~XInputManager(); + bool init(InputTracker *tracker, HWND hwnd); bool poll(); + void operator=(const XInputManager &) = delete; + XInputManager(const XInputManager &) = delete; + XInputManager() = default; + + BOOL di_enum_callback(const DIDEVICEINSTANCEA *inst); + private: + HWND hwnd = nullptr; InputTracker *tracker = nullptr; uint8_t active_pads = 0; XINPUT_STATE pads[4] = {}; + IDirectInput8A *pDI = nullptr; + IDirectInputDevice8A *pDevice[4] = {}; + void create_events(unsigned index, const XINPUT_STATE &state); void try_polling_device(unsigned index); unsigned poll_count = 0; diff --git a/application/platforms/application_glfw.cpp b/application/platforms/application_glfw.cpp index 4d04dc972..0cec6b0c9 100644 --- a/application/platforms/application_glfw.cpp +++ b/application/platforms/application_glfw.cpp @@ -355,7 +355,7 @@ struct WSIPlatformGLFW : GraniteWSIPlatform if (!input_manager.init(LINUX_INPUT_MANAGER_JOYPAD_BIT, &get_input_tracker())) LOGE("Failed to initialize input manager.\n"); #elif defined(HAVE_XINPUT_WINDOWS) - if (!input_manager.init(&get_input_tracker())) + if (!input_manager.init(&get_input_tracker(), glfwGetWin32Window(window))) LOGE("Failed to initialize input manager.\n"); #endif }