Эх сурвалжийг харах

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 <rhy3756547@hotmail.com>

* set Pack = 1 for ISampledDataStruct types, added Unknown field to KeyboardState

* extend size of KeyboardModifier

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
jhorv 3 жил өмнө
parent
commit
49be977588

+ 3 - 3
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<T> where T: unmanaged
+    struct AtomicStorage<T> 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();
 

+ 0 - 7
Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledData.cs

@@ -1,7 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
-{
-    interface ISampledData
-    {
-        ulong SamplingNumber { get; }
-    }
-}

+ 65 - 0
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
+{
+    /// <summary>
+    /// This is a "marker interface" to add some compile-time safety to a convention-based optimization.
+    ///
+    /// Any struct implementing this interface should:
+    ///   - use <c>StructLayoutAttribute</c> (and related attributes) to explicity control how the struct is laid out in memory.
+    ///   - ensure that the method <c>ISampledDataStruct.GetSamplingNumberFieldOffset()</c> 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:
+    /// 
+    /// <c>
+    ///         [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...
+    ///         }
+    /// </c>
+    /// </summary>
+    internal interface ISampledDataStruct
+    {
+        // No Instance Members - marker interface only
+
+        public static ulong GetSamplingNumber<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
+        {
+            ReadOnlySpan<T> structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1);
+
+            ReadOnlySpan<byte> byteSpan = MemoryMarshal.Cast<T, byte>(structSpan);
+
+            int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct);
+
+            if (fieldOffset > 0)
+            {
+                byteSpan = byteSpan.Slice(fieldOffset);
+            }
+
+            ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan);
+
+            return value;
+        }
+
+        private static int GetSamplingNumberFieldOffset<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
+        {
+            return sampledDataStruct switch
+            {
+                Npad.SixAxisSensorState _ => sizeof(ulong),
+                _ => 0
+            };
+        }
+    }
+}

+ 1 - 1
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<T> where T: unmanaged
+    struct RingLifo<T> where T: unmanaged, ISampledDataStruct
     {
         private const ulong MaxEntries = 17;
 

+ 3 - 3
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;
     }
 }

+ 1 - 2
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,

+ 3 - 3
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;
     }
 }

+ 3 - 3
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;
     }
 }

+ 3 - 3
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;
     }
 }

+ 3 - 3
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;
     }
 }

+ 3 - 3
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<float> Direction;
         public SixAxisSensorAttribute Attributes;
         private uint _reserved;
-
-        ulong ISampledData.SamplingNumber => SamplingNumber;
     }
 }

+ 3 - 3
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<TouchState> Touches;
-
-        ulong ISampledData.SamplingNumber => SamplingNumber;
     }
 }