From 49be977588c358e92eb9d9f5b0a6f49ee52003f4 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:42:32 -0400 Subject: [PATCH] Eliminate boxing allocations caused by ISampledData structs (#4556) * Redesign use of ISampledData for accessing the SamplingNumber value on input data structs. * Always read SamplingNumber as little-endian * Restored field order for SixAxisSensorState. Rework to allow possibility of non-zero offsets for the SamplingNumber field. Set StructLayout Pack=8 - the KeyboardState struct is 4 bytes shorter with any other value. * fix spelling Co-authored-by: riperiperi * set Pack = 1 for ISampledDataStruct types, added Unknown field to KeyboardState * extend size of KeyboardModifier --------- Co-authored-by: riperiperi --- .../SharedMemory/Common/AtomicStorage.cs | 6 +- .../Types/SharedMemory/Common/ISampledData.cs | 7 -- .../SharedMemory/Common/ISampledDataStruct.cs | 65 +++++++++++++++++++ .../Hid/Types/SharedMemory/Common/RingLifo.cs | 2 +- .../SharedMemory/DebugPad/DebugPadState.cs | 6 +- .../SharedMemory/Keyboard/KeyboardModifier.cs | 3 +- .../SharedMemory/Keyboard/KeyboardState.cs | 6 +- .../Types/SharedMemory/Mouse/MouseState.cs | 6 +- .../SharedMemory/Npad/NpadCommonState.cs | 6 +- .../SharedMemory/Npad/NpadGcTriggerState.cs | 6 +- .../SharedMemory/Npad/SixAxisSensorState.cs | 6 +- .../TouchScreen/TouchScreenState.cs | 6 +- 12 files changed, 91 insertions(+), 34 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledData.cs create mode 100644 Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs index 45b92ba98..da53e4211 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common { - struct AtomicStorage where T: unmanaged + struct AtomicStorage where T: unmanaged, ISampledDataStruct { public ulong SamplingNumber; public T Object; @@ -14,9 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common public void SetObject(ref T obj) { - ISampledData samplingProvider = obj as ISampledData; + ulong samplingNumber = ISampledDataStruct.GetSamplingNumber(ref obj); - Interlocked.Exchange(ref SamplingNumber, samplingProvider.SamplingNumber); + Interlocked.Exchange(ref SamplingNumber, samplingNumber); Thread.MemoryBarrier(); diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledData.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledData.cs deleted file mode 100644 index 08f76747e..000000000 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledData.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common -{ - interface ISampledData - { - ulong SamplingNumber { get; } - } -} diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs new file mode 100644 index 000000000..a382c0c28 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs @@ -0,0 +1,65 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + /// + /// This is a "marker interface" to add some compile-time safety to a convention-based optimization. + /// + /// Any struct implementing this interface should: + /// - use StructLayoutAttribute (and related attributes) to explicity control how the struct is laid out in memory. + /// - ensure that the method ISampledDataStruct.GetSamplingNumberFieldOffset() correctly returns the offset, in bytes, + /// to the ulong "Sampling Number" field within the struct. Most types have it as the first field, so the default offset is 0. + /// + /// Example: + /// + /// + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct DebugPadState : ISampledDataStruct + /// { + /// public ulong SamplingNumber; // 1st field, so no need to add special handling to GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct SixAxisSensorState : ISampledDataStruct + /// { + /// public ulong DeltaTime; + /// public ulong SamplingNumber; // Not the first field - needs special handling in GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// + /// + internal interface ISampledDataStruct + { + // No Instance Members - marker interface only + + public static ulong GetSamplingNumber(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + ReadOnlySpan structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1); + + ReadOnlySpan byteSpan = MemoryMarshal.Cast(structSpan); + + int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct); + + if (fieldOffset > 0) + { + byteSpan = byteSpan.Slice(fieldOffset); + } + + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan); + + return value; + } + + private static int GetSamplingNumberFieldOffset(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + return sampledDataStruct switch + { + Npad.SixAxisSensorState _ => sizeof(ulong), + _ => 0 + }; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs index 615e38930..ae654d6f8 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs @@ -5,7 +5,7 @@ using System.Threading; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common { - struct RingLifo where T: unmanaged + struct RingLifo where T: unmanaged, ISampledDataStruct { private const ulong MaxEntries = 17; diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs index 3e1e1ad8e..0846cfc73 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs @@ -1,15 +1,15 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad { - struct DebugPadState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DebugPadState : ISampledDataStruct { public ulong SamplingNumber; public DebugPadAttribute Attributes; public DebugPadButton Buttons; public AnalogStickState AnalogStickR; public AnalogStickState AnalogStickL; - - ulong ISampledData.SamplingNumber => SamplingNumber; } } diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs index 72d1603aa..839a4e829 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs @@ -2,9 +2,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard { - // TODO: This seems entirely wrong [Flags] - enum KeyboardModifier : uint + enum KeyboardModifier : ulong { None = 0, Control = 1 << 0, diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs index 376085066..4de92813b 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs @@ -1,13 +1,13 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard { - struct KeyboardState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct KeyboardState : ISampledDataStruct { public ulong SamplingNumber; public KeyboardModifier Modifiers; public KeyboardKey Keys; - - ulong ISampledData.SamplingNumber => SamplingNumber; } } diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs index 67ad6bf1a..c953c7945 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs @@ -1,8 +1,10 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse { - struct MouseState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MouseState : ISampledDataStruct { public ulong SamplingNumber; public int X; @@ -13,7 +15,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse public int WheelDeltaY; public MouseButton Buttons; public MouseAttribute Attributes; - - ulong ISampledData.SamplingNumber => SamplingNumber; } } diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs index eaccef802..64f75ce9a 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs @@ -1,8 +1,10 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad { - struct NpadCommonState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadCommonState : ISampledDataStruct { public ulong SamplingNumber; public NpadButton Buttons; @@ -10,7 +12,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad public AnalogStickState AnalogStickR; public NpadAttribute Attributes; private uint _reserved; - - ulong ISampledData.SamplingNumber => SamplingNumber; } } diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs index 52668f85e..bddd6212d 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs @@ -1,15 +1,15 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad { - struct NpadGcTriggerState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadGcTriggerState : ISampledDataStruct { #pragma warning disable CS0649 public ulong SamplingNumber; public uint TriggerL; public uint TriggerR; #pragma warning restore CS0649 - - ulong ISampledData.SamplingNumber => SamplingNumber; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs index d024b0b0e..18be32763 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs @@ -1,9 +1,11 @@ using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad { - struct SixAxisSensorState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SixAxisSensorState : ISampledDataStruct { public ulong DeltaTime; public ulong SamplingNumber; @@ -13,7 +15,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad public Array9 Direction; public SixAxisSensorAttribute Attributes; private uint _reserved; - - ulong ISampledData.SamplingNumber => SamplingNumber; } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs index 8203e49b5..cdd4cc45e 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs @@ -1,15 +1,15 @@ using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen { - struct TouchScreenState : ISampledData + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TouchScreenState : ISampledDataStruct { public ulong SamplingNumber; public int TouchesCount; private int _reserved; public Array16 Touches; - - ulong ISampledData.SamplingNumber => SamplingNumber; } }