diff --git a/src/Ryujinx.Input.SDL3/NpadHdRumble.cs b/src/Ryujinx.Input.SDL3/NpadHdRumble.cs index c022df74a..93672c3b8 100644 --- a/src/Ryujinx.Input.SDL3/NpadHdRumble.cs +++ b/src/Ryujinx.Input.SDL3/NpadHdRumble.cs @@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Services.Hid; using SDL; using static SDL.SDL3; using System; +using System.Runtime.InteropServices; namespace Ryujinx.Input.SDL3 { @@ -14,6 +15,8 @@ namespace Ryujinx.Input.SDL3 private readonly SDL_hid_device* _hidHandle; private int _globalCount; + private readonly byte[] _lastRumbleData = new byte[10]; + private ulong _lastWriteTicks; private NpadHdRumble(SDL_hid_device* hidHandle) { @@ -29,7 +32,7 @@ namespace Ryujinx.Input.SDL3 } ushort product = SDL_GetGamepadProduct(gamepadHandle); - if (Enum.IsDefined(typeof(HDRumbleSupported), product)) + if (!Enum.IsDefined(typeof(HDRumbleSupported), product)) { return null; } @@ -59,17 +62,20 @@ namespace Ryujinx.Input.SDL3 buf[8] = (byte)(encRightLowFreq + ((encRightLowAmp >> 8) & 0xFF)); buf[9] = (byte)(encRightLowAmp & 0xFF); - if (_globalCount > 0x5) + if (_globalCount > 0xF) { _globalCount = 0x0; } fixed (byte* ptr = buf) { - if (SDL_hid_write(_hidHandle, ptr, (nuint)buf.Length) == -1) + if (SendHDRumble(ptr, (nuint)buf.Length) < 0) { - Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError()); - SDL_ClearError(); + if (!String.IsNullOrEmpty(SDL_GetError())) + { + Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError()); + SDL_ClearError(); + } return false; } } @@ -77,7 +83,6 @@ namespace Ryujinx.Input.SDL3 return true; } - private static int EncodeLowFreq(float lowFreq) { float lf = Math.Clamp(lowFreq, 40.875885f, 626.286133f); @@ -149,7 +154,41 @@ namespace Ryujinx.Input.SDL3 EncodeHighFreq(right.FrequencyHigh), EncodeHighAmp(right.AmplitudeHigh)); } - + + public int SendHDRumble(byte* data, nuint length) + { + byte[] dataArray = new ReadOnlySpan(data, (int) length).ToArray(); + int result = 0; + ulong currentTicks = SDL_GetTicks(); + + // Ditch rumble if we haven't hit the poll-rate yet. + if ((currentTicks - _lastWriteTicks) < 8) // https://docs.handheldlegend.com/s/progcc-3/doc/lag-comparison-aAR1mV3JLX + { + result = 1; + return result; + } + + bool match = true; + + for (int i = 0; i < (int) length; i++) + { + if (dataArray[i] != _lastRumbleData[i]) + { + match = false; + break; + } + } + + if (!match) + { + result = SDL_hid_write(_hidHandle, data, length); + Buffer.BlockCopy(dataArray, 0, _lastRumbleData, 0, (int) length); + _lastWriteTicks = currentTicks; + } + + return result; + } + public void Dispose() { SDL_hid_close(_hidHandle); diff --git a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs index c23b64304..57f2940c8 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs @@ -197,10 +197,12 @@ namespace Ryujinx.Input.SDL3 return _hdRumble?.HdRumble(left, right) ?? false; } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { if ((Features & GamepadFeaturesFlag.Rumble) == 0) - return; + { + return false; + } ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); @@ -219,6 +221,15 @@ namespace Ryujinx.Input.SDL3 if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs)) Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); } + + if (!String.IsNullOrEmpty(SDL_GetError())) + { + Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError()); + SDL_ClearError(); + return false; + } + + return true; } public Vector3 GetMotionData(MotionInputId inputId) diff --git a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs index 5d779518d..e9f11d713 100644 --- a/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyCon.cs @@ -162,7 +162,7 @@ namespace Ryujinx.Input.SDL3 public void SetTriggerThreshold(float triggerThreshold) { - + // No operations } public bool HDRumble(VibrationValue left, VibrationValue right) @@ -170,10 +170,12 @@ namespace Ryujinx.Input.SDL3 return _hdRumble?.HdRumble(left, right) ?? false; } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { if ((Features & GamepadFeaturesFlag.Rumble) == 0) - return; + { + return false; + } ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); @@ -192,6 +194,15 @@ namespace Ryujinx.Input.SDL3 if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs)) Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); } + + if (!String.IsNullOrEmpty(SDL_GetError())) + { + Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError()); + SDL_ClearError(); + return false; + } + + return true; } public Vector3 GetMotionData(MotionInputId inputId) diff --git a/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs index 82f120cc2..6114674ad 100644 --- a/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs +++ b/src/Ryujinx.Input.SDL3/SDL3JoyConPair.cs @@ -1,4 +1,6 @@ +using Gommon; using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System.Collections.Generic; using System.Linq; @@ -69,7 +71,7 @@ namespace Ryujinx.Input.SDL3 return false; } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { if (lowFrequency != 0) { @@ -86,6 +88,15 @@ namespace Ryujinx.Input.SDL3 left.Rumble(0, 0, durationMs); right.Rumble(0, 0, durationMs); } + + if (!SDL_GetError().IsNullOrEmpty()) + { + Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError()); + SDL_ClearError(); + return false; + } + + return true; } public void SetConfiguration(InputConfig configuration) diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 834c372bb..8b179f43f 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -402,9 +402,9 @@ namespace Ryujinx.Input.SDL3 return false; } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { - // No operations + return false; } public Vector3 GetMotionData(MotionInputId inputId) diff --git a/src/Ryujinx.Input.SDL3/SDL3Mouse.cs b/src/Ryujinx.Input.SDL3/SDL3Mouse.cs index 810ddb64e..289a60d85 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Mouse.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Mouse.cs @@ -73,7 +73,7 @@ namespace Ryujinx.Input.SDL3 throw new NotImplementedException(); } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { throw new NotImplementedException(); } diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index a46ff8daf..fe225111b 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -5,7 +5,6 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System; -using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; @@ -555,34 +554,36 @@ namespace Ryujinx.Input.HLE { if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue)) { - if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) + if (_config is not StandardControllerInputConfig controllerConfig || + !controllerConfig.Rumble.EnableRumble) { - VibrationValue leftVibrationValue = dualVibrationValue.Item1; - VibrationValue rightVibrationValue = dualVibrationValue.Item2; + return; + } + + VibrationValue leftVibrationValue = dualVibrationValue.Item1; + VibrationValue rightVibrationValue = dualVibrationValue.Item2; + leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; + leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; + rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; + rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; + + if (_gamepad?.HDRumble(leftVibrationValue, rightVibrationValue) == false) + { float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); - - leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; - leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; - rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble; - rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble; - - if (_gamepad?.HDRumble(leftVibrationValue, rightVibrationValue) == false) - { - _gamepad?.Rumble(low, high, uint.MaxValue); - } - - Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + - $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + - $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + - $"L.low.freq={leftVibrationValue.FrequencyLow}, " + - $"L.high.freq={leftVibrationValue.FrequencyHigh}, " + - $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + - $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + - $"R.low.freq={rightVibrationValue.FrequencyLow}, " + - $"R.high.freq={rightVibrationValue.FrequencyHigh}"); + _gamepad?.Rumble(low, high, 0xFFFFFFFF); } + + Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + + $"L.low.freq={leftVibrationValue.FrequencyLow}, " + + $"L.high.freq={leftVibrationValue.FrequencyHigh}, " + + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + + $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + + $"R.low.freq={rightVibrationValue.FrequencyLow}, " + + $"R.high.freq={rightVibrationValue.FrequencyHigh}"); } } } diff --git a/src/Ryujinx.Input/IGamepad.cs b/src/Ryujinx.Input/IGamepad.cs index fdc148b41..587fd53c0 100644 --- a/src/Ryujinx.Input/IGamepad.cs +++ b/src/Ryujinx.Input/IGamepad.cs @@ -88,10 +88,10 @@ namespace Ryujinx.Input /// The intensity of the low frequency from 0.0f to 1.0f /// The intensity of the high frequency from 0.0f to 1.0f /// The duration of the rumble effect in milliseconds. - void Rumble(float lowFrequency, float highFrequency, uint durationMs); + bool Rumble(float lowFrequency, float highFrequency, uint durationMs); /// - /// Get a snaphost of the state of the gamepad that is remapped with the informations from the set via . + /// Get a snaphost of the state of the gamepad that is remapped with the information from the set via . /// /// A remapped snaphost of the state of the gamepad. GamepadStateSnapshot GetMappedStateSnapshot(); diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index ff7250fa5..704a15ba7 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -152,7 +152,7 @@ namespace Ryujinx.Ava.Input public void SetTriggerThreshold(float triggerThreshold) { - // No operations. + // No operations } public bool HDRumble(VibrationValue left, VibrationValue right) @@ -160,9 +160,9 @@ namespace Ryujinx.Ava.Input return false; } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { - // No operations. + return false; } public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero; diff --git a/src/Ryujinx/Input/AvaloniaMouse.cs b/src/Ryujinx/Input/AvaloniaMouse.cs index 3b91526b5..8c449b9ee 100644 --- a/src/Ryujinx/Input/AvaloniaMouse.cs +++ b/src/Ryujinx/Input/AvaloniaMouse.cs @@ -71,7 +71,7 @@ namespace Ryujinx.Ava.Input throw new NotImplementedException(); } - public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) { throw new NotImplementedException(); }