From 70f79e689bc947313aab11c41e59928ce43be517 Mon Sep 17 00:00:00 2001 From: mpnico Date: Thu, 5 Aug 2021 00:39:40 +0200 Subject: [PATCH] Implement vibrations (#2468) * First working vibration implementation * Fix Infinite Rumble in SDL2Mouse * Stop ignoring one vibValues every 2 * Remove RumbleInfinity as suggested * Reworked all the vibration handle / calculation * Revert HidVibrationDevicePosition changes * Add UI to enable and tune rumble * Remove some stub logs * Add PlayerIndex in rumble debug log * Fix all requested changes * Implements hid::GetVibrationDeviceInfo * Better implements HidVibrationValue.Equals/GetHashCode * Added requested changes from code review * Last fixes from review * Update configuration file version for rebase --- .../GenericControllerInputConfig.cs | 5 + .../Hid/Controller/RumbleConfigController.cs | 20 +++ .../Services/Hid/HidDevices/NpadDevices.cs | 56 +++++++ .../Vibration/HidVibrationDeviceHandle.cs | 10 ++ .../Types/Vibration/HidVibrationDeviceType.cs | 3 +- .../Types/Vibration/HidVibrationValue.cs | 17 +- Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 145 +++++++++++++----- Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs | 5 +- .../Services/Hid/Types/Npad/NpadStyleIndex.cs | 13 ++ Ryujinx.Headless.SDL2/Program.cs | 6 + Ryujinx.Input.SDL2/SDL2Gamepad.cs | 14 +- Ryujinx.Input/HLE/NpadController.cs | 27 ++++ Ryujinx.Input/HLE/NpadManager.cs | 9 +- Ryujinx.Input/IGamepad.cs | 2 +- .../Configuration/ConfigurationFileFormat.cs | 2 +- Ryujinx/Configuration/ConfigurationState.cs | 21 +++ Ryujinx/Ui/Windows/ControllerWindow.cs | 25 ++- Ryujinx/Ui/Windows/ControllerWindow.glade | 138 +++++++++++++++++ 18 files changed, 468 insertions(+), 50 deletions(-) create mode 100644 Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs create mode 100644 Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs create mode 100644 Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs diff --git a/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs index e3423bb5e..6c4562cfb 100644 --- a/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs +++ b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs @@ -33,5 +33,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller /// Controller Motion Settings /// public MotionConfigController Motion { get; set; } + + /// + /// Controller Rumble Settings + /// + public RumbleConfigController Rumble { get; set; } } } diff --git a/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs new file mode 100644 index 000000000..48be4f13e --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + public class RumbleConfigController + { + /// + /// Controller Strong Rumble Multiplier + /// + public float StrongRumble { get; set; } + + /// + /// Controller Weak Rumble Multiplier + /// + public float WeakRumble { get; set; } + + /// + /// Enable Rumble + /// + public bool EnableRumble { get; set; } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs index bbc30172c..55f8070a7 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Hid.Types; @@ -20,11 +22,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid private ControllerType[] _configuredTypes; private KEvent[] _styleSetUpdateEvents; private bool[] _supportedPlayers; + private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue + { + AmplitudeLow = 0f, + FrequencyLow = 160f, + AmplitudeHigh = 0f, + FrequencyHigh = 320f + }; internal NpadJoyHoldType JoyHold { get; set; } internal bool SixAxisActive = false; // TODO: link to hidserver when implemented internal ControllerType SupportedStyleSets { get; set; } + public Dictionary> RumbleQueues = new Dictionary>(); + public Dictionary LastVibrationValues = new Dictionary(); + public NpadDevices(Switch device, bool active = true) : base(device, active) { _configuredTypes = new ControllerType[MaxControllers]; @@ -596,5 +608,49 @@ namespace Ryujinx.HLE.HOS.Services.Hid WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState); WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState); } + + public void UpdateRumbleQueue(PlayerIndex index, Dictionary dualVibrationValues) + { + if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue)) + { + if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue)) + { + leftVibrationValue = _neutralVibrationValue; + } + + if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue)) + { + rightVibrationValue = _neutralVibrationValue; + } + + if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) + { + currentQueue.Enqueue((leftVibrationValue, rightVibrationValue)); + + LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue); + } + } + } + + public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position) + { + if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) + { + return _neutralVibrationValue; + } + + return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2; + } + + public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index) + { + if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue)) + { + rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>(); + _device.Hid.Npads.RumbleQueues[index] = rumbleQueue; + } + + return rumbleQueue; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs new file mode 100644 index 000000000..4501c721a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct HidVibrationDeviceHandle + { + public byte DeviceType; + public byte PlayerId; + public byte Position; + public byte Reserved; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs index cf9e64985..898384be7 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs @@ -3,6 +3,7 @@ public enum HidVibrationDeviceType { None, - LinearResonantActuator + LinearResonantActuator, + GcErm } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs index 7211396e4..3f45d2699 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs @@ -1,4 +1,7 @@ -namespace Ryujinx.HLE.HOS.Services.Hid +using Ryujinx.HLE.HOS.Tamper; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid { public struct HidVibrationValue { @@ -6,5 +9,17 @@ public float FrequencyLow; public float AmplitudeHigh; public float FrequencyHigh; + + public override bool Equals(object obj) + { + return obj is HidVibrationValue value && + AmplitudeLow == value.AmplitudeLow && + AmplitudeHigh == value.AmplitudeHigh; + } + + public override int GetHashCode() + { + return HashCode.Combine(AmplitudeLow, AmplitudeHigh); + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 3f2aae356..140545ad9 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -1,10 +1,13 @@ +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -37,7 +40,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid private HidSensorFusionParameters _sensorFusionParams; private HidAccelerometerParameters _accelerometerParams; - private HidVibrationValue _vibrationValue; public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer) { @@ -52,7 +54,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid _sensorFusionParams = new HidSensorFusionParameters(); _accelerometerParams = new HidAccelerometerParameters(); - _vibrationValue = new HidVibrationValue(); // TODO: signal event at right place _xpadIdEvent.ReadableEvent.Signal(); @@ -1025,29 +1026,78 @@ namespace Ryujinx.HLE.HOS.Services.Hid // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo public ResultCode GetVibrationDeviceInfo(ServiceCtx context) { - int vibrationDeviceHandle = context.RequestData.ReadInt32(); + HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct(); + NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; + NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; - HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue + if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) { - DeviceType = HidVibrationDeviceType.None, - Position = HidVibrationDevicePosition.None - }; + if (npadIdType >= (NpadIdType.Player8 + 1) && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown) + { + return ResultCode.InvalidNpadIdType; + } - context.ResponseData.Write((int)deviceInfo.DeviceType); - context.ResponseData.Write((int)deviceInfo.Position); + if (deviceHandle.Position > 1) + { + return ResultCode.InvalidDeviceIndex; + } - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { vibrationDeviceHandle, deviceInfo.DeviceType, deviceInfo.Position }); + HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None; - return ResultCode.Success; + if (Enum.IsDefined(typeof(NpadStyleIndex), deviceType)) + { + vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator; + } + else if ((uint)deviceType == 8) + { + vibrationDeviceType = HidVibrationDeviceType.GcErm; + } + + HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None; + + if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator) + { + if (deviceHandle.Position == 0) + { + vibrationDevicePosition = HidVibrationDevicePosition.Left; + } + else if (deviceHandle.Position == 1) + { + vibrationDevicePosition = HidVibrationDevicePosition.Right; + } + else + { + throw new ArgumentOutOfRangeException(nameof(deviceHandle.Position)); + } + } + + HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue + { + DeviceType = vibrationDeviceType, + Position = vibrationDevicePosition + }; + + context.ResponseData.WriteStruct(deviceInfo); + + return ResultCode.Success; + } + + return ResultCode.InvalidNpadDeviceType; } [CommandHipc(201)] // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) public ResultCode SendVibrationValue(ServiceCtx context) { - int vibrationDeviceHandle = context.RequestData.ReadInt32(); + HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() + }; - _vibrationValue = new HidVibrationValue + HidVibrationValue vibrationValue = new HidVibrationValue { AmplitudeLow = context.RequestData.ReadSingle(), FrequencyLow = context.RequestData.ReadSingle(), @@ -1057,14 +1107,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid long appletResourceUserId = context.RequestData.ReadInt64(); - Logger.Debug?.PrintStub(LogClass.ServiceHid, new { - appletResourceUserId, - vibrationDeviceHandle, - _vibrationValue.AmplitudeLow, - _vibrationValue.FrequencyLow, - _vibrationValue.AmplitudeHigh, - _vibrationValue.FrequencyHigh - }); + Dictionary dualVibrationValues = new Dictionary(); + + dualVibrationValues[deviceHandle.Position] = vibrationValue; + + context.Device.Hid.Npads.UpdateRumbleQueue((PlayerIndex)deviceHandle.PlayerId, dualVibrationValues); return ResultCode.Success; } @@ -1073,22 +1120,22 @@ namespace Ryujinx.HLE.HOS.Services.Hid // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue public ResultCode GetActualVibrationValue(ServiceCtx context) { - int vibrationDeviceHandle = context.RequestData.ReadInt32(); + HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() + }; + long appletResourceUserId = context.RequestData.ReadInt64(); - context.ResponseData.Write(_vibrationValue.AmplitudeLow); - context.ResponseData.Write(_vibrationValue.FrequencyLow); - context.ResponseData.Write(_vibrationValue.AmplitudeHigh); - context.ResponseData.Write(_vibrationValue.FrequencyHigh); + HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); - Logger.Stub?.PrintStub(LogClass.ServiceHid, new { - appletResourceUserId, - vibrationDeviceHandle, - _vibrationValue.AmplitudeLow, - _vibrationValue.FrequencyLow, - _vibrationValue.AmplitudeHigh, - _vibrationValue.FrequencyHigh - }); + context.ResponseData.Write(vibrationValue.AmplitudeLow); + context.ResponseData.Write(vibrationValue.FrequencyLow); + context.ResponseData.Write(vibrationValue.AmplitudeHigh); + context.ResponseData.Write(vibrationValue.FrequencyHigh); return ResultCode.Success; } @@ -1138,13 +1185,31 @@ namespace Ryujinx.HLE.HOS.Services.Hid context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); - // TODO: Read all handles and values from buffer. + Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); + Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); - Logger.Debug?.PrintStub(LogClass.ServiceHid, new { - appletResourceUserId, - VibrationDeviceHandleBufferLength = vibrationDeviceHandleBuffer.Length, - VibrationValueBufferLength = vibrationValueBuffer.Length - }); + if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) + { + Dictionary dualVibrationValues = new Dictionary(); + PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId; + + for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++) + { + PlayerIndex index = (PlayerIndex)deviceHandles[deviceCounter].PlayerId; + byte position = deviceHandles[deviceCounter].Position; + + if (index != currentIndex || dualVibrationValues.Count == 2) + { + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + dualVibrationValues = new Dictionary(); + } + + dualVibrationValues[position] = vibrationValues[deviceCounter]; + currentIndex = index; + } + + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + } return ResultCode.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs index 9b829cc50..9c87ac1dc 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs @@ -7,6 +7,9 @@ Success = 0, - InvalidNpadIdType = (710 << ErrorCodeShift) | ModuleId + InvalidNpadDeviceType = (122 << ErrorCodeShift) | ModuleId, + InvalidNpadIdType = (123 << ErrorCodeShift) | ModuleId, + InvalidDeviceIndex = (124 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (131 << ErrorCodeShift) | ModuleId } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs new file mode 100644 index 000000000..b85681d27 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadStyleIndex : byte + { + FullKey = 3, + Handheld = 4, + JoyDual = 5, + JoyLeft = 6, + JoyRight = 7, + SystemExt = 32, + System = 33 + } +} diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 2884f38a3..c3a10929d 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -236,6 +236,12 @@ namespace Ryujinx.Headless.SDL2 EnableMotion = true, Sensitivity = 100, GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false } }; } diff --git a/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/Ryujinx.Input.SDL2/SDL2Gamepad.cs index 26a808e49..0ccd8bb34 100644 --- a/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Logging; using System; using System.Collections.Generic; using System.Numerics; @@ -151,7 +152,18 @@ namespace Ryujinx.Input.SDL2 ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); - SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs); + if (durationMs == uint.MaxValue) + { + SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY); + } + else if (durationMs > SDL_HAPTIC_INFINITY) + { + Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}"); + } + else + { + SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs); + } } } diff --git a/Ryujinx.Input/HLE/NpadController.cs b/Ryujinx.Input/HLE/NpadController.cs index 3559b015c..79c18ecf7 100644 --- a/Ryujinx.Input/HLE/NpadController.cs +++ b/Ryujinx.Input/HLE/NpadController.cs @@ -2,8 +2,11 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Collections.Generic; +using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; @@ -534,5 +537,29 @@ namespace Ryujinx.Input.HLE { Dispose(true); } + + public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue) + { + if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) + { + if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) + { + HidVibrationValue leftVibrationValue = dualVibrationValue.Item1; + HidVibrationValue rightVibrationValue = dualVibrationValue.Item2; + + 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)); + + _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}, " + + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + + $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + + $"--> ({low}, {high})"); + } + } + } } } diff --git a/Ryujinx.Input/HLE/NpadManager.cs b/Ryujinx.Input/HLE/NpadManager.cs index 03bde64b7..a0d2e513f 100644 --- a/Ryujinx.Input/HLE/NpadManager.cs +++ b/Ryujinx.Input/HLE/NpadManager.cs @@ -4,6 +4,7 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.HLE.HOS.Services.Hid; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -167,6 +168,7 @@ namespace Ryujinx.Input.HLE (SixAxisInput, SixAxisInput) motionState = default; NpadController controller = _controllers[(int)inputConfig.PlayerIndex]; + Ryujinx.HLE.HOS.Services.Hid.PlayerIndex playerIndex = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; bool isJoyconPair = false; @@ -177,6 +179,7 @@ namespace Ryujinx.Input.HLE controller.UpdateUserConfiguration(inputConfig); controller.Update(); + controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex)); inputState = controller.GetHLEInputState(); @@ -199,15 +202,15 @@ namespace Ryujinx.Input.HLE motionState.Item1.Orientation = new float[9]; } - inputState.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; - motionState.Item1.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; + inputState.PlayerId = playerIndex; + motionState.Item1.PlayerId = playerIndex; hleInputStates.Add(inputState); hleMotionStates.Add(motionState.Item1); if (isJoyconPair && !motionState.Item2.Equals(default)) { - motionState.Item2.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; + motionState.Item2.PlayerId = playerIndex; hleMotionStates.Add(motionState.Item2); } diff --git a/Ryujinx.Input/IGamepad.cs b/Ryujinx.Input/IGamepad.cs index cc788333b..c83ad5f82 100644 --- a/Ryujinx.Input/IGamepad.cs +++ b/Ryujinx.Input/IGamepad.cs @@ -66,7 +66,7 @@ namespace Ryujinx.Input void SetConfiguration(InputConfig configuration); /// - /// Starts a rumble effect on the gampead. + /// Starts a rumble effect on the gamepad. /// /// The intensity of the low frequency from 0.0f to 1.0f /// The intensity of the high frequency from 0.0f to 1.0f diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs index 65165d8db..ae43d587d 100644 --- a/Ryujinx/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx/Configuration/ConfigurationFileFormat.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 29; + public const int CurrentVersion = 30; public int Version { get; set; } diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs index 41bd64a7b..fe4ff7746 100644 --- a/Ryujinx/Configuration/ConfigurationState.cs +++ b/Ryujinx/Configuration/ConfigurationState.cs @@ -1,6 +1,7 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using Ryujinx.Configuration.System; @@ -874,6 +875,26 @@ namespace Ryujinx.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 30) + { + Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 30."); + + foreach(InputConfig config in configurationFileFormat.InputConfig) + { + if (config is StandardControllerInputConfig controllerConfig) + { + controllerConfig.Rumble = new RumbleConfigController + { + EnableRumble = false, + StrongRumble = 1f, + WeakRumble = 1f + }; + } + } + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs index c57a62c70..36c2a7aaf 100644 --- a/Ryujinx/Ui/Windows/ControllerWindow.cs +++ b/Ryujinx/Ui/Windows/ControllerWindow.cs @@ -34,6 +34,8 @@ namespace Ryujinx.Ui.Windows private bool _isWaitingForInput; #pragma warning disable CS0649, IDE0044 + [GUI] Adjustment _controllerStrongRumble; + [GUI] Adjustment _controllerWeakRumble; [GUI] Adjustment _controllerDeadzoneLeft; [GUI] Adjustment _controllerDeadzoneRight; [GUI] Adjustment _controllerTriggerThreshold; @@ -99,6 +101,8 @@ namespace Ryujinx.Ui.Windows [GUI] ToggleButton _rSl; [GUI] ToggleButton _rSr; [GUI] Image _controllerImage; + [GUI] CheckButton _enableRumble; + [GUI] Box _rumbleBox; #pragma warning restore CS0649, IDE0044 private MainWindow _mainWindow; @@ -314,6 +318,7 @@ namespace Ryujinx.Ui.Windows _deadZoneRightBox.Hide(); _triggerThresholdBox.Hide(); _motionBox.Hide(); + _rumbleBox.Hide(); } else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) { @@ -407,6 +412,8 @@ namespace Ryujinx.Ui.Windows _zR.Label = "Unbound"; _rSl.Label = "Unbound"; _rSr.Label = "Unbound"; + _controllerStrongRumble.Value = 1; + _controllerWeakRumble.Value = 1; _controllerDeadzoneLeft.Value = 0; _controllerDeadzoneRight.Value = 0; _controllerTriggerThreshold.Value = 0; @@ -419,6 +426,7 @@ namespace Ryujinx.Ui.Windows _gyroDeadzone.Value = 1; _dsuServerHost.Buffer.Text = ""; _dsuServerPort.Buffer.Text = ""; + _enableRumble.Active = false; } private void SetValues(InputConfig config) @@ -497,6 +505,9 @@ namespace Ryujinx.Ui.Windows _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString(); _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString(); _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString(); + _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble; + _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble; + _enableRumble.Active = controllerConfig.Rumble.EnableRumble; _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft; _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight; _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold; @@ -706,7 +717,13 @@ namespace Ryujinx.Ui.Windows InvertStickY = _invertRStickY.Active, StickButton = rStickButton, }, - Motion = motionConfig + Motion = motionConfig, + Rumble = new RumbleConfigController + { + StrongRumble = (float)_controllerStrongRumble.Value, + WeakRumble = (float)_controllerWeakRumble.Value, + EnableRumble = _enableRumble.Active + } }; } @@ -1045,6 +1062,12 @@ namespace Ryujinx.Ui.Windows EnableMotion = true, Sensitivity = 100, GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false } }; } diff --git a/Ryujinx/Ui/Windows/ControllerWindow.glade b/Ryujinx/Ui/Windows/ControllerWindow.glade index 8e897795a..a396df03c 100644 --- a/Ryujinx/Ui/Windows/ControllerWindow.glade +++ b/Ryujinx/Ui/Windows/ControllerWindow.glade @@ -7,6 +7,20 @@ 1 4 + + 0.1 + 10 + 1.0 + 0.1 + 1.0 + + + 0.1 + 10 + 1.0 + 0.1 + 1.0 + 1 0.050000000000000003 @@ -1249,6 +1263,130 @@ 1 + + + True + False + 10 + vertical + + + True + False + 10 + 5 + Rumble + + + + + + False + True + 0 + + + + + Enable + True + True + False + True + + + False + True + 1 + + + + + True + False + 10 + vertical + + + True + False + start + Strong rumble multiplier + + + False + True + 0 + + + + + True + True + _controllerStrongRumble + 1 + 1 + + + True + True + 1 + + + + + False + True + 2 + + + + + True + False + 10 + vertical + + + True + False + start + Weak rumble multiplier + + + False + True + 0 + + + + + True + True + _controllerWeakRumble + 1 + 1 + + + True + True + 1 + + + + + False + True + 3 + + + + + False + True + 2 + + False