diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 0f55cda5d744..1e3be768d47d 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -63,22 +63,14 @@ JoypadWindows::JoypadWindows(HWND *hwnd) { for (int i = 0; i < JOYPADS_MAX; i++) { attached_joypads[i] = false; } - - HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr); - if (result == DI_OK) { - probe_joypads(); - } else { - ERR_PRINT("Couldn't initialize DirectInput. Error: " + itos(result)); - if (result == DIERR_OUTOFMEMORY) { - ERR_PRINT("The Windows DirectInput subsystem could not allocate sufficient memory."); - ERR_PRINT("Rebooting your PC may solve this issue."); - } - // Ensure dinput is still a nullptr. - dinput = nullptr; - } + probe_joypads(); } JoypadWindows::~JoypadWindows() { + if (dinput_probe_task != WorkerThreadPool::INVALID_TASK_ID) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(dinput_probe_task); + } + close_joypad(); if (dinput) { dinput->Release(); @@ -97,7 +89,7 @@ bool JoypadWindows::have_device(const GUID &p_guid) { } // adapted from SDL2, works a lot better than the MSDN version -bool JoypadWindows::is_xinput_device(const GUID *p_guid) { +static bool is_xinput_device(const GUID *p_guid) { static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x28DE, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; @@ -269,11 +261,16 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ } BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) { - JoypadWindows *self = static_cast(p_context); - if (self->is_xinput_device(&p_instance->guidProduct)) { + JoypadWindows::dinput_init_task_context *self = static_cast(p_context); + if (self->xinput_enabled && is_xinput_device(&p_instance->guidProduct)) { return DIENUM_CONTINUE; } - self->setup_dinput_joypad(p_instance); + + if (self->instances_count < JOYPADS_MAX) { + // Copy is needed because DIDEVICEINSTANCE* is invalidated after enumCallback is completed. + memcpy(&self->instances[self->instances_count], p_instance, sizeof(DIDEVICEINSTANCE)); + self->instances_count += 1; + } return DIENUM_CONTINUE; } @@ -284,6 +281,28 @@ BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_ins return DIENUM_CONTINUE; } +void JoypadWindows::dinput_probe_joypads_task(void *p_context) { + JoypadWindows::dinput_init_task_context *self = static_cast(p_context); + + if (!self->dinput) { + HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&self->dinput, nullptr); + if (result != DI_OK) { + ERR_PRINT("Couldn't initialize DirectInput. Error: " + itos(result)); + if (result == DIERR_OUTOFMEMORY) { + ERR_PRINT("The Windows DirectInput subsystem could not allocate sufficient memory."); + ERR_PRINT("Rebooting your PC may solve this issue."); + } + // Ensure dinput is still a nullptr. + self->dinput = nullptr; + } + } + + if (self->dinput) { + self->dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, self, DIEDFL_ATTACHEDONLY); + } + self->ready = true; +} + void JoypadWindows::close_joypad(int id) { if (id == -1) { for (int i = 0; i < JOYPADS_MAX; i++) { @@ -306,7 +325,6 @@ void JoypadWindows::close_joypad(int id) { } void JoypadWindows::probe_joypads() { - ERR_FAIL_NULL_MSG(dinput, "DirectInput not initialized. Rebooting your PC may solve this issue."); DWORD dwResult; for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE)); @@ -332,21 +350,23 @@ void JoypadWindows::probe_joypads() { } } - for (int i = 0; i < joypad_count; i++) { - d_joypads[i].confirmed = false; + if (dinput_probe_task != WorkerThreadPool::INVALID_TASK_ID) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(dinput_probe_task); + dinput_probe_task = WorkerThreadPool::INVALID_TASK_ID; } - dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY); - - for (int i = 0; i < joypad_count; i++) { - if (!d_joypads[i].confirmed) { - close_joypad(i); - } - } + dinput_init_task_ctx.dinput = dinput; + dinput_init_task_ctx.xinput_enabled = xinput_dll != nullptr; + dinput_init_task_ctx.ready = false; + dinput_init_task_ctx.instances_count = 0; + dinput_probe_task = WorkerThreadPool::get_singleton()->add_native_task(&JoypadWindows::dinput_probe_joypads_task, &dinput_init_task_ctx); } void JoypadWindows::process_joypads() { HRESULT hr; + if (dinput_init_task_ctx.ready) { + process_dinput_init_task_result(dinput_init_task_ctx); + } for (int i = 0; i < XUSER_MAX_COUNT; i++) { xinput_gamepad &joy = x_joypads[i]; @@ -505,6 +525,26 @@ float JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool return value; } +void JoypadWindows::process_dinput_init_task_result(dinput_init_task_context &context) { + dinput_probe_task = WorkerThreadPool::INVALID_TASK_ID; + dinput_init_task_ctx.ready = false; + + for (int i = 0; i < joypad_count; i++) { + d_joypads[i].confirmed = false; + } + + for (int i = 0; i < dinput_init_task_ctx.instances_count; ++i) { + setup_dinput_joypad(&dinput_init_task_ctx.instances[i]); + } + dinput_init_task_ctx.instances_count = 0; + + for (int i = 0; i < joypad_count; i++) { + if (!d_joypads[i].confirmed) { + close_joypad(i); + } + } +} + void JoypadWindows::joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { xinput_gamepad &joy = x_joypads[p_device]; if (joy.attached) { @@ -565,5 +605,6 @@ void JoypadWindows::load_xinput() { void JoypadWindows::unload_xinput() { if (xinput_dll) { FreeLibrary((HMODULE)xinput_dll); + xinput_dll = nullptr; } } diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 87c7af76577d..fd463ffe0663 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -86,7 +86,7 @@ class JoypadWindows { attached = false; confirmed = false; di_joy = nullptr; - guid = {}; + guid = GUID_NULL; for (int i = 0; i < MAX_JOY_BUTTONS; i++) { last_buttons[i] = false; @@ -104,12 +104,24 @@ class JoypadWindows { uint64_t ff_end_timestamp = 0; }; + struct dinput_init_task_context { + bool xinput_enabled = false; + LPDIRECTINPUT8 dinput = nullptr; + DIDEVICEINSTANCE instances[JOYPADS_MAX]; + int instances_count = 0; + std::atomic_bool ready = false; + }; + typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState); typedef DWORD(WINAPI *XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); HWND *hWnd = nullptr; HANDLE xinput_dll; - LPDIRECTINPUT8 dinput; + + LPDIRECTINPUT8 dinput = nullptr; + dinput_init_task_context dinput_init_task_ctx; + WorkerThreadPool::TaskID dinput_probe_task = WorkerThreadPool::INVALID_TASK_ID; + Input *input = nullptr; int id_to_change; @@ -121,6 +133,7 @@ class JoypadWindows { static BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context); static BOOL CALLBACK objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context); + static void dinput_probe_joypads_task(void *p_context); void setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id); void close_joypad(int id = -1); @@ -130,7 +143,7 @@ class JoypadWindows { void post_hat(int p_device, DWORD p_dpad); bool have_device(const GUID &p_guid); - bool is_xinput_device(const GUID *p_guid); + void process_dinput_init_task_result(dinput_init_task_context &context); bool setup_dinput_joypad(const DIDEVICEINSTANCE *instance); void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp);