From 6102db0e68e9780993e0b09a604d110c6ad86787 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Sun, 28 Mar 2021 08:06:45 +1000 Subject: [PATCH] Wire up host haptics, hold Fast Forward key to test DirectInput works, OpenTK 3 doesn't seem to actually support it even though the method is present and has documentation -_- --- .../DirectInputAdapter.cs | 13 +++++++ src/BizHawk.Bizware.DirectX/GamePad360.cs | 7 ++++ src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs | 24 ++++++------ .../OpenTKInputAdapter.cs | 37 +++++++++++++++++++ .../controllers/SimpleController.cs | 8 ++++ .../input/HostInputAdapter.cs | 6 +++ src/BizHawk.Client.EmuHawk/MainForm.cs | 16 +++++++- 7 files changed, 98 insertions(+), 13 deletions(-) diff --git a/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs b/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs index 8765129fcc..0e20199737 100644 --- a/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs +++ b/src/BizHawk.Bizware.DirectX/DirectInputAdapter.cs @@ -10,6 +10,10 @@ namespace BizHawk.Bizware.DirectX { public sealed class DirectInputAdapter : IHostInputAdapter { + private static readonly IReadOnlyCollection XINPUT_HAPTIC_CHANNEL_NAMES = new[] { "Left", "Right" }; // doesn't seem to be a way to detect this via XInput, so assuming x360/xbone will be good enough + + private IReadOnlyDictionary _lastHapticsSnapshot = new Dictionary(); + private Config? _config; public void DeInitAll() @@ -25,6 +29,9 @@ namespace BizHawk.Bizware.DirectX ReInitGamepads(mainFormHandle); } + public IReadOnlyDictionary> GetHapticsChannels() + => GamePad360.EnumerateDevices().ToDictionary(pad => pad.InputNamePrefix, _ => XINPUT_HAPTIC_CHANNEL_NAMES); + public void ReInitGamepads(IntPtr mainFormHandle) { GamePad.Initialize(mainFormHandle); @@ -43,6 +50,9 @@ namespace BizHawk.Bizware.DirectX { for (int b = 0, n = pad.NumButtons; b < n; b++) handleButton(pad.InputNamePrefix + pad.ButtonName(b), pad.Pressed(b), ClientInputFocus.Pad); foreach (var (axisName, f) in pad.GetAxes()) handleAxis(pad.InputNamePrefix + axisName, (int) f); + _lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + "Left", out var leftStrength); + _lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + "Right", out var rightStrength); + pad.SetVibration(leftStrength, rightStrength); // values will be 0 if not found } foreach (var pad in GamePad.EnumerateDevices()) { @@ -54,6 +64,9 @@ namespace BizHawk.Bizware.DirectX public IEnumerable ProcessHostKeyboards() => KeyInput.Update(_config ?? throw new Exception(nameof(ProcessHostKeyboards) + " called before the global config was passed")) .Concat(IPCKeyInput.Update()); + public void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot) + => _lastHapticsSnapshot = hapticsSnapshot.ToDictionary(tuple => tuple.Name, tuple => tuple.Strength); + public void UpdateConfig(Config config) => _config = config; } } diff --git a/src/BizHawk.Bizware.DirectX/GamePad360.cs b/src/BizHawk.Bizware.DirectX/GamePad360.cs index 62dbb3e589..8bd498cc93 100644 --- a/src/BizHawk.Bizware.DirectX/GamePad360.cs +++ b/src/BizHawk.Bizware.DirectX/GamePad360.cs @@ -231,5 +231,12 @@ namespace BizHawk.Bizware.DirectX public string ButtonName(int index) => _names[index]; public bool Pressed(int index) => _actions[index](); + + /// and are in 0.. + public void SetVibration(int left, int right) + { + static ushort Conv(int i) => unchecked((ushort) ((i >> 15) & 0xFFFF)); + _controller.SetVibration(new() { LeftMotorSpeed = Conv(left), RightMotorSpeed = Conv(right) }); + } } } diff --git a/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs b/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs index b03fc8bb1c..0b088b1b77 100644 --- a/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs +++ b/src/BizHawk.Bizware.OpenTK3/OTK_Gamepad.cs @@ -107,6 +107,8 @@ namespace BizHawk.Bizware.OpenTK3 /// The object returned by private readonly JoystickCapabilities? _joystickCapabilities; + public readonly IReadOnlyCollection HapticsChannels; + /// For use in keybind boxes public readonly string InputNamePrefix; @@ -145,7 +147,9 @@ namespace BizHawk.Bizware.OpenTK3 { _name = "OTK GamePad Undetermined Name"; } - + HapticsChannels = _gamePadCapabilities != null && _gamePadCapabilities.Value.HasLeftVibrationMotor && _gamePadCapabilities.Value.HasRightVibrationMotor + ? new[] { "Left", "Right" } // two haptic motors + : new[] { "Mono" }; // one or zero haptic motors -- in the latter case, pretend it's mono anyway as that doesn't seem to cause problems InputNamePrefix = $"{(MappedGamePad ? "X" : "J")}{_playerIndex} "; Update(); @@ -360,17 +364,13 @@ namespace BizHawk.Bizware.OpenTK3 AddItem("RightTrigger", () => state.Triggers.Right > dzt); } - /// - /// Sets the gamepad's left and right vibration - /// We don't currently use this in Bizhawk - do we have any cores that support this? - /// - /// - /// - public void SetVibration(float left, float right) => OpenTKGamePad.SetVibration( - _deviceIndex, - _gamePadCapabilities?.HasLeftVibrationMotor == true ? left : 0, - _gamePadCapabilities?.HasRightVibrationMotor == true ? right : 0 - ); + /// and are in 0.. + public void SetVibration(int left, int right) + { + const double SCALE = 1.0 / int.MaxValue; + static float Conv(int i) => (float) (i * SCALE); + OpenTKGamePad.SetVibration(_deviceIndex, Conv(left), Conv(right)); + } } } diff --git a/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs b/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs index 99ddcab0c8..14a1a78283 100644 --- a/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs +++ b/src/BizHawk.Bizware.OpenTK3/OpenTKInputAdapter.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using BizHawk.Client.Common; @@ -9,6 +10,8 @@ namespace BizHawk.Bizware.OpenTK3 { public sealed class OpenTKInputAdapter : IHostInputAdapter { + private IReadOnlyDictionary _lastHapticsSnapshot = new Dictionary(); + public void DeInitAll() {} public void FirstInitAll(IntPtr mainFormHandle) @@ -17,6 +20,9 @@ namespace BizHawk.Bizware.OpenTK3 OTK_GamePad.Initialize(); } + public IReadOnlyDictionary> GetHapticsChannels() + => OTK_GamePad.EnumerateDevices().ToDictionary(pad => pad.InputNamePrefix, pad => pad.HapticsChannels); + public void ReInitGamepads(IntPtr mainFormHandle) {} public void PreprocessHostGamepads() => OTK_GamePad.UpdateAll(); @@ -27,11 +33,42 @@ namespace BizHawk.Bizware.OpenTK3 { foreach (var but in pad.buttonObjects) handleButton(pad.InputNamePrefix + but.ButtonName, but.ButtonAction(), ClientInputFocus.Pad); foreach (var (axisID, f) in pad.GetAxes()) handleAxis($"{pad.InputNamePrefix}{axisID} Axis", (int) f); +#if DEBUG // effectively no-op as OpenTK 3 doesn't seem to actually support haptic feedback + foreach (var channel in pad.HapticsChannels) + { + if (!_lastHapticsSnapshot.TryGetValue(pad.InputNamePrefix + channel, out var strength)) + { + pad.SetVibration(0, 0); + continue; + } + switch (channel) + { + case "Mono": + pad.SetVibration(strength, strength); + break; + case "Left": // presence of left channel implies presence of right channel, so we'll use it here... + pad.SetVibration(strength, _lastHapticsSnapshot[pad.InputNamePrefix + "Right"]); + break; + case "Right": // ...and ignore it here + break; + default: + Console.WriteLine(nameof(OTK_GamePad) + " has a new kind of haptic channel? (Dev forgot to update this file too?)"); + break; + } + } +#endif } } public IEnumerable ProcessHostKeyboards() => OTK_Keyboard.Update(); + public void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot) +#if DEBUG // effectively no-op as OpenTK 3 doesn't seem to actually support haptic feedback + => _lastHapticsSnapshot = hapticsSnapshot.ToDictionary(tuple => tuple.Name, tuple => tuple.Strength); +#else + {} +#endif + public void UpdateConfig(Config config) {} } } diff --git a/src/BizHawk.Client.Common/controllers/SimpleController.cs b/src/BizHawk.Client.Common/controllers/SimpleController.cs index 868dcb92f9..d27768e267 100644 --- a/src/BizHawk.Client.Common/controllers/SimpleController.cs +++ b/src/BizHawk.Client.Common/controllers/SimpleController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using BizHawk.Common; using BizHawk.Emulation.Common; @@ -14,11 +15,13 @@ namespace BizHawk.Client.Common protected WorkingDictionary Buttons { get; private set; } = new WorkingDictionary(); protected WorkingDictionary Axes { get; private set; } = new WorkingDictionary(); + protected WorkingDictionary HapticFeedback { get; private set; } = new WorkingDictionary(); public void Clear() { Buttons = new WorkingDictionary(); Axes = new WorkingDictionary(); + HapticFeedback = new WorkingDictionary(); } public bool this[string button] @@ -31,6 +34,11 @@ namespace BizHawk.Client.Common public int AxisValue(string name) => Axes[name]; + public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() + => HapticFeedback.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + + public void SetHapticChannelStrength(string name, int strength) => HapticFeedback[name] = strength; + public IDictionary BoolButtons() => Buttons; public void AcceptNewAxis(string axisId, int value) diff --git a/src/BizHawk.Client.Common/input/HostInputAdapter.cs b/src/BizHawk.Client.Common/input/HostInputAdapter.cs index f02b915852..de9d3dbec9 100644 --- a/src/BizHawk.Client.Common/input/HostInputAdapter.cs +++ b/src/BizHawk.Client.Common/input/HostInputAdapter.cs @@ -12,6 +12,9 @@ namespace BizHawk.Client.Common void FirstInitAll(IntPtr mainFormHandle); + /// keys are pad prefixes "X# "/"J# " (with the trailing space) + IReadOnlyDictionary> GetHapticsChannels(); + void ReInitGamepads(IntPtr mainFormHandle); void PreprocessHostGamepads(); @@ -20,6 +23,9 @@ namespace BizHawk.Client.Common IEnumerable ProcessHostKeyboards(); + /// implementors may store this for use during the next call + void SetHaptics(IReadOnlyCollection<(string Name, int Strength)> hapticsSnapshot); + void UpdateConfig(Config config); } } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 88946e9c46..7301c0d862 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -310,7 +310,6 @@ namespace BizHawk.Client.EmuHawk InputManager = new InputManager { - ControllerInputCoalescer = new ControllerInputCoalescer(), GetMainFormMouseInfo = () => { var b = Control.MouseButtons; @@ -669,6 +668,14 @@ namespace BizHawk.Client.EmuHawk public override bool BlocksInputWhenFocused { get; } = false; + private static readonly IReadOnlyCollection DEBUG_HAPTIC_CHANNELS = new[] + { + "J0 Mono", "J0 Left", "J0 Right", + "X0 Mono", "X0 Left", "X0 Right", + "J1 Mono", "J1 Left", "J1 Right", + "X1 Mono", "X1 Left", "X1 Right" + }; + public int ProgramRunLoop() { // needs to be done late, after the log console snaps on top @@ -701,7 +708,11 @@ namespace BizHawk.Client.EmuHawk Input.Instance.Update(); // handle events and dispatch as a hotkey action, or a hotkey button, or an input button + // ...but prepare haptics first, those get read in ProcessInput var finalHostController = (ControllerInputCoalescer) InputManager.ControllerInputCoalescer; + // for now, vibrate the first gamepad when the Fast Forward hotkey is held, using the value from the previous (host) frame + var debugVibrating = InputManager.ClientControls.IsPressed("Fast Forward") ? int.MaxValue : 0; + foreach (var channel in DEBUG_HAPTIC_CHANNELS) finalHostController.SetHapticChannelStrength(channel, debugVibrating); ProcessInput( _hotkeyCoalescer, finalHostController, @@ -976,6 +987,8 @@ namespace BizHawk.Client.EmuHawk Func> searchHotkeyBindings, Func activeControllerHasBinding) { + Input.Instance.Adapter.SetHaptics(finalHostController.GetHapticsSnapshot()); + // loop through all available events Input.InputEvent ie; while ((ie = Input.Instance.DequeueEvent()) != null) @@ -2053,6 +2066,7 @@ namespace BizHawk.Client.EmuHawk } InputManager.ClientControls = controls; + InputManager.ControllerInputCoalescer = new ControllerInputCoalescer(); // ctor initialises values for host haptics _autofireNullControls = new AutofireController( Emulator, Config.AutofireOn,