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>
This commit is contained in:
jhorv 2023-04-05 16:42:32 -04:00 committed by GitHub
parent c95be55091
commit 49be977588
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 34 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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