// SPDX-License-Identifier: GPL-3.0-or-later #if defined(MKW_ENABLE_OPENXR) #if defined(_WIN32) #if !defined(NOMINMAX) #define NOMINMAX #endif #include #endif #include "vr/openxr_input.h" #include "physical_wheel.h" #include "runtime_config.h" #include "settings_overlay.h" #include "vr/mkw_vr_first_person.h" #include "vr/openxr_diagnostics.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__ANDROID__) #include #include #endif namespace mkw::vr { namespace { // SDL holds its joystick lock for as long as an enumeration takes, and the // Bluetooth Wii Remote rescan (F10 > Controller settings > Keep scanning) // closes and reopens every HID device twice per scan: 15 ms on a plain desk, // over 200 ms on a machine carrying several HID devices, such as a Lighthouse // setup's base-station dongles. The pacing thread must never wait on that, // because the OpenXR frame it holds open costs the compositor every display // slot that passes. So it leaves the gamepad here, and the game thread writes // it to SDL where it already polls controllers. // // m_sdl is the lock the SDL work runs under; the pacing thread only ever takes // m_state, and only for the copy. Both are taken in that order. class VirtualGamepadRelay { public: struct Pad { std::array axes{}; std::array buttons{}; }; void Attach(SDL_Joystick* joystick) { std::scoped_lock lock(m_sdl, m_state); m_joystick = joystick; m_pending = false; } void Detach() { std::scoped_lock lock(m_sdl, m_state); m_joystick = nullptr; m_pending = false; } // Pacing thread. void Publish(const Pad& pad) { std::lock_guard lock(m_state); m_pad = pad; m_pending = true; } // Game thread. Holding m_sdl here is what keeps Detach from closing the // joystick underneath the writes. void Apply() { std::lock_guard sdl(m_sdl); Pad pad; SDL_Joystick* joystick = nullptr; { std::lock_guard lock(m_state); if (!m_pending || m_joystick == nullptr) { return; } pad = m_pad; joystick = m_joystick; m_pending = false; } for (int axis = 0; axis < SDL_GAMEPAD_AXIS_COUNT; ++axis) { SDL_SetJoystickVirtualAxis(joystick, static_cast(axis), pad.axes[static_cast(axis)]); } for (int button = 0; button < SDL_GAMEPAD_BUTTON_COUNT; ++button) { SDL_SetJoystickVirtualButton(joystick, static_cast(button), pad.buttons[static_cast(button)]); } } private: std::mutex m_sdl; std::mutex m_state; SDL_Joystick* m_joystick = nullptr; Pad m_pad; bool m_pending = false; }; VirtualGamepadRelay& Relay() { static VirtualGamepadRelay relay; return relay; } #if defined(__ANDROID__) // Debug-only remote button presses for headset experiments driven over adb, so // a menu can be reached without someone wearing the headset: // adb shell setprop debug.wiicompiled.inject :