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
This commit is contained in:
mpnico 2021-08-05 00:39:40 +02:00 committed by GitHub
parent 46ffc81d90
commit 70f79e689b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 468 additions and 50 deletions

View file

@ -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<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>();
public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>();
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<byte, HidVibrationValue> 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;
}
}
}

View file

@ -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;
}
}

View file

@ -3,6 +3,7 @@
public enum HidVibrationDeviceType
{
None,
LinearResonantActuator
LinearResonantActuator,
GcErm
}
}

View file

@ -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);
}
}
}

View file

@ -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<HidVibrationDeviceHandle>();
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<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
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<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer);
Logger.Debug?.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
VibrationDeviceHandleBufferLength = vibrationDeviceHandleBuffer.Length,
VibrationValueBufferLength = vibrationValueBuffer.Length
});
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
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<byte, HidVibrationValue>();
}
dualVibrationValues[position] = vibrationValues[deviceCounter];
currentIndex = index;
}
context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
}
return ResultCode.Success;
}

View file

@ -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
}
}

View file

@ -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
}
}